@shipfox/api-auth 15.0.0 → 17.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 (101) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +41 -0
  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 +24 -2
  9. package/dist/core/administration.d.ts.map +1 -1
  10. package/dist/core/administration.js +126 -3
  11. package/dist/core/administration.js.map +1 -1
  12. package/dist/core/auth.d.ts +31 -0
  13. package/dist/core/auth.d.ts.map +1 -1
  14. package/dist/core/auth.js +70 -2
  15. package/dist/core/auth.js.map +1 -1
  16. package/dist/core/errors.d.ts +15 -0
  17. package/dist/core/errors.d.ts.map +1 -1
  18. package/dist/core/errors.js +30 -0
  19. package/dist/core/errors.js.map +1 -1
  20. package/dist/core/jwt.d.ts +2 -0
  21. package/dist/core/jwt.d.ts.map +1 -1
  22. package/dist/core/jwt.js +22 -1
  23. package/dist/core/jwt.js.map +1 -1
  24. package/dist/db/admin-command.d.ts +12 -4
  25. package/dist/db/admin-command.d.ts.map +1 -1
  26. package/dist/db/admin-command.js +10 -1
  27. package/dist/db/admin-command.js.map +1 -1
  28. package/dist/db/admin-grants.d.ts.map +1 -1
  29. package/dist/db/admin-grants.js +4 -1
  30. package/dist/db/admin-grants.js.map +1 -1
  31. package/dist/db/admin-user-moderation.d.ts +3 -0
  32. package/dist/db/admin-user-moderation.d.ts.map +1 -1
  33. package/dist/db/admin-user-moderation.js +6 -2
  34. package/dist/db/admin-user-moderation.js.map +1 -1
  35. package/dist/db/impersonation.d.ts +55 -0
  36. package/dist/db/impersonation.d.ts.map +1 -0
  37. package/dist/db/impersonation.js +212 -0
  38. package/dist/db/impersonation.js.map +1 -0
  39. package/dist/db/schema/admin-command-results.d.ts +16 -0
  40. package/dist/db/schema/admin-command-results.d.ts.map +1 -1
  41. package/dist/db/schema/admin-command-results.js.map +1 -1
  42. package/dist/index.d.ts +5 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +5 -5
  45. package/dist/index.js.map +1 -1
  46. package/dist/metrics/index.d.ts +1 -1
  47. package/dist/metrics/index.d.ts.map +1 -1
  48. package/dist/metrics/index.js +1 -1
  49. package/dist/metrics/index.js.map +1 -1
  50. package/dist/metrics/instance.d.ts +4 -2
  51. package/dist/metrics/instance.d.ts.map +1 -1
  52. package/dist/metrics/instance.js +8 -0
  53. package/dist/metrics/instance.js.map +1 -1
  54. package/dist/presentation/auth/bearer-token-auth.d.ts +2 -2
  55. package/dist/presentation/auth/bearer-token-auth.d.ts.map +1 -1
  56. package/dist/presentation/auth/bearer-token-auth.js +2 -2
  57. package/dist/presentation/auth/bearer-token-auth.js.map +1 -1
  58. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  59. package/dist/presentation/auth/jwt-auth.js +26 -2
  60. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  61. package/dist/presentation/routes/administration.d.ts +2 -1
  62. package/dist/presentation/routes/administration.d.ts.map +1 -1
  63. package/dist/presentation/routes/administration.js +127 -19
  64. package/dist/presentation/routes/administration.js.map +1 -1
  65. package/dist/presentation/routes/rate-limit.d.ts +8 -0
  66. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  67. package/dist/presentation/routes/rate-limit.js +44 -0
  68. package/dist/presentation/routes/rate-limit.js.map +1 -1
  69. package/dist/presentation/routes/session/me.d.ts.map +1 -1
  70. package/dist/presentation/routes/session/me.js +2 -1
  71. package/dist/presentation/routes/session/me.js.map +1 -1
  72. package/dist/tsconfig.test.tsbuildinfo +1 -1
  73. package/package.json +5 -5
  74. package/src/config.ts +4 -0
  75. package/src/core/administration.ts +172 -1
  76. package/src/core/auth.test.ts +79 -0
  77. package/src/core/auth.ts +98 -1
  78. package/src/core/errors.ts +35 -0
  79. package/src/core/jwt.test.ts +92 -0
  80. package/src/core/jwt.ts +38 -9
  81. package/src/db/admin-command.ts +35 -6
  82. package/src/db/admin-grants.ts +5 -1
  83. package/src/db/admin-user-moderation.ts +8 -2
  84. package/src/db/impersonation.ts +323 -0
  85. package/src/db/schema/admin-command-results.ts +18 -0
  86. package/src/index.test.ts +1 -1
  87. package/src/index.ts +12 -3
  88. package/src/metrics/index.ts +2 -0
  89. package/src/metrics/instance.ts +13 -2
  90. package/src/presentation/auth/bearer-token-auth.ts +4 -4
  91. package/src/presentation/auth/jwt-auth.test.ts +191 -0
  92. package/src/presentation/auth/jwt-auth.ts +19 -1
  93. package/src/presentation/routes/administration.test.ts +828 -3
  94. package/src/presentation/routes/administration.ts +130 -11
  95. package/src/presentation/routes/rate-limit.ts +40 -0
  96. package/src/presentation/routes/session/login.test.ts +5 -0
  97. package/src/presentation/routes/session/me.test.ts +24 -0
  98. package/src/presentation/routes/session/me.ts +1 -0
  99. package/src/presentation/routes/session/refresh.test.ts +11 -0
  100. package/test/routes.ts +43 -16
  101. package/tsconfig.build.tsbuildinfo +1 -1
@@ -22,10 +22,26 @@ export interface StoredAdminUserModerationResult {
22
22
  correlationId: string;
23
23
  sessionsRevoked: number;
24
24
  }
25
+ /**
26
+ * The stored impersonation command result: fingerprint-only, never a bearer
27
+ * token or a claims snapshot. Each entry is the SHA-256 of one token issued
28
+ * under the idempotency key, so a token recovered from a log or proxy capture
29
+ * can be matched back to the command, its actor, and its reason. A replay
30
+ * issues a token with different signature bytes and appends its fingerprint.
31
+ * `expiresAt` is canonical for replays: they re-sign with the original
32
+ * expiry and never extend the window.
33
+ */
34
+ export interface StoredImpersonationResult {
35
+ targetUserId: string;
36
+ expiresAt: string;
37
+ tokenFingerprints: string[];
38
+ }
25
39
  export type StoredAdminCommandResult = {
26
40
  grant: StoredAdminGrant;
27
41
  } | {
28
42
  userModeration: StoredAdminUserModerationResult;
43
+ } | {
44
+ impersonation: StoredImpersonationResult;
29
45
  };
30
46
  export declare const adminCommandResults: import("drizzle-orm/pg-core").PgTableWithColumns<{
31
47
  name: "admin_command_results";
@@ -1 +1 @@
1
- {"version":3,"file":"admin-command-results.d.ts","sourceRoot":"","sources":["../../../src/db/schema/admin-command-results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAIvD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,8BAA8B,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,KAAK,EAAE,gBAAgB,CAAC;CACzB,GACD;IACE,cAAc,EAAE,+BAA+B,CAAC;CACjD,CAAC;AAEN,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC"}
1
+ {"version":3,"file":"admin-command-results.d.ts","sourceRoot":"","sources":["../../../src/db/schema/admin-command-results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAIvD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,8BAA8B,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,KAAK,EAAE,gBAAgB,CAAC;CACzB,GACD;IACE,cAAc,EAAE,+BAA+B,CAAC;CACjD,GACD;IACE,aAAa,EAAE,yBAAyB,CAAC;CAC1C,CAAC;AAEN,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/admin-command-results.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {jsonb, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {UserStatus} from '#core/entities/user.js';\nimport {pgTable} from './common.js';\nimport {users} from './users.js';\n\nexport interface StoredAdminGrant {\n id: string;\n userId: string;\n role: AdminRole;\n revokedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface StoredAdministratorUserSummary {\n id: string;\n email: string;\n name: string | null;\n emailVerifiedAt: string | null;\n status: UserStatus;\n createdAt: string;\n adminRole: AdminRole | null;\n}\n\nexport interface StoredAdminUserModerationResult {\n user: StoredAdministratorUserSummary;\n correlationId: string;\n sessionsRevoked: number;\n}\n\nexport type StoredAdminCommandResult =\n | {\n grant: StoredAdminGrant;\n }\n | {\n userModeration: StoredAdminUserModerationResult;\n };\n\nexport const adminCommandResults = pgTable(\n 'admin_command_results',\n {\n id: uuidv7PrimaryKey(),\n actorId: uuid('actor_id')\n .notNull()\n .references(() => users.id, {onDelete: 'cascade'}),\n idempotencyKeyFingerprint: text('idempotency_key_fingerprint').notNull(),\n command: text('command').notNull(),\n requestFingerprint: text('request_fingerprint').notNull(),\n result: jsonb('result').$type<StoredAdminCommandResult>().notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('auth_admin_command_results_actor_key_unique').on(\n table.actorId,\n table.idempotencyKeyFingerprint,\n ),\n ],\n);\n\nexport type AdminCommandResultDb = typeof adminCommandResults.$inferSelect;\n"],"names":["uuidv7PrimaryKey","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","users","adminCommandResults","id","actorId","notNull","references","onDelete","idempotencyKeyFingerprint","command","requestFingerprint","result","$type","createdAt","withTimezone","defaultNow","table","on"],"mappings":"AACA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,KAAK,QAAO,aAAa;AAmCjC,OAAO,MAAMC,sBAAsBF,QACjC,yBACA;IACEG,IAAIT;IACJU,SAASL,KAAK,YACXM,OAAO,GACPC,UAAU,CAAC,IAAML,MAAME,EAAE,EAAE;QAACI,UAAU;IAAS;IAClDC,2BAA2BZ,KAAK,+BAA+BS,OAAO;IACtEI,SAASb,KAAK,WAAWS,OAAO;IAChCK,oBAAoBd,KAAK,uBAAuBS,OAAO;IACvDM,QAAQhB,MAAM,UAAUiB,KAAK,GAA6BP,OAAO;IACjEQ,WAAWhB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGT,OAAO,GAAGU,UAAU;AAC/E,GACA,CAACC,QAAU;QACTlB,YAAY,+CAA+CmB,EAAE,CAC3DD,MAAMZ,OAAO,EACbY,MAAMR,yBAAyB;KAElC,EACD"}
1
+ {"version":3,"sources":["../../../src/db/schema/admin-command-results.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {jsonb, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {UserStatus} from '#core/entities/user.js';\nimport {pgTable} from './common.js';\nimport {users} from './users.js';\n\nexport interface StoredAdminGrant {\n id: string;\n userId: string;\n role: AdminRole;\n revokedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface StoredAdministratorUserSummary {\n id: string;\n email: string;\n name: string | null;\n emailVerifiedAt: string | null;\n status: UserStatus;\n createdAt: string;\n adminRole: AdminRole | null;\n}\n\nexport interface StoredAdminUserModerationResult {\n user: StoredAdministratorUserSummary;\n correlationId: string;\n sessionsRevoked: number;\n}\n\n/**\n * The stored impersonation command result: fingerprint-only, never a bearer\n * token or a claims snapshot. Each entry is the SHA-256 of one token issued\n * under the idempotency key, so a token recovered from a log or proxy capture\n * can be matched back to the command, its actor, and its reason. A replay\n * issues a token with different signature bytes and appends its fingerprint.\n * `expiresAt` is canonical for replays: they re-sign with the original\n * expiry and never extend the window.\n */\nexport interface StoredImpersonationResult {\n targetUserId: string;\n expiresAt: string;\n tokenFingerprints: string[];\n}\n\nexport type StoredAdminCommandResult =\n | {\n grant: StoredAdminGrant;\n }\n | {\n userModeration: StoredAdminUserModerationResult;\n }\n | {\n impersonation: StoredImpersonationResult;\n };\n\nexport const adminCommandResults = pgTable(\n 'admin_command_results',\n {\n id: uuidv7PrimaryKey(),\n actorId: uuid('actor_id')\n .notNull()\n .references(() => users.id, {onDelete: 'cascade'}),\n idempotencyKeyFingerprint: text('idempotency_key_fingerprint').notNull(),\n command: text('command').notNull(),\n requestFingerprint: text('request_fingerprint').notNull(),\n result: jsonb('result').$type<StoredAdminCommandResult>().notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('auth_admin_command_results_actor_key_unique').on(\n table.actorId,\n table.idempotencyKeyFingerprint,\n ),\n ],\n);\n\nexport type AdminCommandResultDb = typeof adminCommandResults.$inferSelect;\n"],"names":["uuidv7PrimaryKey","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","users","adminCommandResults","id","actorId","notNull","references","onDelete","idempotencyKeyFingerprint","command","requestFingerprint","result","$type","createdAt","withTimezone","defaultNow","table","on"],"mappings":"AACA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,KAAK,QAAO,aAAa;AAqDjC,OAAO,MAAMC,sBAAsBF,QACjC,yBACA;IACEG,IAAIT;IACJU,SAASL,KAAK,YACXM,OAAO,GACPC,UAAU,CAAC,IAAML,MAAME,EAAE,EAAE;QAACI,UAAU;IAAS;IAClDC,2BAA2BZ,KAAK,+BAA+BS,OAAO;IACtEI,SAASb,KAAK,WAAWS,OAAO;IAChCK,oBAAoBd,KAAK,uBAAuBS,OAAO;IACvDM,QAAQhB,MAAM,UAAUiB,KAAK,GAA6BP,OAAO;IACjEQ,WAAWhB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGT,OAAO,GAAGU,UAAU;AAC/E,GACA,CAACC,QAAU;QACTlB,YAAY,+CAA+CmB,EAAE,CAC3DD,MAAMZ,OAAO,EACbY,MAAMR,yBAAyB;KAElC,EACD"}
package/dist/index.d.ts CHANGED
@@ -2,18 +2,19 @@ import type { ShipfoxModule } from '@shipfox/node-module';
2
2
  import type { SignupPolicy } from '#core/ports.js';
3
3
  export type { AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims } from '@shipfox/api-auth-dto';
4
4
  export { ADMIN_ROLES, getCurrentAdminRole, hasMinimumAdminRole, highestAdminRole, requireAdminRole, revokeAdminGrant, } from '#core/admin-role.js';
5
- export { bootstrapFirstAdminOwner, grantAdministratorRole, reactivateAdministratorUser, revokeAdministratorGrant, revokeAdministratorUserSessions, suspendAdministratorUser, } from '#core/administration.js';
6
- export type { CreateSessionForUserError, CreateSessionForUserParams, CreateSessionForUserResult, ProvisionUserParams, } from '#core/auth.js';
7
- export { createSessionForUser, provisionUser } from '#core/auth.js';
5
+ export { bootstrapFirstAdminOwner, grantAdministratorRole, impersonateUser, reactivateAdministratorUser, revokeAdministratorGrant, revokeAdministratorUserSessions, suspendAdministratorUser, } from '#core/administration.js';
6
+ export type { CreateImpersonatedSessionTokenParams, CreateImpersonatedSessionTokenResult, CreateSessionForUserError, CreateSessionForUserParams, CreateSessionForUserResult, ProvisionUserParams, } from '#core/auth.js';
7
+ export { createImpersonatedSessionToken, createSessionForUser, provisionUser } from '#core/auth.js';
8
8
  export type { EmailOwner, FindUserByEmailParams } from '#core/email-owner.js';
9
9
  export { findUserByEmail } from '#core/email-owner.js';
10
10
  export type { AdminGrant } from '#core/entities/admin-grant.js';
11
11
  export type { User, UserStatus } from '#core/entities/user.js';
12
- export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError, } from '#core/errors.js';
12
+ export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, CannotImpersonateAdministratorError, CannotImpersonateSelfError, EmailNotVerifiedError, ImpersonationDisabledError, ImpersonationExpiredError, ImpersonationTargetNotActiveError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError, } from '#core/errors.js';
13
13
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken, } from '#core/job-lease-token.js';
14
14
  export type { SignupDenialMessageFormat, SignupPolicy } from '#core/ports.js';
15
15
  export { issueRunnerSessionToken, verifyRunnerSessionToken, } from '#core/runner-session-token.js';
16
16
  export { createEnvironmentSignupPolicy, DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE, } from '#core/signup-policy.js';
17
+ export type { ImpersonationResult } from '#db/impersonation.js';
17
18
  export { type AuthenticatedSessionContext, createJwtAuthMethod, getAuthenticatedSessionContext, type RefreshSessionId, type UserId, } from '#presentation/auth/jwt-auth.js';
18
19
  export { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.js';
19
20
  export { authCookiePlugin, clearRefreshTokenCookie, getRefreshTokenCookie, setRefreshTokenCookie, } from '#presentation/auth/refresh-cookie.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAqBjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,2BAA2B,EAC3B,wBAAwB,EACxB,+BAA+B,EAC/B,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,yBAAyB,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAiBzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAqBjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,eAAe,EACf,2BAA2B,EAC3B,wBAAwB,EACxB,+BAA+B,EAC/B,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,oCAAoC,EACpC,oCAAoC,EACpC,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,8BAA8B,EAAE,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClG,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,8BAA8B,EAC9B,mCAAmC,EACnC,0BAA0B,EAC1B,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,iCAAiC,EACjC,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,yBAAyB,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAiBzC"}
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.
11
11
  import { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
12
12
  import { createAuthE2eRoutes } from '#presentation/e2eRoutes/index.js';
13
13
  import { createAuthInterModulePresentation } from '#presentation/inter-module.js';
14
- import { administrationBootstrapRoutes, administrationRoutes, administrationUserRoutes } from '#presentation/routes/administration.js';
14
+ import { administrationBootstrapRoutes, administrationRoutes, createAdministrationUserRoutes } from '#presentation/routes/administration.js';
15
15
  import { buildAuthRoutes } from '#presentation/routes/index.js';
16
16
  import { onPasswordResetSendRequested } from '#presentation/subscribers/index.js';
17
17
  import { passwordLoginMethods } from './login-methods.js';
@@ -20,10 +20,10 @@ const authPublisherEventSchemas = {
20
20
  ...administrationActionEventSchemas
21
21
  };
22
22
  export { ADMIN_ROLES, getCurrentAdminRole, hasMinimumAdminRole, highestAdminRole, requireAdminRole, revokeAdminGrant } from '#core/admin-role.js';
23
- export { bootstrapFirstAdminOwner, grantAdministratorRole, reactivateAdministratorUser, revokeAdministratorGrant, revokeAdministratorUserSessions, suspendAdministratorUser } from '#core/administration.js';
24
- export { createSessionForUser, provisionUser } from '#core/auth.js';
23
+ export { bootstrapFirstAdminOwner, grantAdministratorRole, impersonateUser, reactivateAdministratorUser, revokeAdministratorGrant, revokeAdministratorUserSessions, suspendAdministratorUser } from '#core/administration.js';
24
+ export { createImpersonatedSessionToken, createSessionForUser, provisionUser } from '#core/auth.js';
25
25
  export { findUserByEmail } from '#core/email-owner.js';
26
- export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
26
+ export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, CannotImpersonateAdministratorError, CannotImpersonateSelfError, EmailNotVerifiedError, ImpersonationDisabledError, ImpersonationExpiredError, ImpersonationTargetNotActiveError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
27
27
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken } from '#core/job-lease-token.js';
28
28
  export { issueRunnerSessionToken, verifyRunnerSessionToken } from '#core/runner-session-token.js';
29
29
  export { createEnvironmentSignupPolicy, DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE } from '#core/signup-policy.js';
@@ -50,7 +50,7 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
50
50
  buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),
51
51
  administrationBootstrapRoutes,
52
52
  administrationRoutes,
53
- administrationUserRoutes
53
+ ...createAdministrationUserRoutes(workspaces)
54
54
  ],
55
55
  e2eRoutes: [
56
56
  createAuthE2eRoutes(workspaces)
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 {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 administrationBootstrapRoutes,\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 reactivateAdministratorUser,\n revokeAdministratorGrant,\n revokeAdministratorUserSessions,\n suspendAdministratorUser,\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 {SignupDenialMessageFormat, 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 administrationBootstrapRoutes,\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","administrationBootstrapRoutes","administrationRoutes","administrationUserRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","reactivateAdministratorUser","revokeAdministratorGrant","revokeAdministratorUserSessions","suspendAdministratorUser","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,6BAA6B,EAC7BC,oBAAoB,EACpBC,wBAAwB,QACnB,yCAAyC;AAChD,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGnB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEmB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,2BAA2B,EAC3BC,wBAAwB,EACxBC,+BAA+B,EAC/BC,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,SACE/C,6BAA6B,EAC7BgD,kCAAkC,QAC7B,yBAAyB;AAChC,SAEE5C,mBAAmB,EACnB6C,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQ5C,0BAA0B,QAAO,yCAAyC;AAClF,SACE6C,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQ/C,6BAA6B,QAAO,4CAA4C;AAExF,MAAMgD,aAAaxD;AAOnB,OAAO,SAASyD,iBAAiB,EAC/BC,UAAU,EACVC,eAAezD,+BAA+B,EACtB;IACxB,OAAO;QACL0D,MAAM;QACNC,UAAU;YAAC1D;YAAIC;YAAgB0D,mBAAmB;QAAM;QACxDC,MAAM;YAACzD;YAAuBC;YAA8BC;SAAgC;QAC5FwD,cAAchD,qBAAqBf,OAAOgE,qBAAqB;QAC/DC,QAAQ;YACNpD,gBAAgBb,OAAOgE,qBAAqB,EAAEP,YAAYC;YAC1DhD;YACAC;YACAC;SACD;QACDsD,WAAW;YAAC1D,oBAAoBiD;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAOhE;gBAAYF;gBAAImE,cAAcrD;YAAyB;SAAE;QAC5FsD,aAAa;YAACf,WAAW3D,oCAAoCkB;SAA8B;QAC3FyD,0BAA0B;YAAC9D;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 administrationBootstrapRoutes,\n administrationRoutes,\n createAdministrationUserRoutes,\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 impersonateUser,\n reactivateAdministratorUser,\n revokeAdministratorGrant,\n revokeAdministratorUserSessions,\n suspendAdministratorUser,\n} from '#core/administration.js';\nexport type {\n CreateImpersonatedSessionTokenParams,\n CreateImpersonatedSessionTokenResult,\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createImpersonatedSessionToken, 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 CannotImpersonateAdministratorError,\n CannotImpersonateSelfError,\n EmailNotVerifiedError,\n ImpersonationDisabledError,\n ImpersonationExpiredError,\n ImpersonationTargetNotActiveError,\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 {SignupDenialMessageFormat, 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 type {ImpersonationResult} from '#db/impersonation.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 administrationBootstrapRoutes,\n administrationRoutes,\n ...createAdministrationUserRoutes(workspaces),\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","administrationBootstrapRoutes","administrationRoutes","createAdministrationUserRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","impersonateUser","reactivateAdministratorUser","revokeAdministratorGrant","revokeAdministratorUserSessions","suspendAdministratorUser","createImpersonatedSessionToken","createSessionForUser","provisionUser","findUserByEmail","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","AuthDependencyUnavailableError","CannotImpersonateAdministratorError","CannotImpersonateSelfError","EmailNotVerifiedError","ImpersonationDisabledError","ImpersonationExpiredError","ImpersonationTargetNotActiveError","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,6BAA6B,EAC7BC,oBAAoB,EACpBC,8BAA8B,QACzB,yCAAyC;AAChD,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGnB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEmB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,eAAe,EACfC,2BAA2B,EAC3BC,wBAAwB,EACxBC,+BAA+B,EAC/BC,wBAAwB,QACnB,0BAA0B;AASjC,SAAQC,8BAA8B,EAAEC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElG,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,mCAAmC,EACnCC,0BAA0B,EAC1BC,qBAAqB,EACrBC,0BAA0B,EAC1BC,yBAAyB,EACzBC,iCAAiC,EACjCC,+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,SACEtD,6BAA6B,EAC7BuD,kCAAkC,QAC7B,yBAAyB;AAEhC,SAEEnD,mBAAmB,EACnBoD,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQnD,0BAA0B,QAAO,yCAAyC;AAClF,SACEoD,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQtD,6BAA6B,QAAO,4CAA4C;AAExF,MAAMuD,aAAa/D;AAOnB,OAAO,SAASgE,iBAAiB,EAC/BC,UAAU,EACVC,eAAehE,+BAA+B,EACtB;IACxB,OAAO;QACLiE,MAAM;QACNC,UAAU;YAACjE;YAAIC;YAAgBiE,mBAAmB;QAAM;QACxDC,MAAM;YAAChE;YAAuBC;YAA8BC;SAAgC;QAC5F+D,cAAcvD,qBAAqBf,OAAOuE,qBAAqB;QAC/DC,QAAQ;YACN3D,gBAAgBb,OAAOuE,qBAAqB,EAAEP,YAAYC;YAC1DvD;YACAC;eACGC,+BAA+BoD;SACnC;QACDS,WAAW;YAACjE,oBAAoBwD;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAOvE;gBAAYF;gBAAI0E,cAAc5D;YAAyB;SAAE;QAC5F6D,aAAa;YAACf,WAAWlE,oCAAoCkB;SAA8B;QAC3FgE,0BAA0B;YAACrE;SAAoC;IACjE;AACF"}
@@ -1,2 +1,2 @@
1
- export { type AuthRateLimitAction, type AuthRateLimitOutcome, type AuthRateLimitScope, type AuthTokenRefreshOutcome, type AuthTokenType, type AuthTokenVerificationOutcome, recordAuthRateLimitCheck, recordAuthRateLimitPruneFailure, recordTokenIssued, recordTokenRefreshed, recordTokenVerified, } from './instance.js';
1
+ export { type AuthImpersonationOutcome, type AuthRateLimitAction, type AuthRateLimitOutcome, type AuthRateLimitScope, type AuthTokenRefreshOutcome, type AuthTokenType, type AuthTokenVerificationOutcome, recordAuthRateLimitCheck, recordAuthRateLimitPruneFailure, recordImpersonationOutcome, recordTokenIssued, recordTokenRefreshed, recordTokenVerified, } from './instance.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,4BAA4B,EACjC,wBAAwB,EACxB,+BAA+B,EAC/B,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,4BAA4B,EACjC,wBAAwB,EACxB,+BAA+B,EAC/B,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,eAAe,CAAC"}
@@ -1,3 +1,3 @@
1
- export { recordAuthRateLimitCheck, recordAuthRateLimitPruneFailure, recordTokenIssued, recordTokenRefreshed, recordTokenVerified } from './instance.js';
1
+ export { recordAuthRateLimitCheck, recordAuthRateLimitPruneFailure, recordImpersonationOutcome, recordTokenIssued, recordTokenRefreshed, recordTokenVerified } from './instance.js';
2
2
 
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/metrics/index.ts"],"sourcesContent":["export {\n type AuthRateLimitAction,\n type AuthRateLimitOutcome,\n type AuthRateLimitScope,\n type AuthTokenRefreshOutcome,\n type AuthTokenType,\n type AuthTokenVerificationOutcome,\n recordAuthRateLimitCheck,\n recordAuthRateLimitPruneFailure,\n recordTokenIssued,\n recordTokenRefreshed,\n recordTokenVerified,\n} from './instance.js';\n"],"names":["recordAuthRateLimitCheck","recordAuthRateLimitPruneFailure","recordTokenIssued","recordTokenRefreshed","recordTokenVerified"],"mappings":"AAAA,SAOEA,wBAAwB,EACxBC,+BAA+B,EAC/BC,iBAAiB,EACjBC,oBAAoB,EACpBC,mBAAmB,QACd,gBAAgB"}
1
+ {"version":3,"sources":["../../src/metrics/index.ts"],"sourcesContent":["export {\n type AuthImpersonationOutcome,\n type AuthRateLimitAction,\n type AuthRateLimitOutcome,\n type AuthRateLimitScope,\n type AuthTokenRefreshOutcome,\n type AuthTokenType,\n type AuthTokenVerificationOutcome,\n recordAuthRateLimitCheck,\n recordAuthRateLimitPruneFailure,\n recordImpersonationOutcome,\n recordTokenIssued,\n recordTokenRefreshed,\n recordTokenVerified,\n} from './instance.js';\n"],"names":["recordAuthRateLimitCheck","recordAuthRateLimitPruneFailure","recordImpersonationOutcome","recordTokenIssued","recordTokenRefreshed","recordTokenVerified"],"mappings":"AAAA,SAQEA,wBAAwB,EACxBC,+BAA+B,EAC/BC,0BAA0B,EAC1BC,iBAAiB,EACjBC,oBAAoB,EACpBC,mBAAmB,QACd,gBAAgB"}
@@ -1,9 +1,10 @@
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' | 'bootstrap' | 'bootstrap-state' | 'lookup';
5
- export type AuthRateLimitScope = 'ip' | 'email';
4
+ export type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'bootstrap-state' | 'lookup' | 'impersonate';
5
+ export type AuthRateLimitScope = 'ip' | 'email' | 'actor';
6
6
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
7
+ export type AuthImpersonationOutcome = 'succeeded' | 'failed';
7
8
  export declare function recordTokenIssued(tokenType: AuthTokenType): void;
8
9
  export declare function recordTokenVerified(tokenType: AuthTokenType, outcome: AuthTokenVerificationOutcome): void;
9
10
  export declare function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void;
@@ -13,4 +14,5 @@ export declare function recordAuthRateLimitCheck(params: {
13
14
  outcome: AuthRateLimitOutcome;
14
15
  }): void;
15
16
  export declare function recordAuthRateLimitPruneFailure(): void;
17
+ export declare function recordImpersonationOutcome(outcome: AuthImpersonationOutcome): void;
16
18
  //# sourceMappingURL=instance.d.ts.map
@@ -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,GAC3B,OAAO,GACP,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,QAAQ,CAAC;AACb,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,GAC3B,OAAO,GACP,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,QAAQ,GACR,aAAa,CAAC;AAClB,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AACzE,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,QAAQ,CAAC;AA2C9D,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;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAElF"}
@@ -15,6 +15,9 @@ const rateLimitCheckCount = meter.createCounter('auth_rate_limit_checks', {
15
15
  const rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {
16
16
  description: 'Authentication rate limit prune failures'
17
17
  });
18
+ const impersonationCommandCount = meter.createCounter('auth_impersonation_commands', {
19
+ description: 'Impersonation mint command attempts by outcome'
20
+ });
18
21
  function recordMetric(record) {
19
22
  try {
20
23
  record();
@@ -48,5 +51,10 @@ export function recordAuthRateLimitCheck(params) {
48
51
  export function recordAuthRateLimitPruneFailure() {
49
52
  recordMetric(()=>rateLimitPruneFailureCount.add(1));
50
53
  }
54
+ export function recordImpersonationOutcome(outcome) {
55
+ recordMetric(()=>impersonationCommandCount.add(1, {
56
+ outcome
57
+ }));
58
+ }
51
59
 
52
60
  //# sourceMappingURL=instance.js.map
@@ -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 =\n | 'login'\n | 'email-send'\n | 'bootstrap'\n | 'bootstrap-state'\n | '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;AAc5D,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 =\n | 'login'\n | 'email-send'\n | 'bootstrap'\n | 'bootstrap-state'\n | 'lookup'\n | 'impersonate';\nexport type AuthRateLimitScope = 'ip' | 'email' | 'actor';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\nexport type AuthImpersonationOutcome = 'succeeded' | 'failed';\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\nconst impersonationCommandCount = meter.createCounter<{outcome: AuthImpersonationOutcome}>(\n 'auth_impersonation_commands',\n {description: 'Impersonation mint command attempts by outcome'},\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\nexport function recordImpersonationOutcome(outcome: AuthImpersonationOutcome): void {\n recordMetric(() => impersonationCommandCount.add(1, {outcome}));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","impersonationCommandCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure","recordImpersonationOutcome"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAgB5D,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,MAAMK,4BAA4BT,MAAMG,aAAa,CACnD,+BACA;IAACC,aAAa;AAAgD;AAGhE,SAASM,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMR,iBAAiBY,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAML,mBAAmBS,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMJ,oBAAoBQ,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXH,oBAAoBO,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMF,2BAA2BM,GAAG,CAAC;AACpD;AAEA,OAAO,SAASU,2BAA2BP,OAAiC;IAC1EP,aAAa,IAAMD,0BAA0BK,GAAG,CAAC,GAAG;YAACG;QAAO;AAC9D"}
@@ -1,5 +1,5 @@
1
1
  import { type AuthMethod } from '@shipfox/node-fastify';
2
- import type { FastifyRequest } from 'fastify';
2
+ import type { FastifyReply, FastifyRequest } from 'fastify';
3
3
  interface UnauthorizedErrorParams {
4
4
  message: string;
5
5
  code: string;
@@ -9,7 +9,7 @@ interface BearerTokenAuthMethodOptions<TClaims> {
9
9
  verifyToken: (token: string) => Promise<TClaims | null>;
10
10
  isInvalidTokenError?: (error: unknown) => boolean;
11
11
  invalidTokenError: UnauthorizedErrorParams;
12
- setContext: (request: FastifyRequest, claims: TClaims) => void;
12
+ setContext: (request: FastifyRequest, claims: TClaims, reply?: FastifyReply) => void;
13
13
  }
14
14
  export declare function createBearerTokenAuthMethod<TClaims>(options: BearerTokenAuthMethodOptions<TClaims>): AuthMethod;
15
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"bearer-token-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/bearer-token-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,UAAU,EAAkC,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAE5C,UAAU,uBAAuB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,4BAA4B,CAAC,OAAO;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACxD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAClD,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAChE;AAOD,wBAAgB,2BAA2B,CAAC,OAAO,EACjD,OAAO,EAAE,4BAA4B,CAAC,OAAO,CAAC,GAC7C,UAAU,CAiBZ"}
1
+ {"version":3,"file":"bearer-token-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/bearer-token-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,UAAU,EAAkC,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,SAAS,CAAC;AAE1D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,4BAA4B,CAAC,OAAO;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACxD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAClD,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;CACtF;AAOD,wBAAgB,2BAA2B,CAAC,OAAO,EACjD,OAAO,EAAE,4BAA4B,CAAC,OAAO,CAAC,GAC7C,UAAU,CAiBZ"}
@@ -6,12 +6,12 @@ const missingBearerError = {
6
6
  export function createBearerTokenAuthMethod(options) {
7
7
  return {
8
8
  name: options.name,
9
- authenticate: async (request)=>{
9
+ authenticate: async (request, reply)=>{
10
10
  const token = extractBearerToken(request.headers.authorization);
11
11
  if (!token) throwUnauthorized(missingBearerError);
12
12
  const claims = await verifyBearerToken(token, options.verifyToken, options.isInvalidTokenError);
13
13
  if (!claims) throwUnauthorized(options.invalidTokenError);
14
- options.setContext(request, claims);
14
+ options.setContext(request, claims, reply);
15
15
  }
16
16
  };
17
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/auth/bearer-token-auth.ts"],"sourcesContent":["import {type AuthMethod, ClientError, extractBearerToken} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\n\ninterface UnauthorizedErrorParams {\n message: string;\n code: string;\n}\n\ninterface BearerTokenAuthMethodOptions<TClaims> {\n name: string;\n verifyToken: (token: string) => Promise<TClaims | null>;\n isInvalidTokenError?: (error: unknown) => boolean;\n invalidTokenError: UnauthorizedErrorParams;\n setContext: (request: FastifyRequest, claims: TClaims) => void;\n}\n\nconst missingBearerError: UnauthorizedErrorParams = {\n message: 'Missing or invalid Authorization header',\n code: 'unauthorized',\n};\n\nexport function createBearerTokenAuthMethod<TClaims>(\n options: BearerTokenAuthMethodOptions<TClaims>,\n): AuthMethod {\n return {\n name: options.name,\n authenticate: async (request) => {\n const token = extractBearerToken(request.headers.authorization);\n if (!token) throwUnauthorized(missingBearerError);\n\n const claims = await verifyBearerToken(\n token,\n options.verifyToken,\n options.isInvalidTokenError,\n );\n if (!claims) throwUnauthorized(options.invalidTokenError);\n\n options.setContext(request, claims);\n },\n };\n}\n\nasync function verifyBearerToken<TClaims>(\n token: string,\n verifyToken: (token: string) => Promise<TClaims | null>,\n isInvalidTokenError: ((error: unknown) => boolean) | undefined,\n): Promise<TClaims | null> {\n try {\n return await verifyToken(token);\n } catch (error) {\n if (isInvalidTokenError && !isInvalidTokenError(error)) throw error;\n return null;\n }\n}\n\nfunction throwUnauthorized(error: UnauthorizedErrorParams): never {\n throw new ClientError(error.message, error.code, {status: 401});\n}\n"],"names":["ClientError","extractBearerToken","missingBearerError","message","code","createBearerTokenAuthMethod","options","name","authenticate","request","token","headers","authorization","throwUnauthorized","claims","verifyBearerToken","verifyToken","isInvalidTokenError","invalidTokenError","setContext","error","status"],"mappings":"AAAA,SAAyBA,WAAW,EAAEC,kBAAkB,QAAO,wBAAwB;AAgBvF,MAAMC,qBAA8C;IAClDC,SAAS;IACTC,MAAM;AACR;AAEA,OAAO,SAASC,4BACdC,OAA8C;IAE9C,OAAO;QACLC,MAAMD,QAAQC,IAAI;QAClBC,cAAc,OAAOC;YACnB,MAAMC,QAAQT,mBAAmBQ,QAAQE,OAAO,CAACC,aAAa;YAC9D,IAAI,CAACF,OAAOG,kBAAkBX;YAE9B,MAAMY,SAAS,MAAMC,kBACnBL,OACAJ,QAAQU,WAAW,EACnBV,QAAQW,mBAAmB;YAE7B,IAAI,CAACH,QAAQD,kBAAkBP,QAAQY,iBAAiB;YAExDZ,QAAQa,UAAU,CAACV,SAASK;QAC9B;IACF;AACF;AAEA,eAAeC,kBACbL,KAAa,EACbM,WAAuD,EACvDC,mBAA8D;IAE9D,IAAI;QACF,OAAO,MAAMD,YAAYN;IAC3B,EAAE,OAAOU,OAAO;QACd,IAAIH,uBAAuB,CAACA,oBAAoBG,QAAQ,MAAMA;QAC9D,OAAO;IACT;AACF;AAEA,SAASP,kBAAkBO,KAA8B;IACvD,MAAM,IAAIpB,YAAYoB,MAAMjB,OAAO,EAAEiB,MAAMhB,IAAI,EAAE;QAACiB,QAAQ;IAAG;AAC/D"}
1
+ {"version":3,"sources":["../../../src/presentation/auth/bearer-token-auth.ts"],"sourcesContent":["import {type AuthMethod, ClientError, extractBearerToken} from '@shipfox/node-fastify';\nimport type {FastifyReply, FastifyRequest} from 'fastify';\n\ninterface UnauthorizedErrorParams {\n message: string;\n code: string;\n}\n\ninterface BearerTokenAuthMethodOptions<TClaims> {\n name: string;\n verifyToken: (token: string) => Promise<TClaims | null>;\n isInvalidTokenError?: (error: unknown) => boolean;\n invalidTokenError: UnauthorizedErrorParams;\n setContext: (request: FastifyRequest, claims: TClaims, reply?: FastifyReply) => void;\n}\n\nconst missingBearerError: UnauthorizedErrorParams = {\n message: 'Missing or invalid Authorization header',\n code: 'unauthorized',\n};\n\nexport function createBearerTokenAuthMethod<TClaims>(\n options: BearerTokenAuthMethodOptions<TClaims>,\n): AuthMethod {\n return {\n name: options.name,\n authenticate: async (request, reply) => {\n const token = extractBearerToken(request.headers.authorization);\n if (!token) throwUnauthorized(missingBearerError);\n\n const claims = await verifyBearerToken(\n token,\n options.verifyToken,\n options.isInvalidTokenError,\n );\n if (!claims) throwUnauthorized(options.invalidTokenError);\n\n options.setContext(request, claims, reply);\n },\n };\n}\n\nasync function verifyBearerToken<TClaims>(\n token: string,\n verifyToken: (token: string) => Promise<TClaims | null>,\n isInvalidTokenError: ((error: unknown) => boolean) | undefined,\n): Promise<TClaims | null> {\n try {\n return await verifyToken(token);\n } catch (error) {\n if (isInvalidTokenError && !isInvalidTokenError(error)) throw error;\n return null;\n }\n}\n\nfunction throwUnauthorized(error: UnauthorizedErrorParams): never {\n throw new ClientError(error.message, error.code, {status: 401});\n}\n"],"names":["ClientError","extractBearerToken","missingBearerError","message","code","createBearerTokenAuthMethod","options","name","authenticate","request","reply","token","headers","authorization","throwUnauthorized","claims","verifyBearerToken","verifyToken","isInvalidTokenError","invalidTokenError","setContext","error","status"],"mappings":"AAAA,SAAyBA,WAAW,EAAEC,kBAAkB,QAAO,wBAAwB;AAgBvF,MAAMC,qBAA8C;IAClDC,SAAS;IACTC,MAAM;AACR;AAEA,OAAO,SAASC,4BACdC,OAA8C;IAE9C,OAAO;QACLC,MAAMD,QAAQC,IAAI;QAClBC,cAAc,OAAOC,SAASC;YAC5B,MAAMC,QAAQV,mBAAmBQ,QAAQG,OAAO,CAACC,aAAa;YAC9D,IAAI,CAACF,OAAOG,kBAAkBZ;YAE9B,MAAMa,SAAS,MAAMC,kBACnBL,OACAL,QAAQW,WAAW,EACnBX,QAAQY,mBAAmB;YAE7B,IAAI,CAACH,QAAQD,kBAAkBR,QAAQa,iBAAiB;YAExDb,QAAQc,UAAU,CAACX,SAASM,QAAQL;QACtC;IACF;AACF;AAEA,eAAeM,kBACbL,KAAa,EACbM,WAAuD,EACvDC,mBAA8D;IAE9D,IAAI;QACF,OAAO,MAAMD,YAAYN;IAC3B,EAAE,OAAOU,OAAO;QACd,IAAIH,uBAAuB,CAACA,oBAAoBG,QAAQ,MAAMA;QAC9D,OAAO;IACT;AACF;AAEA,SAASP,kBAAkBO,KAA8B;IACvD,MAAM,IAAIrB,YAAYqB,MAAMlB,OAAO,EAAEkB,MAAMjB,IAAI,EAAE;QAACkB,QAAQ;IAAG;AAC/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAOjD,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEzD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAoBD,+EAA+E;AAC/E,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAOtC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAyBhD"}
1
+ {"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AAEnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAOjD,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEzD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAoBD,+EAA+E;AAC/E,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAOtC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CA0ChD"}
@@ -1,6 +1,7 @@
1
1
  import { AUTH_USER, buildUserContext, getUserContext, setUserContext } from '@shipfox/api-auth-context';
2
2
  import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
3
3
  import { ClientError } from '@shipfox/node-fastify';
4
+ import { enrichSpanWithMetadata } from '@shipfox/node-opentelemetry';
4
5
  import { verifyUserToken } from '#core/jwt.js';
5
6
  import { createBearerTokenAuthMethod } from './bearer-token-auth.js';
6
7
  const AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');
@@ -53,15 +54,38 @@ export function createJwtAuthMethod() {
53
54
  message: 'Invalid or expired token',
54
55
  code: 'unauthorized'
55
56
  },
56
- setContext: (request, claims)=>{
57
+ setContext: (request, claims, reply)=>{
57
58
  const clientContext = buildUserContext({
58
59
  userId: claims.sub,
59
60
  email: claims.email,
60
61
  name: claims.name ?? null,
61
- memberships: claims.memberships
62
+ memberships: claims.memberships,
63
+ impersonatorId: claims.impersonatorId
62
64
  });
63
65
  setUserContext(request, clientContext);
64
66
  setAuthenticatedSessionContext(request, claims);
67
+ if (claims.impersonatorId !== undefined) {
68
+ const impersonatorId = claims.impersonatorId;
69
+ // Marked sessions must stay attributable: bind the marker on the
70
+ // request logger and on the reply logger (Fastify captured the reply's
71
+ // logger before hooks ran), and on the request span rather than the
72
+ // short-lived hook span.
73
+ const markedLogger = request.log.child({
74
+ impersonatorId
75
+ });
76
+ request.log = markedLogger;
77
+ if (reply) {
78
+ reply.log = markedLogger;
79
+ }
80
+ const requestSpan = request.opentelemetry?.().span;
81
+ if (requestSpan) {
82
+ enrichSpanWithMetadata({
83
+ impersonatorId
84
+ }, {
85
+ span: requestSpan
86
+ });
87
+ }
88
+ }
65
89
  }
66
90
  });
67
91
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nconst AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');\n\nexport type ClientContext = UserContext;\n\nexport type UserId = User['id'];\nexport type RefreshSessionId = RefreshToken['sessionId'];\n\nexport type AuthenticatedSessionContext = {\n userId: UserId;\n refreshSessionId: RefreshSessionId;\n};\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nclass InvalidJwtTokenError extends Error {\n constructor() {\n super('Invalid JWT');\n this.name = 'InvalidJwtTokenError';\n }\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nfunction setAuthenticatedSessionContext(request: FastifyRequest, claims: UserTokenClaims): void {\n (request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] = {\n userId: claims.sub,\n refreshSessionId: claims.refreshSessionId,\n };\n}\n\nfunction getAuthenticatedSessionTokenContext(request: FastifyRequest): {\n userId: UserId;\n refreshSessionId: RefreshSessionId | undefined;\n} | null {\n return (\n ((request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] as\n | {userId: UserId; refreshSessionId: RefreshSessionId | undefined}\n | undefined) ?? null\n );\n}\n\n/** Returns the refresh-session metadata carried by a verified access token. */\nexport async function getAuthenticatedSessionContext(\n request: FastifyRequest,\n): Promise<AuthenticatedSessionContext> {\n const client = getClientContext(request);\n const token = getAuthenticatedSessionTokenContext(request);\n if (!client || !token || token.userId !== client.userId || !token.refreshSessionId)\n throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return await Promise.resolve({userId: client.userId, refreshSessionId: token.refreshSessionId});\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: async (token) => {\n let claims: UserTokenClaims;\n try {\n claims = await verifyUserToken({token, secret: userAccessTokenKey()});\n } catch {\n throw new InvalidJwtTokenError();\n }\n return claims;\n },\n isInvalidTokenError: (error) => error instanceof InvalidJwtTokenError,\n invalidTokenError: {message: 'Invalid or expired token', code: 'unauthorized'},\n setContext: (request, claims) => {\n const clientContext: ClientContext = buildUserContext({\n userId: claims.sub,\n email: claims.email,\n name: claims.name ?? null,\n memberships: claims.memberships,\n });\n setUserContext(request, clientContext);\n setAuthenticatedSessionContext(request, claims);\n },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","ClientError","verifyUserToken","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","Promise","resolve","createJwtAuthMethod","verifyToken","secret","isInvalidTokenError","error","invalidTokenError","message","code","setContext","clientContext","email","memberships"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAyBC,WAAW,QAAO,wBAAwB;AAKnE,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,IAAA,AAAMC,uBAAN,MAAMA,6BAA6BC;IACjC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOb,eAAea;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACP,kCAAkC,GAAG;QACnFU,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACP,kCAAkC,IAEjE;AAEtB;AAEA,6EAA6E,GAC7E,OAAO,eAAec,+BACpBP,OAAuB;IAEvB,MAAMQ,SAAST,iBAAiBC;IAChC,MAAMS,QAAQH,oCAAoCN;IAClD,IAAI,CAACQ,UAAU,CAACC,SAASA,MAAMN,MAAM,KAAKK,OAAOL,MAAM,IAAI,CAACM,MAAMJ,gBAAgB,EAChF,MAAM,IAAIf,YAAY,4BAA4B,gBAAgB;QAACoB,QAAQ;IAAG;IAEhF,OAAO,MAAMC,QAAQC,OAAO,CAAC;QAACT,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBI,MAAMJ,gBAAgB;IAAA;AAC/F;AAEA,OAAO,SAASQ;IACd,OAAOrB,4BAA4B;QACjCM,MAAMb;QACN6B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMX,gBAAgB;oBAACkB;oBAAOM,QAAQ1B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIO;YACZ;YACA,OAAOM;QACT;QACAc,qBAAqB,CAACC,QAAUA,iBAAiBrB;QACjDsB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACrB,SAASE;YACpB,MAAMoB,gBAA+BpC,iBAAiB;gBACpDiB,QAAQD,OAAOE,GAAG;gBAClBmB,OAAOrB,OAAOqB,KAAK;gBACnBzB,MAAMI,OAAOJ,IAAI,IAAI;gBACrB0B,aAAatB,OAAOsB,WAAW;YACjC;YACApC,eAAeY,SAASsB;YACxBrB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport {enrichSpanWithMetadata} from '@shipfox/node-opentelemetry';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nconst AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');\n\nexport type ClientContext = UserContext;\n\nexport type UserId = User['id'];\nexport type RefreshSessionId = RefreshToken['sessionId'];\n\nexport type AuthenticatedSessionContext = {\n userId: UserId;\n refreshSessionId: RefreshSessionId;\n};\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nclass InvalidJwtTokenError extends Error {\n constructor() {\n super('Invalid JWT');\n this.name = 'InvalidJwtTokenError';\n }\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nfunction setAuthenticatedSessionContext(request: FastifyRequest, claims: UserTokenClaims): void {\n (request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] = {\n userId: claims.sub,\n refreshSessionId: claims.refreshSessionId,\n };\n}\n\nfunction getAuthenticatedSessionTokenContext(request: FastifyRequest): {\n userId: UserId;\n refreshSessionId: RefreshSessionId | undefined;\n} | null {\n return (\n ((request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] as\n | {userId: UserId; refreshSessionId: RefreshSessionId | undefined}\n | undefined) ?? null\n );\n}\n\n/** Returns the refresh-session metadata carried by a verified access token. */\nexport async function getAuthenticatedSessionContext(\n request: FastifyRequest,\n): Promise<AuthenticatedSessionContext> {\n const client = getClientContext(request);\n const token = getAuthenticatedSessionTokenContext(request);\n if (!client || !token || token.userId !== client.userId || !token.refreshSessionId)\n throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return await Promise.resolve({userId: client.userId, refreshSessionId: token.refreshSessionId});\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: async (token) => {\n let claims: UserTokenClaims;\n try {\n claims = await verifyUserToken({token, secret: userAccessTokenKey()});\n } catch {\n throw new InvalidJwtTokenError();\n }\n return claims;\n },\n isInvalidTokenError: (error) => error instanceof InvalidJwtTokenError,\n invalidTokenError: {message: 'Invalid or expired token', code: 'unauthorized'},\n setContext: (request, claims, reply) => {\n const clientContext: ClientContext = buildUserContext({\n userId: claims.sub,\n email: claims.email,\n name: claims.name ?? null,\n memberships: claims.memberships,\n impersonatorId: claims.impersonatorId,\n });\n setUserContext(request, clientContext);\n setAuthenticatedSessionContext(request, claims);\n if (claims.impersonatorId !== undefined) {\n const impersonatorId = claims.impersonatorId;\n // Marked sessions must stay attributable: bind the marker on the\n // request logger and on the reply logger (Fastify captured the reply's\n // logger before hooks ran), and on the request span rather than the\n // short-lived hook span.\n const markedLogger = request.log.child({impersonatorId});\n request.log = markedLogger;\n if (reply) {\n reply.log = markedLogger;\n }\n const requestSpan = request.opentelemetry?.().span;\n if (requestSpan) {\n enrichSpanWithMetadata({impersonatorId}, {span: requestSpan});\n }\n }\n },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","ClientError","enrichSpanWithMetadata","verifyUserToken","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","Promise","resolve","createJwtAuthMethod","verifyToken","secret","isInvalidTokenError","error","invalidTokenError","message","code","setContext","reply","clientContext","email","memberships","impersonatorId","undefined","markedLogger","log","child","requestSpan","opentelemetry","span"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAyBC,WAAW,QAAO,wBAAwB;AACnE,SAAQC,sBAAsB,QAAO,8BAA8B;AAKnE,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,IAAA,AAAMC,uBAAN,MAAMA,6BAA6BC;IACjC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOd,eAAec;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACP,kCAAkC,GAAG;QACnFU,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACP,kCAAkC,IAEjE;AAEtB;AAEA,6EAA6E,GAC7E,OAAO,eAAec,+BACpBP,OAAuB;IAEvB,MAAMQ,SAAST,iBAAiBC;IAChC,MAAMS,QAAQH,oCAAoCN;IAClD,IAAI,CAACQ,UAAU,CAACC,SAASA,MAAMN,MAAM,KAAKK,OAAOL,MAAM,IAAI,CAACM,MAAMJ,gBAAgB,EAChF,MAAM,IAAIhB,YAAY,4BAA4B,gBAAgB;QAACqB,QAAQ;IAAG;IAEhF,OAAO,MAAMC,QAAQC,OAAO,CAAC;QAACT,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBI,MAAMJ,gBAAgB;IAAA;AAC/F;AAEA,OAAO,SAASQ;IACd,OAAOrB,4BAA4B;QACjCM,MAAMd;QACN8B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMX,gBAAgB;oBAACkB;oBAAOM,QAAQ3B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIQ;YACZ;YACA,OAAOM;QACT;QACAc,qBAAqB,CAACC,QAAUA,iBAAiBrB;QACjDsB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACrB,SAASE,QAAQoB;YAC5B,MAAMC,gBAA+BtC,iBAAiB;gBACpDkB,QAAQD,OAAOE,GAAG;gBAClBoB,OAAOtB,OAAOsB,KAAK;gBACnB1B,MAAMI,OAAOJ,IAAI,IAAI;gBACrB2B,aAAavB,OAAOuB,WAAW;gBAC/BC,gBAAgBxB,OAAOwB,cAAc;YACvC;YACAvC,eAAea,SAASuB;YACxBtB,+BAA+BD,SAASE;YACxC,IAAIA,OAAOwB,cAAc,KAAKC,WAAW;gBACvC,MAAMD,iBAAiBxB,OAAOwB,cAAc;gBAC5C,iEAAiE;gBACjE,uEAAuE;gBACvE,oEAAoE;gBACpE,yBAAyB;gBACzB,MAAME,eAAe5B,QAAQ6B,GAAG,CAACC,KAAK,CAAC;oBAACJ;gBAAc;gBACtD1B,QAAQ6B,GAAG,GAAGD;gBACd,IAAIN,OAAO;oBACTA,MAAMO,GAAG,GAAGD;gBACd;gBACA,MAAMG,cAAc/B,QAAQgC,aAAa,KAAKC;gBAC9C,IAAIF,aAAa;oBACfzC,uBAAuB;wBAACoC;oBAAc,GAAG;wBAACO,MAAMF;oBAAW;gBAC7D;YACF;QACF;IACF;AACF"}
@@ -1,5 +1,6 @@
1
+ import type { WorkspacesInterModuleClient } from '@shipfox/api-workspaces-dto/inter-module';
1
2
  import { type RouteGroup } from '@shipfox/node-fastify';
2
3
  export declare const administrationRoutes: RouteGroup;
3
4
  export declare const administrationBootstrapRoutes: RouteGroup;
4
- export declare const administrationUserRoutes: RouteGroup;
5
+ export declare function createAdministrationUserRoutes(workspaces: WorkspacesInterModuleClient): RouteGroup[];
5
6
  //# sourceMappingURL=administration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AAoBA,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAwVhF,eAAO,MAAM,oBAAoB,EAAE,UAIlC,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,UAI3C,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAItC,CAAC"}
1
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,0CAA0C,CAAC;AAE1F,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAmYhF,eAAO,MAAM,oBAAoB,EAAE,UAIjC,CAAC;AAEH,eAAO,MAAM,6BAA6B,EAAE,UAI1C,CAAC;AAuDH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,2BAA2B,GACtC,UAAU,EAAE,CAiBd"}