@de-otio/trellis 0.6.0 → 0.7.0

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.
Files changed (339) hide show
  1. package/dist/env.d.ts +21 -0
  2. package/dist/env.d.ts.map +1 -1
  3. package/dist/env.js +12 -0
  4. package/dist/env.js.map +1 -1
  5. package/dist/lambda/nightly-cron.d.ts.map +1 -1
  6. package/dist/lambda/nightly-cron.js +5 -2
  7. package/dist/lambda/nightly-cron.js.map +1 -1
  8. package/dist/lambda/post-confirmation.d.ts +30 -0
  9. package/dist/lambda/post-confirmation.d.ts.map +1 -1
  10. package/dist/lambda/post-confirmation.js +333 -29
  11. package/dist/lambda/post-confirmation.js.map +1 -1
  12. package/dist/lambda/pre-token-generation.d.ts +20 -0
  13. package/dist/lambda/pre-token-generation.d.ts.map +1 -1
  14. package/dist/lambda/pre-token-generation.js +233 -48
  15. package/dist/lambda/pre-token-generation.js.map +1 -1
  16. package/dist/lib/activitypub/activity-processor.d.ts.map +1 -1
  17. package/dist/lib/activitypub/activity-processor.js +2 -1
  18. package/dist/lib/activitypub/activity-processor.js.map +1 -1
  19. package/dist/lib/activitypub/group-service.d.ts +2 -2
  20. package/dist/lib/activitypub/group-service.d.ts.map +1 -1
  21. package/dist/lib/activitypub/group-service.js +5 -2
  22. package/dist/lib/activitypub/group-service.js.map +1 -1
  23. package/dist/lib/age-tier-transition.d.ts.map +1 -1
  24. package/dist/lib/age-tier-transition.js +19 -10
  25. package/dist/lib/age-tier-transition.js.map +1 -1
  26. package/dist/lib/audit/csv-export.d.ts +25 -0
  27. package/dist/lib/audit/csv-export.d.ts.map +1 -0
  28. package/dist/lib/audit/csv-export.js +54 -0
  29. package/dist/lib/audit/csv-export.js.map +1 -0
  30. package/dist/lib/audit/emit.d.ts +56 -0
  31. package/dist/lib/audit/emit.d.ts.map +1 -0
  32. package/dist/lib/audit/emit.js +124 -0
  33. package/dist/lib/audit/emit.js.map +1 -0
  34. package/dist/lib/audit/event-types.d.ts +36 -0
  35. package/dist/lib/audit/event-types.d.ts.map +1 -0
  36. package/dist/lib/audit/event-types.js +69 -0
  37. package/dist/lib/audit/event-types.js.map +1 -0
  38. package/dist/lib/audit/pii-filter.d.ts +22 -0
  39. package/dist/lib/audit/pii-filter.d.ts.map +1 -0
  40. package/dist/lib/audit/pii-filter.js +51 -0
  41. package/dist/lib/audit/pii-filter.js.map +1 -0
  42. package/dist/lib/audit-logger.js +1 -1
  43. package/dist/lib/audit-logger.js.map +1 -1
  44. package/dist/lib/auth/auth-context.d.ts +34 -0
  45. package/dist/lib/auth/auth-context.d.ts.map +1 -0
  46. package/dist/lib/auth/auth-context.js +10 -0
  47. package/dist/lib/auth/auth-context.js.map +1 -0
  48. package/dist/lib/auth/auth-middleware.d.ts +50 -0
  49. package/dist/lib/auth/auth-middleware.d.ts.map +1 -0
  50. package/dist/lib/auth/auth-middleware.js +153 -0
  51. package/dist/lib/auth/auth-middleware.js.map +1 -0
  52. package/dist/lib/auth/capabilities.d.ts +40 -0
  53. package/dist/lib/auth/capabilities.d.ts.map +1 -0
  54. package/dist/lib/auth/capabilities.js +44 -0
  55. package/dist/lib/auth/capabilities.js.map +1 -0
  56. package/dist/lib/auth/claims-cache.d.ts +70 -0
  57. package/dist/lib/auth/claims-cache.d.ts.map +1 -0
  58. package/dist/lib/auth/claims-cache.js +139 -0
  59. package/dist/lib/auth/claims-cache.js.map +1 -0
  60. package/dist/lib/auth/cognito-jwt.d.ts +6 -0
  61. package/dist/lib/auth/cognito-jwt.d.ts.map +1 -1
  62. package/dist/lib/auth/cognito-jwt.js.map +1 -1
  63. package/dist/lib/auth/idp-redirect-builder.d.ts +43 -0
  64. package/dist/lib/auth/idp-redirect-builder.d.ts.map +1 -0
  65. package/dist/lib/auth/idp-redirect-builder.js +48 -0
  66. package/dist/lib/auth/idp-redirect-builder.js.map +1 -0
  67. package/dist/lib/auth/require.d.ts +51 -0
  68. package/dist/lib/auth/require.d.ts.map +1 -0
  69. package/dist/lib/auth/require.js +99 -0
  70. package/dist/lib/auth/require.js.map +1 -0
  71. package/dist/lib/auth/role-grants.d.ts +18 -0
  72. package/dist/lib/auth/role-grants.d.ts.map +1 -0
  73. package/dist/lib/auth/role-grants.js +62 -0
  74. package/dist/lib/auth/role-grants.js.map +1 -0
  75. package/dist/lib/cognito/idp-sdk.d.ts +80 -0
  76. package/dist/lib/cognito/idp-sdk.d.ts.map +1 -0
  77. package/dist/lib/cognito/idp-sdk.js +186 -0
  78. package/dist/lib/cognito/idp-sdk.js.map +1 -0
  79. package/dist/lib/cognito/issuer-probe.d.ts +47 -0
  80. package/dist/lib/cognito/issuer-probe.d.ts.map +1 -0
  81. package/dist/lib/cognito/issuer-probe.js +319 -0
  82. package/dist/lib/cognito/issuer-probe.js.map +1 -0
  83. package/dist/lib/comment-handler.d.ts +7 -7
  84. package/dist/lib/comment-handler.d.ts.map +1 -1
  85. package/dist/lib/comment-handler.js +23 -20
  86. package/dist/lib/comment-handler.js.map +1 -1
  87. package/dist/lib/compliance/baseline.d.ts +15 -0
  88. package/dist/lib/compliance/baseline.d.ts.map +1 -0
  89. package/dist/lib/compliance/baseline.js +205 -0
  90. package/dist/lib/compliance/baseline.js.map +1 -0
  91. package/dist/lib/compliance/tenant-merge.d.ts +35 -0
  92. package/dist/lib/compliance/tenant-merge.d.ts.map +1 -0
  93. package/dist/lib/compliance/tenant-merge.js +80 -0
  94. package/dist/lib/compliance/tenant-merge.js.map +1 -0
  95. package/dist/lib/compliance/types.d.ts +135 -0
  96. package/dist/lib/compliance/types.d.ts.map +1 -0
  97. package/dist/lib/compliance/types.js +9 -0
  98. package/dist/lib/compliance/types.js.map +1 -0
  99. package/dist/lib/connection-code-handler.d.ts +4 -4
  100. package/dist/lib/connection-code-handler.d.ts.map +1 -1
  101. package/dist/lib/connection-code-handler.js +21 -11
  102. package/dist/lib/connection-code-handler.js.map +1 -1
  103. package/dist/lib/feed-handler.d.ts +2 -2
  104. package/dist/lib/feed-handler.d.ts.map +1 -1
  105. package/dist/lib/feed-handler.js +5 -9
  106. package/dist/lib/feed-handler.js.map +1 -1
  107. package/dist/lib/middleware/idempotency-store.d.ts +86 -0
  108. package/dist/lib/middleware/idempotency-store.d.ts.map +1 -0
  109. package/dist/lib/middleware/idempotency-store.js +109 -0
  110. package/dist/lib/middleware/idempotency-store.js.map +1 -0
  111. package/dist/lib/middleware/idempotency.d.ts +37 -0
  112. package/dist/lib/middleware/idempotency.d.ts.map +1 -0
  113. package/dist/lib/middleware/idempotency.js +358 -0
  114. package/dist/lib/middleware/idempotency.js.map +1 -0
  115. package/dist/lib/net/trusted-client-ip.d.ts +39 -0
  116. package/dist/lib/net/trusted-client-ip.d.ts.map +1 -0
  117. package/dist/lib/net/trusted-client-ip.js +100 -0
  118. package/dist/lib/net/trusted-client-ip.js.map +1 -0
  119. package/dist/lib/notification-handler.d.ts +5 -5
  120. package/dist/lib/notification-handler.d.ts.map +1 -1
  121. package/dist/lib/notification-handler.js +11 -9
  122. package/dist/lib/notification-handler.js.map +1 -1
  123. package/dist/lib/oauth/cognito-issuer.d.ts +34 -0
  124. package/dist/lib/oauth/cognito-issuer.d.ts.map +1 -0
  125. package/dist/lib/oauth/cognito-issuer.js +53 -0
  126. package/dist/lib/oauth/cognito-issuer.js.map +1 -0
  127. package/dist/lib/oauth/device-authorization.d.ts +145 -0
  128. package/dist/lib/oauth/device-authorization.d.ts.map +1 -0
  129. package/dist/lib/oauth/device-authorization.js +312 -0
  130. package/dist/lib/oauth/device-authorization.js.map +1 -0
  131. package/dist/lib/oauth/envelope-crypto.d.ts +101 -0
  132. package/dist/lib/oauth/envelope-crypto.d.ts.map +1 -0
  133. package/dist/lib/oauth/envelope-crypto.js +223 -0
  134. package/dist/lib/oauth/envelope-crypto.js.map +1 -0
  135. package/dist/lib/oauth/refresh-detection.d.ts +126 -0
  136. package/dist/lib/oauth/refresh-detection.d.ts.map +1 -0
  137. package/dist/lib/oauth/refresh-detection.js +248 -0
  138. package/dist/lib/oauth/refresh-detection.js.map +1 -0
  139. package/dist/lib/openapi/generator.d.ts +78 -0
  140. package/dist/lib/openapi/generator.d.ts.map +1 -0
  141. package/dist/lib/openapi/generator.js +201 -0
  142. package/dist/lib/openapi/generator.js.map +1 -0
  143. package/dist/lib/post-handler.d.ts +1 -1
  144. package/dist/lib/post-handler.d.ts.map +1 -1
  145. package/dist/lib/post-handler.js +4 -15
  146. package/dist/lib/post-handler.js.map +1 -1
  147. package/dist/lib/rate-limit.d.ts.map +1 -1
  148. package/dist/lib/rate-limit.js +11 -3
  149. package/dist/lib/rate-limit.js.map +1 -1
  150. package/dist/lib/routes/agent-authorize.d.ts +32 -0
  151. package/dist/lib/routes/agent-authorize.d.ts.map +1 -0
  152. package/dist/lib/routes/agent-authorize.js +479 -0
  153. package/dist/lib/routes/agent-authorize.js.map +1 -0
  154. package/dist/lib/routes/agent-sessions.d.ts +20 -0
  155. package/dist/lib/routes/agent-sessions.d.ts.map +1 -0
  156. package/dist/lib/routes/agent-sessions.js +124 -0
  157. package/dist/lib/routes/agent-sessions.js.map +1 -0
  158. package/dist/lib/routes/agent-surface.d.ts +37 -0
  159. package/dist/lib/routes/agent-surface.d.ts.map +1 -0
  160. package/dist/lib/routes/agent-surface.js +208 -0
  161. package/dist/lib/routes/agent-surface.js.map +1 -0
  162. package/dist/lib/routes/auth-discover.d.ts +18 -0
  163. package/dist/lib/routes/auth-discover.d.ts.map +1 -0
  164. package/dist/lib/routes/auth-discover.js +177 -0
  165. package/dist/lib/routes/auth-discover.js.map +1 -0
  166. package/dist/lib/routes/comments.d.ts.map +1 -1
  167. package/dist/lib/routes/comments.js +36 -7
  168. package/dist/lib/routes/comments.js.map +1 -1
  169. package/dist/lib/routes/connection-codes.d.ts.map +1 -1
  170. package/dist/lib/routes/connection-codes.js +21 -4
  171. package/dist/lib/routes/connection-codes.js.map +1 -1
  172. package/dist/lib/routes/content-discovery.d.ts.map +1 -1
  173. package/dist/lib/routes/content-discovery.js +18 -13
  174. package/dist/lib/routes/content-discovery.js.map +1 -1
  175. package/dist/lib/routes/dashboard.js +1 -1
  176. package/dist/lib/routes/dashboard.js.map +1 -1
  177. package/dist/lib/routes/employees.d.ts.map +1 -1
  178. package/dist/lib/routes/employees.js +57 -15
  179. package/dist/lib/routes/employees.js.map +1 -1
  180. package/dist/lib/routes/entities.d.ts.map +1 -1
  181. package/dist/lib/routes/entities.js +35 -19
  182. package/dist/lib/routes/entities.js.map +1 -1
  183. package/dist/lib/routes/errors.d.ts +34 -0
  184. package/dist/lib/routes/errors.d.ts.map +1 -0
  185. package/dist/lib/routes/errors.js +57 -0
  186. package/dist/lib/routes/errors.js.map +1 -0
  187. package/dist/lib/routes/feeds.d.ts.map +1 -1
  188. package/dist/lib/routes/feeds.js +12 -2
  189. package/dist/lib/routes/feeds.js.map +1 -1
  190. package/dist/lib/routes/index.d.ts.map +1 -1
  191. package/dist/lib/routes/index.js +50 -0
  192. package/dist/lib/routes/index.js.map +1 -1
  193. package/dist/lib/routes/mfa.d.ts.map +1 -1
  194. package/dist/lib/routes/mfa.js +1 -0
  195. package/dist/lib/routes/mfa.js.map +1 -1
  196. package/dist/lib/routes/notifications.d.ts.map +1 -1
  197. package/dist/lib/routes/notifications.js +21 -4
  198. package/dist/lib/routes/notifications.js.map +1 -1
  199. package/dist/lib/routes/oauth.d.ts +15 -0
  200. package/dist/lib/routes/oauth.d.ts.map +1 -0
  201. package/dist/lib/routes/oauth.js +139 -0
  202. package/dist/lib/routes/oauth.js.map +1 -0
  203. package/dist/lib/routes/posts.d.ts.map +1 -1
  204. package/dist/lib/routes/posts.js +30 -19
  205. package/dist/lib/routes/posts.js.map +1 -1
  206. package/dist/lib/routes/products.d.ts.map +1 -1
  207. package/dist/lib/routes/products.js +19 -22
  208. package/dist/lib/routes/products.js.map +1 -1
  209. package/dist/lib/routes/setup-status.d.ts +34 -0
  210. package/dist/lib/routes/setup-status.d.ts.map +1 -0
  211. package/dist/lib/routes/setup-status.js +87 -0
  212. package/dist/lib/routes/setup-status.js.map +1 -0
  213. package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -1
  214. package/dist/lib/routes/taxonomy-analytics.js +15 -14
  215. package/dist/lib/routes/taxonomy-analytics.js.map +1 -1
  216. package/dist/lib/routes/taxonomy.d.ts.map +1 -1
  217. package/dist/lib/routes/taxonomy.js +19 -16
  218. package/dist/lib/routes/taxonomy.js.map +1 -1
  219. package/dist/lib/routes/tenant-audit.d.ts +19 -0
  220. package/dist/lib/routes/tenant-audit.d.ts.map +1 -0
  221. package/dist/lib/routes/tenant-audit.js +244 -0
  222. package/dist/lib/routes/tenant-audit.js.map +1 -0
  223. package/dist/lib/routes/tenant-compliance.d.ts +21 -0
  224. package/dist/lib/routes/tenant-compliance.d.ts.map +1 -0
  225. package/dist/lib/routes/tenant-compliance.js +122 -0
  226. package/dist/lib/routes/tenant-compliance.js.map +1 -0
  227. package/dist/lib/routes/tenant-domains.d.ts +11 -0
  228. package/dist/lib/routes/tenant-domains.d.ts.map +1 -0
  229. package/dist/lib/routes/tenant-domains.js +95 -0
  230. package/dist/lib/routes/tenant-domains.js.map +1 -0
  231. package/dist/lib/routes/tenant-idp.d.ts +3 -0
  232. package/dist/lib/routes/tenant-idp.d.ts.map +1 -0
  233. package/dist/lib/routes/tenant-idp.js +89 -0
  234. package/dist/lib/routes/tenant-idp.js.map +1 -0
  235. package/dist/lib/routes/tenant-members.d.ts +13 -0
  236. package/dist/lib/routes/tenant-members.d.ts.map +1 -0
  237. package/dist/lib/routes/tenant-members.js +75 -0
  238. package/dist/lib/routes/tenant-members.js.map +1 -0
  239. package/dist/lib/routes/tenant-role-mappings.d.ts +11 -0
  240. package/dist/lib/routes/tenant-role-mappings.d.ts.map +1 -0
  241. package/dist/lib/routes/tenant-role-mappings.js +90 -0
  242. package/dist/lib/routes/tenant-role-mappings.js.map +1 -0
  243. package/dist/lib/routes/tenants.d.ts +13 -0
  244. package/dist/lib/routes/tenants.d.ts.map +1 -0
  245. package/dist/lib/routes/tenants.js +121 -0
  246. package/dist/lib/routes/tenants.js.map +1 -0
  247. package/dist/lib/routes/types.d.ts +9 -0
  248. package/dist/lib/routes/types.d.ts.map +1 -1
  249. package/dist/lib/schemas.d.ts +2 -2
  250. package/dist/lib/secrets/idp-secrets.d.ts +51 -0
  251. package/dist/lib/secrets/idp-secrets.d.ts.map +1 -0
  252. package/dist/lib/secrets/idp-secrets.js +111 -0
  253. package/dist/lib/secrets/idp-secrets.js.map +1 -0
  254. package/dist/lib/security-monitor.d.ts.map +1 -1
  255. package/dist/lib/security-monitor.js +6 -1
  256. package/dist/lib/security-monitor.js.map +1 -1
  257. package/dist/lib/session-manager.d.ts +1 -0
  258. package/dist/lib/session-manager.d.ts.map +1 -1
  259. package/dist/lib/session-manager.js.map +1 -1
  260. package/dist/lib/taxonomy-handler-factory.d.ts +4 -2
  261. package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -1
  262. package/dist/lib/taxonomy-handler-factory.js +8 -7
  263. package/dist/lib/taxonomy-handler-factory.js.map +1 -1
  264. package/dist/lib/tenant/audit-emit.d.ts +18 -0
  265. package/dist/lib/tenant/audit-emit.d.ts.map +1 -0
  266. package/dist/lib/tenant/audit-emit.js +16 -0
  267. package/dist/lib/tenant/audit-emit.js.map +1 -0
  268. package/dist/lib/tenant/derive-domain.d.ts +19 -0
  269. package/dist/lib/tenant/derive-domain.d.ts.map +1 -0
  270. package/dist/lib/tenant/derive-domain.js +38 -0
  271. package/dist/lib/tenant/derive-domain.js.map +1 -0
  272. package/dist/lib/tenant/domain-handler.d.ts +42 -0
  273. package/dist/lib/tenant/domain-handler.d.ts.map +1 -0
  274. package/dist/lib/tenant/domain-handler.js +344 -0
  275. package/dist/lib/tenant/domain-handler.js.map +1 -0
  276. package/dist/lib/tenant/domain-validator.d.ts +28 -0
  277. package/dist/lib/tenant/domain-validator.d.ts.map +1 -0
  278. package/dist/lib/tenant/domain-validator.js +145 -0
  279. package/dist/lib/tenant/domain-validator.js.map +1 -0
  280. package/dist/lib/tenant/domain-verifier.d.ts +30 -0
  281. package/dist/lib/tenant/domain-verifier.d.ts.map +1 -0
  282. package/dist/lib/tenant/domain-verifier.js +53 -0
  283. package/dist/lib/tenant/domain-verifier.js.map +1 -0
  284. package/dist/lib/tenant/idp-handler.d.ts +29 -0
  285. package/dist/lib/tenant/idp-handler.d.ts.map +1 -0
  286. package/dist/lib/tenant/idp-handler.js +693 -0
  287. package/dist/lib/tenant/idp-handler.js.map +1 -0
  288. package/dist/lib/tenant/idp-name.d.ts +2 -0
  289. package/dist/lib/tenant/idp-name.d.ts.map +1 -0
  290. package/dist/lib/tenant/idp-name.js +20 -0
  291. package/dist/lib/tenant/idp-name.js.map +1 -0
  292. package/dist/lib/tenant/member-handler.d.ts +31 -0
  293. package/dist/lib/tenant/member-handler.d.ts.map +1 -0
  294. package/dist/lib/tenant/member-handler.js +343 -0
  295. package/dist/lib/tenant/member-handler.js.map +1 -0
  296. package/dist/lib/tenant/reserved-slugs.d.ts +37 -0
  297. package/dist/lib/tenant/reserved-slugs.d.ts.map +1 -0
  298. package/dist/lib/tenant/reserved-slugs.js +116 -0
  299. package/dist/lib/tenant/reserved-slugs.js.map +1 -0
  300. package/dist/lib/tenant/resolve-role.d.ts +39 -0
  301. package/dist/lib/tenant/resolve-role.d.ts.map +1 -0
  302. package/dist/lib/tenant/resolve-role.js +60 -0
  303. package/dist/lib/tenant/resolve-role.js.map +1 -0
  304. package/dist/lib/tenant/role-mapping-handler.d.ts +26 -0
  305. package/dist/lib/tenant/role-mapping-handler.d.ts.map +1 -0
  306. package/dist/lib/tenant/role-mapping-handler.js +260 -0
  307. package/dist/lib/tenant/role-mapping-handler.js.map +1 -0
  308. package/dist/lib/tenant/setup-status.d.ts +83 -0
  309. package/dist/lib/tenant/setup-status.d.ts.map +1 -0
  310. package/dist/lib/tenant/setup-status.js +201 -0
  311. package/dist/lib/tenant/setup-status.js.map +1 -0
  312. package/dist/lib/tenant/slug-validator.d.ts +31 -0
  313. package/dist/lib/tenant/slug-validator.d.ts.map +1 -0
  314. package/dist/lib/tenant/slug-validator.js +42 -0
  315. package/dist/lib/tenant/slug-validator.js.map +1 -0
  316. package/dist/lib/tenant/tenant-handler.d.ts +49 -0
  317. package/dist/lib/tenant/tenant-handler.d.ts.map +1 -0
  318. package/dist/lib/tenant/tenant-handler.js +377 -0
  319. package/dist/lib/tenant/tenant-handler.js.map +1 -0
  320. package/dist/lib/tenant/transfer-ownership.d.ts +39 -0
  321. package/dist/lib/tenant/transfer-ownership.d.ts.map +1 -0
  322. package/dist/lib/tenant/transfer-ownership.js +66 -0
  323. package/dist/lib/tenant/transfer-ownership.js.map +1 -0
  324. package/dist/lib/user/derive-handle.d.ts +29 -0
  325. package/dist/lib/user/derive-handle.d.ts.map +1 -0
  326. package/dist/lib/user/derive-handle.js +65 -0
  327. package/dist/lib/user/derive-handle.js.map +1 -0
  328. package/dist/lib/user-deprovisioning.d.ts +11 -1
  329. package/dist/lib/user-deprovisioning.d.ts.map +1 -1
  330. package/dist/lib/user-deprovisioning.js +46 -2
  331. package/dist/lib/user-deprovisioning.js.map +1 -1
  332. package/dist/lib/validation/feature-toggle-schemas.d.ts +10 -10
  333. package/package.json +6 -3
  334. package/prisma/migrations/20260502094501_add_tenancy_model/migration.sql +334 -0
  335. package/prisma/migrations/20260503000000_add_tenant_region/migration.sql +4 -0
  336. package/prisma/schema.prisma +324 -74
  337. package/src/lambda/nightly-cron.ts +4 -1
  338. package/src/lambda/post-confirmation.ts +405 -29
  339. 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
- import type { PostConfirmationTriggerEvent, PostConfirmationTriggerHandler } from "aws-lambda";
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 { PrismaClient, type AgeTier } from "@prisma/client";
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(new GetSecretValueCommand({ SecretId: process.env.DB_SECRET_ARN! }));
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: { db: { url: `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbname}?connection_limit=1` } },
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 !== "PostConfirmation_ConfirmSignUp") return event;
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 db = await getPrisma();
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 (dateOfBirthStr) {
42
- dateOfBirth = new Date(dateOfBirthStr);
43
- if (!isNaN(dateOfBirth.getTime()) && dateOfBirth < new Date()) {
44
- ageTier = computeAgeTier(dateOfBirth);
45
- } else {
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 user = await db.user.upsert({
51
- where: { cognitoSub },
52
- create: {
153
+ const db = await getPrisma();
154
+
155
+ const result = await db.$transaction(
156
+ async (tx) => provisionUserAndTenancy(tx, {
53
157
  cognitoSub,
54
158
  email,
55
- handle: handle || email.split("@")[0],
56
- role: "END_USER",
57
- ...(dateOfBirth && { dateOfBirth, ageTier }),
58
- },
59
- update: {
60
- email,
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 = event.request.userAttributes["custom: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: user.id, guardianId: guardian.id } },
72
- create: { childId: user.id, guardianId: guardian.id, status: "PENDING" },
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
+ }