@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.
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 +7 -5
  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
@@ -1,93 +1,334 @@
1
- import type { PreTokenGenerationV2TriggerEvent, PreTokenGenerationV2TriggerHandler } from "aws-lambda";
2
- import { DynamoDBClient, GetItemCommand, PutItemCommand } from "@aws-sdk/client-dynamodb";
3
- import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
4
- import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
5
- import { PrismaClient } from "@prisma/client";
1
+ /**
2
+ * Cognito PreTokenGeneration trigger (V2 access-token override).
3
+ *
4
+ * Runs on every token issuance and refresh. Responsibilities:
5
+ * 1. Read the cached claims from DynamoDB.
6
+ * 2. On miss: load from RDS (User + active TenantMember + Tenant slug).
7
+ * 3. For federated users: re-resolve the tenant role from the current
8
+ * `custom:idpGroups` against `TenantRoleMapping`. This catches admin-side
9
+ * group changes within the access-token TTL.
10
+ * 4. Write the (possibly refreshed) claims back to DDB.
11
+ * 5. Override the access-token claims via the V2 response shape.
12
+ *
13
+ * Failure modes:
14
+ * - User row missing (drift after RDS restore): return minimal claims —
15
+ * the API responds 403 to tenant-scoped endpoints, never a 500 at sign-in.
16
+ * - DDB or RDS error: bubble up; Cognito treats the issuance as failed.
17
+ *
18
+ * No PII is logged. We log counts and decisions ("cache_hit", "drift",
19
+ * "role_refreshed") and the opaque cognitoSub.
20
+ */
6
21
 
7
- const dynamo = new DynamoDBClient({ region: process.env.AWS_REGION });
8
- const TABLE = process.env.DYNAMODB_TABLE!;
9
- const CACHE_TTL_SECONDS = 300; // 5 minutes (S1.8 — reduced from 1 hour)
22
+ import type {
23
+ PreTokenGenerationV2TriggerEvent,
24
+ PreTokenGenerationV2TriggerHandler,
25
+ } from "aws-lambda";
26
+ import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
27
+ import { PrismaClient, type TenantRole } from "@prisma/client";
28
+ import {
29
+ ClaimsCache,
30
+ createClaimsCacheFromEnv,
31
+ DEFAULT_CACHE_TTL_SECONDS,
32
+ type CachedClaims,
33
+ } from "../lib/auth/claims-cache";
34
+ import { resolveTenantRole, type RoleMappingInput } from "../lib/tenant/resolve-role";
10
35
 
11
36
  const secretsClient = new SecretsManagerClient({ region: process.env.AWS_REGION });
12
37
  let prisma: PrismaClient | null = null;
38
+ let cache: ClaimsCache | null = null;
13
39
 
14
40
  async function getPrisma(): Promise<PrismaClient> {
15
41
  if (prisma) return prisma;
16
- const secret = await secretsClient.send(new GetSecretValueCommand({ SecretId: process.env.DB_SECRET_ARN! }));
42
+ const secret = await secretsClient.send(
43
+ new GetSecretValueCommand({ SecretId: process.env.DB_SECRET_ARN! }),
44
+ );
17
45
  const { username, password, host, port, dbname } = JSON.parse(secret.SecretString!);
18
46
  prisma = new PrismaClient({
19
- datasources: { db: { url: `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbname}?connection_limit=1` } },
47
+ datasources: {
48
+ db: {
49
+ url: `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbname}?connection_limit=1`,
50
+ },
51
+ },
20
52
  });
21
53
  return prisma;
22
54
  }
23
55
 
24
- interface CachedClaims {
25
- userId: string;
26
- role: string;
27
- handle: string;
56
+ function getCache(): ClaimsCache {
57
+ if (!cache) cache = createClaimsCacheFromEnv();
58
+ return cache;
28
59
  }
29
60
 
30
- async function getClaims(cognitoSub: string): Promise<CachedClaims | null> {
31
- // Check DynamoDB cache first
32
- const cacheResult = await dynamo.send(new GetItemCommand({
33
- TableName: TABLE,
34
- Key: marshall({ pk: `claims:${cognitoSub}`, sk: "meta" }),
35
- }));
36
-
37
- if (cacheResult.Item) {
38
- const cached = unmarshall(cacheResult.Item);
39
- if (!cached.ttl || cached.ttl > Math.floor(Date.now() / 1000)) {
40
- return { userId: cached.userId, role: cached.role, handle: cached.handle };
41
- }
61
+ const DRIFT_CLAIMS: CachedClaims = {
62
+ userId: "",
63
+ globalRole: "",
64
+ activeTenantId: "",
65
+ tenantSlug: "",
66
+ tenantRole: "",
67
+ handle: "",
68
+ };
69
+
70
+ function parseIdpGroups(raw: string | undefined | null): string[] {
71
+ if (!raw) return [];
72
+ return raw
73
+ .split(/[,;]+/)
74
+ .map((s) => s.trim())
75
+ .filter(Boolean);
76
+ }
77
+
78
+ function isFederatedEvent(event: PreTokenGenerationV2TriggerEvent): boolean {
79
+ const identitiesRaw = event.request.userAttributes["identities"];
80
+ if (!identitiesRaw) return false;
81
+ try {
82
+ const parsed = JSON.parse(identitiesRaw);
83
+ return Array.isArray(parsed) && parsed.length > 0;
84
+ } catch {
85
+ return false;
42
86
  }
87
+ }
43
88
 
44
- // Cache miss — query RDS
45
- const db = await getPrisma();
89
+ interface RdsClaimsLoad {
90
+ user: {
91
+ id: string;
92
+ role: string;
93
+ handle: string | null;
94
+ suspended: boolean;
95
+ suspendedAt: Date | null;
96
+ } | null;
97
+ activeMembership: {
98
+ tenantId: string;
99
+ role: TenantRole;
100
+ tenant: { slug: string; status: string };
101
+ } | null;
102
+ }
103
+
104
+ async function loadFromRds(
105
+ db: PrismaClient,
106
+ cognitoSub: string,
107
+ preferOrgTenant: boolean,
108
+ preferredTenantId: string | null,
109
+ ): Promise<RdsClaimsLoad> {
46
110
  const user = await db.user.findUnique({
47
111
  where: { cognitoSub },
48
- select: { id: true, role: true, handle: true, suspendedAt: true },
112
+ select: {
113
+ id: true,
114
+ role: true,
115
+ handle: true,
116
+ suspended: true,
117
+ suspendedAt: true,
118
+ personalTenantId: true,
119
+ },
49
120
  });
121
+ if (!user) return { user: null, activeMembership: null };
50
122
 
51
- if (!user) return null;
123
+ const memberships = await db.tenantMember.findMany({
124
+ where: { userId: user.id, status: "ACTIVE" },
125
+ include: { tenant: { select: { id: true, slug: true, status: true, type: true } } },
126
+ });
52
127
 
53
- // S1.8 Check if user is suspended
54
- if (user.suspendedAt && new Date(user.suspendedAt) > new Date()) {
55
- console.warn(`User ${cognitoSub} is suspended until ${user.suspendedAt}`);
56
- return null;
128
+ // Honor an explicit user choice (from a prior switch-tenant call) above
129
+ // any heuristic, provided the membership is still active.
130
+ let active = preferredTenantId
131
+ ? memberships.find(
132
+ (m) => m.tenant.id === preferredTenantId && m.tenant.status === "ACTIVE",
133
+ )
134
+ : undefined;
135
+ if (!active) {
136
+ active = memberships.find(
137
+ (m) =>
138
+ preferOrgTenant && m.tenant.type === "ORGANIZATION" && m.tenant.status === "ACTIVE",
139
+ );
140
+ }
141
+ if (!active) {
142
+ active = memberships.find(
143
+ (m) => m.tenant.id === user.personalTenantId && m.tenant.status === "ACTIVE",
144
+ );
145
+ }
146
+ if (!active) {
147
+ active = memberships.find((m) => m.tenant.status === "ACTIVE");
57
148
  }
58
149
 
59
- const claims: CachedClaims = { userId: user.id, role: user.role, handle: user.handle ?? "" };
150
+ return {
151
+ user: {
152
+ id: user.id,
153
+ role: user.role,
154
+ handle: user.handle,
155
+ suspended: user.suspended,
156
+ suspendedAt: user.suspendedAt,
157
+ },
158
+ activeMembership: active
159
+ ? {
160
+ tenantId: active.tenantId,
161
+ role: active.role,
162
+ tenant: { slug: active.tenant.slug, status: active.tenant.status },
163
+ }
164
+ : null,
165
+ };
166
+ }
60
167
 
61
- // Write to cache
62
- await dynamo.send(new PutItemCommand({
63
- TableName: TABLE,
64
- Item: marshall({
65
- pk: `claims:${cognitoSub}`,
66
- sk: "meta",
67
- ...claims,
68
- ttl: Math.floor(Date.now() / 1000) + CACHE_TTL_SECONDS,
69
- }),
70
- }));
168
+ async function maybeRefreshFederatedRole(
169
+ db: PrismaClient,
170
+ tenantId: string,
171
+ idpGroups: string[],
172
+ currentRole: string,
173
+ ): Promise<TenantRole | null> {
174
+ const mappings = await db.tenantRoleMapping.findMany({
175
+ where: { tenantId },
176
+ select: { idpGroupName: true, tenantRole: true, priority: true },
177
+ });
178
+ const idp = await db.tenantIdentityProvider.findUnique({
179
+ where: { tenantId },
180
+ select: { defaultRole: true, status: true },
181
+ });
182
+ if (!idp || idp.status !== "ACTIVE") return null;
71
183
 
72
- return claims;
184
+ const resolved = resolveTenantRole(
185
+ idpGroups,
186
+ mappings as RoleMappingInput[],
187
+ idp.defaultRole,
188
+ );
189
+ if (!resolved || resolved === currentRole) return null;
190
+ return resolved;
73
191
  }
74
192
 
75
193
  export const handler: PreTokenGenerationV2TriggerHandler = async (event) => {
76
- const claims = await getClaims(event.userName);
77
-
78
- if (claims) {
79
- event.response = {
80
- claimsAndScopeOverrideDetails: {
81
- accessTokenGeneration: {
82
- claimsToAddOrOverride: {
83
- "custom:userId": claims.userId,
84
- "custom:role": claims.role,
85
- "custom:handle": claims.handle,
86
- },
87
- },
88
- },
194
+ const cognitoSub = event.userName;
195
+ const claimsCache = getCache();
196
+ const federated = isFederatedEvent(event);
197
+ const idpGroups = parseIdpGroups(event.request.userAttributes["custom:idpGroups"]);
198
+
199
+ // Cache hits skip the user-suspension and tenant-status checks below
200
+ // (RDS is only consulted on miss). The mitigation is *active invalidation*:
201
+ // - User suspension paths MUST call `claimsCache.invalidate(cognitoSub)`.
202
+ // - TODO(T3): tenant-suspension API must invalidate caches for all members.
203
+ // Without invalidation, suspended users keep valid claims for up to one
204
+ // cache TTL (DEFAULT_CACHE_TTL_SECONDS = 3600s). Tracked as G2 finding H3.
205
+ let claims = await claimsCache.get(cognitoSub);
206
+ let cacheHit = !!claims;
207
+
208
+ if (!claims) {
209
+ const db = await getPrisma();
210
+ // Read the user's last explicit tenant preference, even from an expired
211
+ // cache row, so an admin-side switch-tenant call survives cache TTL.
212
+ let preferredTenantId: string | null = null;
213
+ try {
214
+ preferredTenantId = await claimsCache.getActiveTenantPreference(cognitoSub);
215
+ } catch (err) {
216
+ console.warn(
217
+ JSON.stringify({
218
+ event: "pretoken.preference_lookup_failed",
219
+ cognitoSub,
220
+ error: (err as { code?: string })?.code ?? "unknown",
221
+ }),
222
+ );
223
+ }
224
+ const loaded = await loadFromRds(db, cognitoSub, federated, preferredTenantId);
225
+
226
+ if (!loaded.user) {
227
+ console.warn(JSON.stringify({ event: "pretoken.drift", cognitoSub }));
228
+ claims = { ...DRIFT_CLAIMS };
229
+ writeAccessTokenClaims(event, claims);
230
+ return event;
231
+ }
232
+
233
+ // `suspended` is the authoritative flag set by user-deprovisioning + admin
234
+ // dashboard; `suspendedAt` is the timestamp of the action (always a past
235
+ // value when present). Treat either signal as suspension. Defense-in-depth:
236
+ // even if a writer forgets one column, the other still blocks issuance.
237
+ if (loaded.user.suspended || loaded.user.suspendedAt !== null) {
238
+ console.warn(JSON.stringify({ event: "pretoken.suspended", cognitoSub }));
239
+ claims = { ...DRIFT_CLAIMS };
240
+ writeAccessTokenClaims(event, claims);
241
+ return event;
242
+ }
243
+
244
+ claims = {
245
+ userId: loaded.user.id,
246
+ globalRole: loaded.user.role,
247
+ activeTenantId: loaded.activeMembership?.tenantId ?? "",
248
+ tenantSlug: loaded.activeMembership?.tenant.slug ?? "",
249
+ tenantRole: loaded.activeMembership?.role ?? "",
250
+ handle: loaded.user.handle ?? "",
89
251
  };
90
252
  }
91
253
 
254
+ if (federated && claims.activeTenantId && idpGroups.length > 0) {
255
+ try {
256
+ const db = await getPrisma();
257
+ const refreshed = await maybeRefreshFederatedRole(
258
+ db,
259
+ claims.activeTenantId,
260
+ idpGroups,
261
+ claims.tenantRole,
262
+ );
263
+ if (refreshed) {
264
+ // Only emit the new role into the JWT after the DB persist succeeds.
265
+ // Otherwise a transient DB error would oscillate the user's effective
266
+ // role between cached-old and JWT-new on alternating refreshes (G2 H2).
267
+ let persisted = false;
268
+ try {
269
+ await db.tenantMember.update({
270
+ where: {
271
+ tenantId_userId: { tenantId: claims.activeTenantId, userId: claims.userId },
272
+ },
273
+ data: { role: refreshed },
274
+ });
275
+ persisted = true;
276
+ } catch (err) {
277
+ console.warn(
278
+ JSON.stringify({
279
+ event: "pretoken.role_refresh_persist_failed",
280
+ cognitoSub,
281
+ error: (err as { code?: string })?.code ?? "unknown",
282
+ }),
283
+ );
284
+ }
285
+ if (persisted) {
286
+ claims = { ...claims, tenantRole: refreshed };
287
+ cacheHit = false;
288
+ console.log(
289
+ JSON.stringify({
290
+ event: "pretoken.role_refreshed",
291
+ cognitoSub,
292
+ tenantId: claims.activeTenantId,
293
+ }),
294
+ );
295
+ }
296
+ }
297
+ } catch (err) {
298
+ console.warn(
299
+ JSON.stringify({
300
+ event: "pretoken.role_refresh_failed",
301
+ cognitoSub,
302
+ error: (err as { code?: string }).code ?? "unknown",
303
+ }),
304
+ );
305
+ }
306
+ }
307
+
308
+ if (!cacheHit && claims.userId) {
309
+ await claimsCache.put(cognitoSub, claims, DEFAULT_CACHE_TTL_SECONDS);
310
+ }
311
+
312
+ writeAccessTokenClaims(event, claims);
92
313
  return event;
93
314
  };
315
+
316
+ function writeAccessTokenClaims(
317
+ event: PreTokenGenerationV2TriggerEvent,
318
+ claims: CachedClaims,
319
+ ): void {
320
+ event.response = {
321
+ claimsAndScopeOverrideDetails: {
322
+ accessTokenGeneration: {
323
+ claimsToAddOrOverride: {
324
+ "custom:userId": claims.userId,
325
+ "custom:globalRole": claims.globalRole,
326
+ "custom:activeTenantId": claims.activeTenantId,
327
+ "custom:tenantSlug": claims.tenantSlug,
328
+ "custom:tenantRole": claims.tenantRole,
329
+ "custom:handle": claims.handle,
330
+ },
331
+ },
332
+ },
333
+ };
334
+ }