@openhi/constructs 0.0.108 → 0.0.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chunk-2TPJ6HOF.mjs +289 -0
- package/lib/chunk-2TPJ6HOF.mjs.map +1 -0
- package/lib/{chunk-EST32BJ2.mjs → chunk-7FUAMZOF.mjs} +2 -2
- package/lib/{chunk-L3QHWDHB.mjs → chunk-7Q2IJ2J5.mjs} +4 -4
- package/lib/{chunk-ZGOHB4RA.mjs → chunk-BB5MK4L3.mjs} +2 -2
- package/lib/{chunk-LKG3I536.mjs → chunk-IS4VQRI4.mjs} +8 -1
- package/lib/chunk-IS4VQRI4.mjs.map +1 -0
- package/lib/{chunk-WWGJZNXJ.mjs → chunk-MULKGFIJ.mjs} +2 -2
- package/lib/index.mjs +4 -4
- package/lib/pre-token-generation.handler.js +17 -14
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +3 -4
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +17 -14
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +3 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +1504 -1156
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1341 -1153
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +4 -4
- package/package.json +2 -2
- package/lib/chunk-6J7NQ6A4.mjs +0 -45
- package/lib/chunk-6J7NQ6A4.mjs.map +0 -1
- package/lib/chunk-GT7SFZLP.mjs +0 -126
- package/lib/chunk-GT7SFZLP.mjs.map +0 -1
- package/lib/chunk-LKG3I536.mjs.map +0 -1
- /package/lib/{chunk-EST32BJ2.mjs.map → chunk-7FUAMZOF.mjs.map} +0 -0
- /package/lib/{chunk-L3QHWDHB.mjs.map → chunk-7Q2IJ2J5.mjs.map} +0 -0
- /package/lib/{chunk-ZGOHB4RA.mjs.map → chunk-BB5MK4L3.mjs.map} +0 -0
- /package/lib/{chunk-WWGJZNXJ.mjs.map → chunk-MULKGFIJ.mjs.map} +0 -0
|
@@ -2,14 +2,13 @@ import {
|
|
|
2
2
|
listMembershipsOperation,
|
|
3
3
|
listPractitionerRolesOperation,
|
|
4
4
|
listRoleAssignmentsOperation
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BB5MK4L3.mjs";
|
|
6
6
|
import {
|
|
7
7
|
findUserBySubOperation,
|
|
8
8
|
idFromReference,
|
|
9
9
|
parseUserResource
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-LKG3I536.mjs";
|
|
10
|
+
} from "./chunk-2TPJ6HOF.mjs";
|
|
11
|
+
import "./chunk-IS4VQRI4.mjs";
|
|
13
12
|
import "./chunk-QR5JVSCF.mjs";
|
|
14
13
|
import "./chunk-LZOMFHX3.mjs";
|
|
15
14
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts"],"sourcesContent":["import {\n getLinkedDataIdentity,\n type HumanName,\n organizationRoleCodeSchema,\n type OrganizationRoleCode,\n type PlatformRoleCode,\n platformRoleCodeSchema,\n type PractitionerRole,\n} from \"@openhi/types\";\nimport type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\nimport type { OpenHiContext } from \"../../data/openhi-context\";\nimport { listMembershipsOperation } from \"../../data/operations/control/membership/membership-list-operation\";\nimport { listRoleAssignmentsOperation } from \"../../data/operations/control/roleassignment/roleassignment-list-operation\";\nimport {\n findUserBySubOperation,\n parseUserResource,\n type UserResource,\n} from \"../../data/operations/control/user\";\nimport { listPractitionerRolesOperation } from \"../../data/operations/data/practitionerrole/practitionerrole-list-operation\";\nimport { idFromReference } from \"../../data/operations/fhir-reference\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/pre-token-generation-lambda.md\n *\n * Cognito Pre Token Generation trigger (V2_0).\n *\n * Resolves the OpenHI User by Cognito `sub` via GSI2 and injects `ohi_tid`,\n * `ohi_wid`, `ohi_uid`, `ohi_uname` into both the ID token and the access\n * token (ADR 2026-03-17-01 §3, ADR-014).\n *\n * Absent-claims behavior (ADR-014): if the User cannot be resolved (legacy /\n * unprovisioned account) or the resolved User is missing `currentTenant` /\n * `currentWorkspace`, the Lambda emits the token without OpenHI claims —\n * `openHiContextMiddleware` will then reject the resulting token. Token\n * issuance itself must never fail: any DynamoDB or unexpected error is logged\n * and the original event is returned unchanged.\n *\n * No fallback to Membership: #770 populates `currentTenant`/`currentWorkspace`\n * at sign-up, so a missing pointer indicates legacy/corrupt data that is\n * cheaper to surface (via the absent-claims path) than to repair on the hot\n * token path. Repair belongs in a separate backfill, not here — keeping this\n * Lambda's IAM grant to read-only on the table.\n */\n\nconst REFERENCE_TYPES = {\n Tenant: \"Tenant/\",\n Workspace: \"Workspace/\",\n} as const;\n\nfunction displayNameFor(user: UserResource): string | undefined {\n const names = user.name as Array<HumanName> | undefined;\n const first = names?.[0];\n return first?.text ?? first?.family ?? undefined;\n}\n\ninterface ResolvedClaims {\n ohi_tid: string;\n ohi_wid: string;\n ohi_uid: string;\n ohi_uname: string;\n /**\n * Flat, deduplicated list of organization-role codes for the user in the\n * current workspace. Absent when no Membership / no linked-data-identity\n * exists; present and possibly empty when source data exists but yields\n * no PractitionerRole codes.\n */\n ohi_organization_roles?: ReadonlyArray<OrganizationRoleCode>;\n /**\n * Flat, deduplicated list of platform-role codes for the user that apply\n * to the current workspace scope (tenant-scoped assignments + assignments\n * scoped to this workspace). Absent when no RoleAssignment exists for the\n * user; present and possibly empty when assignments exist but yield no\n * recognized codes.\n */\n ohi_platform_roles?: ReadonlyArray<PlatformRoleCode>;\n}\n\n// Pre Token Generation runs before tenant/workspace claims exist — the User\n// lookup is the operation we use to discover them. The data operation requires\n// a context object for convention, but findUserBySubOperation does not read\n// any of its fields, so a synthetic placeholder is correct here.\nconst lookupContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: \"\",\n actorId: \"\",\n actorName: \"\",\n actorType: \"internal-system\",\n};\n\nasync function resolveClaims(\n cognitoSub: string,\n): Promise<ResolvedClaims | undefined> {\n const user = await findUserBySubOperation({\n context: lookupContext,\n cognitoSub,\n });\n if (!user) {\n console.warn(\n `PreTokenGeneration: no User found for cognitoSub; emitting token without OpenHI claims (sub=${cognitoSub})`,\n );\n return undefined;\n }\n const parsed = parseUserResource(user.resource);\n if (!parsed) {\n console.warn(\n `PreTokenGeneration: User resource JSON could not be parsed (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n const tenantId = idFromReference(\n parsed.currentTenant?.reference,\n REFERENCE_TYPES.Tenant,\n );\n const workspaceId = idFromReference(\n parsed.currentWorkspace?.reference,\n REFERENCE_TYPES.Workspace,\n );\n const displayName = displayNameFor(parsed);\n\n if (!tenantId || !workspaceId || !displayName) {\n console.warn(\n `PreTokenGeneration: resolved User missing currentTenant/currentWorkspace/name; emitting token without OpenHI claims (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n const [ohi_organization_roles, ohi_platform_roles] = await Promise.all([\n resolveOrganizationRoles(tenantId, workspaceId, user.id),\n resolvePlatformRoles(tenantId, workspaceId, user.id),\n ]);\n\n return {\n ohi_tid: tenantId,\n ohi_wid: workspaceId,\n ohi_uid: user.id,\n ohi_uname: displayName,\n ...(ohi_organization_roles !== undefined ? { ohi_organization_roles } : {}),\n ...(ohi_platform_roles !== undefined ? { ohi_platform_roles } : {}),\n };\n}\n\n/**\n * Read `PractitionerRole` resources for the user in the current workspace and\n * return the deduplicated `organization-role` codes. Returns `undefined`\n * (claim absent) when the user has no Membership in the workspace or the\n * Membership lacks a `linked-data-identity` extension. Returns an empty array\n * when the Membership is linked but no matching PractitionerRoles exist.\n *\n * Read path per ADR-019 §1.2 — invariant across Phase 1 and Phase 2 (Phase 2\n * enriches PractitionerRole fields but does not change the source).\n */\nasync function resolveOrganizationRoles(\n tenantId: string,\n workspaceId: string,\n userId: string,\n): Promise<ReadonlyArray<OrganizationRoleCode> | undefined> {\n const claimContext: OpenHiContext = {\n ...lookupContext,\n tenantId,\n workspaceId,\n };\n\n const memberships = await listMembershipsOperation({\n context: claimContext,\n mode: \"full\",\n });\n const userRef = `User/${userId}`;\n const workspaceRef = `Workspace/${workspaceId}`;\n const matching = memberships.entries.find((entry) => {\n const r = entry.resource as {\n user?: { reference?: string };\n workspace?: { reference?: string };\n };\n return (\n r.user?.reference === userRef && r.workspace?.reference === workspaceRef\n );\n });\n if (matching === undefined) {\n return undefined;\n }\n\n const linked = getLinkedDataIdentity(\n matching.resource as { extension?: Array<{ url: string }> },\n );\n if (linked?.reference === undefined) {\n return undefined;\n }\n\n const practitionerRoleList = await listPractitionerRolesOperation({\n context: claimContext,\n mode: \"full\",\n });\n const organizationRef = `Organization/${workspaceId}`;\n const codes = new Set<OrganizationRoleCode>();\n for (const entry of practitionerRoleList.entries) {\n const role: PractitionerRole = entry.resource;\n if (role.practitioner?.reference !== linked.reference) continue;\n if (role.organization?.reference !== organizationRef) continue;\n const codings = role.code?.flatMap((cc) => cc.coding ?? []) ?? [];\n for (const c of codings) {\n if (typeof c.code !== \"string\") continue;\n const parsed = organizationRoleCodeSchema.safeParse(c.code);\n if (parsed.success) {\n codes.add(parsed.data);\n }\n }\n }\n return Array.from(codes);\n}\n\n/**\n * Read `RoleAssignment` resources for the user that apply to the current\n * workspace scope and return the deduplicated platform-role codes. Tenant-\n * scoped assignments (no `workspace` field) and workspace-scoped assignments\n * matching the current workspace both contribute, per ADR 2026-03-13-02 §2 / §5.\n *\n * Returns `undefined` (claim absent) when the user has no RoleAssignments at\n * all. Returns an empty array when assignments exist but yield no recognized\n * platform-role codes.\n */\nasync function resolvePlatformRoles(\n tenantId: string,\n workspaceId: string,\n userId: string,\n): Promise<ReadonlyArray<PlatformRoleCode> | undefined> {\n const claimContext: OpenHiContext = {\n ...lookupContext,\n tenantId,\n workspaceId,\n };\n\n const assignments = await listRoleAssignmentsOperation({\n context: claimContext,\n mode: \"full\",\n });\n const userRef = `User/${userId}`;\n const workspaceRef = `Workspace/${workspaceId}`;\n\n const userAssignments = assignments.entries.filter((entry) => {\n const r = entry.resource as {\n user?: { reference?: string };\n workspace?: { reference?: string };\n };\n if (r.user?.reference !== userRef) return false;\n const wsRef = r.workspace?.reference;\n return wsRef === undefined || wsRef === workspaceRef;\n });\n if (userAssignments.length === 0) {\n return undefined;\n }\n\n const codes = new Set<PlatformRoleCode>();\n for (const entry of userAssignments) {\n const r = entry.resource as { role?: { reference?: string } };\n const ref = r.role?.reference;\n if (typeof ref !== \"string\") continue;\n const roleId = idFromReference(ref, \"Role/\");\n if (roleId === undefined) continue;\n const candidate = roleId.startsWith(\"role-\")\n ? roleId.slice(\"role-\".length)\n : roleId;\n const parsed = platformRoleCodeSchema.safeParse(candidate);\n if (parsed.success) {\n codes.add(parsed.data);\n }\n }\n return Array.from(codes);\n}\n\nexport const handler: PreTokenGenerationTriggerHandler = async (\n event: PreTokenGenerationTriggerEvent,\n _context: Context,\n): Promise<PreTokenGenerationTriggerEvent> => {\n try {\n const cognitoSub = event.request?.userAttributes?.sub;\n if (!cognitoSub) {\n console.warn(\n \"PreTokenGeneration: event has no Cognito sub; returning event unchanged\",\n );\n return event;\n }\n\n let claims = await resolveClaims(cognitoSub);\n\n /**\n * temporarily provide safe fallback during transition period.\n */\n //if (!claims) return event;\n if (!claims) {\n claims = {\n ohi_tid: \"placeholder-tenant-id\",\n ohi_wid: \"placeholder-workspace-id\",\n ohi_uid: \"placeholder-user-id\",\n ohi_uname: \"placeholder\",\n };\n }\n\n if (!event.response) {\n (event as { response: Record<string, unknown> }).response = {};\n }\n const response = event.response as Record<string, unknown>;\n response.claimsAndScopeOverrideDetails = {\n accessTokenGeneration: {\n claimsToAddOrOverride: { ...claims },\n },\n idTokenGeneration: {\n claimsToAddOrOverride: { ...claims },\n },\n };\n } catch (err) {\n // Token issuance must never fail: log and return event unchanged.\n console.warn(\n \"PreTokenGeneration: unexpected error; returning event unchanged\",\n err,\n );\n }\n return event;\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts"],"sourcesContent":["import {\n getLinkedDataIdentity,\n type HumanName,\n organizationRoleCodeSchema,\n type OrganizationRoleCode,\n type PlatformRoleCode,\n platformRoleCodeSchema,\n type PractitionerRole,\n} from \"@openhi/types\";\nimport type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\nimport type { OpenHiContext } from \"../../data/openhi-context\";\nimport { listMembershipsOperation } from \"../../data/operations/control/membership/membership-list-operation\";\nimport { listRoleAssignmentsOperation } from \"../../data/operations/control/roleassignment/roleassignment-list-operation\";\nimport {\n findUserBySubOperation,\n parseUserResource,\n type UserResource,\n} from \"../../data/operations/control/user\";\nimport { listPractitionerRolesOperation } from \"../../data/operations/data/practitionerrole/practitionerrole-list-operation\";\nimport { idFromReference } from \"../../data/operations/fhir-reference\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/pre-token-generation-lambda.md\n *\n * Cognito Pre Token Generation trigger (V2_0).\n *\n * Resolves the OpenHI User by Cognito `sub` via GSI2 and injects `ohi_tid`,\n * `ohi_wid`, `ohi_uid`, `ohi_uname` into both the ID token and the access\n * token (ADR 2026-03-17-01 §3, ADR-014).\n *\n * Absent-claims behavior (ADR-014): if the User cannot be resolved (legacy /\n * unprovisioned account) or the resolved User is missing `currentTenant` /\n * `currentWorkspace`, the Lambda emits the token without OpenHI claims —\n * `openHiContextMiddleware` will then reject the resulting token. Token\n * issuance itself must never fail: any DynamoDB or unexpected error is logged\n * and the original event is returned unchanged.\n *\n * No fallback to Membership: #770 populates `currentTenant`/`currentWorkspace`\n * at sign-up, so a missing pointer indicates legacy/corrupt data that is\n * cheaper to surface (via the absent-claims path) than to repair on the hot\n * token path. Repair belongs in a separate backfill, not here — keeping this\n * Lambda's IAM grant to read-only on the table.\n */\n\nconst REFERENCE_TYPES = {\n Tenant: \"Tenant/\",\n Workspace: \"Workspace/\",\n} as const;\n\nfunction displayNameFor(user: UserResource): string | undefined {\n const names = user.name as Array<HumanName> | undefined;\n const first = names?.[0];\n return first?.text ?? first?.family ?? undefined;\n}\n\ninterface ResolvedClaims {\n ohi_tid: string;\n ohi_wid: string;\n ohi_uid: string;\n ohi_uname: string;\n /**\n * Flat, deduplicated list of organization-role codes for the user in the\n * current workspace. Absent when no Membership / no linked-data-identity\n * exists; present and possibly empty when source data exists but yields\n * no PractitionerRole codes.\n */\n ohi_organization_roles?: ReadonlyArray<OrganizationRoleCode>;\n /**\n * Flat, deduplicated list of platform-role codes for the user that apply\n * to the current workspace scope (tenant-scoped assignments + assignments\n * scoped to this workspace). Absent when no RoleAssignment exists for the\n * user; present and possibly empty when assignments exist but yield no\n * recognized codes.\n */\n ohi_platform_roles?: ReadonlyArray<PlatformRoleCode>;\n}\n\n// Pre Token Generation runs before tenant/workspace claims exist — the User\n// lookup is the operation we use to discover them. The data operation requires\n// a context object for convention, but findUserBySubOperation does not read\n// any of its fields, so a synthetic placeholder is correct here.\nconst lookupContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: \"\",\n actorId: \"\",\n actorName: \"\",\n actorType: \"internal-system\",\n};\n\nasync function resolveClaims(\n cognitoSub: string,\n): Promise<ResolvedClaims | undefined> {\n const user = await findUserBySubOperation({\n context: lookupContext,\n cognitoSub,\n });\n if (!user) {\n console.warn(\n `PreTokenGeneration: no User found for cognitoSub; emitting token without OpenHI claims (sub=${cognitoSub})`,\n );\n return undefined;\n }\n const parsed = parseUserResource(user.resource);\n if (!parsed) {\n console.warn(\n `PreTokenGeneration: User resource JSON could not be parsed (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n const tenantId = idFromReference(\n parsed.currentTenant?.reference,\n REFERENCE_TYPES.Tenant,\n );\n const workspaceId = idFromReference(\n parsed.currentWorkspace?.reference,\n REFERENCE_TYPES.Workspace,\n );\n const displayName = displayNameFor(parsed);\n\n if (!tenantId || !workspaceId || !displayName) {\n console.warn(\n `PreTokenGeneration: resolved User missing currentTenant/currentWorkspace/name; emitting token without OpenHI claims (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n const [ohi_organization_roles, ohi_platform_roles] = await Promise.all([\n resolveOrganizationRoles(tenantId, workspaceId, user.id),\n resolvePlatformRoles(tenantId, workspaceId, user.id),\n ]);\n\n return {\n ohi_tid: tenantId,\n ohi_wid: workspaceId,\n ohi_uid: user.id,\n ohi_uname: displayName,\n ...(ohi_organization_roles !== undefined ? { ohi_organization_roles } : {}),\n ...(ohi_platform_roles !== undefined ? { ohi_platform_roles } : {}),\n };\n}\n\n/**\n * Read `PractitionerRole` resources for the user in the current workspace and\n * return the deduplicated `organization-role` codes. Returns `undefined`\n * (claim absent) when the user has no Membership in the workspace or the\n * Membership lacks a `linked-data-identity` extension. Returns an empty array\n * when the Membership is linked but no matching PractitionerRoles exist.\n *\n * Read path per ADR-019 §1.2 — invariant across Phase 1 and Phase 2 (Phase 2\n * enriches PractitionerRole fields but does not change the source).\n */\nasync function resolveOrganizationRoles(\n tenantId: string,\n workspaceId: string,\n userId: string,\n): Promise<ReadonlyArray<OrganizationRoleCode> | undefined> {\n const claimContext: OpenHiContext = {\n ...lookupContext,\n tenantId,\n workspaceId,\n };\n\n const memberships = await listMembershipsOperation({\n context: claimContext,\n mode: \"full\",\n });\n const userRef = `User/${userId}`;\n const workspaceRef = `Workspace/${workspaceId}`;\n const matching = memberships.entries.find((entry) => {\n const r = entry.resource as {\n user?: { reference?: string };\n workspace?: { reference?: string };\n };\n return (\n r.user?.reference === userRef && r.workspace?.reference === workspaceRef\n );\n });\n if (matching === undefined) {\n return undefined;\n }\n\n const linked = getLinkedDataIdentity(\n matching.resource as { extension?: Array<{ url: string }> },\n );\n if (linked?.reference === undefined) {\n return undefined;\n }\n\n const practitionerRoleList = await listPractitionerRolesOperation({\n context: claimContext,\n mode: \"full\",\n });\n const organizationRef = `Organization/${workspaceId}`;\n const codes = new Set<OrganizationRoleCode>();\n for (const entry of practitionerRoleList.entries) {\n const role: PractitionerRole = entry.resource;\n if (role.practitioner?.reference !== linked.reference) continue;\n if (role.organization?.reference !== organizationRef) continue;\n const codings = role.code?.flatMap((cc) => cc.coding ?? []) ?? [];\n for (const c of codings) {\n if (typeof c.code !== \"string\") continue;\n const parsed = organizationRoleCodeSchema.safeParse(c.code);\n if (parsed.success) {\n codes.add(parsed.data);\n }\n }\n }\n return Array.from(codes);\n}\n\n/**\n * Read `RoleAssignment` resources for the user that apply to the current\n * workspace scope and return the deduplicated platform-role codes. Tenant-\n * scoped assignments (no `workspace` field) and workspace-scoped assignments\n * matching the current workspace both contribute, per ADR 2026-03-13-02 §2 / §5.\n *\n * Returns `undefined` (claim absent) when the user has no RoleAssignments at\n * all. Returns an empty array when assignments exist but yield no recognized\n * platform-role codes.\n */\nasync function resolvePlatformRoles(\n tenantId: string,\n workspaceId: string,\n userId: string,\n): Promise<ReadonlyArray<PlatformRoleCode> | undefined> {\n const claimContext: OpenHiContext = {\n ...lookupContext,\n tenantId,\n workspaceId,\n };\n\n const assignments = await listRoleAssignmentsOperation({\n context: claimContext,\n mode: \"full\",\n });\n const userRef = `User/${userId}`;\n const workspaceRef = `Workspace/${workspaceId}`;\n\n const userAssignments = assignments.entries.filter((entry) => {\n const r = entry.resource as {\n user?: { reference?: string };\n workspace?: { reference?: string };\n };\n if (r.user?.reference !== userRef) return false;\n const wsRef = r.workspace?.reference;\n return wsRef === undefined || wsRef === workspaceRef;\n });\n if (userAssignments.length === 0) {\n return undefined;\n }\n\n const codes = new Set<PlatformRoleCode>();\n for (const entry of userAssignments) {\n const r = entry.resource as { role?: { reference?: string } };\n const ref = r.role?.reference;\n if (typeof ref !== \"string\") continue;\n const roleId = idFromReference(ref, \"Role/\");\n if (roleId === undefined) continue;\n const candidate = roleId.startsWith(\"role-\")\n ? roleId.slice(\"role-\".length)\n : roleId;\n const parsed = platformRoleCodeSchema.safeParse(candidate);\n if (parsed.success) {\n codes.add(parsed.data);\n }\n }\n return Array.from(codes);\n}\n\nexport const handler: PreTokenGenerationTriggerHandler = async (\n event: PreTokenGenerationTriggerEvent,\n _context: Context,\n): Promise<PreTokenGenerationTriggerEvent> => {\n try {\n const cognitoSub = event.request?.userAttributes?.sub;\n if (!cognitoSub) {\n console.warn(\n \"PreTokenGeneration: event has no Cognito sub; returning event unchanged\",\n );\n return event;\n }\n\n let claims = await resolveClaims(cognitoSub);\n\n /**\n * temporarily provide safe fallback during transition period.\n */\n //if (!claims) return event;\n if (!claims) {\n claims = {\n ohi_tid: \"placeholder-tenant-id\",\n ohi_wid: \"placeholder-workspace-id\",\n ohi_uid: \"placeholder-user-id\",\n ohi_uname: \"placeholder\",\n };\n }\n\n if (!event.response) {\n (event as { response: Record<string, unknown> }).response = {};\n }\n const response = event.response as Record<string, unknown>;\n response.claimsAndScopeOverrideDetails = {\n accessTokenGeneration: {\n claimsToAddOrOverride: { ...claims },\n },\n idTokenGeneration: {\n claimsToAddOrOverride: { ...claims },\n },\n };\n } catch (err) {\n // Token issuance must never fail: log and return event unchanged.\n console.warn(\n \"PreTokenGeneration: unexpected error; returning event unchanged\",\n err,\n );\n }\n return event;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -24,7 +24,7 @@ __export(provision_default_workspace_handler_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(provision_default_workspace_handler_exports);
|
|
26
26
|
var import_node_crypto = require("crypto");
|
|
27
|
-
var
|
|
27
|
+
var import_types10 = require("@openhi/types");
|
|
28
28
|
|
|
29
29
|
// src/data/dynamo/dynamo-control-service.ts
|
|
30
30
|
var import_electrodb8 = require("electrodb");
|
|
@@ -1062,7 +1062,7 @@ async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourc
|
|
|
1062
1062
|
};
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
|
-
// src/data/operations/control/user/user-
|
|
1065
|
+
// src/data/operations/control/user/user-switch-tenant-workspace-operation.ts
|
|
1066
1066
|
var import_types7 = require("@openhi/types");
|
|
1067
1067
|
|
|
1068
1068
|
// src/data/operations/control/user/user-resource-helpers.ts
|
|
@@ -1074,9 +1074,21 @@ function parseUserResource(resource) {
|
|
|
1074
1074
|
}
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
|
-
// src/data/operations/
|
|
1077
|
+
// src/data/operations/fhir-reference.ts
|
|
1078
|
+
function idFromReference(reference, prefix) {
|
|
1079
|
+
if (!reference || !reference.startsWith(prefix)) {
|
|
1080
|
+
return void 0;
|
|
1081
|
+
}
|
|
1082
|
+
const id = reference.slice(prefix.length);
|
|
1083
|
+
return id.length > 0 ? id : void 0;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/data/operations/control/user/user-update-operation.ts
|
|
1078
1087
|
var import_types8 = require("@openhi/types");
|
|
1079
1088
|
|
|
1089
|
+
// src/data/operations/control/workspace/workspace-create-operation.ts
|
|
1090
|
+
var import_types9 = require("@openhi/types");
|
|
1091
|
+
|
|
1080
1092
|
// src/data/dynamo/dynamo-data-service.ts
|
|
1081
1093
|
var import_electrodb10 = require("electrodb");
|
|
1082
1094
|
|
|
@@ -2157,7 +2169,7 @@ async function createWorkspaceOperation(params) {
|
|
|
2157
2169
|
const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
|
|
2158
2170
|
const parsedResource = typeof body.resource === "string" ? JSON.parse(body.resource) : body.resource ?? {};
|
|
2159
2171
|
const resource = { resourceType: "Workspace", id, ...parsedResource };
|
|
2160
|
-
const summary = JSON.stringify((0,
|
|
2172
|
+
const summary = JSON.stringify((0, import_types9.extractSummary)(resource));
|
|
2161
2173
|
await service.entities.workspace.put({
|
|
2162
2174
|
tenantId,
|
|
2163
2175
|
id,
|
|
@@ -2176,20 +2188,11 @@ async function createWorkspaceOperation(params) {
|
|
|
2176
2188
|
return { id, resource, meta: { lastUpdated, versionId: vid } };
|
|
2177
2189
|
}
|
|
2178
2190
|
|
|
2179
|
-
// src/data/operations/fhir-reference.ts
|
|
2180
|
-
function idFromReference(reference, prefix) {
|
|
2181
|
-
if (!reference || !reference.startsWith(prefix)) {
|
|
2182
|
-
return void 0;
|
|
2183
|
-
}
|
|
2184
|
-
const id = reference.slice(prefix.length);
|
|
2185
|
-
return id.length > 0 ? id : void 0;
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
2191
|
// src/workflows/control-plane/user-onboarding/provision-default-workspace.handler.ts
|
|
2189
2192
|
var CURRENT_SK = "CURRENT";
|
|
2190
2193
|
var VID = "1";
|
|
2191
2194
|
var summaryFor = (resource) => {
|
|
2192
|
-
return JSON.stringify((0,
|
|
2195
|
+
return JSON.stringify((0, import_types10.extractSummary)(resource));
|
|
2193
2196
|
};
|
|
2194
2197
|
var stableOnboardingId = (kind, cognitoSub) => {
|
|
2195
2198
|
return (0, import_node_crypto.createHash)("sha256").update(kind).update("\0").update(cognitoSub).digest("hex").slice(0, 26).toUpperCase();
|