@openhi/constructs 0.0.91 → 0.0.93

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts"],"sourcesContent":["import type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\n\n/* Hardcoded OpenHI claims. These will be made dynamic in a future update. */\nconst OPENHI_CLAIMS = {\n ohi_tid: \"placeholder-tenant-id\",\n ohi_wid: \"placeholder-workspace-id\",\n ohi_uid: \"placeholder-user-id\",\n ohi_uname: \"placeholder\",\n} as const;\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 * Adds openhi_* claims to both the ID token and the access token. Invoked on sign-in and token refresh.\n * Token issuance must not fail: on error we return the event unchanged.\n */\nexport const handler: PreTokenGenerationTriggerHandler = async (\n event: PreTokenGenerationTriggerEvent,\n _context: Context,\n): Promise<PreTokenGenerationTriggerEvent> => {\n console.debug(`Raw event=${event}`);\n try {\n if (!event.response) {\n (event as { response: Record<string, unknown> }).response = {};\n }\n const response = event.response as Record<string, unknown>;\n const claimsToAdd = { ...OPENHI_CLAIMS };\n response.claimsAndScopeOverrideDetails = {\n accessTokenGeneration: {\n claimsToAddOrOverride: claimsToAdd,\n },\n idTokenGeneration: {\n claimsToAddOrOverride: claimsToAdd,\n },\n };\n } catch {\n // Do not fail token issuance; return event unchanged.\n console.warn(\"Event is missing tenant or workspace ID...\");\n }\n return event;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;AASO,IAAM,UAA4C,OACvD,OACA,aAC4C;AAC5C,UAAQ,MAAM,aAAa,KAAK,EAAE;AAClC,MAAI;AACF,QAAI,CAAC,MAAM,UAAU;AACnB,MAAC,MAAgD,WAAW,CAAC;AAAA,IAC/D;AACA,UAAM,WAAW,MAAM;AACvB,UAAM,cAAc,EAAE,GAAG,cAAc;AACvC,aAAS,gCAAgC;AAAA,MACvC,uBAAuB;AAAA,QACrB,uBAAuB;AAAA,MACzB;AAAA,MACA,mBAAmB;AAAA,QACjB,uBAAuB;AAAA,MACzB;AAAA,IACF;AAAA,EACF,QAAQ;AAEN,YAAQ,KAAK,4CAA4C;AAAA,EAC3D;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts","../src/data/dynamo/dynamo-control-service.ts","../src/data/dynamo/dynamo-client.ts","../src/data/dynamo/entities/control/configuration-entity.ts","../src/data/dynamo/shard.ts","../src/data/dynamo/entities/control/control-entity-common.ts","../src/data/dynamo/entities/control/membership-entity.ts","../src/data/dynamo/entities/control/role-entity.ts","../src/data/dynamo/entities/control/roleassignment-entity.ts","../src/data/dynamo/entities/control/tenant-entity.ts","../src/data/dynamo/entities/control/user-entity.ts","../src/data/dynamo/entities/control/workspace-entity.ts"],"sourcesContent":["import type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\nimport {\n getDynamoControlService,\n type DynamoControlServiceType,\n} from \"../../data/dynamo/dynamo-control-service\";\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\ninterface UserResource {\n resourceType?: string;\n id?: string;\n displayName?: string;\n currentTenant?: { reference?: string };\n currentWorkspace?: { reference?: string };\n name?: Array<{ text?: string; family?: string }>;\n}\n\nconst REFERENCE_TYPES = {\n Tenant: \"Tenant/\",\n Workspace: \"Workspace/\",\n} as const;\n\nfunction idFromReference(\n reference: string | undefined,\n prefix: string,\n): string | undefined {\n if (!reference || !reference.startsWith(prefix)) return undefined;\n const id = reference.slice(prefix.length);\n return id.length > 0 ? id : undefined;\n}\n\nfunction displayNameFor(user: UserResource): string | undefined {\n const first = user.name?.[0];\n return first?.text ?? first?.family ?? user.displayName ?? undefined;\n}\n\nfunction safeParseUser(json: string): UserResource | undefined {\n try {\n return JSON.parse(json) as UserResource;\n } catch {\n return undefined;\n }\n}\n\ninterface UserRecord {\n id: string;\n cognitoSub?: string;\n resource: string;\n vid: string;\n}\n\nasync function findUserBySub(\n service: DynamoControlServiceType,\n cognitoSub: string,\n): Promise<UserRecord | undefined> {\n const result = await service.entities.user.query\n .gsi2({ cognitoSub })\n .go({ limit: 1 });\n const item = result.data?.[0];\n if (!item) return undefined;\n return {\n id: item.id,\n cognitoSub: item.cognitoSub,\n resource: item.resource,\n vid: item.vid,\n };\n}\n\ninterface ResolvedClaims {\n ohi_tid: string;\n ohi_wid: string;\n ohi_uid: string;\n ohi_uname: string;\n}\n\nasync function resolveClaims(\n cognitoSub: string,\n): Promise<ResolvedClaims | undefined> {\n const service = getDynamoControlService();\n const user = await findUserBySub(service, cognitoSub);\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 = safeParseUser(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/displayName; emitting token without OpenHI claims (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n return {\n ohi_tid: tenantId,\n ohi_wid: workspaceId,\n ohi_uid: user.id,\n ohi_uname: displayName,\n };\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 const claims = await resolveClaims(cognitoSub);\n if (!claims) return event;\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","import { Service } from \"electrodb\";\nimport { defaultTableName, dynamoClient } from \"./dynamo-client\";\nimport { ConfigurationEntity } from \"./entities/control/configuration-entity\";\nimport { MembershipEntity } from \"./entities/control/membership-entity\";\nimport { RoleEntity } from \"./entities/control/role-entity\";\nimport { RoleAssignmentEntity } from \"./entities/control/roleassignment-entity\";\nimport { TenantEntity } from \"./entities/control/tenant-entity\";\nimport { UserEntity } from \"./entities/control/user-entity\";\nimport { WorkspaceEntity } from \"./entities/control/workspace-entity\";\n\n/**\n * Control-plane entities only (service \"control\"). Same table as data plane; use\n * DynamoDataService for data-plane entities.\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n */\n\nconst controlPlaneEntities = {\n configuration: ConfigurationEntity,\n membership: MembershipEntity,\n role: RoleEntity,\n roleAssignment: RoleAssignmentEntity,\n tenant: TenantEntity,\n user: UserEntity,\n workspace: WorkspaceEntity,\n};\n\nconst controlPlaneService = new Service(controlPlaneEntities, {\n table: defaultTableName,\n client: dynamoClient,\n});\n\n/**\n * Control-plane service: entities for configuration and control. Use with the\n * data store table (PK, SK, GSI1; UserEntity also uses GSI2).\n */\nexport const DynamoControlService = {\n entities: controlPlaneService.entities,\n};\n\nexport type DynamoControlServiceType = typeof DynamoControlService;\n\n/**\n * Returns the control-plane service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoControlService(\n tableName?: string,\n): DynamoControlServiceType {\n const resolved = tableName ?? defaultTableName;\n const service = new Service(controlPlaneEntities, {\n table: resolved,\n client: dynamoClient,\n });\n return {\n entities: service.entities,\n };\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nexport const defaultTableName =\n process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nexport const dynamoClient = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Configuration data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Partially tenant-isolated, control plane. Cascade of scope\n * levels: resolution order user → workspace → tenant → baseline. Sentinels: tenantId \"BASELINE\" for\n * baseline tier; workspaceId/userId/roleId \"-\" for absent dimension.\n *\n * Key structure: PK = CONFIG#TID#<tenantId>#WID#<workspaceId>#UID#<userId>#RID#<roleId>,\n * SK = KEY#<key>#SK#<sk>. Uniqueness: one Configuration per (tenantId, workspaceId, userId, roleId, key).\n * Standard attributes and key-building conventions align with single-table design.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Configuration entries in a tenant/workspace\n * across the four shards.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entities/configuration.md\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entity-standards.md — Key-building conventions (keys built inside entity)\n */\nexport const ConfigurationEntity = new Entity({\n model: {\n entity: \"configuration\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant scope. Use \"BASELINE\" when the config is baseline default (no tenant). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n default: \"BASELINE\",\n },\n /** Workspace scope. Use \"-\" when absent. */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** User scope. Use \"-\" when absent. */\n userId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Role scope. Use \"-\" when absent. */\n roleId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Config type (category), e.g. endpoints, branding, display. */\n key: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and for the Configuration resource. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, key, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"userId\", \"roleId\"],\n template:\n \"CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"key\", \"sk\"],\n template: \"KEY#${key}#SK#${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a\n * (tenant, workspace) across the four shards. Use for \"list configs scoped to this tenant\"\n * (workspaceId = \"-\") or \"list configs scoped to this workspace\". Does not support\n * hierarchical resolution in one query; use base table GetItem in fallback order\n * (user → workspace → tenant → baseline) for that.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"workspaceId\", \"gsi1Shard\"],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","/**\n * Shard selection for the data-plane single-table GSI1 partitioning per ADR-011.\n *\n * GSI1's partition key embeds a `SHARD#<n>` segment with `n = computeShard(id)`.\n * The hash is deterministic so updates to the same resource id always land on\n * the same shard (no cross-shard migration on update); reads fan out to all\n * shards in parallel and merge by SK.\n *\n * @see sites/www-docs/content/architecture/adr/ — ADR-011 (single-table DynamoDB)\n */\n\n/** Number of shards in the GSI1 partition key. Fixed at 4 in v1; raising it later is a backfill, not a schema migration. */\nexport const SHARD_COUNT = 4;\n\n/**\n * Returns a deterministic shard index in [0, SHARD_COUNT) for the given resource id.\n *\n * Implementation: 32-bit FNV-1a over the UTF-16 code units of the id, modulo SHARD_COUNT.\n * The function is pure and stable; the same id always returns the same shard.\n *\n * ESLint's `no-bitwise` rule is disabled inside this function because FNV-1a is\n * defined in terms of XOR and unsigned-right-shift — the bitwise ops are the\n * algorithm, not an accidental logical-operator confusion.\n */\nexport function computeShard(id: string): number {\n /* eslint-disable no-bitwise */\n let hash = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n hash ^= id.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) % SHARD_COUNT;\n /* eslint-enable no-bitwise */\n}\n","import { computeShard } from \"../../shard\";\n\n/**\n * Shared GSI1 shard attribute for control-plane entities.\n *\n * Control-plane entities (User, Tenant, Workspace, Membership, Role, RoleAssignment,\n * Configuration) use the same `TID#/WID#/RT#/SHARD#` PK shape on GSI1 as data-plane\n * FHIR resources per ADR-011. The shard index is derived deterministically from `id`\n * via `computeShard` so updates always land on the same shard. Stored as a string\n * because it appears as a literal segment in the GSI1 PK template; the underlying\n * value is 0..3.\n *\n * Not `required` because the value is derived via `watch`/`set`; ElectroDB's\n * required-field check runs before watch propagation, so callers must not fail\n * validation on a derived field.\n */\nexport const gsi1ShardAttribute = {\n type: \"string\" as const,\n watch: [\"id\"] as const,\n set: (_val?: string, item?: { id?: string }) => {\n if (typeof item?.id !== \"string\" || item.id.length === 0) {\n return undefined;\n }\n return String(computeShard(item.id));\n },\n};\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Membership data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Membership links a User\n * to a Tenant (and optionally a Workspace). One record per (tenantId, id).\n *\n * Key structure: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT.\n * Uniqueness: one Membership per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Memberships in a tenant across the four\n * shards. Membership is tenant-scoped (not workspace-scoped), so the GSI1 PK uses `WID#-` as a\n * sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const MembershipEntity = new Entity({\n model: {\n entity: \"membership\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the user has membership (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; membership id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Membership resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#MEMBERSHIP#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the\n * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Role data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. Role is a\n * platform-wide role catalog (e.g. tenant-admin, tenant-user, system-admin); not scoped by tenant.\n * RoleAssignment references Role to assign a role to a User in a Tenant/Workspace context.\n *\n * Key structure: PK = ROLE#ID#<id>, SK = CURRENT.\n * The ROLE# prefix prevents key collisions with other non-tenant-isolated entities (User, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one Role per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Roles across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleEntity = new Entity({\n model: {\n entity: \"role\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; role id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Role resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"ROLE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * RoleAssignment data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. RoleAssignment assigns\n * a Role to a User in a Tenant (and optionally Workspace) context.\n *\n * Key structure: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT.\n * Uniqueness: one RoleAssignment per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all RoleAssignments in a tenant across the four\n * shards. Tenant-scoped only (workspace context lives inside the resource), so the GSI1 PK uses\n * `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleAssignmentEntity = new Entity({\n model: {\n entity: \"roleassignment\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the role assignment applies (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; role assignment id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full RoleAssignment resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#ROLEASSIGNMENT#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the\n * four shards. Tenant-scoped only, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Tenant data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Tenant IS the top scope;\n * the workspace dimension is not applicable and uses the sentinel `TENANT`. The tenant's own `id`\n * is stored as `tenantId` to drive the partition key.\n *\n * Key structure: PK = TENANT#ID#<tenantId>, SK = CURRENT.\n * Uniqueness: one Tenant per tenantId (id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Tenants across the four shards. Tenant has\n * no parent tenant or workspace, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const TenantEntity = new Entity({\n model: {\n entity: \"tenant\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** The tenant's own id (= resource id). Drives the partition key. */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. Equals tenantId. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Tenant resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\"],\n template: \"TENANT#ID#${tenantId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.\n * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`\n * sentinels precede `RT#Tenant#SHARD#<n>`. SK is `<ISO-8601 lastUpdated>#<id>` (control-plane\n * unlabeled per DR-004). `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * User data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. User is a\n * platform-wide identity; association with tenants and workspaces is through Membership and\n * RoleAssignment, not the User entity's own key.\n *\n * Key structure: PK = USER#ID#<id>, SK = CURRENT.\n * The USER# prefix prevents key collisions with other non-tenant-isolated entities (Role, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one User per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Users across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n * GSI2 — Cognito sub-lookup per ADR-011: resolves a UserEntity from a Cognito `sub` claim\n * (`USER#SUB#<cognitoSub>` PK, `CURRENT` SK). The `cognitoSub` attribute is populated by the\n * Post Confirmation Lambda (Epic #765 / #770); kept optional here until that write path lands.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-11-01-user-type-definition-fhir-and-data-layer.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const UserEntity = new Entity({\n model: {\n entity: \"user\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; platform user id (ohi_uid). */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full User resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the\n * Post Confirmation Lambda (#770) lands; required thereafter.\n */\n cognitoSub: {\n type: \"string\" as const,\n required: false,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"USER#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z` characters.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#User#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n\n /**\n * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.\n * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are\n * not indexed.\n */\n gsi2: {\n index: \"GSI2\",\n condition: (attrs: { cognitoSub?: string }) =>\n typeof attrs.cognitoSub === \"string\" && attrs.cognitoSub.length > 0,\n pk: {\n field: \"GSI2PK\",\n casing: \"none\" as const,\n composite: [\"cognitoSub\"],\n template: \"USER#SUB#${cognitoSub}\",\n },\n sk: {\n field: \"GSI2SK\",\n casing: \"none\" as const,\n composite: [],\n template: \"CURRENT\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Workspace data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Each workspace belongs\n * to exactly one tenant; both tenantId and workspace id are in the partition key.\n *\n * Key structure: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT.\n * Uniqueness: one Workspace per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Workspaces in a tenant across the four\n * shards. Workspace is itself the workspace identity, so the GSI1 PK uses `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const WorkspaceEntity = new Entity({\n model: {\n entity: \"workspace\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant that contains this workspace (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Workspace resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the\n * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.\n * SK is `<ISO-8601 lastUpdated>#<id>` (control-plane unlabeled per DR-004).\n * `casing: \"none\"` on the SK preserves ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"lastUpdated\", \"id\"],\n template: \"${lastUpdated}#${id}\",\n },\n },\n },\n});\n"],"mappings":"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","names":["import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb"]}
@@ -1,30 +1,106 @@
1
+ import {
2
+ getDynamoControlService
3
+ } from "./chunk-MLTYFMSE.mjs";
1
4
  import "./chunk-3QS3WKRC.mjs";
2
5
 
3
6
  // src/components/cognito/pre-token-generation.handler.ts
4
- var OPENHI_CLAIMS = {
5
- ohi_tid: "placeholder-tenant-id",
6
- ohi_wid: "placeholder-workspace-id",
7
- ohi_uid: "placeholder-user-id",
8
- ohi_uname: "placeholder"
7
+ var REFERENCE_TYPES = {
8
+ Tenant: "Tenant/",
9
+ Workspace: "Workspace/"
9
10
  };
11
+ function idFromReference(reference, prefix) {
12
+ if (!reference || !reference.startsWith(prefix)) return void 0;
13
+ const id = reference.slice(prefix.length);
14
+ return id.length > 0 ? id : void 0;
15
+ }
16
+ function displayNameFor(user) {
17
+ const first = user.name?.[0];
18
+ return first?.text ?? first?.family ?? user.displayName ?? void 0;
19
+ }
20
+ function safeParseUser(json) {
21
+ try {
22
+ return JSON.parse(json);
23
+ } catch {
24
+ return void 0;
25
+ }
26
+ }
27
+ async function findUserBySub(service, cognitoSub) {
28
+ const result = await service.entities.user.query.gsi2({ cognitoSub }).go({ limit: 1 });
29
+ const item = result.data?.[0];
30
+ if (!item) return void 0;
31
+ return {
32
+ id: item.id,
33
+ cognitoSub: item.cognitoSub,
34
+ resource: item.resource,
35
+ vid: item.vid
36
+ };
37
+ }
38
+ async function resolveClaims(cognitoSub) {
39
+ const service = getDynamoControlService();
40
+ const user = await findUserBySub(service, cognitoSub);
41
+ if (!user) {
42
+ console.warn(
43
+ `PreTokenGeneration: no User found for cognitoSub; emitting token without OpenHI claims (sub=${cognitoSub})`
44
+ );
45
+ return void 0;
46
+ }
47
+ const parsed = safeParseUser(user.resource);
48
+ if (!parsed) {
49
+ console.warn(
50
+ `PreTokenGeneration: User resource JSON could not be parsed (sub=${cognitoSub}, id=${user.id})`
51
+ );
52
+ return void 0;
53
+ }
54
+ const tenantId = idFromReference(
55
+ parsed.currentTenant?.reference,
56
+ REFERENCE_TYPES.Tenant
57
+ );
58
+ const workspaceId = idFromReference(
59
+ parsed.currentWorkspace?.reference,
60
+ REFERENCE_TYPES.Workspace
61
+ );
62
+ const displayName = displayNameFor(parsed);
63
+ if (!tenantId || !workspaceId || !displayName) {
64
+ console.warn(
65
+ `PreTokenGeneration: resolved User missing currentTenant/currentWorkspace/displayName; emitting token without OpenHI claims (sub=${cognitoSub}, id=${user.id})`
66
+ );
67
+ return void 0;
68
+ }
69
+ return {
70
+ ohi_tid: tenantId,
71
+ ohi_wid: workspaceId,
72
+ ohi_uid: user.id,
73
+ ohi_uname: displayName
74
+ };
75
+ }
10
76
  var handler = async (event, _context) => {
11
- console.debug(`Raw event=${event}`);
12
77
  try {
78
+ const cognitoSub = event.request?.userAttributes?.sub;
79
+ if (!cognitoSub) {
80
+ console.warn(
81
+ "PreTokenGeneration: event has no Cognito sub; returning event unchanged"
82
+ );
83
+ return event;
84
+ }
85
+ const claims = await resolveClaims(cognitoSub);
86
+ if (!claims) return event;
13
87
  if (!event.response) {
14
88
  event.response = {};
15
89
  }
16
90
  const response = event.response;
17
- const claimsToAdd = { ...OPENHI_CLAIMS };
18
91
  response.claimsAndScopeOverrideDetails = {
19
92
  accessTokenGeneration: {
20
- claimsToAddOrOverride: claimsToAdd
93
+ claimsToAddOrOverride: { ...claims }
21
94
  },
22
95
  idTokenGeneration: {
23
- claimsToAddOrOverride: claimsToAdd
96
+ claimsToAddOrOverride: { ...claims }
24
97
  }
25
98
  };
26
- } catch {
27
- console.warn("Event is missing tenant or workspace ID...");
99
+ } catch (err) {
100
+ console.warn(
101
+ "PreTokenGeneration: unexpected error; returning event unchanged",
102
+ err
103
+ );
28
104
  }
29
105
  return event;
30
106
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts"],"sourcesContent":["import type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\n\n/* Hardcoded OpenHI claims. These will be made dynamic in a future update. */\nconst OPENHI_CLAIMS = {\n ohi_tid: \"placeholder-tenant-id\",\n ohi_wid: \"placeholder-workspace-id\",\n ohi_uid: \"placeholder-user-id\",\n ohi_uname: \"placeholder\",\n} as const;\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 * Adds openhi_* claims to both the ID token and the access token. Invoked on sign-in and token refresh.\n * Token issuance must not fail: on error we return the event unchanged.\n */\nexport const handler: PreTokenGenerationTriggerHandler = async (\n event: PreTokenGenerationTriggerEvent,\n _context: Context,\n): Promise<PreTokenGenerationTriggerEvent> => {\n console.debug(`Raw event=${event}`);\n try {\n if (!event.response) {\n (event as { response: Record<string, unknown> }).response = {};\n }\n const response = event.response as Record<string, unknown>;\n const claimsToAdd = { ...OPENHI_CLAIMS };\n response.claimsAndScopeOverrideDetails = {\n accessTokenGeneration: {\n claimsToAddOrOverride: claimsToAdd,\n },\n idTokenGeneration: {\n claimsToAddOrOverride: claimsToAdd,\n },\n };\n } catch {\n // Do not fail token issuance; return event unchanged.\n console.warn(\"Event is missing tenant or workspace ID...\");\n }\n return event;\n};\n"],"mappings":";;;AAOA,IAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACb;AASO,IAAM,UAA4C,OACvD,OACA,aAC4C;AAC5C,UAAQ,MAAM,aAAa,KAAK,EAAE;AAClC,MAAI;AACF,QAAI,CAAC,MAAM,UAAU;AACnB,MAAC,MAAgD,WAAW,CAAC;AAAA,IAC/D;AACA,UAAM,WAAW,MAAM;AACvB,UAAM,cAAc,EAAE,GAAG,cAAc;AACvC,aAAS,gCAAgC;AAAA,MACvC,uBAAuB;AAAA,QACrB,uBAAuB;AAAA,MACzB;AAAA,MACA,mBAAmB;AAAA,QACjB,uBAAuB;AAAA,MACzB;AAAA,IACF;AAAA,EACF,QAAQ;AAEN,YAAQ,KAAK,4CAA4C;AAAA,EAC3D;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/components/cognito/pre-token-generation.handler.ts"],"sourcesContent":["import type {\n Context,\n PreTokenGenerationTriggerEvent,\n PreTokenGenerationTriggerHandler,\n} from \"aws-lambda\";\nimport {\n getDynamoControlService,\n type DynamoControlServiceType,\n} from \"../../data/dynamo/dynamo-control-service\";\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\ninterface UserResource {\n resourceType?: string;\n id?: string;\n displayName?: string;\n currentTenant?: { reference?: string };\n currentWorkspace?: { reference?: string };\n name?: Array<{ text?: string; family?: string }>;\n}\n\nconst REFERENCE_TYPES = {\n Tenant: \"Tenant/\",\n Workspace: \"Workspace/\",\n} as const;\n\nfunction idFromReference(\n reference: string | undefined,\n prefix: string,\n): string | undefined {\n if (!reference || !reference.startsWith(prefix)) return undefined;\n const id = reference.slice(prefix.length);\n return id.length > 0 ? id : undefined;\n}\n\nfunction displayNameFor(user: UserResource): string | undefined {\n const first = user.name?.[0];\n return first?.text ?? first?.family ?? user.displayName ?? undefined;\n}\n\nfunction safeParseUser(json: string): UserResource | undefined {\n try {\n return JSON.parse(json) as UserResource;\n } catch {\n return undefined;\n }\n}\n\ninterface UserRecord {\n id: string;\n cognitoSub?: string;\n resource: string;\n vid: string;\n}\n\nasync function findUserBySub(\n service: DynamoControlServiceType,\n cognitoSub: string,\n): Promise<UserRecord | undefined> {\n const result = await service.entities.user.query\n .gsi2({ cognitoSub })\n .go({ limit: 1 });\n const item = result.data?.[0];\n if (!item) return undefined;\n return {\n id: item.id,\n cognitoSub: item.cognitoSub,\n resource: item.resource,\n vid: item.vid,\n };\n}\n\ninterface ResolvedClaims {\n ohi_tid: string;\n ohi_wid: string;\n ohi_uid: string;\n ohi_uname: string;\n}\n\nasync function resolveClaims(\n cognitoSub: string,\n): Promise<ResolvedClaims | undefined> {\n const service = getDynamoControlService();\n const user = await findUserBySub(service, cognitoSub);\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 = safeParseUser(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/displayName; emitting token without OpenHI claims (sub=${cognitoSub}, id=${user.id})`,\n );\n return undefined;\n }\n\n return {\n ohi_tid: tenantId,\n ohi_wid: workspaceId,\n ohi_uid: user.id,\n ohi_uname: displayName,\n };\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 const claims = await resolveClaims(cognitoSub);\n if (!claims) return event;\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":";;;;;;AA0CA,IAAM,kBAAkB;AAAA,EACtB,QAAQ;AAAA,EACR,WAAW;AACb;AAEA,SAAS,gBACP,WACA,QACoB;AACpB,MAAI,CAAC,aAAa,CAAC,UAAU,WAAW,MAAM,EAAG,QAAO;AACxD,QAAM,KAAK,UAAU,MAAM,OAAO,MAAM;AACxC,SAAO,GAAG,SAAS,IAAI,KAAK;AAC9B;AAEA,SAAS,eAAe,MAAwC;AAC9D,QAAM,QAAQ,KAAK,OAAO,CAAC;AAC3B,SAAO,OAAO,QAAQ,OAAO,UAAU,KAAK,eAAe;AAC7D;AAEA,SAAS,cAAc,MAAwC;AAC7D,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAe,cACb,SACA,YACiC;AACjC,QAAM,SAAS,MAAM,QAAQ,SAAS,KAAK,MACxC,KAAK,EAAE,WAAW,CAAC,EACnB,GAAG,EAAE,OAAO,EAAE,CAAC;AAClB,QAAM,OAAO,OAAO,OAAO,CAAC;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,KAAK,KAAK;AAAA,EACZ;AACF;AASA,eAAe,cACb,YACqC;AACrC,QAAM,UAAU,wBAAwB;AACxC,QAAM,OAAO,MAAM,cAAc,SAAS,UAAU;AACpD,MAAI,CAAC,MAAM;AACT,YAAQ;AAAA,MACN,+FAA+F,UAAU;AAAA,IAC3G;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,cAAc,KAAK,QAAQ;AAC1C,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN,mEAAmE,UAAU,QAAQ,KAAK,EAAE;AAAA,IAC9F;AACA,WAAO;AAAA,EACT;AAEA,QAAM,WAAW;AAAA,IACf,OAAO,eAAe;AAAA,IACtB,gBAAgB;AAAA,EAClB;AACA,QAAM,cAAc;AAAA,IAClB,OAAO,kBAAkB;AAAA,IACzB,gBAAgB;AAAA,EAClB;AACA,QAAM,cAAc,eAAe,MAAM;AAEzC,MAAI,CAAC,YAAY,CAAC,eAAe,CAAC,aAAa;AAC7C,YAAQ;AAAA,MACN,mIAAmI,UAAU,QAAQ,KAAK,EAAE;AAAA,IAC9J;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,KAAK;AAAA,IACd,WAAW;AAAA,EACb;AACF;AAEO,IAAM,UAA4C,OACvD,OACA,aAC4C;AAC5C,MAAI;AACF,UAAM,aAAa,MAAM,SAAS,gBAAgB;AAClD,QAAI,CAAC,YAAY;AACf,cAAQ;AAAA,QACN;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,MAAM,cAAc,UAAU;AAC7C,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,CAAC,MAAM,UAAU;AACnB,MAAC,MAAgD,WAAW,CAAC;AAAA,IAC/D;AACA,UAAM,WAAW,MAAM;AACvB,aAAS,gCAAgC;AAAA,MACvC,uBAAuB;AAAA,QACrB,uBAAuB,EAAE,GAAG,OAAO;AAAA,MACrC;AAAA,MACA,mBAAmB;AAAA,QACjB,uBAAuB,EAAE,GAAG,OAAO;AAAA,MACrC;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AAEZ,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}