@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
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Envelope encryption helpers for OAuth device-authorization records.
3
+ *
4
+ * Pattern:
5
+ * 1. A per-record data-encryption key (DEK) is derived via HKDF-SHA256
6
+ * from the device_code (held only by the polling agent) and a
7
+ * KMS-protected wrap key (KEK).
8
+ * 2. The DEK encrypts the payload (Cognito tokens) with AES-256-GCM.
9
+ * 3. The DynamoDB row stores only iv, auth tag, ciphertext, and the
10
+ * KMS-wrapped KEK reference. A direct GetItem without device_code
11
+ * cannot derive the DEK and therefore cannot decrypt.
12
+ *
13
+ * The KEK in the MVP path is a 32-byte key fetched from a static SSM
14
+ * parameter / env var; production wraps that fetch in KMS:Decrypt so the
15
+ * key never leaves a memory-only buffer. The DEK derivation salt is
16
+ * random per-record and stored alongside the ciphertext — without
17
+ * device_code the salt + KEK alone are insufficient.
18
+ *
19
+ * Algorithms are pinned. Unit tests assert that swapping device_code at
20
+ * decrypt time fails with an authentication error rather than returning
21
+ * silently truncated plaintext.
22
+ */
23
+ /** HKDF info string. Versioned so future schema changes are detectable. */
24
+ export declare const DEK_INFO = "trellis-device-auth-dek-v1";
25
+ export interface SealedEnvelope {
26
+ /** Random per-record HKDF salt, base64url. */
27
+ salt: string;
28
+ /** AES-GCM IV, base64url. */
29
+ iv: string;
30
+ /** AES-GCM authentication tag, base64url. */
31
+ tag: string;
32
+ /** AES-GCM ciphertext, base64url. */
33
+ ciphertext: string;
34
+ /** Algorithm tag — pinned for forward compatibility. */
35
+ alg: "AES-256-GCM+HKDF-SHA256";
36
+ /** Info string used in HKDF; lets us rotate without re-keying. */
37
+ info: string;
38
+ /**
39
+ * KEK version (G4 MEDIUM-1). Forward-compatible field that lets us
40
+ * rotate the wrap key without re-encrypting every record. MVP path
41
+ * writes 1 and `open()` only knows how to dispatch the version-1
42
+ * fetcher; future rotations register additional fetchers via
43
+ * `setKmsKekFetcherForVersion()` and bump the value written by `seal`.
44
+ */
45
+ keyVersion: number;
46
+ }
47
+ /** Current KEK version written by `seal`. */
48
+ export declare const CURRENT_KEK_VERSION = 1;
49
+ /**
50
+ * Derive the per-record DEK from `(deviceCode, kek, salt)` via HKDF-SHA256.
51
+ * The device_code is the IKM; the KEK is mixed in as part of the salt so a
52
+ * stolen DynamoDB row alone cannot derive the DEK.
53
+ */
54
+ export declare function deriveDek(deviceCode: string, kek: Buffer, salt: Buffer): Buffer;
55
+ /**
56
+ * Seal `plaintext` (UTF-8 string) under a DEK derived from `(deviceCode, kek)`.
57
+ * Returns the storable envelope; ciphertext + iv + tag are sufficient for
58
+ * decryption only when device_code is supplied at open time.
59
+ */
60
+ export declare function seal(plaintext: string, deviceCode: string, kek: Buffer): SealedEnvelope;
61
+ /**
62
+ * Open an envelope produced by `seal`. Returns the plaintext UTF-8 string.
63
+ * Throws on auth-tag mismatch (wrong device_code, tampered payload).
64
+ *
65
+ * The supplied `kek` parameter is the version-1 KEK. For envelopes
66
+ * stamped with a different `keyVersion`, callers should resolve the
67
+ * matching KEK before invoking this function (see `resolveKekForVersion`).
68
+ * The MVP runs version 1 only; this signature stays compatible for
69
+ * forward-rotation scenarios (G4 MEDIUM-1).
70
+ */
71
+ export declare function open(envelope: SealedEnvelope, deviceCode: string, kek: Buffer): string;
72
+ /**
73
+ * Resolve the KEK for a given envelope version. The MVP path is a
74
+ * single-version trampoline; future rotations register additional
75
+ * fetchers and dispatch on `version`. Callers reading a previously
76
+ * sealed envelope should call this rather than `resolveKek()` directly
77
+ * (G4 MEDIUM-1).
78
+ */
79
+ export declare function resolveKekForVersion(version: number): Promise<Buffer>;
80
+ /**
81
+ * Constant-time compare for two strings expected to be of the same length.
82
+ * Used by callers that need to compare device_code candidates against a
83
+ * stored hash (we don't store the device_code itself, so this is reserved
84
+ * for refresh-jti comparisons).
85
+ */
86
+ export declare function safeEqual(a: string, b: string): boolean;
87
+ /**
88
+ * Hook for tests + production wiring. Defaults to a stub that throws,
89
+ * directing callers to install `@aws-sdk/client-kms` and override via
90
+ * `setKmsKekFetcher`. The skybber CDK wires the AWS-SDK-backed fetcher
91
+ * during process bootstrap.
92
+ */
93
+ export type KmsKekFetcher = (kmsKeyId: string, region: string) => Promise<Buffer>;
94
+ /** Wire the production KMS path. Skybber CDK calls this during bootstrap. */
95
+ export declare function setKmsKekFetcher(fn: KmsKekFetcher): void;
96
+ export declare function resolveKek(): Promise<Buffer>;
97
+ /** Reset the cached KEK. Test-only. */
98
+ export declare function _resetKekCacheForTest(): void;
99
+ /** Reset the KMS fetcher to its default (test-only). */
100
+ export declare function _resetKmsKekFetcherForTest(): void;
101
+ //# sourceMappingURL=envelope-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope-crypto.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/envelope-crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAUH,2EAA2E;AAC3E,eAAO,MAAM,QAAQ,+BAA+B,CAAC;AAWrD,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,GAAG,EAAE,yBAAyB,CAAC;IAC/B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,6CAA6C;AAC7C,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAUrC;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX,MAAM,CAeR;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,cAAc,CAsBhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAClB,QAAQ,EAAE,cAAc,EACxB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,CAmCR;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK3E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKvD;AAYD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAQlF,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI,CAExD;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CA2BlD;AAED,uCAAuC;AACvC,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wDAAwD;AACxD,wBAAgB,0BAA0B,IAAI,IAAI,CAMjD"}
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ /**
3
+ * Envelope encryption helpers for OAuth device-authorization records.
4
+ *
5
+ * Pattern:
6
+ * 1. A per-record data-encryption key (DEK) is derived via HKDF-SHA256
7
+ * from the device_code (held only by the polling agent) and a
8
+ * KMS-protected wrap key (KEK).
9
+ * 2. The DEK encrypts the payload (Cognito tokens) with AES-256-GCM.
10
+ * 3. The DynamoDB row stores only iv, auth tag, ciphertext, and the
11
+ * KMS-wrapped KEK reference. A direct GetItem without device_code
12
+ * cannot derive the DEK and therefore cannot decrypt.
13
+ *
14
+ * The KEK in the MVP path is a 32-byte key fetched from a static SSM
15
+ * parameter / env var; production wraps that fetch in KMS:Decrypt so the
16
+ * key never leaves a memory-only buffer. The DEK derivation salt is
17
+ * random per-record and stored alongside the ciphertext — without
18
+ * device_code the salt + KEK alone are insufficient.
19
+ *
20
+ * Algorithms are pinned. Unit tests assert that swapping device_code at
21
+ * decrypt time fails with an authentication error rather than returning
22
+ * silently truncated plaintext.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.CURRENT_KEK_VERSION = exports.DEK_INFO = void 0;
26
+ exports.deriveDek = deriveDek;
27
+ exports.seal = seal;
28
+ exports.open = open;
29
+ exports.resolveKekForVersion = resolveKekForVersion;
30
+ exports.safeEqual = safeEqual;
31
+ exports.setKmsKekFetcher = setKmsKekFetcher;
32
+ exports.resolveKek = resolveKek;
33
+ exports._resetKekCacheForTest = _resetKekCacheForTest;
34
+ exports._resetKmsKekFetcherForTest = _resetKmsKekFetcherForTest;
35
+ const node_crypto_1 = require("node:crypto");
36
+ /** HKDF info string. Versioned so future schema changes are detectable. */
37
+ exports.DEK_INFO = "trellis-device-auth-dek-v1";
38
+ /** Length of the AES-256 DEK in bytes. */
39
+ const DEK_LEN = 32;
40
+ /** Length of the HKDF salt stored alongside the ciphertext. */
41
+ const SALT_LEN = 32;
42
+ /** AES-GCM IV length (96 bits is the recommended NIST size). */
43
+ const IV_LEN = 12;
44
+ /** AES-GCM authentication tag length (16 bytes / 128 bits). */
45
+ const TAG_LEN = 16;
46
+ /** Current KEK version written by `seal`. */
47
+ exports.CURRENT_KEK_VERSION = 1;
48
+ function b64url(b) {
49
+ return b.toString("base64url");
50
+ }
51
+ function fromB64url(s) {
52
+ return Buffer.from(s, "base64url");
53
+ }
54
+ /**
55
+ * Derive the per-record DEK from `(deviceCode, kek, salt)` via HKDF-SHA256.
56
+ * The device_code is the IKM; the KEK is mixed in as part of the salt so a
57
+ * stolen DynamoDB row alone cannot derive the DEK.
58
+ */
59
+ function deriveDek(deviceCode, kek, salt) {
60
+ if (!deviceCode || deviceCode.length < 16) {
61
+ throw new Error("device_code too short");
62
+ }
63
+ if (kek.length !== 32) {
64
+ throw new Error("KEK must be 32 bytes");
65
+ }
66
+ if (salt.length !== SALT_LEN) {
67
+ throw new Error(`salt must be ${SALT_LEN} bytes`);
68
+ }
69
+ // HKDF: ikm = device_code; salt = (kek || salt); info = DEK_INFO.
70
+ // Mixing KEK into the salt means the DEK depends on both pieces.
71
+ const combinedSalt = Buffer.concat([kek, salt]);
72
+ const dek = (0, node_crypto_1.hkdfSync)("sha256", deviceCode, combinedSalt, exports.DEK_INFO, DEK_LEN);
73
+ return Buffer.from(dek);
74
+ }
75
+ /**
76
+ * Seal `plaintext` (UTF-8 string) under a DEK derived from `(deviceCode, kek)`.
77
+ * Returns the storable envelope; ciphertext + iv + tag are sufficient for
78
+ * decryption only when device_code is supplied at open time.
79
+ */
80
+ function seal(plaintext, deviceCode, kek) {
81
+ const salt = (0, node_crypto_1.randomBytes)(SALT_LEN);
82
+ const iv = (0, node_crypto_1.randomBytes)(IV_LEN);
83
+ const dek = deriveDek(deviceCode, kek, salt);
84
+ const cipher = (0, node_crypto_1.createCipheriv)("aes-256-gcm", dek, iv, { authTagLength: TAG_LEN });
85
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
86
+ const tag = cipher.getAuthTag();
87
+ // Wipe the DEK from our reference. (V8 doesn't guarantee wipe, but at
88
+ // least we don't keep a reachable reference past the call.)
89
+ dek.fill(0);
90
+ return {
91
+ salt: b64url(salt),
92
+ iv: b64url(iv),
93
+ tag: b64url(tag),
94
+ ciphertext: b64url(ct),
95
+ alg: "AES-256-GCM+HKDF-SHA256",
96
+ info: exports.DEK_INFO,
97
+ keyVersion: exports.CURRENT_KEK_VERSION,
98
+ };
99
+ }
100
+ /**
101
+ * Open an envelope produced by `seal`. Returns the plaintext UTF-8 string.
102
+ * Throws on auth-tag mismatch (wrong device_code, tampered payload).
103
+ *
104
+ * The supplied `kek` parameter is the version-1 KEK. For envelopes
105
+ * stamped with a different `keyVersion`, callers should resolve the
106
+ * matching KEK before invoking this function (see `resolveKekForVersion`).
107
+ * The MVP runs version 1 only; this signature stays compatible for
108
+ * forward-rotation scenarios (G4 MEDIUM-1).
109
+ */
110
+ function open(envelope, deviceCode, kek) {
111
+ if (envelope.alg !== "AES-256-GCM+HKDF-SHA256") {
112
+ throw new Error(`unsupported envelope alg: ${envelope.alg}`);
113
+ }
114
+ if (envelope.info !== exports.DEK_INFO) {
115
+ throw new Error(`unsupported envelope info: ${envelope.info}`);
116
+ }
117
+ // Reject envelopes from versions we don't yet know how to dispatch.
118
+ // Older envelopes without keyVersion default to version 1 for
119
+ // backward compatibility with rows already written under the old
120
+ // shape — those still decrypt with the version-1 KEK.
121
+ const declaredVersion = envelope.keyVersion ?? 1;
122
+ if (declaredVersion !== 1) {
123
+ throw new Error(`unsupported envelope keyVersion: ${declaredVersion}`);
124
+ }
125
+ const salt = fromB64url(envelope.salt);
126
+ const iv = fromB64url(envelope.iv);
127
+ const tag = fromB64url(envelope.tag);
128
+ const ct = fromB64url(envelope.ciphertext);
129
+ if (salt.length !== SALT_LEN)
130
+ throw new Error("envelope salt length");
131
+ if (iv.length !== IV_LEN)
132
+ throw new Error("envelope iv length");
133
+ if (tag.length !== TAG_LEN)
134
+ throw new Error("envelope tag length");
135
+ const dek = deriveDek(deviceCode, kek, salt);
136
+ const decipher = (0, node_crypto_1.createDecipheriv)("aes-256-gcm", dek, iv, { authTagLength: TAG_LEN });
137
+ decipher.setAuthTag(tag);
138
+ let pt;
139
+ try {
140
+ pt = Buffer.concat([decipher.update(ct), decipher.final()]);
141
+ }
142
+ finally {
143
+ dek.fill(0);
144
+ }
145
+ return pt.toString("utf8");
146
+ }
147
+ /**
148
+ * Resolve the KEK for a given envelope version. The MVP path is a
149
+ * single-version trampoline; future rotations register additional
150
+ * fetchers and dispatch on `version`. Callers reading a previously
151
+ * sealed envelope should call this rather than `resolveKek()` directly
152
+ * (G4 MEDIUM-1).
153
+ */
154
+ async function resolveKekForVersion(version) {
155
+ if (version !== 1) {
156
+ throw new Error(`unsupported envelope keyVersion: ${version}`);
157
+ }
158
+ return resolveKek();
159
+ }
160
+ /**
161
+ * Constant-time compare for two strings expected to be of the same length.
162
+ * Used by callers that need to compare device_code candidates against a
163
+ * stored hash (we don't store the device_code itself, so this is reserved
164
+ * for refresh-jti comparisons).
165
+ */
166
+ function safeEqual(a, b) {
167
+ const ab = Buffer.from(a, "utf8");
168
+ const bb = Buffer.from(b, "utf8");
169
+ if (ab.length !== bb.length)
170
+ return false;
171
+ return (0, node_crypto_1.timingSafeEqual)(ab, bb);
172
+ }
173
+ /**
174
+ * Resolve the KEK at runtime. Two paths:
175
+ * 1. process.env.DEVICE_AUTH_KEK_BASE64 — local dev / test fast path.
176
+ * 2. KMS:GenerateDataKey via DEVICE_AUTH_KMS_KEY_ID. Production path.
177
+ *
178
+ * The result is cached for the lifetime of the process. Callers must
179
+ * NOT log the buffer.
180
+ */
181
+ let cachedKek;
182
+ let kmsKekFetcher = async () => {
183
+ throw new Error("KMS KEK fetcher not configured; call setKmsKekFetcher() at startup or set DEVICE_AUTH_KEK_BASE64");
184
+ };
185
+ /** Wire the production KMS path. Skybber CDK calls this during bootstrap. */
186
+ function setKmsKekFetcher(fn) {
187
+ kmsKekFetcher = fn;
188
+ }
189
+ async function resolveKek() {
190
+ if (cachedKek)
191
+ return cachedKek;
192
+ const inline = process.env.DEVICE_AUTH_KEK_BASE64;
193
+ if (inline) {
194
+ const b = Buffer.from(inline, "base64");
195
+ if (b.length !== 32) {
196
+ throw new Error("DEVICE_AUTH_KEK_BASE64 must decode to 32 bytes");
197
+ }
198
+ cachedKek = b;
199
+ return cachedKek;
200
+ }
201
+ const kmsKeyId = process.env.DEVICE_AUTH_KMS_KEY_ID;
202
+ if (!kmsKeyId) {
203
+ throw new Error("Device-auth KEK not configured: set DEVICE_AUTH_KEK_BASE64 (dev) or DEVICE_AUTH_KMS_KEY_ID (prod)");
204
+ }
205
+ const region = process.env.AWS_REGION || "us-east-1";
206
+ const plain = await kmsKekFetcher(kmsKeyId, region);
207
+ if (plain.length !== 32) {
208
+ throw new Error("KMS plaintext key wrong length");
209
+ }
210
+ cachedKek = plain;
211
+ return cachedKek;
212
+ }
213
+ /** Reset the cached KEK. Test-only. */
214
+ function _resetKekCacheForTest() {
215
+ cachedKek = undefined;
216
+ }
217
+ /** Reset the KMS fetcher to its default (test-only). */
218
+ function _resetKmsKekFetcherForTest() {
219
+ kmsKekFetcher = async () => {
220
+ throw new Error("KMS KEK fetcher not configured; call setKmsKekFetcher() at startup or set DEVICE_AUTH_KEK_BASE64");
221
+ };
222
+ }
223
+ //# sourceMappingURL=envelope-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope-crypto.js","sourceRoot":"","sources":["../../../src/lib/oauth/envelope-crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Refresh-token reuse detection (RFC 6819 §5.2.2.5, T9b-d).
3
+ *
4
+ * Each refresh-token JTI is recorded once. On refresh:
5
+ * - load the row by jti
6
+ * - if absent → unknown token; treat as suspect, deny
7
+ * - if `consumed` → REPLAY; revoke all sessions for that user via
8
+ * AdminUserGlobalSignOut and emit `auth.refresh_replay`
9
+ * - if `active` → mark consumed, issue new refresh with new jti
10
+ *
11
+ * Storage: AGENT_REFRESH_TABLE in DynamoDB. Same table also holds the
12
+ * agent-session metadata listed by `/api/users/me/agent-sessions`.
13
+ */
14
+ export type AgentSessionStatus = "active" | "consumed" | "revoked";
15
+ export interface AgentSessionRecord {
16
+ /** Trellis session id (cuid-ish opaque). */
17
+ sessionId: string;
18
+ /** Owning user's trellis user id. */
19
+ userId: string;
20
+ /** Cognito sub bound to the issued refresh token. */
21
+ cognitoSub: string;
22
+ /** Tenant id from the session at approval time. */
23
+ tenantId: string;
24
+ /** Currently-active refresh-token JTI (null after revoke). */
25
+ currentJti: string | null;
26
+ /** Set of all jti's ever issued for this session, for replay detection. */
27
+ /** We track the latest only — older are flipped to consumed individually. */
28
+ status: AgentSessionStatus;
29
+ /** Free-form label set by the device-auth flow (User-Agent excerpt). */
30
+ agentLabel?: string;
31
+ sourceIp?: string;
32
+ createdAt: number;
33
+ lastUsedAt: number;
34
+ }
35
+ export interface RefreshJtiRecord {
36
+ jti: string;
37
+ sessionId: string;
38
+ userId: string;
39
+ cognitoSub: string;
40
+ status: "active" | "consumed";
41
+ issuedAt: number;
42
+ consumedAt?: number;
43
+ }
44
+ export interface CognitoRevoker {
45
+ globalSignOut(input: {
46
+ userPoolId: string;
47
+ cognitoUsername: string;
48
+ }): Promise<void>;
49
+ }
50
+ export interface AuditEmitter {
51
+ emit(input: {
52
+ type: string;
53
+ tenantId: string;
54
+ actorUserId: string;
55
+ payload: Record<string, unknown>;
56
+ sourceIp?: string;
57
+ agentSessionId?: string;
58
+ }): Promise<void>;
59
+ }
60
+ /** Initial record write at session creation (after approval). */
61
+ export declare function recordAgentSession(input: {
62
+ session: AgentSessionRecord;
63
+ initialJti: string;
64
+ }): Promise<void>;
65
+ /**
66
+ * Validate a refresh-token JTI. Three outcomes:
67
+ * - "ok" → the jti was active; row is now flipped to consumed.
68
+ * - "replay" → jti was already consumed → caller must revoke session globally.
69
+ * - "unknown" → jti not present (foreign or expired); deny.
70
+ */
71
+ export declare function consumeRefreshJti(jti: string): Promise<{
72
+ outcome: "ok" | "replay" | "unknown";
73
+ record?: RefreshJtiRecord;
74
+ }>;
75
+ /**
76
+ * On confirmed replay: revoke all sessions for the user, mark the session
77
+ * row revoked, emit `auth.refresh_replay`. The Cognito SDK call uses
78
+ * AdminUserGlobalSignOut to invalidate every refresh-derived token.
79
+ *
80
+ * Hardening (G4 CRITICAL-1, CRITICAL-2):
81
+ * - The Cognito username used for revocation is read directly from
82
+ * `jtiRecord.cognitoSub` (the value bound at session-creation time).
83
+ * Callers may not supply an alternate identity; this prevents the
84
+ * wrong account from being revoked if a caller forwards a request-
85
+ * scoped value here.
86
+ * - The audit event is emitted FIRST so a downstream Cognito or DDB
87
+ * failure cannot suppress the `auth.refresh_replay` signal. The
88
+ * mutation steps follow in order; each is wrapped so that audit
89
+ * emit completes even when a later step throws.
90
+ */
91
+ export declare function handleRefreshReplay(input: {
92
+ jtiRecord: RefreshJtiRecord;
93
+ tenantId: string;
94
+ userPoolId: string;
95
+ cognito: CognitoRevoker;
96
+ audit: AuditEmitter;
97
+ sourceIp?: string;
98
+ }): Promise<void>;
99
+ /**
100
+ * Issue a new refresh JTI for a session. The old JTI must already be
101
+ * consumed (caller flips it before calling). Updates the session row.
102
+ */
103
+ export declare function rotateRefreshJti(input: {
104
+ sessionId: string;
105
+ userId: string;
106
+ cognitoSub: string;
107
+ newJti: string;
108
+ }): Promise<void>;
109
+ /** List all active sessions for a user. Used by `/api/users/me/agent-sessions`. */
110
+ export declare function listAgentSessions(userId: string): Promise<AgentSessionRecord[]>;
111
+ /** Revoke a session by id; caller must verify the session belongs to the user. */
112
+ export declare function revokeAgentSession(input: {
113
+ sessionId: string;
114
+ userPoolId: string;
115
+ cognitoUsername: string;
116
+ cognito: CognitoRevoker;
117
+ audit: AuditEmitter;
118
+ tenantId: string;
119
+ actorUserId: string;
120
+ sourceIp?: string;
121
+ }): Promise<void>;
122
+ /** Look up a session by id (auth check helper). */
123
+ export declare function getAgentSession(sessionId: string): Promise<AgentSessionRecord | null>;
124
+ /** Test-only: helper to clear a session row directly. */
125
+ export declare function _deleteAgentSessionForTest(sessionId: string): Promise<void>;
126
+ //# sourceMappingURL=refresh-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-detection.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/refresh-detection.ts"],"names":[],"mappings":"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"}