@de-otio/trellis 0.6.1 → 0.7.1
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/dist/env.d.ts +21 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -1
- package/dist/lambda/nightly-cron.d.ts.map +1 -1
- package/dist/lambda/nightly-cron.js +5 -2
- package/dist/lambda/nightly-cron.js.map +1 -1
- package/dist/lambda/post-confirmation.d.ts +30 -0
- package/dist/lambda/post-confirmation.d.ts.map +1 -1
- package/dist/lambda/post-confirmation.js +333 -29
- package/dist/lambda/post-confirmation.js.map +1 -1
- package/dist/lambda/pre-token-generation.d.ts +20 -0
- package/dist/lambda/pre-token-generation.d.ts.map +1 -1
- package/dist/lambda/pre-token-generation.js +233 -48
- package/dist/lambda/pre-token-generation.js.map +1 -1
- package/dist/lib/activitypub/activity-processor.d.ts.map +1 -1
- package/dist/lib/activitypub/activity-processor.js +2 -1
- package/dist/lib/activitypub/activity-processor.js.map +1 -1
- package/dist/lib/activitypub/group-service.d.ts +2 -2
- package/dist/lib/activitypub/group-service.d.ts.map +1 -1
- package/dist/lib/activitypub/group-service.js +5 -2
- package/dist/lib/activitypub/group-service.js.map +1 -1
- package/dist/lib/age-tier-transition.d.ts.map +1 -1
- package/dist/lib/age-tier-transition.js +19 -10
- package/dist/lib/age-tier-transition.js.map +1 -1
- package/dist/lib/audit/csv-export.d.ts +25 -0
- package/dist/lib/audit/csv-export.d.ts.map +1 -0
- package/dist/lib/audit/csv-export.js +54 -0
- package/dist/lib/audit/csv-export.js.map +1 -0
- package/dist/lib/audit/emit.d.ts +56 -0
- package/dist/lib/audit/emit.d.ts.map +1 -0
- package/dist/lib/audit/emit.js +124 -0
- package/dist/lib/audit/emit.js.map +1 -0
- package/dist/lib/audit/event-types.d.ts +36 -0
- package/dist/lib/audit/event-types.d.ts.map +1 -0
- package/dist/lib/audit/event-types.js +69 -0
- package/dist/lib/audit/event-types.js.map +1 -0
- package/dist/lib/audit/pii-filter.d.ts +22 -0
- package/dist/lib/audit/pii-filter.d.ts.map +1 -0
- package/dist/lib/audit/pii-filter.js +51 -0
- package/dist/lib/audit/pii-filter.js.map +1 -0
- package/dist/lib/audit-logger.js +1 -1
- package/dist/lib/audit-logger.js.map +1 -1
- package/dist/lib/auth/auth-context.d.ts +34 -0
- package/dist/lib/auth/auth-context.d.ts.map +1 -0
- package/dist/lib/auth/auth-context.js +10 -0
- package/dist/lib/auth/auth-context.js.map +1 -0
- package/dist/lib/auth/auth-middleware.d.ts +50 -0
- package/dist/lib/auth/auth-middleware.d.ts.map +1 -0
- package/dist/lib/auth/auth-middleware.js +153 -0
- package/dist/lib/auth/auth-middleware.js.map +1 -0
- package/dist/lib/auth/capabilities.d.ts +40 -0
- package/dist/lib/auth/capabilities.d.ts.map +1 -0
- package/dist/lib/auth/capabilities.js +44 -0
- package/dist/lib/auth/capabilities.js.map +1 -0
- package/dist/lib/auth/claims-cache.d.ts +70 -0
- package/dist/lib/auth/claims-cache.d.ts.map +1 -0
- package/dist/lib/auth/claims-cache.js +139 -0
- package/dist/lib/auth/claims-cache.js.map +1 -0
- package/dist/lib/auth/cognito-jwt.d.ts +6 -0
- package/dist/lib/auth/cognito-jwt.d.ts.map +1 -1
- package/dist/lib/auth/cognito-jwt.js.map +1 -1
- package/dist/lib/auth/idp-redirect-builder.d.ts +43 -0
- package/dist/lib/auth/idp-redirect-builder.d.ts.map +1 -0
- package/dist/lib/auth/idp-redirect-builder.js +48 -0
- package/dist/lib/auth/idp-redirect-builder.js.map +1 -0
- package/dist/lib/auth/require.d.ts +51 -0
- package/dist/lib/auth/require.d.ts.map +1 -0
- package/dist/lib/auth/require.js +99 -0
- package/dist/lib/auth/require.js.map +1 -0
- package/dist/lib/auth/role-grants.d.ts +18 -0
- package/dist/lib/auth/role-grants.d.ts.map +1 -0
- package/dist/lib/auth/role-grants.js +62 -0
- package/dist/lib/auth/role-grants.js.map +1 -0
- package/dist/lib/cognito/idp-sdk.d.ts +80 -0
- package/dist/lib/cognito/idp-sdk.d.ts.map +1 -0
- package/dist/lib/cognito/idp-sdk.js +186 -0
- package/dist/lib/cognito/idp-sdk.js.map +1 -0
- package/dist/lib/cognito/issuer-probe.d.ts +47 -0
- package/dist/lib/cognito/issuer-probe.d.ts.map +1 -0
- package/dist/lib/cognito/issuer-probe.js +319 -0
- package/dist/lib/cognito/issuer-probe.js.map +1 -0
- package/dist/lib/comment-handler.d.ts +7 -7
- package/dist/lib/comment-handler.d.ts.map +1 -1
- package/dist/lib/comment-handler.js +23 -20
- package/dist/lib/comment-handler.js.map +1 -1
- package/dist/lib/compliance/baseline.d.ts +15 -0
- package/dist/lib/compliance/baseline.d.ts.map +1 -0
- package/dist/lib/compliance/baseline.js +205 -0
- package/dist/lib/compliance/baseline.js.map +1 -0
- package/dist/lib/compliance/tenant-merge.d.ts +35 -0
- package/dist/lib/compliance/tenant-merge.d.ts.map +1 -0
- package/dist/lib/compliance/tenant-merge.js +80 -0
- package/dist/lib/compliance/tenant-merge.js.map +1 -0
- package/dist/lib/compliance/types.d.ts +135 -0
- package/dist/lib/compliance/types.d.ts.map +1 -0
- package/dist/lib/compliance/types.js +9 -0
- package/dist/lib/compliance/types.js.map +1 -0
- package/dist/lib/connection-code-handler.d.ts +4 -4
- package/dist/lib/connection-code-handler.d.ts.map +1 -1
- package/dist/lib/connection-code-handler.js +21 -11
- package/dist/lib/connection-code-handler.js.map +1 -1
- package/dist/lib/feed-handler.d.ts +2 -2
- package/dist/lib/feed-handler.d.ts.map +1 -1
- package/dist/lib/feed-handler.js +5 -9
- package/dist/lib/feed-handler.js.map +1 -1
- package/dist/lib/middleware/idempotency-store.d.ts +86 -0
- package/dist/lib/middleware/idempotency-store.d.ts.map +1 -0
- package/dist/lib/middleware/idempotency-store.js +109 -0
- package/dist/lib/middleware/idempotency-store.js.map +1 -0
- package/dist/lib/middleware/idempotency.d.ts +37 -0
- package/dist/lib/middleware/idempotency.d.ts.map +1 -0
- package/dist/lib/middleware/idempotency.js +358 -0
- package/dist/lib/middleware/idempotency.js.map +1 -0
- package/dist/lib/net/trusted-client-ip.d.ts +39 -0
- package/dist/lib/net/trusted-client-ip.d.ts.map +1 -0
- package/dist/lib/net/trusted-client-ip.js +100 -0
- package/dist/lib/net/trusted-client-ip.js.map +1 -0
- package/dist/lib/notification-handler.d.ts +5 -5
- package/dist/lib/notification-handler.d.ts.map +1 -1
- package/dist/lib/notification-handler.js +11 -9
- package/dist/lib/notification-handler.js.map +1 -1
- package/dist/lib/oauth/cognito-issuer.d.ts +34 -0
- package/dist/lib/oauth/cognito-issuer.d.ts.map +1 -0
- package/dist/lib/oauth/cognito-issuer.js +53 -0
- package/dist/lib/oauth/cognito-issuer.js.map +1 -0
- package/dist/lib/oauth/device-authorization.d.ts +145 -0
- package/dist/lib/oauth/device-authorization.d.ts.map +1 -0
- package/dist/lib/oauth/device-authorization.js +312 -0
- package/dist/lib/oauth/device-authorization.js.map +1 -0
- package/dist/lib/oauth/envelope-crypto.d.ts +101 -0
- package/dist/lib/oauth/envelope-crypto.d.ts.map +1 -0
- package/dist/lib/oauth/envelope-crypto.js +223 -0
- package/dist/lib/oauth/envelope-crypto.js.map +1 -0
- package/dist/lib/oauth/refresh-detection.d.ts +126 -0
- package/dist/lib/oauth/refresh-detection.d.ts.map +1 -0
- package/dist/lib/oauth/refresh-detection.js +248 -0
- package/dist/lib/oauth/refresh-detection.js.map +1 -0
- package/dist/lib/openapi/generator.d.ts +78 -0
- package/dist/lib/openapi/generator.d.ts.map +1 -0
- package/dist/lib/openapi/generator.js +201 -0
- package/dist/lib/openapi/generator.js.map +1 -0
- package/dist/lib/post-handler.d.ts +1 -1
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +4 -15
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/rate-limit.d.ts.map +1 -1
- package/dist/lib/rate-limit.js +11 -3
- package/dist/lib/rate-limit.js.map +1 -1
- package/dist/lib/routes/agent-authorize.d.ts +32 -0
- package/dist/lib/routes/agent-authorize.d.ts.map +1 -0
- package/dist/lib/routes/agent-authorize.js +479 -0
- package/dist/lib/routes/agent-authorize.js.map +1 -0
- package/dist/lib/routes/agent-sessions.d.ts +20 -0
- package/dist/lib/routes/agent-sessions.d.ts.map +1 -0
- package/dist/lib/routes/agent-sessions.js +124 -0
- package/dist/lib/routes/agent-sessions.js.map +1 -0
- package/dist/lib/routes/agent-surface.d.ts +37 -0
- package/dist/lib/routes/agent-surface.d.ts.map +1 -0
- package/dist/lib/routes/agent-surface.js +208 -0
- package/dist/lib/routes/agent-surface.js.map +1 -0
- package/dist/lib/routes/auth-discover.d.ts +18 -0
- package/dist/lib/routes/auth-discover.d.ts.map +1 -0
- package/dist/lib/routes/auth-discover.js +177 -0
- package/dist/lib/routes/auth-discover.js.map +1 -0
- package/dist/lib/routes/comments.d.ts.map +1 -1
- package/dist/lib/routes/comments.js +36 -7
- package/dist/lib/routes/comments.js.map +1 -1
- package/dist/lib/routes/connection-codes.d.ts.map +1 -1
- package/dist/lib/routes/connection-codes.js +21 -4
- package/dist/lib/routes/connection-codes.js.map +1 -1
- package/dist/lib/routes/content-discovery.d.ts.map +1 -1
- package/dist/lib/routes/content-discovery.js +18 -13
- package/dist/lib/routes/content-discovery.js.map +1 -1
- package/dist/lib/routes/dashboard.js +1 -1
- package/dist/lib/routes/dashboard.js.map +1 -1
- package/dist/lib/routes/employees.d.ts.map +1 -1
- package/dist/lib/routes/employees.js +57 -15
- package/dist/lib/routes/employees.js.map +1 -1
- package/dist/lib/routes/entities.d.ts.map +1 -1
- package/dist/lib/routes/entities.js +35 -19
- package/dist/lib/routes/entities.js.map +1 -1
- package/dist/lib/routes/errors.d.ts +34 -0
- package/dist/lib/routes/errors.d.ts.map +1 -0
- package/dist/lib/routes/errors.js +57 -0
- package/dist/lib/routes/errors.js.map +1 -0
- package/dist/lib/routes/feeds.d.ts.map +1 -1
- package/dist/lib/routes/feeds.js +12 -2
- package/dist/lib/routes/feeds.js.map +1 -1
- package/dist/lib/routes/index.d.ts.map +1 -1
- package/dist/lib/routes/index.js +50 -0
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/mfa.d.ts.map +1 -1
- package/dist/lib/routes/mfa.js +1 -0
- package/dist/lib/routes/mfa.js.map +1 -1
- package/dist/lib/routes/notifications.d.ts.map +1 -1
- package/dist/lib/routes/notifications.js +21 -4
- package/dist/lib/routes/notifications.js.map +1 -1
- package/dist/lib/routes/oauth.d.ts +15 -0
- package/dist/lib/routes/oauth.d.ts.map +1 -0
- package/dist/lib/routes/oauth.js +139 -0
- package/dist/lib/routes/oauth.js.map +1 -0
- package/dist/lib/routes/posts.d.ts.map +1 -1
- package/dist/lib/routes/posts.js +30 -19
- package/dist/lib/routes/posts.js.map +1 -1
- package/dist/lib/routes/products.d.ts.map +1 -1
- package/dist/lib/routes/products.js +19 -22
- package/dist/lib/routes/products.js.map +1 -1
- package/dist/lib/routes/setup-status.d.ts +34 -0
- package/dist/lib/routes/setup-status.d.ts.map +1 -0
- package/dist/lib/routes/setup-status.js +87 -0
- package/dist/lib/routes/setup-status.js.map +1 -0
- package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -1
- package/dist/lib/routes/taxonomy-analytics.js +15 -14
- package/dist/lib/routes/taxonomy-analytics.js.map +1 -1
- package/dist/lib/routes/taxonomy.d.ts.map +1 -1
- package/dist/lib/routes/taxonomy.js +19 -16
- package/dist/lib/routes/taxonomy.js.map +1 -1
- package/dist/lib/routes/tenant-audit.d.ts +19 -0
- package/dist/lib/routes/tenant-audit.d.ts.map +1 -0
- package/dist/lib/routes/tenant-audit.js +244 -0
- package/dist/lib/routes/tenant-audit.js.map +1 -0
- package/dist/lib/routes/tenant-compliance.d.ts +21 -0
- package/dist/lib/routes/tenant-compliance.d.ts.map +1 -0
- package/dist/lib/routes/tenant-compliance.js +122 -0
- package/dist/lib/routes/tenant-compliance.js.map +1 -0
- package/dist/lib/routes/tenant-domains.d.ts +11 -0
- package/dist/lib/routes/tenant-domains.d.ts.map +1 -0
- package/dist/lib/routes/tenant-domains.js +95 -0
- package/dist/lib/routes/tenant-domains.js.map +1 -0
- package/dist/lib/routes/tenant-idp.d.ts +3 -0
- package/dist/lib/routes/tenant-idp.d.ts.map +1 -0
- package/dist/lib/routes/tenant-idp.js +89 -0
- package/dist/lib/routes/tenant-idp.js.map +1 -0
- package/dist/lib/routes/tenant-members.d.ts +13 -0
- package/dist/lib/routes/tenant-members.d.ts.map +1 -0
- package/dist/lib/routes/tenant-members.js +75 -0
- package/dist/lib/routes/tenant-members.js.map +1 -0
- package/dist/lib/routes/tenant-role-mappings.d.ts +11 -0
- package/dist/lib/routes/tenant-role-mappings.d.ts.map +1 -0
- package/dist/lib/routes/tenant-role-mappings.js +90 -0
- package/dist/lib/routes/tenant-role-mappings.js.map +1 -0
- package/dist/lib/routes/tenants.d.ts +13 -0
- package/dist/lib/routes/tenants.d.ts.map +1 -0
- package/dist/lib/routes/tenants.js +121 -0
- package/dist/lib/routes/tenants.js.map +1 -0
- package/dist/lib/routes/types.d.ts +9 -0
- package/dist/lib/routes/types.d.ts.map +1 -1
- package/dist/lib/schemas.d.ts +2 -2
- package/dist/lib/secrets/idp-secrets.d.ts +51 -0
- package/dist/lib/secrets/idp-secrets.d.ts.map +1 -0
- package/dist/lib/secrets/idp-secrets.js +111 -0
- package/dist/lib/secrets/idp-secrets.js.map +1 -0
- package/dist/lib/security-monitor.d.ts.map +1 -1
- package/dist/lib/security-monitor.js +6 -1
- package/dist/lib/security-monitor.js.map +1 -1
- package/dist/lib/session-manager.d.ts +1 -0
- package/dist/lib/session-manager.d.ts.map +1 -1
- package/dist/lib/session-manager.js.map +1 -1
- package/dist/lib/taxonomy-handler-factory.d.ts +4 -2
- package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -1
- package/dist/lib/taxonomy-handler-factory.js +8 -7
- package/dist/lib/taxonomy-handler-factory.js.map +1 -1
- package/dist/lib/tenant/audit-emit.d.ts +18 -0
- package/dist/lib/tenant/audit-emit.d.ts.map +1 -0
- package/dist/lib/tenant/audit-emit.js +16 -0
- package/dist/lib/tenant/audit-emit.js.map +1 -0
- package/dist/lib/tenant/derive-domain.d.ts +19 -0
- package/dist/lib/tenant/derive-domain.d.ts.map +1 -0
- package/dist/lib/tenant/derive-domain.js +38 -0
- package/dist/lib/tenant/derive-domain.js.map +1 -0
- package/dist/lib/tenant/domain-handler.d.ts +42 -0
- package/dist/lib/tenant/domain-handler.d.ts.map +1 -0
- package/dist/lib/tenant/domain-handler.js +344 -0
- package/dist/lib/tenant/domain-handler.js.map +1 -0
- package/dist/lib/tenant/domain-validator.d.ts +28 -0
- package/dist/lib/tenant/domain-validator.d.ts.map +1 -0
- package/dist/lib/tenant/domain-validator.js +145 -0
- package/dist/lib/tenant/domain-validator.js.map +1 -0
- package/dist/lib/tenant/domain-verifier.d.ts +30 -0
- package/dist/lib/tenant/domain-verifier.d.ts.map +1 -0
- package/dist/lib/tenant/domain-verifier.js +53 -0
- package/dist/lib/tenant/domain-verifier.js.map +1 -0
- package/dist/lib/tenant/idp-handler.d.ts +29 -0
- package/dist/lib/tenant/idp-handler.d.ts.map +1 -0
- package/dist/lib/tenant/idp-handler.js +693 -0
- package/dist/lib/tenant/idp-handler.js.map +1 -0
- package/dist/lib/tenant/idp-name.d.ts +2 -0
- package/dist/lib/tenant/idp-name.d.ts.map +1 -0
- package/dist/lib/tenant/idp-name.js +20 -0
- package/dist/lib/tenant/idp-name.js.map +1 -0
- package/dist/lib/tenant/member-handler.d.ts +31 -0
- package/dist/lib/tenant/member-handler.d.ts.map +1 -0
- package/dist/lib/tenant/member-handler.js +343 -0
- package/dist/lib/tenant/member-handler.js.map +1 -0
- package/dist/lib/tenant/reserved-slugs.d.ts +37 -0
- package/dist/lib/tenant/reserved-slugs.d.ts.map +1 -0
- package/dist/lib/tenant/reserved-slugs.js +116 -0
- package/dist/lib/tenant/reserved-slugs.js.map +1 -0
- package/dist/lib/tenant/resolve-role.d.ts +39 -0
- package/dist/lib/tenant/resolve-role.d.ts.map +1 -0
- package/dist/lib/tenant/resolve-role.js +60 -0
- package/dist/lib/tenant/resolve-role.js.map +1 -0
- package/dist/lib/tenant/role-mapping-handler.d.ts +26 -0
- package/dist/lib/tenant/role-mapping-handler.d.ts.map +1 -0
- package/dist/lib/tenant/role-mapping-handler.js +260 -0
- package/dist/lib/tenant/role-mapping-handler.js.map +1 -0
- package/dist/lib/tenant/setup-status.d.ts +83 -0
- package/dist/lib/tenant/setup-status.d.ts.map +1 -0
- package/dist/lib/tenant/setup-status.js +201 -0
- package/dist/lib/tenant/setup-status.js.map +1 -0
- package/dist/lib/tenant/slug-validator.d.ts +31 -0
- package/dist/lib/tenant/slug-validator.d.ts.map +1 -0
- package/dist/lib/tenant/slug-validator.js +42 -0
- package/dist/lib/tenant/slug-validator.js.map +1 -0
- package/dist/lib/tenant/tenant-handler.d.ts +49 -0
- package/dist/lib/tenant/tenant-handler.d.ts.map +1 -0
- package/dist/lib/tenant/tenant-handler.js +377 -0
- package/dist/lib/tenant/tenant-handler.js.map +1 -0
- package/dist/lib/tenant/transfer-ownership.d.ts +39 -0
- package/dist/lib/tenant/transfer-ownership.d.ts.map +1 -0
- package/dist/lib/tenant/transfer-ownership.js +66 -0
- package/dist/lib/tenant/transfer-ownership.js.map +1 -0
- package/dist/lib/user/derive-handle.d.ts +29 -0
- package/dist/lib/user/derive-handle.d.ts.map +1 -0
- package/dist/lib/user/derive-handle.js +65 -0
- package/dist/lib/user/derive-handle.js.map +1 -0
- package/dist/lib/user-deprovisioning.d.ts +11 -1
- package/dist/lib/user-deprovisioning.d.ts.map +1 -1
- package/dist/lib/user-deprovisioning.js +46 -2
- package/dist/lib/user-deprovisioning.js.map +1 -1
- package/dist/lib/validation/feature-toggle-schemas.d.ts +10 -10
- package/package.json +7 -5
- package/prisma/migrations/20260502094501_add_tenancy_model/migration.sql +334 -0
- package/prisma/migrations/20260503000000_add_tenant_region/migration.sql +4 -0
- package/prisma/schema.prisma +324 -74
- package/src/lambda/nightly-cron.ts +4 -1
- package/src/lambda/post-confirmation.ts +405 -29
- package/src/lambda/pre-token-generation.ts +300 -59
|
@@ -296,13 +296,15 @@ export const handler = async (): Promise<void> => {
|
|
|
296
296
|
where: {
|
|
297
297
|
dateOfBirth: { not: null },
|
|
298
298
|
suspended: false,
|
|
299
|
+
personalTenantId: { not: null },
|
|
299
300
|
},
|
|
300
|
-
select: { id: true, ageTier: true },
|
|
301
|
+
select: { id: true, ageTier: true, personalTenantId: true },
|
|
301
302
|
take: 500, // Circuit breaker
|
|
302
303
|
});
|
|
303
304
|
|
|
304
305
|
let digestCount = 0;
|
|
305
306
|
for (const user of usersWithDigest) {
|
|
307
|
+
if (!user.personalTenantId) continue;
|
|
306
308
|
try {
|
|
307
309
|
const digest = await generateSentimentDigest(user.id, since, appEnv);
|
|
308
310
|
if (digest.posts.length > 0) {
|
|
@@ -318,6 +320,7 @@ export const handler = async (): Promise<void> => {
|
|
|
318
320
|
body,
|
|
319
321
|
{ postCount: digest.posts.length },
|
|
320
322
|
appEnv,
|
|
323
|
+
user.personalTenantId,
|
|
321
324
|
);
|
|
322
325
|
digestCount++;
|
|
323
326
|
}
|
|
@@ -1,20 +1,76 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Cognito PostConfirmation trigger.
|
|
3
|
+
*
|
|
4
|
+
* Fires once per user-pool record after Cognito accepts a sign-up
|
|
5
|
+
* (`PostConfirmation_ConfirmSignUp`) or a forgotten-password confirmation
|
|
6
|
+
* (`PostConfirmation_ConfirmForgotPassword`). For federated identities the
|
|
7
|
+
* same trigger source is `PostConfirmation_ConfirmSignUp`; the
|
|
8
|
+
* `request.userAttributes.identities` JSON string is the disambiguator.
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities (atomic, single Prisma transaction):
|
|
11
|
+
* 1. Upsert the `User` row (link `cognitoSub` to an existing email match,
|
|
12
|
+
* otherwise create with a derived handle).
|
|
13
|
+
* 2. Ensure a personal `Tenant` of `type=PERSONAL` exists for the user,
|
|
14
|
+
* plus a `TenantMember` with `role=OWNER`.
|
|
15
|
+
* 3. For federated users: exact-match the email domain against
|
|
16
|
+
* `tenant_domains` (verified only). If the domain belongs to a tenant
|
|
17
|
+
* with an `ACTIVE` IdP, resolve the user's role from `TenantRoleMapping`
|
|
18
|
+
* (against the `custom:idpGroups` attribute) and create / refresh a
|
|
19
|
+
* `TenantMember` row with `isJitProvisioned=true`.
|
|
20
|
+
* 4. Preserve the existing `ageTier` + parental-link logic from the v0.6
|
|
21
|
+
* stub (B2C requirement).
|
|
22
|
+
*
|
|
23
|
+
* Idempotency: every write is an upsert. Cognito retries up to 3 times.
|
|
24
|
+
*
|
|
25
|
+
* Cross-tenant isolation: domain lookup is exact-match-only. No substring,
|
|
26
|
+
* no wildcard. See sec finding #8 in
|
|
27
|
+
* plans/mvp/10-trellis-stages/02-cognito-triggers.md.
|
|
28
|
+
*
|
|
29
|
+
* No PII (email body, group claim contents, raw IdP attributes) is logged.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
PostConfirmationTriggerEvent,
|
|
34
|
+
PostConfirmationTriggerHandler,
|
|
35
|
+
} from "aws-lambda";
|
|
2
36
|
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
3
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
PrismaClient,
|
|
39
|
+
type AgeTier,
|
|
40
|
+
type Prisma,
|
|
41
|
+
type TenantRole,
|
|
42
|
+
type UserRole,
|
|
43
|
+
} from "@prisma/client";
|
|
44
|
+
import { ClaimsCache, createClaimsCacheFromEnv, type CachedClaims } from "../lib/auth/claims-cache";
|
|
45
|
+
import { deriveEmailDomain } from "../lib/tenant/derive-domain";
|
|
46
|
+
import { resolveTenantRole, type RoleMappingInput } from "../lib/tenant/resolve-role";
|
|
47
|
+
import { deriveHandle } from "../lib/user/derive-handle";
|
|
4
48
|
|
|
5
49
|
const secretsClient = new SecretsManagerClient({ region: process.env.AWS_REGION });
|
|
6
50
|
let prisma: PrismaClient | null = null;
|
|
51
|
+
let cache: ClaimsCache | null = null;
|
|
7
52
|
|
|
8
53
|
async function getPrisma(): Promise<PrismaClient> {
|
|
9
54
|
if (prisma) return prisma;
|
|
10
|
-
const secret = await secretsClient.send(
|
|
55
|
+
const secret = await secretsClient.send(
|
|
56
|
+
new GetSecretValueCommand({ SecretId: process.env.DB_SECRET_ARN! }),
|
|
57
|
+
);
|
|
11
58
|
const { username, password, host, port, dbname } = JSON.parse(secret.SecretString!);
|
|
12
59
|
prisma = new PrismaClient({
|
|
13
|
-
datasources: {
|
|
60
|
+
datasources: {
|
|
61
|
+
db: {
|
|
62
|
+
url: `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbname}?connection_limit=1`,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
14
65
|
});
|
|
15
66
|
return prisma;
|
|
16
67
|
}
|
|
17
68
|
|
|
69
|
+
function getCache(): ClaimsCache {
|
|
70
|
+
if (!cache) cache = createClaimsCacheFromEnv();
|
|
71
|
+
return cache;
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
function computeAgeTier(dateOfBirth: Date): AgeTier {
|
|
19
75
|
const now = new Date();
|
|
20
76
|
let age = now.getUTCFullYear() - dateOfBirth.getUTCFullYear();
|
|
@@ -27,54 +83,374 @@ function computeAgeTier(dateOfBirth: Date): AgeTier {
|
|
|
27
83
|
return "ADULT";
|
|
28
84
|
}
|
|
29
85
|
|
|
86
|
+
function isFederatedEvent(event: PostConfirmationTriggerEvent): boolean {
|
|
87
|
+
const identitiesRaw = event.request.userAttributes["identities"];
|
|
88
|
+
if (!identitiesRaw) return false;
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(identitiesRaw);
|
|
91
|
+
return Array.isArray(parsed) && parsed.length > 0;
|
|
92
|
+
} catch {
|
|
93
|
+
// Malformed `identities` is not a federation signal we can act on. Return
|
|
94
|
+
// false rather than over-classifying as federated, which would set
|
|
95
|
+
// role=B2B_PARTNER and run the org-tenant resolution path. (G2 M2)
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseIdpGroups(raw: string | undefined | null): string[] {
|
|
101
|
+
if (!raw) return [];
|
|
102
|
+
// Split on `,` and `;` only — IdPs (notably Okta in displayName mode) may
|
|
103
|
+
// emit group names containing whitespace. Cognito's custom-attribute
|
|
104
|
+
// serialization is comma-separated; we accept semicolon as a defensive
|
|
105
|
+
// fallback. (G2 L1)
|
|
106
|
+
return raw
|
|
107
|
+
.split(/[,;]+/)
|
|
108
|
+
.map((s) => s.trim())
|
|
109
|
+
.filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface ProvisioningResult {
|
|
113
|
+
userId: string;
|
|
114
|
+
globalRole: UserRole;
|
|
115
|
+
handle: string;
|
|
116
|
+
personalTenantId: string;
|
|
117
|
+
personalTenantSlug: string;
|
|
118
|
+
orgTenantId: string | null;
|
|
119
|
+
orgTenantSlug: string | null;
|
|
120
|
+
orgTenantRole: TenantRole | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const SUPPORTED_TRIGGERS = new Set([
|
|
124
|
+
"PostConfirmation_ConfirmSignUp",
|
|
125
|
+
"PostConfirmation_ConfirmForgotPassword",
|
|
126
|
+
]);
|
|
127
|
+
|
|
30
128
|
export const handler: PostConfirmationTriggerHandler = async (event) => {
|
|
31
|
-
if (event.triggerSource
|
|
129
|
+
if (!SUPPORTED_TRIGGERS.has(event.triggerSource)) return event;
|
|
32
130
|
|
|
33
|
-
const { email, "custom:handle": handle, "custom:dateOfBirth": dateOfBirthStr } = event.request.userAttributes;
|
|
34
131
|
const cognitoSub = event.userName;
|
|
132
|
+
const attrs = event.request.userAttributes;
|
|
133
|
+
const email = attrs.email?.toLowerCase();
|
|
134
|
+
if (!email) {
|
|
135
|
+
console.warn(JSON.stringify({ event: "postconfirm.no_email", cognitoSub }));
|
|
136
|
+
return event;
|
|
137
|
+
}
|
|
35
138
|
|
|
36
|
-
const
|
|
139
|
+
const federated = isFederatedEvent(event);
|
|
140
|
+
const idpGroups = parseIdpGroups(attrs["custom:idpGroups"]);
|
|
141
|
+
const dobStr = attrs["custom:dateOfBirth"];
|
|
37
142
|
|
|
38
|
-
// Compute age tier from date of birth if provided
|
|
39
143
|
let dateOfBirth: Date | undefined;
|
|
40
144
|
let ageTier: AgeTier = "ADULT";
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
if (!isNaN(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
dateOfBirth = undefined;
|
|
145
|
+
if (dobStr) {
|
|
146
|
+
const parsed = new Date(dobStr);
|
|
147
|
+
if (!isNaN(parsed.getTime()) && parsed < new Date()) {
|
|
148
|
+
dateOfBirth = parsed;
|
|
149
|
+
ageTier = computeAgeTier(parsed);
|
|
47
150
|
}
|
|
48
151
|
}
|
|
49
152
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
153
|
+
const db = await getPrisma();
|
|
154
|
+
|
|
155
|
+
const result = await db.$transaction(
|
|
156
|
+
async (tx) => provisionUserAndTenancy(tx, {
|
|
53
157
|
cognitoSub,
|
|
54
158
|
email,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
|
|
159
|
+
emailVerified: attrs.email_verified,
|
|
160
|
+
federated,
|
|
161
|
+
idpGroups,
|
|
162
|
+
dateOfBirth,
|
|
163
|
+
ageTier,
|
|
164
|
+
providedHandle: attrs["custom:handle"],
|
|
165
|
+
}),
|
|
166
|
+
{ timeout: 8000 },
|
|
167
|
+
);
|
|
63
168
|
|
|
64
|
-
// If child account, create a pending parental link if guardian email is provided
|
|
65
169
|
if (ageTier === "CHILD") {
|
|
66
|
-
const guardianEmail =
|
|
170
|
+
const guardianEmail = attrs["custom:guardianEmail"]?.toLowerCase();
|
|
67
171
|
if (guardianEmail) {
|
|
68
172
|
const guardian = await db.user.findUnique({ where: { email: guardianEmail } });
|
|
69
173
|
if (guardian) {
|
|
70
174
|
await db.parentalLink.upsert({
|
|
71
|
-
where: { childId_guardianId: { childId:
|
|
72
|
-
create: { childId:
|
|
175
|
+
where: { childId_guardianId: { childId: result.userId, guardianId: guardian.id } },
|
|
176
|
+
create: { childId: result.userId, guardianId: guardian.id, status: "PENDING" },
|
|
73
177
|
update: {},
|
|
74
178
|
});
|
|
75
179
|
}
|
|
76
180
|
}
|
|
77
181
|
}
|
|
78
182
|
|
|
183
|
+
await primeClaimsCache(cognitoSub, result);
|
|
184
|
+
|
|
185
|
+
console.log(
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
event: "postconfirm.ok",
|
|
188
|
+
cognitoSub,
|
|
189
|
+
userId: result.userId,
|
|
190
|
+
personalTenantId: result.personalTenantId,
|
|
191
|
+
orgTenantId: result.orgTenantId,
|
|
192
|
+
federated,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
79
196
|
return event;
|
|
80
197
|
};
|
|
198
|
+
|
|
199
|
+
interface ProvisioningInput {
|
|
200
|
+
cognitoSub: string;
|
|
201
|
+
email: string;
|
|
202
|
+
emailVerified: string | undefined;
|
|
203
|
+
federated: boolean;
|
|
204
|
+
idpGroups: string[];
|
|
205
|
+
dateOfBirth: Date | undefined;
|
|
206
|
+
ageTier: AgeTier;
|
|
207
|
+
providedHandle: string | undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function provisionUserAndTenancy(
|
|
211
|
+
tx: Prisma.TransactionClient,
|
|
212
|
+
input: ProvisioningInput,
|
|
213
|
+
): Promise<ProvisioningResult> {
|
|
214
|
+
const {
|
|
215
|
+
cognitoSub,
|
|
216
|
+
email,
|
|
217
|
+
federated,
|
|
218
|
+
idpGroups,
|
|
219
|
+
dateOfBirth,
|
|
220
|
+
ageTier,
|
|
221
|
+
providedHandle,
|
|
222
|
+
} = input;
|
|
223
|
+
|
|
224
|
+
const existing = await tx.user.findFirst({
|
|
225
|
+
where: { OR: [{ cognitoSub }, { email }] },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
let user = existing;
|
|
229
|
+
if (!user) {
|
|
230
|
+
const initialHandle =
|
|
231
|
+
(providedHandle && providedHandle.trim()) ||
|
|
232
|
+
(await deriveHandle(email, async (h) => {
|
|
233
|
+
const found = await tx.user.findFirst({ where: { handle: h }, select: { id: true } });
|
|
234
|
+
return !!found;
|
|
235
|
+
}));
|
|
236
|
+
user = await tx.user.create({
|
|
237
|
+
data: {
|
|
238
|
+
cognitoSub,
|
|
239
|
+
email,
|
|
240
|
+
handle: initialHandle,
|
|
241
|
+
role: federated ? "B2B_PARTNER" : "END_USER",
|
|
242
|
+
...(dateOfBirth && { dateOfBirth, ageTier }),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
const updates: Prisma.UserUpdateInput = {};
|
|
247
|
+
if (!user.cognitoSub) updates.cognitoSub = cognitoSub;
|
|
248
|
+
if (!user.handle) {
|
|
249
|
+
updates.handle = await deriveHandle(email, async (h) => {
|
|
250
|
+
const found = await tx.user.findFirst({
|
|
251
|
+
where: { handle: h, NOT: { id: user!.id } },
|
|
252
|
+
select: { id: true },
|
|
253
|
+
});
|
|
254
|
+
return !!found;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (Object.keys(updates).length > 0) {
|
|
258
|
+
user = await tx.user.update({ where: { id: user.id }, data: updates });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let personalTenantId = user.personalTenantId;
|
|
263
|
+
let personalTenantSlug = "";
|
|
264
|
+
if (!personalTenantId) {
|
|
265
|
+
const personalSlug = `personal-${user.id}`;
|
|
266
|
+
const personalTenant = await tx.tenant.create({
|
|
267
|
+
data: {
|
|
268
|
+
slug: personalSlug,
|
|
269
|
+
displayName: user.handle ?? "personal",
|
|
270
|
+
type: "PERSONAL",
|
|
271
|
+
personalOwnerUserId: user.id,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
personalTenantId = personalTenant.id;
|
|
275
|
+
personalTenantSlug = personalTenant.slug;
|
|
276
|
+
await tx.tenantMember.upsert({
|
|
277
|
+
where: { tenantId_userId: { tenantId: personalTenant.id, userId: user.id } },
|
|
278
|
+
create: {
|
|
279
|
+
tenantId: personalTenant.id,
|
|
280
|
+
userId: user.id,
|
|
281
|
+
role: "OWNER",
|
|
282
|
+
status: "ACTIVE",
|
|
283
|
+
joinedAt: new Date(),
|
|
284
|
+
},
|
|
285
|
+
update: { status: "ACTIVE" },
|
|
286
|
+
});
|
|
287
|
+
await tx.user.update({
|
|
288
|
+
where: { id: user.id },
|
|
289
|
+
data: { personalTenantId: personalTenant.id },
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
const personal = await tx.tenant.findUnique({
|
|
293
|
+
where: { id: personalTenantId },
|
|
294
|
+
select: { slug: true },
|
|
295
|
+
});
|
|
296
|
+
personalTenantSlug = personal?.slug ?? "";
|
|
297
|
+
await tx.tenantMember.upsert({
|
|
298
|
+
where: { tenantId_userId: { tenantId: personalTenantId, userId: user.id } },
|
|
299
|
+
create: {
|
|
300
|
+
tenantId: personalTenantId,
|
|
301
|
+
userId: user.id,
|
|
302
|
+
role: "OWNER",
|
|
303
|
+
status: "ACTIVE",
|
|
304
|
+
joinedAt: new Date(),
|
|
305
|
+
},
|
|
306
|
+
update: {},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let orgTenantId: string | null = null;
|
|
311
|
+
let orgTenantSlug: string | null = null;
|
|
312
|
+
let orgTenantRole: TenantRole | null = null;
|
|
313
|
+
if (federated) {
|
|
314
|
+
// Defensive: only resolve org-tenant membership when Cognito asserts the
|
|
315
|
+
// email is verified by the IdP. Native Cognito sign-ups always reach this
|
|
316
|
+
// trigger with email_verified=true; for federated identities the value
|
|
317
|
+
// depends on the IdP's attribute mapping. Without this check, an IdP
|
|
318
|
+
// misconfigured to skip verification would let a user claim any
|
|
319
|
+
// domain-bound tenant by self-asserting an email. Personal-tenant
|
|
320
|
+
// creation above is unaffected — Cognito has already authenticated them.
|
|
321
|
+
const emailVerified = input.emailVerified === "true";
|
|
322
|
+
if (!emailVerified) {
|
|
323
|
+
console.warn(
|
|
324
|
+
JSON.stringify({ event: "postconfirm.federated.email_unverified", cognitoSub }),
|
|
325
|
+
);
|
|
326
|
+
return {
|
|
327
|
+
userId: user.id,
|
|
328
|
+
globalRole: user.role,
|
|
329
|
+
handle: user.handle ?? "",
|
|
330
|
+
personalTenantId: personalTenantId!,
|
|
331
|
+
personalTenantSlug,
|
|
332
|
+
orgTenantId: null,
|
|
333
|
+
orgTenantSlug: null,
|
|
334
|
+
orgTenantRole: null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const domain = deriveEmailDomain(email);
|
|
338
|
+
if (!domain) {
|
|
339
|
+
console.warn(JSON.stringify({ event: "postconfirm.federated.invalid_email", cognitoSub }));
|
|
340
|
+
} else {
|
|
341
|
+
const tenantDomain = await tx.tenantDomain.findUnique({
|
|
342
|
+
where: { domain },
|
|
343
|
+
include: {
|
|
344
|
+
tenant: {
|
|
345
|
+
include: {
|
|
346
|
+
identityProvider: {
|
|
347
|
+
select: { status: true, defaultRole: true },
|
|
348
|
+
},
|
|
349
|
+
roleMappings: {
|
|
350
|
+
select: { idpGroupName: true, tenantRole: true, priority: true },
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (!tenantDomain) {
|
|
358
|
+
console.warn(
|
|
359
|
+
JSON.stringify({ event: "postconfirm.federated.no_domain_match", cognitoSub }),
|
|
360
|
+
);
|
|
361
|
+
} else if (!tenantDomain.verifiedAt) {
|
|
362
|
+
console.warn(
|
|
363
|
+
JSON.stringify({
|
|
364
|
+
event: "postconfirm.federated.unverified_domain",
|
|
365
|
+
cognitoSub,
|
|
366
|
+
tenantId: tenantDomain.tenantId,
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
} else if (
|
|
370
|
+
!tenantDomain.tenant.identityProvider ||
|
|
371
|
+
tenantDomain.tenant.identityProvider.status !== "ACTIVE"
|
|
372
|
+
) {
|
|
373
|
+
console.warn(
|
|
374
|
+
JSON.stringify({
|
|
375
|
+
event: "postconfirm.federated.inactive_idp",
|
|
376
|
+
cognitoSub,
|
|
377
|
+
tenantId: tenantDomain.tenantId,
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
380
|
+
} else {
|
|
381
|
+
const role = resolveTenantRole(
|
|
382
|
+
idpGroups,
|
|
383
|
+
tenantDomain.tenant.roleMappings as RoleMappingInput[],
|
|
384
|
+
tenantDomain.tenant.identityProvider.defaultRole,
|
|
385
|
+
);
|
|
386
|
+
if (!role) {
|
|
387
|
+
console.warn(
|
|
388
|
+
JSON.stringify({
|
|
389
|
+
event: "postconfirm.federated.no_role",
|
|
390
|
+
cognitoSub,
|
|
391
|
+
tenantId: tenantDomain.tenantId,
|
|
392
|
+
}),
|
|
393
|
+
);
|
|
394
|
+
} else {
|
|
395
|
+
await tx.tenantMember.upsert({
|
|
396
|
+
where: {
|
|
397
|
+
tenantId_userId: { tenantId: tenantDomain.tenantId, userId: user.id },
|
|
398
|
+
},
|
|
399
|
+
create: {
|
|
400
|
+
tenantId: tenantDomain.tenantId,
|
|
401
|
+
userId: user.id,
|
|
402
|
+
role,
|
|
403
|
+
status: "ACTIVE",
|
|
404
|
+
joinedAt: new Date(),
|
|
405
|
+
isJitProvisioned: true,
|
|
406
|
+
},
|
|
407
|
+
update: {
|
|
408
|
+
role,
|
|
409
|
+
status: "ACTIVE",
|
|
410
|
+
lastActiveAt: new Date(),
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
orgTenantId = tenantDomain.tenantId;
|
|
414
|
+
orgTenantSlug = tenantDomain.tenant.slug;
|
|
415
|
+
orgTenantRole = role;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
userId: user.id,
|
|
423
|
+
globalRole: user.role,
|
|
424
|
+
handle: user.handle ?? "",
|
|
425
|
+
personalTenantId: personalTenantId!,
|
|
426
|
+
personalTenantSlug,
|
|
427
|
+
orgTenantId,
|
|
428
|
+
orgTenantSlug,
|
|
429
|
+
orgTenantRole,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function primeClaimsCache(cognitoSub: string, result: ProvisioningResult): Promise<void> {
|
|
434
|
+
const activeTenantId = result.orgTenantId ?? result.personalTenantId;
|
|
435
|
+
const activeTenantSlug = result.orgTenantSlug ?? result.personalTenantSlug;
|
|
436
|
+
const activeTenantRole = result.orgTenantRole ?? "OWNER";
|
|
437
|
+
const claims: CachedClaims = {
|
|
438
|
+
userId: result.userId,
|
|
439
|
+
globalRole: result.globalRole,
|
|
440
|
+
activeTenantId,
|
|
441
|
+
tenantSlug: activeTenantSlug,
|
|
442
|
+
tenantRole: activeTenantRole,
|
|
443
|
+
handle: result.handle,
|
|
444
|
+
};
|
|
445
|
+
try {
|
|
446
|
+
await getCache().put(cognitoSub, claims);
|
|
447
|
+
} catch (err) {
|
|
448
|
+
console.warn(
|
|
449
|
+
JSON.stringify({
|
|
450
|
+
event: "postconfirm.cache_prime_failed",
|
|
451
|
+
cognitoSub,
|
|
452
|
+
error: (err as { code?: string }).code ?? "unknown",
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|