@shipfox/api-auth 19.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 (86) hide show
  1. package/.turbo/turbo-build.log +17 -2
  2. package/CHANGELOG.md +13 -0
  3. package/dist/core/cimd.d.ts +39 -0
  4. package/dist/core/cimd.d.ts.map +1 -0
  5. package/dist/core/cimd.js +392 -0
  6. package/dist/core/cimd.js.map +1 -0
  7. package/dist/core/errors.d.ts +14 -0
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js +26 -0
  10. package/dist/core/errors.js.map +1 -1
  11. package/dist/core/oauth-client-resolver.d.ts +54 -0
  12. package/dist/core/oauth-client-resolver.d.ts.map +1 -0
  13. package/dist/core/oauth-client-resolver.js +154 -0
  14. package/dist/core/oauth-client-resolver.js.map +1 -0
  15. package/dist/core/oauth-client.d.ts +39 -0
  16. package/dist/core/oauth-client.d.ts.map +1 -0
  17. package/dist/core/oauth-client.js +236 -0
  18. package/dist/core/oauth-client.js.map +1 -0
  19. package/dist/db/agent-access-retention.d.ts +10 -0
  20. package/dist/db/agent-access-retention.d.ts.map +1 -0
  21. package/dist/db/agent-access-retention.js +11 -0
  22. package/dist/db/agent-access-retention.js.map +1 -0
  23. package/dist/db/agent-access.d.ts +100 -4
  24. package/dist/db/agent-access.d.ts.map +1 -1
  25. package/dist/db/agent-access.js +483 -45
  26. package/dist/db/agent-access.js.map +1 -1
  27. package/dist/index.d.ts +9 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +25 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/metrics/instance.d.ts +1 -1
  32. package/dist/metrics/instance.d.ts.map +1 -1
  33. package/dist/metrics/instance.js.map +1 -1
  34. package/dist/presentation/routes/oauth.d.ts +9 -0
  35. package/dist/presentation/routes/oauth.d.ts.map +1 -0
  36. package/dist/presentation/routes/oauth.js +150 -0
  37. package/dist/presentation/routes/oauth.js.map +1 -0
  38. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  39. package/dist/presentation/routes/rate-limit.js +24 -0
  40. package/dist/presentation/routes/rate-limit.js.map +1 -1
  41. package/dist/temporal/activities/agent-access-retention.d.ts +13 -0
  42. package/dist/temporal/activities/agent-access-retention.d.ts.map +1 -0
  43. package/dist/temporal/activities/agent-access-retention.js +58 -0
  44. package/dist/temporal/activities/agent-access-retention.js.map +1 -0
  45. package/dist/temporal/activities/index.d.ts +5 -0
  46. package/dist/temporal/activities/index.d.ts.map +1 -0
  47. package/dist/temporal/activities/index.js +8 -0
  48. package/dist/temporal/activities/index.js.map +1 -0
  49. package/dist/temporal/constants.d.ts +2 -0
  50. package/dist/temporal/constants.d.ts.map +1 -0
  51. package/dist/temporal/constants.js +3 -0
  52. package/dist/temporal/constants.js.map +1 -0
  53. package/dist/temporal/workflows/agent-access-retention-cron.d.ts +2 -0
  54. package/dist/temporal/workflows/agent-access-retention-cron.d.ts.map +1 -0
  55. package/dist/temporal/workflows/agent-access-retention-cron.js +22 -0
  56. package/dist/temporal/workflows/agent-access-retention-cron.js.map +1 -0
  57. package/dist/temporal/workflows/index.bundle.js +25227 -0
  58. package/dist/temporal/workflows/index.bundle.meta.json +1 -0
  59. package/dist/temporal/workflows/index.d.ts +2 -0
  60. package/dist/temporal/workflows/index.d.ts.map +1 -0
  61. package/dist/temporal/workflows/index.js +3 -0
  62. package/dist/temporal/workflows/index.js.map +1 -0
  63. package/dist/tsconfig.test.tsbuildinfo +1 -1
  64. package/package.json +7 -4
  65. package/src/core/cimd.test.ts +213 -0
  66. package/src/core/cimd.ts +532 -0
  67. package/src/core/errors.ts +41 -0
  68. package/src/core/oauth-client-resolver.test.ts +198 -0
  69. package/src/core/oauth-client-resolver.ts +253 -0
  70. package/src/core/oauth-client.test.ts +157 -0
  71. package/src/core/oauth-client.ts +331 -0
  72. package/src/db/agent-access-retention.ts +10 -0
  73. package/src/db/agent-access.test.ts +697 -3
  74. package/src/db/agent-access.ts +953 -91
  75. package/src/index.test.ts +27 -0
  76. package/src/index.ts +76 -0
  77. package/src/metrics/instance.ts +5 -1
  78. package/src/presentation/routes/oauth.test.ts +133 -0
  79. package/src/presentation/routes/oauth.ts +144 -0
  80. package/src/presentation/routes/rate-limit.ts +12 -0
  81. package/src/temporal/activities/agent-access-retention.ts +76 -0
  82. package/src/temporal/activities/index.ts +5 -0
  83. package/src/temporal/constants.ts +1 -0
  84. package/src/temporal/workflows/agent-access-retention-cron.ts +23 -0
  85. package/src/temporal/workflows/index.ts +1 -0
  86. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,150 @@
1
+ import { oauthAuthorizationServerMetadataSchema, oauthDynamicClientRegistrationRequestSchema, oauthDynamicClientRegistrationResponseSchema, oauthProtectedResourceMetadataSchema } from '@shipfox/api-auth-dto';
2
+ import { ClientError, defineRoute } from '@shipfox/node-fastify';
3
+ import { InvalidOAuthClientMetadataError, InvalidOAuthConfigurationError, OAuthMetadataFetchError, OAuthRedirectUriNotRegisteredError } from '#core/errors.js';
4
+ import { validateOAuthPublicOrigin } from '#core/oauth-client.js';
5
+ import { registerOAuthClient } from '#core/oauth-client-resolver.js';
6
+ import { createAuthIpRateLimitPreHandler } from './rate-limit.js';
7
+ function apiPublicOrigin(options) {
8
+ return validateOAuthPublicOrigin(options.apiPublicUrl);
9
+ }
10
+ function translateOAuthError(error) {
11
+ if (error instanceof InvalidOAuthClientMetadataError || error instanceof OAuthMetadataFetchError || error instanceof OAuthRedirectUriNotRegisteredError) {
12
+ throw new ClientError('OAuth client metadata is invalid', 'invalid-client-metadata', {
13
+ status: 400
14
+ });
15
+ }
16
+ if (error instanceof InvalidOAuthConfigurationError) {
17
+ throw new ClientError('OAuth configuration is invalid', 'oauth-configuration-invalid', {
18
+ status: 500
19
+ });
20
+ }
21
+ throw error;
22
+ }
23
+ function registrationResponse(result) {
24
+ return {
25
+ client_id: result.metadata.clientId,
26
+ client_name: result.metadata.clientName,
27
+ redirect_uris: result.metadata.redirectUris,
28
+ grant_types: result.metadata.grantTypes,
29
+ response_types: result.metadata.responseTypes,
30
+ token_endpoint_auth_method: result.metadata.tokenEndpointAuthMethod,
31
+ scope: result.metadata.scope
32
+ };
33
+ }
34
+ function createProtectedResourceMetadataRoute(origin) {
35
+ return defineRoute({
36
+ method: 'GET',
37
+ path: '/.well-known/oauth-protected-resource',
38
+ description: 'Describe the protected MCP resource and its authorization server.',
39
+ schema: {
40
+ response: {
41
+ 200: oauthProtectedResourceMetadataSchema
42
+ }
43
+ },
44
+ handler: ()=>({
45
+ resource: `${origin}/mcp`,
46
+ authorization_servers: [
47
+ origin
48
+ ],
49
+ scopes_supported: [
50
+ 'read'
51
+ ]
52
+ })
53
+ });
54
+ }
55
+ function createAuthorizationServerMetadataRoute(origin) {
56
+ return defineRoute({
57
+ method: 'GET',
58
+ path: '/.well-known/oauth-authorization-server',
59
+ description: 'Describe the MCP OAuth authorization server.',
60
+ schema: {
61
+ response: {
62
+ 200: oauthAuthorizationServerMetadataSchema
63
+ }
64
+ },
65
+ handler: ()=>({
66
+ issuer: origin,
67
+ authorization_endpoint: `${origin}/oauth/authorize`,
68
+ token_endpoint: `${origin}/oauth/token`,
69
+ registration_endpoint: `${origin}/oauth/register`,
70
+ response_types_supported: [
71
+ 'code'
72
+ ],
73
+ grant_types_supported: [
74
+ 'authorization_code',
75
+ 'refresh_token'
76
+ ],
77
+ code_challenge_methods_supported: [
78
+ 'S256'
79
+ ],
80
+ token_endpoint_auth_methods_supported: [
81
+ 'none'
82
+ ],
83
+ scopes_supported: [
84
+ 'read'
85
+ ],
86
+ client_id_metadata_document_supported: true
87
+ })
88
+ });
89
+ }
90
+ function createDynamicRegistrationRoute() {
91
+ return defineRoute({
92
+ method: 'POST',
93
+ path: '/oauth/register',
94
+ description: 'Register a public OAuth client for MCP access.',
95
+ options: {
96
+ bodyLimit: 32 * 1024
97
+ },
98
+ schema: {
99
+ body: oauthDynamicClientRegistrationRequestSchema,
100
+ response: {
101
+ 201: oauthDynamicClientRegistrationResponseSchema
102
+ }
103
+ },
104
+ preHandler: createAuthIpRateLimitPreHandler('oauth-register'),
105
+ errorHandler: translateOAuthError,
106
+ handler: async (request, reply)=>{
107
+ const result = await registerOAuthClient({
108
+ clientName: request.body.client_name,
109
+ redirectUris: request.body.redirect_uris,
110
+ grantTypes: request.body.grant_types,
111
+ responseTypes: request.body.response_types,
112
+ tokenEndpointAuthMethod: request.body.token_endpoint_auth_method,
113
+ scope: request.body.scope
114
+ });
115
+ reply.code(201);
116
+ return registrationResponse(result);
117
+ }
118
+ });
119
+ }
120
+ export function createOAuthMetadataRoutes(options) {
121
+ const origin = apiPublicOrigin(options);
122
+ return {
123
+ prefix: '',
124
+ routes: [
125
+ createProtectedResourceMetadataRoute(origin),
126
+ createAuthorizationServerMetadataRoute(origin)
127
+ ]
128
+ };
129
+ }
130
+ export function createOAuthClientIdentificationRoutes() {
131
+ return {
132
+ prefix: '',
133
+ routes: [
134
+ createDynamicRegistrationRoute()
135
+ ]
136
+ };
137
+ }
138
+ export function createOAuthRoutes(options) {
139
+ const metadataRoutes = createOAuthMetadataRoutes(options);
140
+ const clientIdentificationRoutes = createOAuthClientIdentificationRoutes();
141
+ return {
142
+ prefix: '',
143
+ routes: [
144
+ ...metadataRoutes.routes,
145
+ ...clientIdentificationRoutes.routes
146
+ ]
147
+ };
148
+ }
149
+
150
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/routes/oauth.ts"],"sourcesContent":["import {\n type OAuthDynamicClientRegistrationResponseDto,\n oauthAuthorizationServerMetadataSchema,\n oauthDynamicClientRegistrationRequestSchema,\n oauthDynamicClientRegistrationResponseSchema,\n oauthProtectedResourceMetadataSchema,\n} from '@shipfox/api-auth-dto';\nimport {ClientError, defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport {\n InvalidOAuthClientMetadataError,\n InvalidOAuthConfigurationError,\n OAuthMetadataFetchError,\n OAuthRedirectUriNotRegisteredError,\n} from '#core/errors.js';\nimport {validateOAuthPublicOrigin} from '#core/oauth-client.js';\nimport {registerOAuthClient} from '#core/oauth-client-resolver.js';\nimport {createAuthIpRateLimitPreHandler} from './rate-limit.js';\n\nexport interface CreateOAuthRoutesOptions {\n /** Validated here and deliberately injected until production composition. */\n apiPublicUrl: string;\n}\n\nfunction apiPublicOrigin(options: CreateOAuthRoutesOptions): string {\n return validateOAuthPublicOrigin(options.apiPublicUrl);\n}\n\nfunction translateOAuthError(error: unknown): never {\n if (\n error instanceof InvalidOAuthClientMetadataError ||\n error instanceof OAuthMetadataFetchError ||\n error instanceof OAuthRedirectUriNotRegisteredError\n ) {\n throw new ClientError('OAuth client metadata is invalid', 'invalid-client-metadata', {\n status: 400,\n });\n }\n if (error instanceof InvalidOAuthConfigurationError) {\n throw new ClientError('OAuth configuration is invalid', 'oauth-configuration-invalid', {\n status: 500,\n });\n }\n throw error;\n}\n\nfunction registrationResponse(\n result: Awaited<ReturnType<typeof registerOAuthClient>>,\n): OAuthDynamicClientRegistrationResponseDto {\n return {\n client_id: result.metadata.clientId,\n client_name: result.metadata.clientName,\n redirect_uris: result.metadata.redirectUris,\n grant_types: result.metadata.grantTypes,\n response_types: result.metadata.responseTypes,\n token_endpoint_auth_method: result.metadata.tokenEndpointAuthMethod,\n scope: result.metadata.scope,\n };\n}\n\nfunction createProtectedResourceMetadataRoute(origin: string) {\n return defineRoute({\n method: 'GET',\n path: '/.well-known/oauth-protected-resource',\n description: 'Describe the protected MCP resource and its authorization server.',\n schema: {response: {200: oauthProtectedResourceMetadataSchema}},\n handler: () => ({\n resource: `${origin}/mcp`,\n authorization_servers: [origin],\n scopes_supported: ['read'],\n }),\n });\n}\n\nfunction createAuthorizationServerMetadataRoute(origin: string) {\n return defineRoute({\n method: 'GET',\n path: '/.well-known/oauth-authorization-server',\n description: 'Describe the MCP OAuth authorization server.',\n schema: {response: {200: oauthAuthorizationServerMetadataSchema}},\n handler: () => ({\n issuer: origin,\n authorization_endpoint: `${origin}/oauth/authorize`,\n token_endpoint: `${origin}/oauth/token`,\n registration_endpoint: `${origin}/oauth/register`,\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none'],\n scopes_supported: ['read'],\n client_id_metadata_document_supported: true,\n }),\n });\n}\n\nfunction createDynamicRegistrationRoute() {\n return defineRoute({\n method: 'POST',\n path: '/oauth/register',\n description: 'Register a public OAuth client for MCP access.',\n options: {bodyLimit: 32 * 1024},\n schema: {\n body: oauthDynamicClientRegistrationRequestSchema,\n response: {201: oauthDynamicClientRegistrationResponseSchema},\n },\n preHandler: createAuthIpRateLimitPreHandler('oauth-register'),\n errorHandler: translateOAuthError,\n handler: async (request, reply) => {\n const result = await registerOAuthClient({\n clientName: request.body.client_name,\n redirectUris: request.body.redirect_uris,\n grantTypes: request.body.grant_types,\n responseTypes: request.body.response_types,\n tokenEndpointAuthMethod: request.body.token_endpoint_auth_method,\n scope: request.body.scope,\n });\n reply.code(201);\n return registrationResponse(result);\n },\n });\n}\n\nexport function createOAuthMetadataRoutes(options: CreateOAuthRoutesOptions): RouteGroup {\n const origin = apiPublicOrigin(options);\n return {\n prefix: '',\n routes: [\n createProtectedResourceMetadataRoute(origin),\n createAuthorizationServerMetadataRoute(origin),\n ],\n };\n}\n\nexport function createOAuthClientIdentificationRoutes(): RouteGroup {\n return {prefix: '', routes: [createDynamicRegistrationRoute()]};\n}\n\nexport function createOAuthRoutes(options: CreateOAuthRoutesOptions): RouteGroup {\n const metadataRoutes = createOAuthMetadataRoutes(options);\n const clientIdentificationRoutes = createOAuthClientIdentificationRoutes();\n return {\n prefix: '',\n routes: [...metadataRoutes.routes, ...clientIdentificationRoutes.routes],\n };\n}\n"],"names":["oauthAuthorizationServerMetadataSchema","oauthDynamicClientRegistrationRequestSchema","oauthDynamicClientRegistrationResponseSchema","oauthProtectedResourceMetadataSchema","ClientError","defineRoute","InvalidOAuthClientMetadataError","InvalidOAuthConfigurationError","OAuthMetadataFetchError","OAuthRedirectUriNotRegisteredError","validateOAuthPublicOrigin","registerOAuthClient","createAuthIpRateLimitPreHandler","apiPublicOrigin","options","apiPublicUrl","translateOAuthError","error","status","registrationResponse","result","client_id","metadata","clientId","client_name","clientName","redirect_uris","redirectUris","grant_types","grantTypes","response_types","responseTypes","token_endpoint_auth_method","tokenEndpointAuthMethod","scope","createProtectedResourceMetadataRoute","origin","method","path","description","schema","response","handler","resource","authorization_servers","scopes_supported","createAuthorizationServerMetadataRoute","issuer","authorization_endpoint","token_endpoint","registration_endpoint","response_types_supported","grant_types_supported","code_challenge_methods_supported","token_endpoint_auth_methods_supported","client_id_metadata_document_supported","createDynamicRegistrationRoute","bodyLimit","body","preHandler","errorHandler","request","reply","code","createOAuthMetadataRoutes","prefix","routes","createOAuthClientIdentificationRoutes","createOAuthRoutes","metadataRoutes","clientIdentificationRoutes"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,qBAAqB,CAAC;AAuC7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgDD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,aAChD,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,mBAAmB,aAClD,cAAc,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAS3E;AAED;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,mBAAmB,aACrD,cAAc,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAmB3E"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,qBAAqB,CAAC;AAmD7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgDD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,aAChD,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,mBAAmB,aAClD,cAAc,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAS3E;AAED;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,mBAAmB,aACrD,cAAc,SAAS,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAmB3E"}
@@ -66,6 +66,30 @@ const policies = {
66
66
  limit: 20,
67
67
  windowSeconds: 15 * 60
68
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
+ }
69
93
  }
70
94
  };
71
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 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};\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":"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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"}
@@ -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"}