@shipfox/api-auth 9.2.0 → 10.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 (82) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +56 -0
  3. package/README.md +32 -8
  4. package/dist/config.d.ts +1 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +4 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/administration.d.ts +43 -0
  9. package/dist/core/administration.d.ts.map +1 -0
  10. package/dist/core/administration.js +152 -0
  11. package/dist/core/administration.js.map +1 -0
  12. package/dist/core/auth.d.ts.map +1 -1
  13. package/dist/core/auth.js +2 -5
  14. package/dist/core/auth.js.map +1 -1
  15. package/dist/core/entities/administrator-read-model.d.ts +21 -0
  16. package/dist/core/entities/administrator-read-model.d.ts.map +1 -0
  17. package/dist/core/entities/administrator-read-model.js +3 -0
  18. package/dist/core/entities/administrator-read-model.js.map +1 -0
  19. package/dist/core/errors.d.ts +15 -0
  20. package/dist/core/errors.d.ts.map +1 -1
  21. package/dist/core/errors.js +30 -0
  22. package/dist/core/errors.js.map +1 -1
  23. package/dist/db/admin-grants.d.ts +34 -1
  24. package/dist/db/admin-grants.d.ts.map +1 -1
  25. package/dist/db/admin-grants.js +177 -5
  26. package/dist/db/admin-grants.js.map +1 -1
  27. package/dist/db/admin-users.d.ts +21 -0
  28. package/dist/db/admin-users.d.ts.map +1 -0
  29. package/dist/db/admin-users.js +32 -0
  30. package/dist/db/admin-users.js.map +1 -0
  31. package/dist/db/db.d.ts +256 -0
  32. package/dist/db/db.d.ts.map +1 -1
  33. package/dist/db/db.js +2 -0
  34. package/dist/db/db.js.map +1 -1
  35. package/dist/db/schema/admin-command-results.d.ts +142 -0
  36. package/dist/db/schema/admin-command-results.d.ts.map +1 -0
  37. package/dist/db/schema/admin-command-results.js +21 -0
  38. package/dist/db/schema/admin-command-results.js.map +1 -0
  39. package/dist/index.d.ts +2 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/metrics/instance.d.ts +1 -1
  44. package/dist/metrics/instance.d.ts.map +1 -1
  45. package/dist/metrics/instance.js.map +1 -1
  46. package/dist/presentation/routes/administration.d.ts +4 -0
  47. package/dist/presentation/routes/administration.d.ts.map +1 -0
  48. package/dist/presentation/routes/administration.js +267 -0
  49. package/dist/presentation/routes/administration.js.map +1 -0
  50. package/dist/presentation/routes/rate-limit.d.ts +1 -0
  51. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  52. package/dist/presentation/routes/rate-limit.js +26 -1
  53. package/dist/presentation/routes/rate-limit.js.map +1 -1
  54. package/dist/tsconfig.test.tsbuildinfo +1 -1
  55. package/drizzle/0002_burly_whizzer.sql +12 -0
  56. package/drizzle/meta/0002_snapshot.json +791 -0
  57. package/drizzle/meta/_journal.json +7 -0
  58. package/package.json +10 -9
  59. package/src/config.ts +4 -0
  60. package/src/core/administration.ts +213 -0
  61. package/src/core/auth-default-signup-policy.test.ts +65 -0
  62. package/src/core/auth.test.ts +5 -0
  63. package/src/core/auth.ts +2 -3
  64. package/src/core/entities/administrator-read-model.ts +23 -0
  65. package/src/core/errors.ts +35 -0
  66. package/src/db/admin-grants.test.ts +1 -7
  67. package/src/db/admin-grants.ts +310 -5
  68. package/src/db/admin-users.ts +58 -0
  69. package/src/db/db.ts +2 -0
  70. package/src/db/schema/admin-command-results.ts +41 -0
  71. package/src/index.test.ts +6 -2
  72. package/src/index.ts +23 -2
  73. package/src/metrics/instance.ts +1 -1
  74. package/src/presentation/auth/refresh-cookie.test.ts +4 -0
  75. package/src/presentation/e2eRoutes/index.test.ts +1 -0
  76. package/src/presentation/routes/administration.test.ts +537 -0
  77. package/src/presentation/routes/administration.ts +276 -0
  78. package/src/presentation/routes/index.test.ts +1 -0
  79. package/src/presentation/routes/rate-limit.ts +26 -2
  80. package/test/globalSetup.ts +6 -2
  81. package/test/routes.ts +10 -1
  82. package/tsconfig.build.tsbuildinfo +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AUTH_PASSWORD_RESET_SEND_REQUESTED, authEventSchemas } from '@shipfox/api-auth-dto';
2
+ import { administrationActionEventSchemas } from '@shipfox/api-common-dto';
2
3
  import { subscriberFactory } from '@shipfox/node-module';
3
4
  import { config } from '#config.js';
4
5
  import { createEnvironmentSignupPolicy } from '#core/signup-policy.js';
@@ -10,13 +11,19 @@ import { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.
10
11
  import { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
11
12
  import { createAuthE2eRoutes } from '#presentation/e2eRoutes/index.js';
12
13
  import { createAuthInterModulePresentation } from '#presentation/inter-module.js';
14
+ import { administrationRoutes, administrationUserRoutes } from '#presentation/routes/administration.js';
13
15
  import { buildAuthRoutes } from '#presentation/routes/index.js';
14
16
  import { onPasswordResetSendRequested } from '#presentation/subscribers/index.js';
15
17
  import { passwordLoginMethods } from './login-methods.js';
18
+ const authPublisherEventSchemas = {
19
+ ...authEventSchemas,
20
+ ...administrationActionEventSchemas
21
+ };
16
22
  export { ADMIN_ROLES, getCurrentAdminRole, hasMinimumAdminRole, highestAdminRole, requireAdminRole, revokeAdminGrant } from '#core/admin-role.js';
23
+ export { bootstrapFirstAdminOwner, grantAdministratorRole, revokeAdministratorGrant } from '#core/administration.js';
17
24
  export { createSessionForUser, provisionUser } from '#core/auth.js';
18
25
  export { findUserByEmail } from '#core/email-owner.js';
19
- export { AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
26
+ export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
20
27
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken } from '#core/job-lease-token.js';
21
28
  export { issueRunnerSessionToken, verifyRunnerSessionToken } from '#core/runner-session-token.js';
22
29
  export { createEnvironmentSignupPolicy, DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE } from '#core/signup-policy.js';
@@ -40,7 +47,9 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
40
47
  ],
41
48
  loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),
42
49
  routes: [
43
- buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy)
50
+ buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),
51
+ administrationRoutes,
52
+ administrationUserRoutes
44
53
  ],
45
54
  e2eRoutes: [
46
55
  createAuthE2eRoutes(workspaces)
@@ -50,7 +59,7 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
50
59
  name: 'auth',
51
60
  table: authOutbox,
52
61
  db,
53
- eventSchemas: authEventSchemas
62
+ eventSchemas: authPublisherEventSchemas
54
63
  }
55
64
  ],
56
65
  subscribers: [
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy)],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","createSessionForUser","provisionUser","findUserByEmail","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAGxD,SACEC,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAO7B,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACEhC,6BAA6B,EAC7BiC,kCAAkC,QAC7B,yBAAyB;AAChC,SAEE7B,mBAAmB,EACnB8B,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQ7B,0BAA0B,QAAO,yCAAyC;AAClF,SACE8B,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQhC,6BAA6B,QAAO,4CAA4C;AAExF,MAAMiC,aAAazC;AAOnB,OAAO,SAAS0C,iBAAiB,EAC/BC,UAAU,EACVC,eAAe1C,+BAA+B,EACtB;IACxB,OAAO;QACL2C,MAAM;QACNC,UAAU;YAAC3C;YAAIC;YAAgB2C,mBAAmB;QAAM;QACxDC,MAAM;YAAC1C;YAAuBC;YAA8BC;SAAgC;QAC5FyC,cAAcpC,qBAAqBZ,OAAOiD,qBAAqB;QAC/DC,QAAQ;YAACxC,gBAAgBV,OAAOiD,qBAAqB,EAAEP,YAAYC;SAAc;QACjFQ,WAAW;YAAC3C,oBAAoBkC;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAOjD;gBAAYF;gBAAIoD,cAAcxD;YAAgB;SAAE;QACnFyD,aAAa;YAACf,WAAW3C,oCAAoCc;SAA8B;QAC3F6C,0BAA0B;YAAC/C;SAAoC;IACjE;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {\n administrationRoutes,\n administrationUserRoutes,\n} from '#presentation/routes/administration.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nconst authPublisherEventSchemas = {...authEventSchemas, ...administrationActionEventSchemas};\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport {\n bootstrapFirstAdminOwner,\n grantAdministratorRole,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidAdminBootstrapTokenError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [\n buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),\n administrationRoutes,\n administrationUserRoutes,\n ],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authPublisherEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","administrationActionEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","administrationRoutes","administrationUserRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","revokeAdministratorGrant","createSessionForUser","provisionUser","findUserByEmail","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidAdminBootstrapTokenError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAC/B,SAAQC,gCAAgC,QAAO,0BAA0B;AAEzE,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SACEC,oBAAoB,EACpBC,wBAAwB,QACnB,yCAAyC;AAChD,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGlB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEkB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,wBAAwB,QACnB,0BAA0B;AAOjC,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,+BAA+B,EAC/BC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACE3C,6BAA6B,EAC7B4C,kCAAkC,QAC7B,yBAAyB;AAChC,SAEExC,mBAAmB,EACnByC,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQxC,0BAA0B,QAAO,yCAAyC;AAClF,SACEyC,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQ3C,6BAA6B,QAAO,4CAA4C;AAExF,MAAM4C,aAAapD;AAOnB,OAAO,SAASqD,iBAAiB,EAC/BC,UAAU,EACVC,eAAerD,+BAA+B,EACtB;IACxB,OAAO;QACLsD,MAAM;QACNC,UAAU;YAACtD;YAAIC;YAAgBsD,mBAAmB;QAAM;QACxDC,MAAM;YAACrD;YAAuBC;YAA8BC;SAAgC;QAC5FoD,cAAc7C,qBAAqBd,OAAO4D,qBAAqB;QAC/DC,QAAQ;YACNjD,gBAAgBZ,OAAO4D,qBAAqB,EAAEP,YAAYC;YAC1D5C;YACAC;SACD;QACDmD,WAAW;YAACtD,oBAAoB6C;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAO5D;gBAAYF;gBAAI+D,cAAclD;YAAyB;SAAE;QAC5FmD,aAAa;YAACf,WAAWvD,oCAAoCiB;SAA8B;QAC3FsD,0BAA0B;YAAC1D;SAAoC;IACjE;AACF"}
@@ -1,7 +1,7 @@
1
1
  export type AuthTokenType = 'session' | 'job_lease' | 'runner_session';
2
2
  export type AuthTokenVerificationOutcome = 'ok' | 'rejected';
3
3
  export type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';
4
- export type AuthRateLimitAction = 'login' | 'email-send';
4
+ export type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'lookup';
5
5
  export type AuthRateLimitScope = 'ip' | 'email';
6
6
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
7
7
  export declare function recordTokenIssued(tokenType: AuthTokenType): void;
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,YAAY,CAAC;AACzD,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAClF,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction = 'login' | 'email-send';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAS5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
1
+ {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'lookup';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAS5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
@@ -0,0 +1,4 @@
1
+ import { type RouteGroup } from '@shipfox/node-fastify';
2
+ export declare const administrationRoutes: RouteGroup;
3
+ export declare const administrationUserRoutes: RouteGroup;
4
+ //# sourceMappingURL=administration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AA2PhF,eAAO,MAAM,oBAAoB,EAAE,UAIlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAItC,CAAC"}
@@ -0,0 +1,267 @@
1
+ import { AUTH_USER } from '@shipfox/api-auth-context';
2
+ import { administratorUserLookupQuerySchema, administratorUserSummarySchema, bootstrapAdminOwnerBodySchema, bootstrapAdminOwnerResponseSchema, grantAdminRoleBodySchema, grantAdminRoleResponseSchema, listAdminGrantsQuerySchema, listAdminGrantsResponseSchema, revokeAdminGrantBodySchema, revokeAdminGrantResponseSchema } from '@shipfox/api-auth-dto';
3
+ import { decodeTimestampIdCursor, encodeTimestampIdCursor } from '@shipfox/node-drizzle';
4
+ import { ClientError, defineRoute } from '@shipfox/node-fastify';
5
+ import { z } from 'zod';
6
+ import { requireAdminRole } from '#core/admin-role.js';
7
+ import { bootstrapFirstAdminOwner, findAdministratorUserSummary, grantAdministratorRole, listAdministratorGrantSummaries, revokeAdministratorGrant } from '#core/administration.js';
8
+ import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from '#core/errors.js';
9
+ import { getClientContext } from '#presentation/auth/jwt-auth.js';
10
+ import { createAuthIpRateLimitPreHandler } from './rate-limit.js';
11
+ const idempotencyKeyMaxLength = 256;
12
+ function requireActorId(request) {
13
+ const client = getClientContext(request);
14
+ if (!client) {
15
+ throw new ClientError('Authentication required', 'unauthorized', {
16
+ status: 401
17
+ });
18
+ }
19
+ return client.userId;
20
+ }
21
+ function requireIdempotencyKey(request) {
22
+ const value = request.headers['idempotency-key'];
23
+ const key = Array.isArray(value) ? value[0] : value;
24
+ if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {
25
+ throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {
26
+ status: 400
27
+ });
28
+ }
29
+ return key;
30
+ }
31
+ async function requireAdministratorObserver(request) {
32
+ await requireAdminRole({
33
+ userId: requireActorId(request),
34
+ minimumRole: 'admin-observer'
35
+ });
36
+ }
37
+ function toAdminGrantDto(grant) {
38
+ return {
39
+ id: grant.id,
40
+ user_id: grant.userId,
41
+ role: grant.role,
42
+ revoked_at: grant.revokedAt?.toISOString() ?? null,
43
+ created_at: grant.createdAt.toISOString(),
44
+ updated_at: grant.updatedAt.toISOString()
45
+ };
46
+ }
47
+ function toAdministratorUserSummaryDto(user) {
48
+ return {
49
+ id: user.id,
50
+ email: user.email,
51
+ name: user.name,
52
+ status: user.status,
53
+ email_verified_at: user.emailVerifiedAt?.toISOString() ?? null,
54
+ created_at: user.createdAt.toISOString(),
55
+ admin_role: user.adminRole
56
+ };
57
+ }
58
+ function toAdministratorGrantSummaryDto(grant) {
59
+ return {
60
+ grant_id: grant.grantId,
61
+ role: grant.role,
62
+ created_at: grant.createdAt.toISOString(),
63
+ revoked_at: grant.revokedAt?.toISOString() ?? null,
64
+ user: grant.user
65
+ };
66
+ }
67
+ function translateAdministrationError(error) {
68
+ if (error instanceof AdminRoleRequiredError) {
69
+ throw new ClientError('Administrator role required', 'forbidden', {
70
+ status: 403,
71
+ details: {
72
+ required_role: error.minimumRole
73
+ }
74
+ });
75
+ }
76
+ if (error instanceof InvalidAdminBootstrapTokenError) {
77
+ throw new ClientError('Bootstrap token is invalid', 'bootstrap-token-invalid', {
78
+ status: 403
79
+ });
80
+ }
81
+ if (error instanceof AdminBootstrapClosedError) {
82
+ throw new ClientError('First administrator owner already exists', 'bootstrap-closed', {
83
+ status: 409
84
+ });
85
+ }
86
+ if (error instanceof AdminGrantAlreadyExistsError) {
87
+ throw new ClientError('Administrator grant already exists', 'grant-already-exists', {
88
+ status: 409
89
+ });
90
+ }
91
+ if (error instanceof AdminGrantNotFoundError) {
92
+ throw new ClientError('Administrator grant not found', 'not-found', {
93
+ status: 404
94
+ });
95
+ }
96
+ if (error instanceof UserNotFoundError) {
97
+ throw new ClientError('User not found', 'not-found', {
98
+ status: 404
99
+ });
100
+ }
101
+ if (error instanceof LastAdminOwnerError) {
102
+ throw new ClientError('Cannot remove the final active administrator owner', 'last-owner', {
103
+ status: 409
104
+ });
105
+ }
106
+ if (error instanceof AdminIdempotencyKeyReuseError) {
107
+ throw new ClientError('Idempotency-Key was already used for a different command', 'idempotency-key-reused', {
108
+ status: 409
109
+ });
110
+ }
111
+ throw error;
112
+ }
113
+ const bootstrapRoute = defineRoute({
114
+ method: 'POST',
115
+ path: '/bootstrap',
116
+ description: 'Claim the first administrator owner role with the deployment bootstrap token.',
117
+ schema: {
118
+ body: bootstrapAdminOwnerBodySchema,
119
+ response: {
120
+ 201: bootstrapAdminOwnerResponseSchema
121
+ }
122
+ },
123
+ preHandler: createAuthIpRateLimitPreHandler('bootstrap'),
124
+ errorHandler: translateAdministrationError,
125
+ handler: async (request, reply)=>{
126
+ const actorId = requireActorId(request);
127
+ const grant = await bootstrapFirstAdminOwner({
128
+ actorId,
129
+ bootstrapToken: request.body.bootstrap_token,
130
+ idempotencyKey: requireIdempotencyKey(request),
131
+ correlationId: request.id
132
+ });
133
+ reply.code(201);
134
+ return toAdminGrantDto(grant);
135
+ }
136
+ });
137
+ const listRoute = defineRoute({
138
+ method: 'GET',
139
+ path: '/',
140
+ description: 'List bounded local administrator grant summaries.',
141
+ schema: {
142
+ querystring: listAdminGrantsQuerySchema,
143
+ response: {
144
+ 200: listAdminGrantsResponseSchema
145
+ }
146
+ },
147
+ errorHandler: translateAdministrationError,
148
+ handler: async (request)=>{
149
+ const { limit, cursor } = request.query;
150
+ const decodedCursor = decodeTimestampIdCursor(cursor);
151
+ if (cursor && !decodedCursor) {
152
+ throw new ClientError('Invalid cursor', 'invalid-cursor', {
153
+ status: 400
154
+ });
155
+ }
156
+ const result = await listAdministratorGrantSummaries({
157
+ actorId: requireActorId(request),
158
+ limit,
159
+ ...decodedCursor ? {
160
+ cursor: decodedCursor
161
+ } : {}
162
+ });
163
+ return {
164
+ grants: result.grants.map(toAdministratorGrantSummaryDto),
165
+ next_cursor: result.nextCursor ? encodeTimestampIdCursor(result.nextCursor) : null
166
+ };
167
+ }
168
+ });
169
+ const userLookupRoute = defineRoute({
170
+ method: 'GET',
171
+ path: '/',
172
+ description: 'Find one administrator-safe user summary by exact ID or email.',
173
+ schema: {
174
+ querystring: administratorUserLookupQuerySchema,
175
+ response: {
176
+ 200: administratorUserSummarySchema
177
+ }
178
+ },
179
+ preHandler: [
180
+ requireAdministratorObserver,
181
+ createAuthIpRateLimitPreHandler('lookup')
182
+ ],
183
+ errorHandler: translateAdministrationError,
184
+ handler: async (request)=>{
185
+ const { id, user_id: userId, email } = request.query;
186
+ const lookupId = id ?? userId;
187
+ const actorId = requireActorId(request);
188
+ const user = lookupId ? await findAdministratorUserSummary({
189
+ actorId,
190
+ id: lookupId
191
+ }) : email ? await findAdministratorUserSummary({
192
+ actorId,
193
+ email
194
+ }) : undefined;
195
+ if (!user) throw new UserNotFoundError(lookupId ?? email ?? 'unknown');
196
+ return toAdministratorUserSummaryDto(user);
197
+ }
198
+ });
199
+ const grantRoute = defineRoute({
200
+ method: 'POST',
201
+ path: '/',
202
+ description: 'Grant a local administrator role to an active user.',
203
+ schema: {
204
+ body: grantAdminRoleBodySchema,
205
+ response: {
206
+ 201: grantAdminRoleResponseSchema
207
+ }
208
+ },
209
+ errorHandler: translateAdministrationError,
210
+ handler: async (request, reply)=>{
211
+ const actorId = requireActorId(request);
212
+ const grant = await grantAdministratorRole({
213
+ actorId,
214
+ userId: request.body.user_id,
215
+ role: request.body.role,
216
+ reason: request.body.reason,
217
+ idempotencyKey: requireIdempotencyKey(request),
218
+ correlationId: request.id
219
+ });
220
+ reply.code(201);
221
+ return toAdminGrantDto(grant);
222
+ }
223
+ });
224
+ const revokeRoute = defineRoute({
225
+ method: 'DELETE',
226
+ path: '/:grantId',
227
+ description: 'Revoke a local administrator grant.',
228
+ schema: {
229
+ params: z.object({
230
+ grantId: z.string().uuid()
231
+ }),
232
+ body: revokeAdminGrantBodySchema,
233
+ response: {
234
+ 200: revokeAdminGrantResponseSchema
235
+ }
236
+ },
237
+ errorHandler: translateAdministrationError,
238
+ handler: async (request)=>{
239
+ const grant = await revokeAdministratorGrant({
240
+ actorId: requireActorId(request),
241
+ grantId: request.params.grantId,
242
+ reason: request.body.reason,
243
+ idempotencyKey: requireIdempotencyKey(request),
244
+ correlationId: request.id
245
+ });
246
+ return toAdminGrantDto(grant);
247
+ }
248
+ });
249
+ export const administrationRoutes = {
250
+ prefix: '/admin/auth/admin-grants',
251
+ auth: AUTH_USER,
252
+ routes: [
253
+ bootstrapRoute,
254
+ listRoute,
255
+ grantRoute,
256
+ revokeRoute
257
+ ]
258
+ };
259
+ export const administrationUserRoutes = {
260
+ prefix: '/admin/auth/users',
261
+ auth: AUTH_USER,
262
+ routes: [
263
+ userLookupRoute
264
+ ]
265
+ };
266
+
267
+ //# sourceMappingURL=administration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/routes/administration.ts"],"sourcesContent":["import {AUTH_USER} from '@shipfox/api-auth-context';\nimport {\n administratorUserLookupQuerySchema,\n administratorUserSummarySchema,\n bootstrapAdminOwnerBodySchema,\n bootstrapAdminOwnerResponseSchema,\n grantAdminRoleBodySchema,\n grantAdminRoleResponseSchema,\n listAdminGrantsQuerySchema,\n listAdminGrantsResponseSchema,\n revokeAdminGrantBodySchema,\n revokeAdminGrantResponseSchema,\n} from '@shipfox/api-auth-dto';\nimport {decodeTimestampIdCursor, encodeTimestampIdCursor} from '@shipfox/node-drizzle';\nimport {ClientError, defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport {z} from 'zod';\nimport {requireAdminRole} from '#core/admin-role.js';\nimport {\n bootstrapFirstAdminOwner,\n findAdministratorUserSummary,\n grantAdministratorRole,\n listAdministratorGrantSummaries,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nimport type {AdminGrant} from '#core/entities/admin-grant.js';\nimport type {\n AdministratorGrantSummary,\n AdministratorUserSummary,\n} from '#core/entities/administrator-read-model.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from '#core/errors.js';\nimport {getClientContext} from '#presentation/auth/jwt-auth.js';\nimport {createAuthIpRateLimitPreHandler} from './rate-limit.js';\n\nconst idempotencyKeyMaxLength = 256;\n\nfunction requireActorId(request: FastifyRequest): string {\n const client = getClientContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n return client.userId;\n}\n\nfunction requireIdempotencyKey(request: FastifyRequest): string {\n const value = request.headers['idempotency-key'];\n const key = Array.isArray(value) ? value[0] : value;\n if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {\n throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {\n status: 400,\n });\n }\n return key;\n}\n\nasync function requireAdministratorObserver(request: FastifyRequest): Promise<void> {\n await requireAdminRole({userId: requireActorId(request), minimumRole: 'admin-observer'});\n}\n\nfunction toAdminGrantDto(grant: AdminGrant) {\n return {\n id: grant.id,\n user_id: grant.userId,\n role: grant.role,\n revoked_at: grant.revokedAt?.toISOString() ?? null,\n created_at: grant.createdAt.toISOString(),\n updated_at: grant.updatedAt.toISOString(),\n };\n}\n\nfunction toAdministratorUserSummaryDto(user: AdministratorUserSummary) {\n return {\n id: user.id,\n email: user.email,\n name: user.name,\n status: user.status,\n email_verified_at: user.emailVerifiedAt?.toISOString() ?? null,\n created_at: user.createdAt.toISOString(),\n admin_role: user.adminRole,\n };\n}\n\nfunction toAdministratorGrantSummaryDto(grant: AdministratorGrantSummary) {\n return {\n grant_id: grant.grantId,\n role: grant.role,\n created_at: grant.createdAt.toISOString(),\n revoked_at: grant.revokedAt?.toISOString() ?? null,\n user: grant.user,\n };\n}\n\nfunction translateAdministrationError(error: unknown): never {\n if (error instanceof AdminRoleRequiredError) {\n throw new ClientError('Administrator role required', 'forbidden', {\n status: 403,\n details: {required_role: error.minimumRole},\n });\n }\n if (error instanceof InvalidAdminBootstrapTokenError) {\n throw new ClientError('Bootstrap token is invalid', 'bootstrap-token-invalid', {\n status: 403,\n });\n }\n if (error instanceof AdminBootstrapClosedError) {\n throw new ClientError('First administrator owner already exists', 'bootstrap-closed', {\n status: 409,\n });\n }\n if (error instanceof AdminGrantAlreadyExistsError) {\n throw new ClientError('Administrator grant already exists', 'grant-already-exists', {\n status: 409,\n });\n }\n if (error instanceof AdminGrantNotFoundError) {\n throw new ClientError('Administrator grant not found', 'not-found', {status: 404});\n }\n if (error instanceof UserNotFoundError) {\n throw new ClientError('User not found', 'not-found', {status: 404});\n }\n if (error instanceof LastAdminOwnerError) {\n throw new ClientError('Cannot remove the final active administrator owner', 'last-owner', {\n status: 409,\n });\n }\n if (error instanceof AdminIdempotencyKeyReuseError) {\n throw new ClientError(\n 'Idempotency-Key was already used for a different command',\n 'idempotency-key-reused',\n {status: 409},\n );\n }\n throw error;\n}\n\nconst bootstrapRoute = defineRoute({\n method: 'POST',\n path: '/bootstrap',\n description: 'Claim the first administrator owner role with the deployment bootstrap token.',\n schema: {\n body: bootstrapAdminOwnerBodySchema,\n response: {201: bootstrapAdminOwnerResponseSchema},\n },\n preHandler: createAuthIpRateLimitPreHandler('bootstrap'),\n errorHandler: translateAdministrationError,\n handler: async (request, reply) => {\n const actorId = requireActorId(request);\n const grant = await bootstrapFirstAdminOwner({\n actorId,\n bootstrapToken: request.body.bootstrap_token,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n reply.code(201);\n return toAdminGrantDto(grant);\n },\n});\n\nconst listRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List bounded local administrator grant summaries.',\n schema: {\n querystring: listAdminGrantsQuerySchema,\n response: {200: listAdminGrantsResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request) => {\n const {limit, cursor} = request.query;\n const decodedCursor = decodeTimestampIdCursor(cursor);\n if (cursor && !decodedCursor) {\n throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});\n }\n\n const result = await listAdministratorGrantSummaries({\n actorId: requireActorId(request),\n limit,\n ...(decodedCursor ? {cursor: decodedCursor} : {}),\n });\n return {\n grants: result.grants.map(toAdministratorGrantSummaryDto),\n next_cursor: result.nextCursor ? encodeTimestampIdCursor(result.nextCursor) : null,\n };\n },\n});\n\nconst userLookupRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'Find one administrator-safe user summary by exact ID or email.',\n schema: {\n querystring: administratorUserLookupQuerySchema,\n response: {200: administratorUserSummarySchema},\n },\n preHandler: [requireAdministratorObserver, createAuthIpRateLimitPreHandler('lookup')],\n errorHandler: translateAdministrationError,\n handler: async (request) => {\n const {id, user_id: userId, email} = request.query;\n const lookupId = id ?? userId;\n const actorId = requireActorId(request);\n const user = lookupId\n ? await findAdministratorUserSummary({actorId, id: lookupId})\n : email\n ? await findAdministratorUserSummary({actorId, email})\n : undefined;\n if (!user) throw new UserNotFoundError(lookupId ?? email ?? 'unknown');\n return toAdministratorUserSummaryDto(user);\n },\n});\n\nconst grantRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Grant a local administrator role to an active user.',\n schema: {\n body: grantAdminRoleBodySchema,\n response: {201: grantAdminRoleResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request, reply) => {\n const actorId = requireActorId(request);\n const grant = await grantAdministratorRole({\n actorId,\n userId: request.body.user_id,\n role: request.body.role,\n reason: request.body.reason,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n reply.code(201);\n return toAdminGrantDto(grant);\n },\n});\n\nconst revokeRoute = defineRoute({\n method: 'DELETE',\n path: '/:grantId',\n description: 'Revoke a local administrator grant.',\n schema: {\n params: z.object({grantId: z.string().uuid()}),\n body: revokeAdminGrantBodySchema,\n response: {200: revokeAdminGrantResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request) => {\n const grant = await revokeAdministratorGrant({\n actorId: requireActorId(request),\n grantId: request.params.grantId,\n reason: request.body.reason,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n return toAdminGrantDto(grant);\n },\n});\n\nexport const administrationRoutes: RouteGroup = {\n prefix: '/admin/auth/admin-grants',\n auth: AUTH_USER,\n routes: [bootstrapRoute, listRoute, grantRoute, revokeRoute],\n};\n\nexport const administrationUserRoutes: RouteGroup = {\n prefix: '/admin/auth/users',\n auth: AUTH_USER,\n routes: [userLookupRoute],\n};\n"],"names":["AUTH_USER","administratorUserLookupQuerySchema","administratorUserSummarySchema","bootstrapAdminOwnerBodySchema","bootstrapAdminOwnerResponseSchema","grantAdminRoleBodySchema","grantAdminRoleResponseSchema","listAdminGrantsQuerySchema","listAdminGrantsResponseSchema","revokeAdminGrantBodySchema","revokeAdminGrantResponseSchema","decodeTimestampIdCursor","encodeTimestampIdCursor","ClientError","defineRoute","z","requireAdminRole","bootstrapFirstAdminOwner","findAdministratorUserSummary","grantAdministratorRole","listAdministratorGrantSummaries","revokeAdministratorGrant","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","InvalidAdminBootstrapTokenError","LastAdminOwnerError","UserNotFoundError","getClientContext","createAuthIpRateLimitPreHandler","idempotencyKeyMaxLength","requireActorId","request","client","status","userId","requireIdempotencyKey","value","headers","key","Array","isArray","trim","length","requireAdministratorObserver","minimumRole","toAdminGrantDto","grant","id","user_id","role","revoked_at","revokedAt","toISOString","created_at","createdAt","updated_at","updatedAt","toAdministratorUserSummaryDto","user","email","name","email_verified_at","emailVerifiedAt","admin_role","adminRole","toAdministratorGrantSummaryDto","grant_id","grantId","translateAdministrationError","error","details","required_role","bootstrapRoute","method","path","description","schema","body","response","preHandler","errorHandler","handler","reply","actorId","bootstrapToken","bootstrap_token","idempotencyKey","correlationId","code","listRoute","querystring","limit","cursor","query","decodedCursor","result","grants","map","next_cursor","nextCursor","userLookupRoute","lookupId","undefined","grantRoute","reason","revokeRoute","params","object","string","uuid","administrationRoutes","prefix","auth","routes","administrationUserRoutes"],"mappings":"AAAA,SAAQA,SAAS,QAAO,4BAA4B;AACpD,SACEC,kCAAkC,EAClCC,8BAA8B,EAC9BC,6BAA6B,EAC7BC,iCAAiC,EACjCC,wBAAwB,EACxBC,4BAA4B,EAC5BC,0BAA0B,EAC1BC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,8BAA8B,QACzB,wBAAwB;AAC/B,SAAQC,uBAAuB,EAAEC,uBAAuB,QAAO,wBAAwB;AACvF,SAAQC,WAAW,EAAEC,WAAW,QAAwB,wBAAwB;AAEhF,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,gBAAgB,QAAO,sBAAsB;AACrD,SACEC,wBAAwB,EACxBC,4BAA4B,EAC5BC,sBAAsB,EACtBC,+BAA+B,EAC/BC,wBAAwB,QACnB,0BAA0B;AAMjC,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,+BAA+B,EAC/BC,mBAAmB,EACnBC,iBAAiB,QACZ,kBAAkB;AACzB,SAAQC,gBAAgB,QAAO,iCAAiC;AAChE,SAAQC,+BAA+B,QAAO,kBAAkB;AAEhE,MAAMC,0BAA0B;AAEhC,SAASC,eAAeC,OAAuB;IAC7C,MAAMC,SAASL,iBAAiBI;IAChC,IAAI,CAACC,QAAQ;QACX,MAAM,IAAItB,YAAY,2BAA2B,gBAAgB;YAACuB,QAAQ;QAAG;IAC/E;IACA,OAAOD,OAAOE,MAAM;AACtB;AAEA,SAASC,sBAAsBJ,OAAuB;IACpD,MAAMK,QAAQL,QAAQM,OAAO,CAAC,kBAAkB;IAChD,MAAMC,MAAMC,MAAMC,OAAO,CAACJ,SAASA,KAAK,CAAC,EAAE,GAAGA;IAC9C,IAAI,CAACE,OAAOA,IAAIG,IAAI,GAAGC,MAAM,KAAK,KAAKJ,IAAII,MAAM,GAAGb,yBAAyB;QAC3E,MAAM,IAAInB,YAAY,sCAAsC,4BAA4B;YACtFuB,QAAQ;QACV;IACF;IACA,OAAOK;AACT;AAEA,eAAeK,6BAA6BZ,OAAuB;IACjE,MAAMlB,iBAAiB;QAACqB,QAAQJ,eAAeC;QAAUa,aAAa;IAAgB;AACxF;AAEA,SAASC,gBAAgBC,KAAiB;IACxC,OAAO;QACLC,IAAID,MAAMC,EAAE;QACZC,SAASF,MAAMZ,MAAM;QACrBe,MAAMH,MAAMG,IAAI;QAChBC,YAAYJ,MAAMK,SAAS,EAAEC,iBAAiB;QAC9CC,YAAYP,MAAMQ,SAAS,CAACF,WAAW;QACvCG,YAAYT,MAAMU,SAAS,CAACJ,WAAW;IACzC;AACF;AAEA,SAASK,8BAA8BC,IAA8B;IACnE,OAAO;QACLX,IAAIW,KAAKX,EAAE;QACXY,OAAOD,KAAKC,KAAK;QACjBC,MAAMF,KAAKE,IAAI;QACf3B,QAAQyB,KAAKzB,MAAM;QACnB4B,mBAAmBH,KAAKI,eAAe,EAAEV,iBAAiB;QAC1DC,YAAYK,KAAKJ,SAAS,CAACF,WAAW;QACtCW,YAAYL,KAAKM,SAAS;IAC5B;AACF;AAEA,SAASC,+BAA+BnB,KAAgC;IACtE,OAAO;QACLoB,UAAUpB,MAAMqB,OAAO;QACvBlB,MAAMH,MAAMG,IAAI;QAChBI,YAAYP,MAAMQ,SAAS,CAACF,WAAW;QACvCF,YAAYJ,MAAMK,SAAS,EAAEC,iBAAiB;QAC9CM,MAAMZ,MAAMY,IAAI;IAClB;AACF;AAEA,SAASU,6BAA6BC,KAAc;IAClD,IAAIA,iBAAiB9C,wBAAwB;QAC3C,MAAM,IAAIb,YAAY,+BAA+B,aAAa;YAChEuB,QAAQ;YACRqC,SAAS;gBAACC,eAAeF,MAAMzB,WAAW;YAAA;QAC5C;IACF;IACA,IAAIyB,iBAAiB7C,iCAAiC;QACpD,MAAM,IAAId,YAAY,8BAA8B,2BAA2B;YAC7EuB,QAAQ;QACV;IACF;IACA,IAAIoC,iBAAiBlD,2BAA2B;QAC9C,MAAM,IAAIT,YAAY,4CAA4C,oBAAoB;YACpFuB,QAAQ;QACV;IACF;IACA,IAAIoC,iBAAiBjD,8BAA8B;QACjD,MAAM,IAAIV,YAAY,sCAAsC,wBAAwB;YAClFuB,QAAQ;QACV;IACF;IACA,IAAIoC,iBAAiBhD,yBAAyB;QAC5C,MAAM,IAAIX,YAAY,iCAAiC,aAAa;YAACuB,QAAQ;QAAG;IAClF;IACA,IAAIoC,iBAAiB3C,mBAAmB;QACtC,MAAM,IAAIhB,YAAY,kBAAkB,aAAa;YAACuB,QAAQ;QAAG;IACnE;IACA,IAAIoC,iBAAiB5C,qBAAqB;QACxC,MAAM,IAAIf,YAAY,sDAAsD,cAAc;YACxFuB,QAAQ;QACV;IACF;IACA,IAAIoC,iBAAiB/C,+BAA+B;QAClD,MAAM,IAAIZ,YACR,4DACA,0BACA;YAACuB,QAAQ;QAAG;IAEhB;IACA,MAAMoC;AACR;AAEA,MAAMG,iBAAiB7D,YAAY;IACjC8D,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAM7E;QACN8E,UAAU;YAAC,KAAK7E;QAAiC;IACnD;IACA8E,YAAYnD,gCAAgC;IAC5CoD,cAAcZ;IACda,SAAS,OAAOlD,SAASmD;QACvB,MAAMC,UAAUrD,eAAeC;QAC/B,MAAMe,QAAQ,MAAMhC,yBAAyB;YAC3CqE;YACAC,gBAAgBrD,QAAQ8C,IAAI,CAACQ,eAAe;YAC5CC,gBAAgBnD,sBAAsBJ;YACtCwD,eAAexD,QAAQgB,EAAE;QAC3B;QACAmC,MAAMM,IAAI,CAAC;QACX,OAAO3C,gBAAgBC;IACzB;AACF;AAEA,MAAM2C,YAAY9E,YAAY;IAC5B8D,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNc,aAAatF;QACb0E,UAAU;YAAC,KAAKzE;QAA6B;IAC/C;IACA2E,cAAcZ;IACda,SAAS,OAAOlD;QACd,MAAM,EAAC4D,KAAK,EAAEC,MAAM,EAAC,GAAG7D,QAAQ8D,KAAK;QACrC,MAAMC,gBAAgBtF,wBAAwBoF;QAC9C,IAAIA,UAAU,CAACE,eAAe;YAC5B,MAAM,IAAIpF,YAAY,kBAAkB,kBAAkB;gBAACuB,QAAQ;YAAG;QACxE;QAEA,MAAM8D,SAAS,MAAM9E,gCAAgC;YACnDkE,SAASrD,eAAeC;YACxB4D;YACA,GAAIG,gBAAgB;gBAACF,QAAQE;YAAa,IAAI,CAAC,CAAC;QAClD;QACA,OAAO;YACLE,QAAQD,OAAOC,MAAM,CAACC,GAAG,CAAChC;YAC1BiC,aAAaH,OAAOI,UAAU,GAAG1F,wBAAwBsF,OAAOI,UAAU,IAAI;QAChF;IACF;AACF;AAEA,MAAMC,kBAAkBzF,YAAY;IAClC8D,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNc,aAAa5F;QACbgF,UAAU;YAAC,KAAK/E;QAA8B;IAChD;IACAgF,YAAY;QAACpC;QAA8Bf,gCAAgC;KAAU;IACrFoD,cAAcZ;IACda,SAAS,OAAOlD;QACd,MAAM,EAACgB,EAAE,EAAEC,SAASd,MAAM,EAAEyB,KAAK,EAAC,GAAG5B,QAAQ8D,KAAK;QAClD,MAAMQ,WAAWtD,MAAMb;QACvB,MAAMiD,UAAUrD,eAAeC;QAC/B,MAAM2B,OAAO2C,WACT,MAAMtF,6BAA6B;YAACoE;YAASpC,IAAIsD;QAAQ,KACzD1C,QACE,MAAM5C,6BAA6B;YAACoE;YAASxB;QAAK,KAClD2C;QACN,IAAI,CAAC5C,MAAM,MAAM,IAAIhC,kBAAkB2E,YAAY1C,SAAS;QAC5D,OAAOF,8BAA8BC;IACvC;AACF;AAEA,MAAM6C,aAAa5F,YAAY;IAC7B8D,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,MAAM3E;QACN4E,UAAU;YAAC,KAAK3E;QAA4B;IAC9C;IACA6E,cAAcZ;IACda,SAAS,OAAOlD,SAASmD;QACvB,MAAMC,UAAUrD,eAAeC;QAC/B,MAAMe,QAAQ,MAAM9B,uBAAuB;YACzCmE;YACAjD,QAAQH,QAAQ8C,IAAI,CAAC7B,OAAO;YAC5BC,MAAMlB,QAAQ8C,IAAI,CAAC5B,IAAI;YACvBuD,QAAQzE,QAAQ8C,IAAI,CAAC2B,MAAM;YAC3BlB,gBAAgBnD,sBAAsBJ;YACtCwD,eAAexD,QAAQgB,EAAE;QAC3B;QACAmC,MAAMM,IAAI,CAAC;QACX,OAAO3C,gBAAgBC;IACzB;AACF;AAEA,MAAM2D,cAAc9F,YAAY;IAC9B8D,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACN8B,QAAQ9F,EAAE+F,MAAM,CAAC;YAACxC,SAASvD,EAAEgG,MAAM,GAAGC,IAAI;QAAE;QAC5ChC,MAAMvE;QACNwE,UAAU;YAAC,KAAKvE;QAA8B;IAChD;IACAyE,cAAcZ;IACda,SAAS,OAAOlD;QACd,MAAMe,QAAQ,MAAM5B,yBAAyB;YAC3CiE,SAASrD,eAAeC;YACxBoC,SAASpC,QAAQ2E,MAAM,CAACvC,OAAO;YAC/BqC,QAAQzE,QAAQ8C,IAAI,CAAC2B,MAAM;YAC3BlB,gBAAgBnD,sBAAsBJ;YACtCwD,eAAexD,QAAQgB,EAAE;QAC3B;QACA,OAAOF,gBAAgBC;IACzB;AACF;AAEA,OAAO,MAAMgE,uBAAmC;IAC9CC,QAAQ;IACRC,MAAMnH;IACNoH,QAAQ;QAACzC;QAAgBiB;QAAWc;QAAYE;KAAY;AAC9D,EAAE;AAEF,OAAO,MAAMS,2BAAuC;IAClDH,QAAQ;IACRC,MAAMnH;IACNoH,QAAQ;QAACb;KAAgB;AAC3B,EAAE"}
@@ -6,5 +6,6 @@ interface EmailBody {
6
6
  export declare function createAuthRateLimitPreHandler(action: AuthRateLimitAction): (request: FastifyRequest<{
7
7
  Body: EmailBody;
8
8
  }>, reply: FastifyReply) => Promise<void>;
9
+ export declare function createAuthIpRateLimitPreHandler(action: AuthRateLimitAction): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
9
10
  export {};
10
11
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,KAAK,mBAAmB,EAMzB,MAAM,qBAAqB,CAAC;AAa7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AA6ED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,IACzD,SAAS,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,KAAK,mBAAmB,EAMzB,MAAM,qBAAqB,CAAC;AAsB7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgFD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,IACzD,SAAS,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,mBAAmB,IAC3D,SAAS,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAS3E"}
@@ -20,18 +20,32 @@ const policies = {
20
20
  limit: 3,
21
21
  windowSeconds: 60 * 60
22
22
  }
23
+ },
24
+ bootstrap: {
25
+ ip: {
26
+ limit: 5,
27
+ windowSeconds: 15 * 60
28
+ }
29
+ },
30
+ lookup: {
31
+ ip: {
32
+ limit: 60,
33
+ windowSeconds: 5 * 60
34
+ }
23
35
  }
24
36
  };
25
37
  function routeName(request) {
26
38
  return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';
27
39
  }
28
40
  async function enforceRateLimit(params) {
41
+ const policy = policies[params.action][params.scope];
42
+ if (!policy) return;
29
43
  try {
30
44
  await checkAuthRateLimit({
31
45
  action: params.action,
32
46
  scope: params.scope,
33
47
  identifier: params.identifier,
34
- ...policies[params.action][params.scope]
48
+ ...policy
35
49
  });
36
50
  } catch (error) {
37
51
  if (error instanceof AuthRateLimitExceededError) {
@@ -97,5 +111,16 @@ export function createAuthRateLimitPreHandler(action) {
97
111
  });
98
112
  };
99
113
  }
114
+ export function createAuthIpRateLimitPreHandler(action) {
115
+ return async (request, reply)=>{
116
+ await enforceRateLimit({
117
+ request,
118
+ reply,
119
+ action,
120
+ scope: 'ip',
121
+ identifier: request.ip
122
+ });
123
+ };
124
+ }
100
125
 
101
126
  //# sourceMappingURL=rate-limit.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {\n type AuthRateLimitAction,\n AuthRateLimitExceededError,\n type AuthRateLimitPolicy,\n type AuthRateLimitScope,\n AuthRateLimitUnavailableError,\n checkAuthRateLimit,\n} from '#core/rate-limit.js';\n\nconst policies: Record<AuthRateLimitAction, Record<AuthRateLimitScope, AuthRateLimitPolicy>> = {\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};\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 try {\n await checkAuthRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n ...policies[params.action][params.scope],\n });\n } catch (error) {\n if (error instanceof AuthRateLimitExceededError) {\n params.request.log.warn(\n {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n retryAfterSeconds: error.retryAfterSeconds,\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n 'Auth rate limit blocked request',\n );\n params.reply.header('Retry-After', String(error.retryAfterSeconds));\n throw new ClientError('Rate limit exceeded', 'rate-limited', {\n status: 429,\n details: {retry_after_seconds: error.retryAfterSeconds},\n data: {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n cause: error,\n });\n }\n\n if (error instanceof AuthRateLimitUnavailableError) {\n params.request.log.error(\n {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n err: error,\n },\n 'Auth rate limiter unavailable',\n );\n throw new ClientError(\n 'Authentication rate limiter unavailable',\n 'auth-rate-limit-unavailable',\n {\n status: 503,\n data: {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n cause: error,\n },\n );\n }\n\n throw error;\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"],"names":["ClientError","AuthRateLimitExceededError","AuthRateLimitUnavailableError","checkAuthRateLimit","policies","login","ip","limit","windowSeconds","email","routeName","request","routeOptions","url","split","enforceRateLimit","params","action","scope","identifier","error","log","warn","route","retryAfterSeconds","identifierHmacPrefix","reply","header","String","status","details","retry_after_seconds","data","cause","err","createAuthRateLimitPreHandler","body"],"mappings":"AAAA,SAAQA,WAAW,QAA+C,wBAAwB;AAC1F,SAEEC,0BAA0B,EAG1BC,6BAA6B,EAC7BC,kBAAkB,QACb,sBAAsB;AAE7B,MAAMC,WAAyF;IAC7FC,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;AACF;AAMA,SAASE,UAAUC,OAAuB;IACxC,OAAOA,QAAQC,YAAY,CAACC,GAAG,IAAIF,QAAQE,GAAG,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI;AAClE;AAEA,eAAeC,iBAAiBC,MAM/B;IACC,IAAI;QACF,MAAMb,mBAAmB;YACvBc,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBC,YAAYH,OAAOG,UAAU;YAC7B,GAAGf,QAAQ,CAACY,OAAOC,MAAM,CAAC,CAACD,OAAOE,KAAK,CAAC;QAC1C;IACF,EAAE,OAAOE,OAAO;QACd,IAAIA,iBAAiBnB,4BAA4B;YAC/Ce,OAAOL,OAAO,CAACU,GAAG,CAACC,IAAI,CACrB;gBACEL,QAAQG,MAAMH,MAAM;gBACpBC,OAAOE,MAAMF,KAAK;gBAClBK,OAAOb,UAAUM,OAAOL,OAAO;gBAC/Ba,mBAAmBJ,MAAMI,iBAAiB;gBAC1CC,sBAAsBL,MAAMK,oBAAoB;YAClD,GACA;YAEFT,OAAOU,KAAK,CAACC,MAAM,CAAC,eAAeC,OAAOR,MAAMI,iBAAiB;YACjE,MAAM,IAAIxB,YAAY,uBAAuB,gBAAgB;gBAC3D6B,QAAQ;gBACRC,SAAS;oBAACC,qBAAqBX,MAAMI,iBAAiB;gBAAA;gBACtDQ,MAAM;oBACJf,QAAQG,MAAMH,MAAM;oBACpBC,OAAOE,MAAMF,KAAK;oBAClBK,OAAOb,UAAUM,OAAOL,OAAO;oBAC/Bc,sBAAsBL,MAAMK,oBAAoB;gBAClD;gBACAQ,OAAOb;YACT;QACF;QAEA,IAAIA,iBAAiBlB,+BAA+B;YAClDc,OAAOL,OAAO,CAACU,GAAG,CAACD,KAAK,CACtB;gBACEH,QAAQG,MAAMH,MAAM;gBACpBC,OAAOE,MAAMF,KAAK;gBAClBK,OAAOb,UAAUM,OAAOL,OAAO;gBAC/Bc,sBAAsBL,MAAMK,oBAAoB;gBAChDS,KAAKd;YACP,GACA;YAEF,MAAM,IAAIpB,YACR,2CACA,+BACA;gBACE6B,QAAQ;gBACRG,MAAM;oBACJf,QAAQG,MAAMH,MAAM;oBACpBC,OAAOE,MAAMF,KAAK;oBAClBK,OAAOb,UAAUM,OAAOL,OAAO;oBAC/Bc,sBAAsBL,MAAMK,oBAAoB;gBAClD;gBACAQ,OAAOb;YACT;QAEJ;QAEA,MAAMA;IACR;AACF;AAEA,OAAO,SAASe,8BAA8BlB,MAA2B;IACvE,OAAO,OAAON,SAA4Ce;QACxD,MAAMX,iBAAiB;YACrBJ;YACAe;YACAT;YACAC,OAAO;YACPC,YAAYR,QAAQL,EAAE;QACxB;QAEA,MAAMS,iBAAiB;YACrBJ;YACAe;YACAT;YACAC,OAAO;YACPC,YAAYR,QAAQyB,IAAI,CAAC3B,KAAK;QAChC;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/routes/rate-limit.ts"],"sourcesContent":["import {ClientError, type FastifyReply, type FastifyRequest} from '@shipfox/node-fastify';\nimport {\n type AuthRateLimitAction,\n AuthRateLimitExceededError,\n type AuthRateLimitPolicy,\n type AuthRateLimitScope,\n AuthRateLimitUnavailableError,\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 lookup: {\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 try {\n await checkAuthRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n ...policy,\n });\n } catch (error) {\n if (error instanceof AuthRateLimitExceededError) {\n params.request.log.warn(\n {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n retryAfterSeconds: error.retryAfterSeconds,\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n 'Auth rate limit blocked request',\n );\n params.reply.header('Retry-After', String(error.retryAfterSeconds));\n throw new ClientError('Rate limit exceeded', 'rate-limited', {\n status: 429,\n details: {retry_after_seconds: error.retryAfterSeconds},\n data: {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n cause: error,\n });\n }\n\n if (error instanceof AuthRateLimitUnavailableError) {\n params.request.log.error(\n {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n err: error,\n },\n 'Auth rate limiter unavailable',\n );\n throw new ClientError(\n 'Authentication rate limiter unavailable',\n 'auth-rate-limit-unavailable',\n {\n status: 503,\n data: {\n action: error.action,\n scope: error.scope,\n route: routeName(params.request),\n identifierHmacPrefix: error.identifierHmacPrefix,\n },\n cause: error,\n },\n );\n }\n\n throw error;\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"],"names":["ClientError","AuthRateLimitExceededError","AuthRateLimitUnavailableError","checkAuthRateLimit","policies","login","ip","limit","windowSeconds","email","bootstrap","lookup","routeName","request","routeOptions","url","split","enforceRateLimit","params","policy","action","scope","identifier","error","log","warn","route","retryAfterSeconds","identifierHmacPrefix","reply","header","String","status","details","retry_after_seconds","data","cause","err","createAuthRateLimitPreHandler","body","createAuthIpRateLimitPreHandler"],"mappings":"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"}