@shipfox/api-auth 18.0.0 → 20.0.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 (151) hide show
  1. package/.turbo/turbo-build.log +17 -2
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +24 -9
  4. package/dist/core/administration.d.ts +15 -0
  5. package/dist/core/administration.d.ts.map +1 -1
  6. package/dist/core/administration.js +44 -2
  7. package/dist/core/administration.js.map +1 -1
  8. package/dist/core/auth.d.ts.map +1 -1
  9. package/dist/core/auth.js +5 -3
  10. package/dist/core/auth.js.map +1 -1
  11. package/dist/core/cimd.d.ts +39 -0
  12. package/dist/core/cimd.d.ts.map +1 -0
  13. package/dist/core/cimd.js +392 -0
  14. package/dist/core/cimd.js.map +1 -0
  15. package/dist/core/entities/agent-access.d.ts +75 -0
  16. package/dist/core/entities/agent-access.d.ts.map +1 -0
  17. package/dist/core/entities/agent-access.js +3 -0
  18. package/dist/core/entities/agent-access.js.map +1 -0
  19. package/dist/core/errors.d.ts +17 -0
  20. package/dist/core/errors.d.ts.map +1 -1
  21. package/dist/core/errors.js +32 -0
  22. package/dist/core/errors.js.map +1 -1
  23. package/dist/core/oauth-client-resolver.d.ts +54 -0
  24. package/dist/core/oauth-client-resolver.d.ts.map +1 -0
  25. package/dist/core/oauth-client-resolver.js +154 -0
  26. package/dist/core/oauth-client-resolver.js.map +1 -0
  27. package/dist/core/oauth-client.d.ts +39 -0
  28. package/dist/core/oauth-client.d.ts.map +1 -0
  29. package/dist/core/oauth-client.js +236 -0
  30. package/dist/core/oauth-client.js.map +1 -0
  31. package/dist/db/admin-user-moderation.d.ts.map +1 -1
  32. package/dist/db/admin-user-moderation.js +30 -26
  33. package/dist/db/admin-user-moderation.js.map +1 -1
  34. package/dist/db/admin-user-summary.d.ts +22 -0
  35. package/dist/db/admin-user-summary.d.ts.map +1 -1
  36. package/dist/db/admin-user-summary.js +75 -1
  37. package/dist/db/admin-user-summary.js.map +1 -1
  38. package/dist/db/admin-users.d.ts +11 -0
  39. package/dist/db/admin-users.d.ts.map +1 -1
  40. package/dist/db/admin-users.js +4 -1
  41. package/dist/db/admin-users.js.map +1 -1
  42. package/dist/db/agent-access-retention.d.ts +10 -0
  43. package/dist/db/agent-access-retention.d.ts.map +1 -0
  44. package/dist/db/agent-access-retention.js +11 -0
  45. package/dist/db/agent-access-retention.js.map +1 -0
  46. package/dist/db/agent-access.d.ts +255 -0
  47. package/dist/db/agent-access.d.ts.map +1 -0
  48. package/dist/db/agent-access.js +705 -0
  49. package/dist/db/agent-access.js.map +1 -0
  50. package/dist/db/db.d.ts +2604 -238
  51. package/dist/db/db.d.ts.map +1 -1
  52. package/dist/db/db.js +7 -0
  53. package/dist/db/db.js.map +1 -1
  54. package/dist/db/schema/agent-access.d.ts +1204 -0
  55. package/dist/db/schema/agent-access.d.ts.map +1 -0
  56. package/dist/db/schema/agent-access.js +267 -0
  57. package/dist/db/schema/agent-access.js.map +1 -0
  58. package/dist/db/schema/users.d.ts.map +1 -1
  59. package/dist/db/schema/users.js +8 -3
  60. package/dist/db/schema/users.js.map +1 -1
  61. package/dist/index.d.ts +11 -2
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +26 -2
  64. package/dist/index.js.map +1 -1
  65. package/dist/metrics/instance.d.ts +1 -1
  66. package/dist/metrics/instance.d.ts.map +1 -1
  67. package/dist/metrics/instance.js.map +1 -1
  68. package/dist/presentation/routes/administration.d.ts.map +1 -1
  69. package/dist/presentation/routes/administration.js +123 -20
  70. package/dist/presentation/routes/administration.js.map +1 -1
  71. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
  72. package/dist/presentation/routes/email-verification/verify-email-resend.js +6 -1
  73. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
  74. package/dist/presentation/routes/oauth.d.ts +9 -0
  75. package/dist/presentation/routes/oauth.d.ts.map +1 -0
  76. package/dist/presentation/routes/oauth.js +150 -0
  77. package/dist/presentation/routes/oauth.js.map +1 -0
  78. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  79. package/dist/presentation/routes/rate-limit.js +34 -0
  80. package/dist/presentation/routes/rate-limit.js.map +1 -1
  81. package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
  82. package/dist/presentation/routes/registration/signup.js +59 -50
  83. package/dist/presentation/routes/registration/signup.js.map +1 -1
  84. package/dist/temporal/activities/agent-access-retention.d.ts +13 -0
  85. package/dist/temporal/activities/agent-access-retention.d.ts.map +1 -0
  86. package/dist/temporal/activities/agent-access-retention.js +58 -0
  87. package/dist/temporal/activities/agent-access-retention.js.map +1 -0
  88. package/dist/temporal/activities/index.d.ts +5 -0
  89. package/dist/temporal/activities/index.d.ts.map +1 -0
  90. package/dist/temporal/activities/index.js +8 -0
  91. package/dist/temporal/activities/index.js.map +1 -0
  92. package/dist/temporal/constants.d.ts +2 -0
  93. package/dist/temporal/constants.d.ts.map +1 -0
  94. package/dist/temporal/constants.js +3 -0
  95. package/dist/temporal/constants.js.map +1 -0
  96. package/dist/temporal/workflows/agent-access-retention-cron.d.ts +2 -0
  97. package/dist/temporal/workflows/agent-access-retention-cron.d.ts.map +1 -0
  98. package/dist/temporal/workflows/agent-access-retention-cron.js +22 -0
  99. package/dist/temporal/workflows/agent-access-retention-cron.js.map +1 -0
  100. package/dist/temporal/workflows/index.bundle.js +25227 -0
  101. package/dist/temporal/workflows/index.bundle.meta.json +1 -0
  102. package/dist/temporal/workflows/index.d.ts +2 -0
  103. package/dist/temporal/workflows/index.d.ts.map +1 -0
  104. package/dist/temporal/workflows/index.js +3 -0
  105. package/dist/temporal/workflows/index.js.map +1 -0
  106. package/dist/tsconfig.test.tsbuildinfo +1 -1
  107. package/drizzle/0003_brainy_luckman.sql +6 -0
  108. package/drizzle/0004_yummy_runaways.sql +110 -0
  109. package/drizzle/meta/0003_snapshot.json +844 -0
  110. package/drizzle/meta/0004_snapshot.json +1751 -0
  111. package/drizzle/meta/_journal.json +14 -0
  112. package/package.json +11 -8
  113. package/src/core/admin-role.test.ts +70 -0
  114. package/src/core/administration.ts +78 -1
  115. package/src/core/auth.ts +3 -5
  116. package/src/core/cimd.test.ts +213 -0
  117. package/src/core/cimd.ts +532 -0
  118. package/src/core/entities/agent-access.ts +80 -0
  119. package/src/core/errors.ts +48 -0
  120. package/src/core/oauth-client-resolver.test.ts +198 -0
  121. package/src/core/oauth-client-resolver.ts +253 -0
  122. package/src/core/oauth-client.test.ts +157 -0
  123. package/src/core/oauth-client.ts +331 -0
  124. package/src/db/admin-user-moderation.ts +53 -45
  125. package/src/db/admin-user-summary.test.ts +228 -0
  126. package/src/db/admin-user-summary.ts +143 -1
  127. package/src/db/admin-users.ts +21 -1
  128. package/src/db/agent-access-retention.ts +10 -0
  129. package/src/db/agent-access.test.ts +1064 -0
  130. package/src/db/agent-access.ts +1491 -0
  131. package/src/db/db.ts +14 -0
  132. package/src/db/schema/agent-access.ts +273 -0
  133. package/src/db/schema/users.ts +9 -3
  134. package/src/index.test.ts +27 -0
  135. package/src/index.ts +82 -0
  136. package/src/metrics/instance.ts +6 -1
  137. package/src/presentation/routes/administration.test.ts +359 -2
  138. package/src/presentation/routes/administration.ts +142 -20
  139. package/src/presentation/routes/email-verification/verify-email-resend.ts +7 -1
  140. package/src/presentation/routes/oauth.test.ts +133 -0
  141. package/src/presentation/routes/oauth.ts +144 -0
  142. package/src/presentation/routes/rate-limit.ts +16 -0
  143. package/src/presentation/routes/registration/signup.ts +52 -41
  144. package/src/temporal/activities/agent-access-retention.ts +76 -0
  145. package/src/temporal/activities/index.ts +5 -0
  146. package/src/temporal/constants.ts +1 -0
  147. package/src/temporal/workflows/agent-access-retention-cron.ts +23 -0
  148. package/src/temporal/workflows/index.ts +1 -0
  149. package/test/globalSetup.ts +1 -1
  150. package/test/routes.ts +3 -1
  151. package/tsconfig.build.tsbuildinfo +1 -1
@@ -41,6 +41,16 @@ const policies = {
41
41
  windowSeconds: 5 * 60
42
42
  }
43
43
  },
44
+ directory: {
45
+ ip: {
46
+ limit: 60,
47
+ windowSeconds: 5 * 60
48
+ },
49
+ actor: {
50
+ limit: 60,
51
+ windowSeconds: 5 * 60
52
+ }
53
+ },
44
54
  impersonate: {
45
55
  // Bounds mint and probing attempts: the IP bucket bounds aggregate source
46
56
  // traffic, and the actor bucket is a per-actor cap that prevents an actor
@@ -56,6 +66,30 @@ const policies = {
56
66
  limit: 20,
57
67
  windowSeconds: 15 * 60
58
68
  }
69
+ },
70
+ 'oauth-register': {
71
+ ip: {
72
+ limit: 10,
73
+ windowSeconds: 60 * 60
74
+ }
75
+ },
76
+ 'oauth-cimd': {
77
+ ip: {
78
+ limit: 30,
79
+ windowSeconds: 60 * 60
80
+ }
81
+ },
82
+ 'oauth-authorize': {
83
+ ip: {
84
+ limit: 30,
85
+ windowSeconds: 5 * 60
86
+ }
87
+ },
88
+ 'oauth-token': {
89
+ ip: {
90
+ limit: 60,
91
+ windowSeconds: 5 * 60
92
+ }
59
93
  }
60
94
  };
61
95
  function routeName(request) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {getUserContext} from '@shipfox/api-auth-context';\nimport {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {enforceRateLimit as enforceSharedRateLimit} from '@shipfox/node-rate-limit';\nimport {\n type AuthRateLimitAction,\n type AuthRateLimitPolicy,\n type AuthRateLimitScope,\n checkAuthRateLimit,\n} from '#core/rate-limit.js';\n\nconst policies: Record<\n AuthRateLimitAction,\n Partial<Record<AuthRateLimitScope, AuthRateLimitPolicy>>\n> = {\n login: {\n ip: {limit: 60, windowSeconds: 5 * 60},\n email: {limit: 10, windowSeconds: 15 * 60},\n },\n 'email-send': {\n ip: {limit: 30, windowSeconds: 60 * 60},\n email: {limit: 3, windowSeconds: 60 * 60},\n },\n bootstrap: {\n ip: {limit: 5, windowSeconds: 15 * 60},\n },\n 'bootstrap-state': {\n ip: {limit: 60, windowSeconds: 5 * 60},\n },\n lookup: {\n ip: {limit: 60, windowSeconds: 5 * 60},\n },\n impersonate: {\n // Bounds mint and probing attempts: the IP bucket bounds aggregate source\n // traffic, and the actor bucket is a per-actor cap that prevents an actor\n // from evading the bound by rotating IPs (every request still consumes the\n // IP bucket first, so a shared NAT cannot exhaust it faster by pooling\n // actors). Ladder denials by an identifiable actor role are audited as\n // `failed` events.\n ip: {limit: 20, windowSeconds: 15 * 60},\n actor: {limit: 20, windowSeconds: 15 * 60},\n },\n};\n\ninterface EmailBody {\n email: string;\n}\n\nfunction routeName(request: FastifyRequest): string {\n return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';\n}\n\nasync function enforceRateLimit(params: {\n request: FastifyRequest;\n reply: FastifyReply;\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n identifier: string;\n}): Promise<void> {\n const policy = policies[params.action][params.scope];\n if (!policy) return;\n\n await enforceSharedRateLimit({\n request: params.request,\n reply: params.reply,\n check: () =>\n checkAuthRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n ...policy,\n }),\n route: routeName(params.request),\n unavailableCode: 'auth-rate-limit-unavailable',\n unavailableMessage: 'Authentication rate limiter unavailable',\n setRetryAfter: (reply, retryAfterSeconds) => {\n reply.header('Retry-After', String(retryAfterSeconds));\n },\n logWarn: (request, context) => {\n request.log.warn(context, 'Auth rate limit blocked request');\n },\n logError: (request, context) => {\n request.log.error(context, 'Auth rate limiter unavailable');\n },\n createClientError: (presentation, cause) =>\n new ClientError(presentation.message, presentation.code, {\n status: presentation.status,\n ...(presentation.details ? {details: presentation.details} : {}),\n data: presentation.data,\n cause,\n }),\n });\n}\n\nexport function createAuthRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest<{Body: EmailBody}>, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'email',\n identifier: request.body.email,\n });\n };\n}\n\nexport function createAuthIpRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n };\n}\n\n/**\n * Rate limits an authenticated route by source IP and by actor. Every request\n * consumes the IP bucket, which bounds aggregate source traffic; the actor\n * bucket is an additional per-actor cap that prevents an actor from evading\n * the bound by rotating IPs. Runs after authentication (auth is an `onRequest`\n * hook), so the user context is set.\n */\nexport function createAuthActorRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n const client = getUserContext(request);\n if (client) {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'actor',\n identifier: client.userId,\n });\n }\n };\n}\n"],"names":["getUserContext","ClientError","enforceRateLimit","enforceSharedRateLimit","checkAuthRateLimit","policies","login","ip","limit","windowSeconds","email","bootstrap","lookup","impersonate","actor","routeName","request","routeOptions","url","split","params","policy","action","scope","reply","check","identifier","route","unavailableCode","unavailableMessage","setRetryAfter","retryAfterSeconds","header","String","logWarn","context","log","warn","logError","error","createClientError","presentation","cause","message","code","status","details","data","createAuthRateLimitPreHandler","body","createAuthIpRateLimitPreHandler","createAuthActorRateLimitPreHandler","client","userId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {getUserContext} from '@shipfox/api-auth-context';\nimport {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {enforceRateLimit as enforceSharedRateLimit} from '@shipfox/node-rate-limit';\nimport {\n type AuthRateLimitAction,\n type AuthRateLimitPolicy,\n type AuthRateLimitScope,\n checkAuthRateLimit,\n} from '#core/rate-limit.js';\n\nconst policies: Record<\n AuthRateLimitAction,\n Partial<Record<AuthRateLimitScope, AuthRateLimitPolicy>>\n> = {\n login: {\n ip: {limit: 60, windowSeconds: 5 * 60},\n email: {limit: 10, windowSeconds: 15 * 60},\n },\n 'email-send': {\n ip: {limit: 30, windowSeconds: 60 * 60},\n email: {limit: 3, windowSeconds: 60 * 60},\n },\n bootstrap: {\n ip: {limit: 5, windowSeconds: 15 * 60},\n },\n 'bootstrap-state': {\n ip: {limit: 60, windowSeconds: 5 * 60},\n },\n lookup: {\n ip: {limit: 60, windowSeconds: 5 * 60},\n },\n directory: {\n ip: {limit: 60, windowSeconds: 5 * 60},\n actor: {limit: 60, windowSeconds: 5 * 60},\n },\n impersonate: {\n // Bounds mint and probing attempts: the IP bucket bounds aggregate source\n // traffic, and the actor bucket is a per-actor cap that prevents an actor\n // from evading the bound by rotating IPs (every request still consumes the\n // IP bucket first, so a shared NAT cannot exhaust it faster by pooling\n // actors). Ladder denials by an identifiable actor role are audited as\n // `failed` events.\n ip: {limit: 20, windowSeconds: 15 * 60},\n actor: {limit: 20, windowSeconds: 15 * 60},\n },\n 'oauth-register': {\n ip: {limit: 10, windowSeconds: 60 * 60},\n },\n 'oauth-cimd': {\n ip: {limit: 30, windowSeconds: 60 * 60},\n },\n 'oauth-authorize': {\n ip: {limit: 30, windowSeconds: 5 * 60},\n },\n 'oauth-token': {\n ip: {limit: 60, windowSeconds: 5 * 60},\n },\n};\n\ninterface EmailBody {\n email: string;\n}\n\nfunction routeName(request: FastifyRequest): string {\n return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';\n}\n\nasync function enforceRateLimit(params: {\n request: FastifyRequest;\n reply: FastifyReply;\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n identifier: string;\n}): Promise<void> {\n const policy = policies[params.action][params.scope];\n if (!policy) return;\n\n await enforceSharedRateLimit({\n request: params.request,\n reply: params.reply,\n check: () =>\n checkAuthRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n ...policy,\n }),\n route: routeName(params.request),\n unavailableCode: 'auth-rate-limit-unavailable',\n unavailableMessage: 'Authentication rate limiter unavailable',\n setRetryAfter: (reply, retryAfterSeconds) => {\n reply.header('Retry-After', String(retryAfterSeconds));\n },\n logWarn: (request, context) => {\n request.log.warn(context, 'Auth rate limit blocked request');\n },\n logError: (request, context) => {\n request.log.error(context, 'Auth rate limiter unavailable');\n },\n createClientError: (presentation, cause) =>\n new ClientError(presentation.message, presentation.code, {\n status: presentation.status,\n ...(presentation.details ? {details: presentation.details} : {}),\n data: presentation.data,\n cause,\n }),\n });\n}\n\nexport function createAuthRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest<{Body: EmailBody}>, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'email',\n identifier: request.body.email,\n });\n };\n}\n\nexport function createAuthIpRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n };\n}\n\n/**\n * Rate limits an authenticated route by source IP and by actor. Every request\n * consumes the IP bucket, which bounds aggregate source traffic; the actor\n * bucket is an additional per-actor cap that prevents an actor from evading\n * the bound by rotating IPs. Runs after authentication (auth is an `onRequest`\n * hook), so the user context is set.\n */\nexport function createAuthActorRateLimitPreHandler(action: AuthRateLimitAction) {\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'ip',\n identifier: request.ip,\n });\n const client = getUserContext(request);\n if (client) {\n await enforceRateLimit({\n request,\n reply,\n action,\n scope: 'actor',\n identifier: client.userId,\n });\n }\n };\n}\n"],"names":["getUserContext","ClientError","enforceRateLimit","enforceSharedRateLimit","checkAuthRateLimit","policies","login","ip","limit","windowSeconds","email","bootstrap","lookup","directory","actor","impersonate","routeName","request","routeOptions","url","split","params","policy","action","scope","reply","check","identifier","route","unavailableCode","unavailableMessage","setRetryAfter","retryAfterSeconds","header","String","logWarn","context","log","warn","logError","error","createClientError","presentation","cause","message","code","status","details","data","createAuthRateLimitPreHandler","body","createAuthIpRateLimitPreHandler","createAuthActorRateLimitPreHandler","client","userId"],"mappings":"AAAA,SAAQA,cAAc,QAAO,4BAA4B;AACzD,SAAQC,WAAW,QAA+C,wBAAwB;AAC1F,SAAQC,oBAAoBC,sBAAsB,QAAO,2BAA2B;AACpF,SAIEC,kBAAkB,QACb,sBAAsB;AAE7B,MAAMC,WAGF;IACFC,OAAO;QACLC,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;QACrCC,OAAO;YAACF,OAAO;YAAIC,eAAe,KAAK;QAAE;IAC3C;IACA,cAAc;QACZF,IAAI;YAACC,OAAO;YAAIC,eAAe,KAAK;QAAE;QACtCC,OAAO;YAACF,OAAO;YAAGC,eAAe,KAAK;QAAE;IAC1C;IACAE,WAAW;QACTJ,IAAI;YAACC,OAAO;YAAGC,eAAe,KAAK;QAAE;IACvC;IACA,mBAAmB;QACjBF,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;IACvC;IACAG,QAAQ;QACNL,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;IACvC;IACAI,WAAW;QACTN,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;QACrCK,OAAO;YAACN,OAAO;YAAIC,eAAe,IAAI;QAAE;IAC1C;IACAM,aAAa;QACX,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,uEAAuE;QACvE,uEAAuE;QACvE,mBAAmB;QACnBR,IAAI;YAACC,OAAO;YAAIC,eAAe,KAAK;QAAE;QACtCK,OAAO;YAACN,OAAO;YAAIC,eAAe,KAAK;QAAE;IAC3C;IACA,kBAAkB;QAChBF,IAAI;YAACC,OAAO;YAAIC,eAAe,KAAK;QAAE;IACxC;IACA,cAAc;QACZF,IAAI;YAACC,OAAO;YAAIC,eAAe,KAAK;QAAE;IACxC;IACA,mBAAmB;QACjBF,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;IACvC;IACA,eAAe;QACbF,IAAI;YAACC,OAAO;YAAIC,eAAe,IAAI;QAAE;IACvC;AACF;AAMA,SAASO,UAAUC,OAAuB;IACxC,OAAOA,QAAQC,YAAY,CAACC,GAAG,IAAIF,QAAQE,GAAG,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;AAClE;AAEA,eAAelB,iBAAiBmB,MAM/B;IACC,MAAMC,SAASjB,QAAQ,CAACgB,OAAOE,MAAM,CAAC,CAACF,OAAOG,KAAK,CAAC;IACpD,IAAI,CAACF,QAAQ;IAEb,MAAMnB,uBAAuB;QAC3Bc,SAASI,OAAOJ,OAAO;QACvBQ,OAAOJ,OAAOI,KAAK;QACnBC,OAAO,IACLtB,mBAAmB;gBACjBmB,QAAQF,OAAOE,MAAM;gBACrBC,OAAOH,OAAOG,KAAK;gBACnBG,YAAYN,OAAOM,UAAU;gBAC7B,GAAGL,MAAM;YACX;QACFM,OAAOZ,UAAUK,OAAOJ,OAAO;QAC/BY,iBAAiB;QACjBC,oBAAoB;QACpBC,eAAe,CAACN,OAAOO;YACrBP,MAAMQ,MAAM,CAAC,eAAeC,OAAOF;QACrC;QACAG,SAAS,CAAClB,SAASmB;YACjBnB,QAAQoB,GAAG,CAACC,IAAI,CAACF,SAAS;QAC5B;QACAG,UAAU,CAACtB,SAASmB;YAClBnB,QAAQoB,GAAG,CAACG,KAAK,CAACJ,SAAS;QAC7B;QACAK,mBAAmB,CAACC,cAAcC,QAChC,IAAI1C,YAAYyC,aAAaE,OAAO,EAAEF,aAAaG,IAAI,EAAE;gBACvDC,QAAQJ,aAAaI,MAAM;gBAC3B,GAAIJ,aAAaK,OAAO,GAAG;oBAACA,SAASL,aAAaK,OAAO;gBAAA,IAAI,CAAC,CAAC;gBAC/DC,MAAMN,aAAaM,IAAI;gBACvBL;YACF;IACJ;AACF;AAEA,OAAO,SAASM,8BAA8B1B,MAA2B;IACvE,OAAO,OAAON,SAA4CQ;QACxD,MAAMvB,iBAAiB;YACrBe;YACAQ;YACAF;YACAC,OAAO;YACPG,YAAYV,QAAQV,EAAE;QACxB;QAEA,MAAML,iBAAiB;YACrBe;YACAQ;YACAF;YACAC,OAAO;YACPG,YAAYV,QAAQiC,IAAI,CAACxC,KAAK;QAChC;IACF;AACF;AAEA,OAAO,SAASyC,gCAAgC5B,MAA2B;IACzE,OAAO,OAAON,SAAyBQ;QACrC,MAAMvB,iBAAiB;YACrBe;YACAQ;YACAF;YACAC,OAAO;YACPG,YAAYV,QAAQV,EAAE;QACxB;IACF;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAAS6C,mCAAmC7B,MAA2B;IAC5E,OAAO,OAAON,SAAyBQ;QACrC,MAAMvB,iBAAiB;YACrBe;YACAQ;YACAF;YACAC,OAAO;YACPG,YAAYV,QAAQV,EAAE;QACxB;QACA,MAAM8C,SAASrD,eAAeiB;QAC9B,IAAIoC,QAAQ;YACV,MAAMnD,iBAAiB;gBACrBe;gBACAQ;gBACAF;gBACAC,OAAO;gBACPG,YAAY0B,OAAOC,MAAM;YAC3B;QACF;IACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/registration/signup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,0CAA0C,CAAC;AAW1F,OAAO,EAAmC,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAInF,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,2BAA2B,EACvC,YAAY,CAAC,EAAE,YAAY,mDAkG5B"}
1
+ {"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../../../src/presentation/routes/registration/signup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,0CAA0C,CAAC;AAW1F,OAAO,EAAmC,KAAK,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAuDnF,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,2BAA2B,EACvC,YAAY,CAAC,EAAE,YAAY,mDA0D5B"}
@@ -6,6 +6,64 @@ import { EmailTakenError, InvitationEmailMismatchError, SignupNotAllowedError, T
6
6
  import { SIGNUP_DENIAL_MESSAGE_MAX_LENGTH } from '#core/ports.js';
7
7
  import { setRefreshTokenCookie } from '#presentation/auth/refresh-cookie.js';
8
8
  import { toUserDto } from '#presentation/dto/user.js';
9
+ function handleEmailChallengeError(error) {
10
+ if (!(error instanceof EmailChallengeError)) return;
11
+ throw new ClientError(error.message, `email-challenge-${error.code}`, {
12
+ status: error.code === 'limited' ? 429 : 400,
13
+ ...error.retryAt ? {
14
+ details: {
15
+ retry_at: error.retryAt.toISOString()
16
+ }
17
+ } : {}
18
+ });
19
+ }
20
+ function handleSignupPolicyError(error) {
21
+ if (error instanceof EmailTakenError) {
22
+ throw new ClientError('Email already registered', 'email-taken', {
23
+ status: 409
24
+ });
25
+ }
26
+ if (error instanceof SignupNotAllowedError) {
27
+ const message = error.message.slice(0, SIGNUP_DENIAL_MESSAGE_MAX_LENGTH);
28
+ throw new ClientError(message, 'signup-not-allowed', {
29
+ status: 403,
30
+ details: {
31
+ message,
32
+ ...error.format ? {
33
+ format: error.format
34
+ } : {}
35
+ }
36
+ });
37
+ }
38
+ }
39
+ function handleInvitationError(error) {
40
+ if (error instanceof TokenInvalidError) {
41
+ throw new ClientError('Invitation token is invalid', 'invitation-token-invalid', {
42
+ status: 410
43
+ });
44
+ }
45
+ if (error instanceof TokenAlreadyUsedError) {
46
+ throw new ClientError('Invitation has already been accepted', 'invitation-token-used', {
47
+ status: 410
48
+ });
49
+ }
50
+ if (error instanceof TokenExpiredError) {
51
+ throw new ClientError('Invitation has expired', 'invitation-token-expired', {
52
+ status: 410
53
+ });
54
+ }
55
+ if (error instanceof InvitationEmailMismatchError) {
56
+ throw new ClientError('Signup email does not match the invitation', 'invitation-email-mismatch', {
57
+ status: 403
58
+ });
59
+ }
60
+ }
61
+ function handleSignupError(error) {
62
+ handleEmailChallengeError(error);
63
+ handleSignupPolicyError(error);
64
+ handleInvitationError(error);
65
+ throw error;
66
+ }
9
67
  export function createSignupRoute(workspaces, signupPolicy) {
10
68
  return defineRoute({
11
69
  method: 'POST',
@@ -17,56 +75,7 @@ export function createSignupRoute(workspaces, signupPolicy) {
17
75
  201: signupResponseSchema
18
76
  }
19
77
  },
20
- errorHandler: (error)=>{
21
- if (error instanceof EmailChallengeError) {
22
- throw new ClientError(error.message, `email-challenge-${error.code}`, {
23
- status: error.code === 'limited' ? 429 : 400,
24
- ...error.retryAt ? {
25
- details: {
26
- retry_at: error.retryAt.toISOString()
27
- }
28
- } : {}
29
- });
30
- }
31
- if (error instanceof EmailTakenError) {
32
- throw new ClientError('Email already registered', 'email-taken', {
33
- status: 409
34
- });
35
- }
36
- if (error instanceof SignupNotAllowedError) {
37
- const message = error.message.slice(0, SIGNUP_DENIAL_MESSAGE_MAX_LENGTH);
38
- throw new ClientError(message, 'signup-not-allowed', {
39
- status: 403,
40
- details: {
41
- message,
42
- ...error.format ? {
43
- format: error.format
44
- } : {}
45
- }
46
- });
47
- }
48
- if (error instanceof TokenInvalidError) {
49
- throw new ClientError('Invitation token is invalid', 'invitation-token-invalid', {
50
- status: 410
51
- });
52
- }
53
- if (error instanceof TokenAlreadyUsedError) {
54
- throw new ClientError('Invitation has already been accepted', 'invitation-token-used', {
55
- status: 410
56
- });
57
- }
58
- if (error instanceof TokenExpiredError) {
59
- throw new ClientError('Invitation has expired', 'invitation-token-expired', {
60
- status: 410
61
- });
62
- }
63
- if (error instanceof InvitationEmailMismatchError) {
64
- throw new ClientError('Signup email does not match the invitation', 'invitation-email-mismatch', {
65
- status: 403
66
- });
67
- }
68
- throw error;
69
- },
78
+ errorHandler: handleSignupError,
70
79
  handler: async (request, reply)=>{
71
80
  const { email, password, name, invitation_token } = request.body;
72
81
  if (invitation_token === undefined) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/presentation/routes/registration/signup.ts"],"sourcesContent":["import {signupBodySchema, signupResponseSchema} from '@shipfox/api-auth-dto';\nimport {EmailChallengeError} from '@shipfox/api-email-challenges';\nimport type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {signup, signupWithInvitation} from '#core/auth.js';\nimport {\n EmailTakenError,\n InvitationEmailMismatchError,\n SignupNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from '#core/errors.js';\nimport {SIGNUP_DENIAL_MESSAGE_MAX_LENGTH, type SignupPolicy} from '#core/ports.js';\nimport {setRefreshTokenCookie} from '#presentation/auth/refresh-cookie.js';\nimport {toUserDto} from '#presentation/dto/user.js';\n\nexport function createSignupRoute(\n workspaces: WorkspacesInterModuleClient,\n signupPolicy?: SignupPolicy,\n) {\n return defineRoute({\n method: 'POST',\n path: '/signup',\n description:\n 'Create a user account. Sends a verification email unless an invitation token is supplied.',\n schema: {\n body: signupBodySchema,\n response: {\n 201: signupResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof EmailChallengeError) {\n throw new ClientError(error.message, `email-challenge-${error.code}`, {\n status: error.code === 'limited' ? 429 : 400,\n ...(error.retryAt ? {details: {retry_at: error.retryAt.toISOString()}} : {}),\n });\n }\n if (error instanceof EmailTakenError) {\n throw new ClientError('Email already registered', 'email-taken', {status: 409});\n }\n if (error instanceof SignupNotAllowedError) {\n const message = error.message.slice(0, SIGNUP_DENIAL_MESSAGE_MAX_LENGTH);\n throw new ClientError(message, 'signup-not-allowed', {\n status: 403,\n details: {message, ...(error.format ? {format: error.format} : {})},\n });\n }\n if (error instanceof TokenInvalidError) {\n throw new ClientError('Invitation token is invalid', 'invitation-token-invalid', {\n status: 410,\n });\n }\n if (error instanceof TokenAlreadyUsedError) {\n throw new ClientError('Invitation has already been accepted', 'invitation-token-used', {\n status: 410,\n });\n }\n if (error instanceof TokenExpiredError) {\n throw new ClientError('Invitation has expired', 'invitation-token-expired', {\n status: 410,\n });\n }\n if (error instanceof InvitationEmailMismatchError) {\n throw new ClientError(\n 'Signup email does not match the invitation',\n 'invitation-email-mismatch',\n {status: 403},\n );\n }\n throw error;\n },\n handler: async (request, reply) => {\n const {email, password, name, invitation_token} = request.body;\n\n if (invitation_token === undefined) {\n const result = await signup({\n email,\n password,\n name,\n sourceIp: request.ip,\n signupPolicy,\n });\n reply.code(201);\n return {\n user: toUserDto(result),\n email_challenge: {\n id: result.emailChallenge.id,\n next_resend_available_at: result.emailChallenge.nextResendAvailableAt.toISOString(),\n },\n };\n }\n\n const result = await signupWithInvitation({\n email,\n password,\n name,\n invitationToken: invitation_token,\n workspaces,\n });\n setRefreshTokenCookie(reply, result.refreshToken);\n reply.code(201);\n return {\n user: toUserDto(result.user),\n token: result.token,\n membership: result.membership\n ? {\n id: result.membership.id,\n user_id: result.membership.userId,\n workspace_id: result.membership.workspaceId,\n }\n : null,\n ...(result.acceptError ? {accept_error: result.acceptError} : {}),\n };\n },\n });\n}\n"],"names":["signupBodySchema","signupResponseSchema","EmailChallengeError","ClientError","defineRoute","signup","signupWithInvitation","EmailTakenError","InvitationEmailMismatchError","SignupNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","SIGNUP_DENIAL_MESSAGE_MAX_LENGTH","setRefreshTokenCookie","toUserDto","createSignupRoute","workspaces","signupPolicy","method","path","description","schema","body","response","errorHandler","error","message","code","status","retryAt","details","retry_at","toISOString","slice","format","handler","request","reply","email","password","name","invitation_token","undefined","result","sourceIp","ip","user","email_challenge","id","emailChallenge","next_resend_available_at","nextResendAvailableAt","invitationToken","refreshToken","token","membership","user_id","userId","workspace_id","workspaceId","acceptError","accept_error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/presentation/routes/registration/signup.ts"],"sourcesContent":["import {signupBodySchema, signupResponseSchema} from '@shipfox/api-auth-dto';\nimport {EmailChallengeError} from '@shipfox/api-email-challenges';\nimport type {WorkspacesInterModuleClient} from '@shipfox/api-workspaces-dto/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {signup, signupWithInvitation} from '#core/auth.js';\nimport {\n EmailTakenError,\n InvitationEmailMismatchError,\n SignupNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from '#core/errors.js';\nimport {SIGNUP_DENIAL_MESSAGE_MAX_LENGTH, type SignupPolicy} from '#core/ports.js';\nimport {setRefreshTokenCookie} from '#presentation/auth/refresh-cookie.js';\nimport {toUserDto} from '#presentation/dto/user.js';\n\nfunction handleEmailChallengeError(error: unknown): void {\n if (!(error instanceof EmailChallengeError)) return;\n throw new ClientError(error.message, `email-challenge-${error.code}`, {\n status: error.code === 'limited' ? 429 : 400,\n ...(error.retryAt ? {details: {retry_at: error.retryAt.toISOString()}} : {}),\n });\n}\n\nfunction handleSignupPolicyError(error: unknown): void {\n if (error instanceof EmailTakenError) {\n throw new ClientError('Email already registered', 'email-taken', {status: 409});\n }\n if (error instanceof SignupNotAllowedError) {\n const message = error.message.slice(0, SIGNUP_DENIAL_MESSAGE_MAX_LENGTH);\n throw new ClientError(message, 'signup-not-allowed', {\n status: 403,\n details: {message, ...(error.format ? {format: error.format} : {})},\n });\n }\n}\n\nfunction handleInvitationError(error: unknown): void {\n if (error instanceof TokenInvalidError) {\n throw new ClientError('Invitation token is invalid', 'invitation-token-invalid', {status: 410});\n }\n if (error instanceof TokenAlreadyUsedError) {\n throw new ClientError('Invitation has already been accepted', 'invitation-token-used', {\n status: 410,\n });\n }\n if (error instanceof TokenExpiredError) {\n throw new ClientError('Invitation has expired', 'invitation-token-expired', {status: 410});\n }\n if (error instanceof InvitationEmailMismatchError) {\n throw new ClientError(\n 'Signup email does not match the invitation',\n 'invitation-email-mismatch',\n {\n status: 403,\n },\n );\n }\n}\n\nfunction handleSignupError(error: unknown): never {\n handleEmailChallengeError(error);\n handleSignupPolicyError(error);\n handleInvitationError(error);\n throw error;\n}\n\nexport function createSignupRoute(\n workspaces: WorkspacesInterModuleClient,\n signupPolicy?: SignupPolicy,\n) {\n return defineRoute({\n method: 'POST',\n path: '/signup',\n description:\n 'Create a user account. Sends a verification email unless an invitation token is supplied.',\n schema: {\n body: signupBodySchema,\n response: {\n 201: signupResponseSchema,\n },\n },\n errorHandler: handleSignupError,\n handler: async (request, reply) => {\n const {email, password, name, invitation_token} = request.body;\n\n if (invitation_token === undefined) {\n const result = await signup({\n email,\n password,\n name,\n sourceIp: request.ip,\n signupPolicy,\n });\n reply.code(201);\n return {\n user: toUserDto(result),\n email_challenge: {\n id: result.emailChallenge.id,\n next_resend_available_at: result.emailChallenge.nextResendAvailableAt.toISOString(),\n },\n };\n }\n\n const result = await signupWithInvitation({\n email,\n password,\n name,\n invitationToken: invitation_token,\n workspaces,\n });\n setRefreshTokenCookie(reply, result.refreshToken);\n reply.code(201);\n return {\n user: toUserDto(result.user),\n token: result.token,\n membership: result.membership\n ? {\n id: result.membership.id,\n user_id: result.membership.userId,\n workspace_id: result.membership.workspaceId,\n }\n : null,\n ...(result.acceptError ? {accept_error: result.acceptError} : {}),\n };\n },\n });\n}\n"],"names":["signupBodySchema","signupResponseSchema","EmailChallengeError","ClientError","defineRoute","signup","signupWithInvitation","EmailTakenError","InvitationEmailMismatchError","SignupNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","SIGNUP_DENIAL_MESSAGE_MAX_LENGTH","setRefreshTokenCookie","toUserDto","handleEmailChallengeError","error","message","code","status","retryAt","details","retry_at","toISOString","handleSignupPolicyError","slice","format","handleInvitationError","handleSignupError","createSignupRoute","workspaces","signupPolicy","method","path","description","schema","body","response","errorHandler","handler","request","reply","email","password","name","invitation_token","undefined","result","sourceIp","ip","user","email_challenge","id","emailChallenge","next_resend_available_at","nextResendAvailableAt","invitationToken","refreshToken","token","membership","user_id","userId","workspace_id","workspaceId","acceptError","accept_error"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface AgentAccessRetentionResult {
2
+ deleted: number;
3
+ transitioned: number;
4
+ iterations: number;
5
+ timedOut: boolean;
6
+ }
7
+ /**
8
+ * Drains bounded retention batches for the current tick. Heartbeats continue
9
+ * while a database batch waits on a lock, and the wall-clock budget keeps the
10
+ * activity inside its five-minute Temporal start-to-close timeout.
11
+ */
12
+ export declare function agentAccessRetentionActivity(): Promise<AgentAccessRetentionResult>;
13
+ //# sourceMappingURL=agent-access-retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-access-retention.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/agent-access-retention.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAWD;;;;GAIG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,0BAA0B,CAAC,CA8CxF"}
@@ -0,0 +1,58 @@
1
+ import { Context } from '@temporalio/activity';
2
+ import { pruneAgentAccessBatch } from '#db/agent-access.js';
3
+ import { AGENT_ACCESS_RETENTION_BATCH_LIMIT, AGENT_ACCESS_RETENTION_MAX_ITERATIONS, AGENT_ACCESS_RETENTION_TIME_BUDGET_MS } from '#db/agent-access-retention.js';
4
+ function isAgentAccessRetentionTimeout(error) {
5
+ if (typeof error !== 'object' || error === null) return false;
6
+ const details = error;
7
+ if (details.code === '55P03' || details.code === '57014') return true;
8
+ return details.cause !== undefined && details.cause !== error ? isAgentAccessRetentionTimeout(details.cause) : false;
9
+ }
10
+ /**
11
+ * Drains bounded retention batches for the current tick. Heartbeats continue
12
+ * while a database batch waits on a lock, and the wall-clock budget keeps the
13
+ * activity inside its five-minute Temporal start-to-close timeout.
14
+ */ export async function agentAccessRetentionActivity() {
15
+ const ctx = Context.current();
16
+ const startedAt = Date.now();
17
+ const deadline = startedAt + AGENT_ACCESS_RETENTION_TIME_BUDGET_MS;
18
+ const result = {
19
+ deleted: 0,
20
+ transitioned: 0,
21
+ iterations: 0,
22
+ timedOut: false
23
+ };
24
+ const heartbeat = ()=>ctx.heartbeat(result);
25
+ const heartbeatTimer = setInterval(heartbeat, 15_000);
26
+ try {
27
+ while(result.iterations < AGENT_ACCESS_RETENTION_MAX_ITERATIONS){
28
+ if (Date.now() >= deadline) {
29
+ result.timedOut = true;
30
+ break;
31
+ }
32
+ try {
33
+ const batch = await pruneAgentAccessBatch({
34
+ limit: AGENT_ACCESS_RETENTION_BATCH_LIMIT,
35
+ deadlineMs: deadline
36
+ });
37
+ result.deleted += batch.deleted;
38
+ result.transitioned += batch.transitioned;
39
+ result.iterations += 1;
40
+ heartbeat();
41
+ if (batch.deleted === 0 && batch.transitioned === 0) break;
42
+ } catch (error) {
43
+ if (!isAgentAccessRetentionTimeout(error)) throw error;
44
+ result.timedOut = true;
45
+ break;
46
+ }
47
+ }
48
+ if (result.iterations >= AGENT_ACCESS_RETENTION_MAX_ITERATIONS) {
49
+ result.timedOut = true;
50
+ }
51
+ heartbeat();
52
+ return result;
53
+ } finally{
54
+ clearInterval(heartbeatTimer);
55
+ }
56
+ }
57
+
58
+ //# sourceMappingURL=agent-access-retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/agent-access-retention.ts"],"sourcesContent":["import {Context} from '@temporalio/activity';\nimport {pruneAgentAccessBatch} from '#db/agent-access.js';\nimport {\n AGENT_ACCESS_RETENTION_BATCH_LIMIT,\n AGENT_ACCESS_RETENTION_MAX_ITERATIONS,\n AGENT_ACCESS_RETENTION_TIME_BUDGET_MS,\n} from '#db/agent-access-retention.js';\n\nexport interface AgentAccessRetentionResult {\n deleted: number;\n transitioned: number;\n iterations: number;\n timedOut: boolean;\n}\n\nfunction isAgentAccessRetentionTimeout(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const details = error as {cause?: unknown; code?: unknown};\n if (details.code === '55P03' || details.code === '57014') return true;\n return details.cause !== undefined && details.cause !== error\n ? isAgentAccessRetentionTimeout(details.cause)\n : false;\n}\n\n/**\n * Drains bounded retention batches for the current tick. Heartbeats continue\n * while a database batch waits on a lock, and the wall-clock budget keeps the\n * activity inside its five-minute Temporal start-to-close timeout.\n */\nexport async function agentAccessRetentionActivity(): Promise<AgentAccessRetentionResult> {\n const ctx = Context.current();\n const startedAt = Date.now();\n const deadline = startedAt + AGENT_ACCESS_RETENTION_TIME_BUDGET_MS;\n const result: AgentAccessRetentionResult = {\n deleted: 0,\n transitioned: 0,\n iterations: 0,\n timedOut: false,\n };\n const heartbeat = () => ctx.heartbeat(result);\n const heartbeatTimer = setInterval(heartbeat, 15_000);\n\n try {\n while (result.iterations < AGENT_ACCESS_RETENTION_MAX_ITERATIONS) {\n if (Date.now() >= deadline) {\n result.timedOut = true;\n break;\n }\n\n try {\n const batch = await pruneAgentAccessBatch({\n limit: AGENT_ACCESS_RETENTION_BATCH_LIMIT,\n deadlineMs: deadline,\n });\n result.deleted += batch.deleted;\n result.transitioned += batch.transitioned;\n result.iterations += 1;\n heartbeat();\n\n if (batch.deleted === 0 && batch.transitioned === 0) break;\n } catch (error) {\n if (!isAgentAccessRetentionTimeout(error)) throw error;\n result.timedOut = true;\n break;\n }\n }\n\n if (result.iterations >= AGENT_ACCESS_RETENTION_MAX_ITERATIONS) {\n result.timedOut = true;\n }\n heartbeat();\n return result;\n } finally {\n clearInterval(heartbeatTimer);\n }\n}\n"],"names":["Context","pruneAgentAccessBatch","AGENT_ACCESS_RETENTION_BATCH_LIMIT","AGENT_ACCESS_RETENTION_MAX_ITERATIONS","AGENT_ACCESS_RETENTION_TIME_BUDGET_MS","isAgentAccessRetentionTimeout","error","details","code","cause","undefined","agentAccessRetentionActivity","ctx","current","startedAt","Date","now","deadline","result","deleted","transitioned","iterations","timedOut","heartbeat","heartbeatTimer","setInterval","batch","limit","deadlineMs","clearInterval"],"mappings":"AAAA,SAAQA,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,qBAAqB,QAAO,sBAAsB;AAC1D,SACEC,kCAAkC,EAClCC,qCAAqC,EACrCC,qCAAqC,QAChC,gCAAgC;AASvC,SAASC,8BAA8BC,KAAc;IACnD,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM,OAAO;IACxD,MAAMC,UAAUD;IAChB,IAAIC,QAAQC,IAAI,KAAK,WAAWD,QAAQC,IAAI,KAAK,SAAS,OAAO;IACjE,OAAOD,QAAQE,KAAK,KAAKC,aAAaH,QAAQE,KAAK,KAAKH,QACpDD,8BAA8BE,QAAQE,KAAK,IAC3C;AACN;AAEA;;;;CAIC,GACD,OAAO,eAAeE;IACpB,MAAMC,MAAMZ,QAAQa,OAAO;IAC3B,MAAMC,YAAYC,KAAKC,GAAG;IAC1B,MAAMC,WAAWH,YAAYV;IAC7B,MAAMc,SAAqC;QACzCC,SAAS;QACTC,cAAc;QACdC,YAAY;QACZC,UAAU;IACZ;IACA,MAAMC,YAAY,IAAMX,IAAIW,SAAS,CAACL;IACtC,MAAMM,iBAAiBC,YAAYF,WAAW;IAE9C,IAAI;QACF,MAAOL,OAAOG,UAAU,GAAGlB,sCAAuC;YAChE,IAAIY,KAAKC,GAAG,MAAMC,UAAU;gBAC1BC,OAAOI,QAAQ,GAAG;gBAClB;YACF;YAEA,IAAI;gBACF,MAAMI,QAAQ,MAAMzB,sBAAsB;oBACxC0B,OAAOzB;oBACP0B,YAAYX;gBACd;gBACAC,OAAOC,OAAO,IAAIO,MAAMP,OAAO;gBAC/BD,OAAOE,YAAY,IAAIM,MAAMN,YAAY;gBACzCF,OAAOG,UAAU,IAAI;gBACrBE;gBAEA,IAAIG,MAAMP,OAAO,KAAK,KAAKO,MAAMN,YAAY,KAAK,GAAG;YACvD,EAAE,OAAOd,OAAO;gBACd,IAAI,CAACD,8BAA8BC,QAAQ,MAAMA;gBACjDY,OAAOI,QAAQ,GAAG;gBAClB;YACF;QACF;QAEA,IAAIJ,OAAOG,UAAU,IAAIlB,uCAAuC;YAC9De,OAAOI,QAAQ,GAAG;QACpB;QACAC;QACA,OAAOL;IACT,SAAU;QACRW,cAAcL;IAChB;AACF"}
@@ -0,0 +1,5 @@
1
+ import { agentAccessRetentionActivity } from './agent-access-retention.js';
2
+ export declare function createAuthMaintenanceActivities(): {
3
+ agentAccessRetentionActivity: typeof agentAccessRetentionActivity;
4
+ };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,4BAA4B,EAAC,MAAM,6BAA6B,CAAC;AAEzE,wBAAgB,+BAA+B;;EAE9C"}
@@ -0,0 +1,8 @@
1
+ import { agentAccessRetentionActivity } from './agent-access-retention.js';
2
+ export function createAuthMaintenanceActivities() {
3
+ return {
4
+ agentAccessRetentionActivity
5
+ };
6
+ }
7
+
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/index.ts"],"sourcesContent":["import {agentAccessRetentionActivity} from './agent-access-retention.js';\n\nexport function createAuthMaintenanceActivities() {\n return {agentAccessRetentionActivity};\n}\n"],"names":["agentAccessRetentionActivity","createAuthMaintenanceActivities"],"mappings":"AAAA,SAAQA,4BAA4B,QAAO,8BAA8B;AAEzE,OAAO,SAASC;IACd,OAAO;QAACD;IAA4B;AACtC"}
@@ -0,0 +1,2 @@
1
+ export declare const AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE = "auth-agent-access-maintenance";
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/temporal/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wCAAwC,kCAAkC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE = 'auth-agent-access-maintenance';
2
+
3
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/temporal/constants.ts"],"sourcesContent":["export const AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE = 'auth-agent-access-maintenance';\n"],"names":["AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE"],"mappings":"AAAA,OAAO,MAAMA,2CAA2C,gCAAgC"}
@@ -0,0 +1,2 @@
1
+ export declare function agentAccessRetentionCron(): Promise<void>;
2
+ //# sourceMappingURL=agent-access-retention-cron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-access-retention-cron.d.ts","sourceRoot":"","sources":["../../../src/temporal/workflows/agent-access-retention-cron.ts"],"names":[],"mappings":"AAYA,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAU9D"}
@@ -0,0 +1,22 @@
1
+ import { log, proxyActivities } from '@temporalio/workflow';
2
+ const { agentAccessRetentionActivity } = proxyActivities({
3
+ startToCloseTimeout: '5 minutes',
4
+ heartbeatTimeout: '1 minute',
5
+ // Retry on the next cron tick so a timed-out attempt cannot overlap its own sweep.
6
+ retry: {
7
+ maximumAttempts: 1
8
+ }
9
+ });
10
+ export async function agentAccessRetentionCron() {
11
+ const result = await agentAccessRetentionActivity();
12
+ if (result.deleted > 0 || result.transitioned > 0 || result.timedOut) {
13
+ log.info('Agent-access retention sweep complete', {
14
+ deleted: result.deleted,
15
+ transitioned: result.transitioned,
16
+ iterations: result.iterations,
17
+ timedOut: result.timedOut
18
+ });
19
+ }
20
+ }
21
+
22
+ //# sourceMappingURL=agent-access-retention-cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/workflows/agent-access-retention-cron.ts"],"sourcesContent":["import {log, proxyActivities} from '@temporalio/workflow';\nimport type {createAuthMaintenanceActivities} from '../activities/index.js';\n\nconst {agentAccessRetentionActivity} = proxyActivities<\n ReturnType<typeof createAuthMaintenanceActivities>\n>({\n startToCloseTimeout: '5 minutes',\n heartbeatTimeout: '1 minute',\n // Retry on the next cron tick so a timed-out attempt cannot overlap its own sweep.\n retry: {maximumAttempts: 1},\n});\n\nexport async function agentAccessRetentionCron(): Promise<void> {\n const result = await agentAccessRetentionActivity();\n if (result.deleted > 0 || result.transitioned > 0 || result.timedOut) {\n log.info('Agent-access retention sweep complete', {\n deleted: result.deleted,\n transitioned: result.transitioned,\n iterations: result.iterations,\n timedOut: result.timedOut,\n });\n }\n}\n"],"names":["log","proxyActivities","agentAccessRetentionActivity","startToCloseTimeout","heartbeatTimeout","retry","maximumAttempts","agentAccessRetentionCron","result","deleted","transitioned","timedOut","info","iterations"],"mappings":"AAAA,SAAQA,GAAG,EAAEC,eAAe,QAAO,uBAAuB;AAG1D,MAAM,EAACC,4BAA4B,EAAC,GAAGD,gBAErC;IACAE,qBAAqB;IACrBC,kBAAkB;IAClB,mFAAmF;IACnFC,OAAO;QAACC,iBAAiB;IAAC;AAC5B;AAEA,OAAO,eAAeC;IACpB,MAAMC,SAAS,MAAMN;IACrB,IAAIM,OAAOC,OAAO,GAAG,KAAKD,OAAOE,YAAY,GAAG,KAAKF,OAAOG,QAAQ,EAAE;QACpEX,IAAIY,IAAI,CAAC,yCAAyC;YAChDH,SAASD,OAAOC,OAAO;YACvBC,cAAcF,OAAOE,YAAY;YACjCG,YAAYL,OAAOK,UAAU;YAC7BF,UAAUH,OAAOG,QAAQ;QAC3B;IACF;AACF"}