@shipfox/api-auth 18.0.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.turbo/turbo-build.log +17 -2
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +24 -9
  4. package/dist/core/administration.d.ts +15 -0
  5. package/dist/core/administration.d.ts.map +1 -1
  6. package/dist/core/administration.js +44 -2
  7. package/dist/core/administration.js.map +1 -1
  8. package/dist/core/auth.d.ts.map +1 -1
  9. package/dist/core/auth.js +5 -3
  10. package/dist/core/auth.js.map +1 -1
  11. package/dist/core/cimd.d.ts +39 -0
  12. package/dist/core/cimd.d.ts.map +1 -0
  13. package/dist/core/cimd.js +392 -0
  14. package/dist/core/cimd.js.map +1 -0
  15. package/dist/core/entities/agent-access.d.ts +75 -0
  16. package/dist/core/entities/agent-access.d.ts.map +1 -0
  17. package/dist/core/entities/agent-access.js +3 -0
  18. package/dist/core/entities/agent-access.js.map +1 -0
  19. package/dist/core/errors.d.ts +17 -0
  20. package/dist/core/errors.d.ts.map +1 -1
  21. package/dist/core/errors.js +32 -0
  22. package/dist/core/errors.js.map +1 -1
  23. package/dist/core/oauth-client-resolver.d.ts +54 -0
  24. package/dist/core/oauth-client-resolver.d.ts.map +1 -0
  25. package/dist/core/oauth-client-resolver.js +154 -0
  26. package/dist/core/oauth-client-resolver.js.map +1 -0
  27. package/dist/core/oauth-client.d.ts +39 -0
  28. package/dist/core/oauth-client.d.ts.map +1 -0
  29. package/dist/core/oauth-client.js +236 -0
  30. package/dist/core/oauth-client.js.map +1 -0
  31. package/dist/db/admin-user-moderation.d.ts.map +1 -1
  32. package/dist/db/admin-user-moderation.js +30 -26
  33. package/dist/db/admin-user-moderation.js.map +1 -1
  34. package/dist/db/admin-user-summary.d.ts +22 -0
  35. package/dist/db/admin-user-summary.d.ts.map +1 -1
  36. package/dist/db/admin-user-summary.js +75 -1
  37. package/dist/db/admin-user-summary.js.map +1 -1
  38. package/dist/db/admin-users.d.ts +11 -0
  39. package/dist/db/admin-users.d.ts.map +1 -1
  40. package/dist/db/admin-users.js +4 -1
  41. package/dist/db/admin-users.js.map +1 -1
  42. package/dist/db/agent-access-retention.d.ts +10 -0
  43. package/dist/db/agent-access-retention.d.ts.map +1 -0
  44. package/dist/db/agent-access-retention.js +11 -0
  45. package/dist/db/agent-access-retention.js.map +1 -0
  46. package/dist/db/agent-access.d.ts +255 -0
  47. package/dist/db/agent-access.d.ts.map +1 -0
  48. package/dist/db/agent-access.js +705 -0
  49. package/dist/db/agent-access.js.map +1 -0
  50. package/dist/db/db.d.ts +2604 -238
  51. package/dist/db/db.d.ts.map +1 -1
  52. package/dist/db/db.js +7 -0
  53. package/dist/db/db.js.map +1 -1
  54. package/dist/db/schema/agent-access.d.ts +1204 -0
  55. package/dist/db/schema/agent-access.d.ts.map +1 -0
  56. package/dist/db/schema/agent-access.js +267 -0
  57. package/dist/db/schema/agent-access.js.map +1 -0
  58. package/dist/db/schema/users.d.ts.map +1 -1
  59. package/dist/db/schema/users.js +8 -3
  60. package/dist/db/schema/users.js.map +1 -1
  61. package/dist/index.d.ts +11 -2
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +26 -2
  64. package/dist/index.js.map +1 -1
  65. package/dist/metrics/instance.d.ts +1 -1
  66. package/dist/metrics/instance.d.ts.map +1 -1
  67. package/dist/metrics/instance.js.map +1 -1
  68. package/dist/presentation/routes/administration.d.ts.map +1 -1
  69. package/dist/presentation/routes/administration.js +123 -20
  70. package/dist/presentation/routes/administration.js.map +1 -1
  71. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
  72. package/dist/presentation/routes/email-verification/verify-email-resend.js +6 -1
  73. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
  74. package/dist/presentation/routes/oauth.d.ts +9 -0
  75. package/dist/presentation/routes/oauth.d.ts.map +1 -0
  76. package/dist/presentation/routes/oauth.js +150 -0
  77. package/dist/presentation/routes/oauth.js.map +1 -0
  78. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  79. package/dist/presentation/routes/rate-limit.js +34 -0
  80. package/dist/presentation/routes/rate-limit.js.map +1 -1
  81. package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
  82. package/dist/presentation/routes/registration/signup.js +59 -50
  83. package/dist/presentation/routes/registration/signup.js.map +1 -1
  84. package/dist/temporal/activities/agent-access-retention.d.ts +13 -0
  85. package/dist/temporal/activities/agent-access-retention.d.ts.map +1 -0
  86. package/dist/temporal/activities/agent-access-retention.js +58 -0
  87. package/dist/temporal/activities/agent-access-retention.js.map +1 -0
  88. package/dist/temporal/activities/index.d.ts +5 -0
  89. package/dist/temporal/activities/index.d.ts.map +1 -0
  90. package/dist/temporal/activities/index.js +8 -0
  91. package/dist/temporal/activities/index.js.map +1 -0
  92. package/dist/temporal/constants.d.ts +2 -0
  93. package/dist/temporal/constants.d.ts.map +1 -0
  94. package/dist/temporal/constants.js +3 -0
  95. package/dist/temporal/constants.js.map +1 -0
  96. package/dist/temporal/workflows/agent-access-retention-cron.d.ts +2 -0
  97. package/dist/temporal/workflows/agent-access-retention-cron.d.ts.map +1 -0
  98. package/dist/temporal/workflows/agent-access-retention-cron.js +22 -0
  99. package/dist/temporal/workflows/agent-access-retention-cron.js.map +1 -0
  100. package/dist/temporal/workflows/index.bundle.js +25227 -0
  101. package/dist/temporal/workflows/index.bundle.meta.json +1 -0
  102. package/dist/temporal/workflows/index.d.ts +2 -0
  103. package/dist/temporal/workflows/index.d.ts.map +1 -0
  104. package/dist/temporal/workflows/index.js +3 -0
  105. package/dist/temporal/workflows/index.js.map +1 -0
  106. package/dist/tsconfig.test.tsbuildinfo +1 -1
  107. package/drizzle/0003_brainy_luckman.sql +6 -0
  108. package/drizzle/0004_yummy_runaways.sql +110 -0
  109. package/drizzle/meta/0003_snapshot.json +844 -0
  110. package/drizzle/meta/0004_snapshot.json +1751 -0
  111. package/drizzle/meta/_journal.json +14 -0
  112. package/package.json +11 -8
  113. package/src/core/admin-role.test.ts +70 -0
  114. package/src/core/administration.ts +78 -1
  115. package/src/core/auth.ts +3 -5
  116. package/src/core/cimd.test.ts +213 -0
  117. package/src/core/cimd.ts +532 -0
  118. package/src/core/entities/agent-access.ts +80 -0
  119. package/src/core/errors.ts +48 -0
  120. package/src/core/oauth-client-resolver.test.ts +198 -0
  121. package/src/core/oauth-client-resolver.ts +253 -0
  122. package/src/core/oauth-client.test.ts +157 -0
  123. package/src/core/oauth-client.ts +331 -0
  124. package/src/db/admin-user-moderation.ts +53 -45
  125. package/src/db/admin-user-summary.test.ts +228 -0
  126. package/src/db/admin-user-summary.ts +143 -1
  127. package/src/db/admin-users.ts +21 -1
  128. package/src/db/agent-access-retention.ts +10 -0
  129. package/src/db/agent-access.test.ts +1064 -0
  130. package/src/db/agent-access.ts +1491 -0
  131. package/src/db/db.ts +14 -0
  132. package/src/db/schema/agent-access.ts +273 -0
  133. package/src/db/schema/users.ts +9 -3
  134. package/src/index.test.ts +27 -0
  135. package/src/index.ts +82 -0
  136. package/src/metrics/instance.ts +6 -1
  137. package/src/presentation/routes/administration.test.ts +359 -2
  138. package/src/presentation/routes/administration.ts +142 -20
  139. package/src/presentation/routes/email-verification/verify-email-resend.ts +7 -1
  140. package/src/presentation/routes/oauth.test.ts +133 -0
  141. package/src/presentation/routes/oauth.ts +144 -0
  142. package/src/presentation/routes/rate-limit.ts +16 -0
  143. package/src/presentation/routes/registration/signup.ts +52 -41
  144. package/src/temporal/activities/agent-access-retention.ts +76 -0
  145. package/src/temporal/activities/index.ts +5 -0
  146. package/src/temporal/constants.ts +1 -0
  147. package/src/temporal/workflows/agent-access-retention-cron.ts +23 -0
  148. package/src/temporal/workflows/index.ts +1 -0
  149. package/test/globalSetup.ts +1 -1
  150. package/test/routes.ts +3 -1
  151. package/tsconfig.build.tsbuildinfo +1 -1
@@ -89,6 +89,35 @@ async function revokeActiveSessions(tx, userId) {
89
89
  });
90
90
  return new Set(revoked.map(({ sessionId })=>sessionId)).size;
91
91
  }
92
+ async function suspendUser(tx, user) {
93
+ if (user.status !== 'active') return;
94
+ const activeOwners = await tx.select({
95
+ id: adminGrants.id
96
+ }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active'))).limit(2);
97
+ const targetIsActiveOwner = await tx.select({
98
+ id: adminGrants.id
99
+ }).from(adminGrants).where(and(eq(adminGrants.userId, user.id), eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt))).limit(1);
100
+ if (targetIsActiveOwner.length > 0 && activeOwners.length <= 1) throw new LastAdminOwnerError();
101
+ await tx.update(users).set({
102
+ status: 'suspended',
103
+ updatedAt: sql`now()`
104
+ }).where(eq(users.id, user.id));
105
+ }
106
+ async function applyModerationOperation(tx, user, operation) {
107
+ if (operation === 'suspend') {
108
+ await suspendUser(tx, user);
109
+ return await revokeActiveSessions(tx, user.id);
110
+ }
111
+ if (operation === 'reactivate' && user.status === 'suspended') {
112
+ await tx.update(users).set({
113
+ status: 'active',
114
+ updatedAt: sql`now()`
115
+ }).where(eq(users.id, user.id));
116
+ return 0;
117
+ }
118
+ if (operation === 'revoke-sessions') return await revokeActiveSessions(tx, user.id);
119
+ return 0;
120
+ }
92
121
  async function executeUserModerationCommand(params) {
93
122
  return await db().transaction(async (tx)=>{
94
123
  await lockAdminCommand(tx, params);
@@ -101,32 +130,7 @@ async function executeUserModerationCommand(params) {
101
130
  await lockUserSessionMutations(tx, params.userId);
102
131
  const user = await readTargetUserForUpdate(tx, params.userId);
103
132
  await requireActiveAdminOperator(tx, params.actorId);
104
- let sessionsRevoked = 0;
105
- if (params.operation === 'suspend') {
106
- if (user.status === 'active') {
107
- const activeOwners = await tx.select({
108
- id: adminGrants.id
109
- }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active'))).limit(2);
110
- const targetIsActiveOwner = await tx.select({
111
- id: adminGrants.id
112
- }).from(adminGrants).where(and(eq(adminGrants.userId, user.id), eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt))).limit(1);
113
- if (targetIsActiveOwner.length > 0 && activeOwners.length <= 1) {
114
- throw new LastAdminOwnerError();
115
- }
116
- await tx.update(users).set({
117
- status: 'suspended',
118
- updatedAt: sql`now()`
119
- }).where(eq(users.id, user.id));
120
- }
121
- sessionsRevoked = await revokeActiveSessions(tx, user.id);
122
- } else if (params.operation === 'reactivate' && user.status === 'suspended') {
123
- await tx.update(users).set({
124
- status: 'active',
125
- updatedAt: sql`now()`
126
- }).where(eq(users.id, user.id));
127
- } else if (params.operation === 'revoke-sessions') {
128
- sessionsRevoked = await revokeActiveSessions(tx, user.id);
129
- }
133
+ const sessionsRevoked = await applyModerationOperation(tx, user, params.operation);
130
134
  const summary = await findUserSummary(tx, user.id);
131
135
  const result = {
132
136
  user: summary,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/admin-user-moderation.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport type {AdministrationActionEvent} from '@shipfox/api-common-dto';\nimport {and, eq, gt, isNull, sql} from 'drizzle-orm';\nimport {hasMinimumAdminRole, highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport {AdminRoleRequiredError, LastAdminOwnerError, UserNotFoundError} from '#core/errors.js';\nimport {\n findAdminCommandResult,\n lockAdminCommand,\n lockAdminOwnerGrants,\n storeAdminCommandResult,\n type Tx,\n writeAdminAction,\n} from './admin-command.js';\nimport {findAdministratorUserSummary} from './admin-user-summary.js';\nimport {db} from './db.js';\nimport {lockUserSessionMutations} from './refresh-tokens.js';\nimport type {\n StoredAdministratorUserSummary,\n StoredAdminUserModerationResult,\n} from './schema/admin-command-results.js';\nimport {adminGrants} from './schema/admin-grants.js';\nimport {refreshTokens} from './schema/refresh-tokens.js';\nimport {users} from './schema/users.js';\n\ninterface UserModerationCommandParams {\n actorId: string;\n userId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: AdministrationActionEvent;\n}\n\nfunction toStoredUserSummary(user: AdministratorUserSummary): StoredAdministratorUserSummary {\n return {\n id: user.id,\n email: user.email,\n name: user.name,\n emailVerifiedAt: user.emailVerifiedAt?.toISOString() ?? null,\n status: user.status,\n createdAt: user.createdAt.toISOString(),\n adminRole: user.adminRole,\n };\n}\n\nasync function findUserSummary(tx: Tx, userId: string): Promise<StoredAdministratorUserSummary> {\n const user = await findAdministratorUserSummary(tx, {id: userId});\n if (!user) throw new Error('User summary query returned no rows');\n return toStoredUserSummary(user);\n}\n\nfunction fromStoredResult(result: StoredAdminUserModerationResult) {\n return {\n user: {\n id: result.user.id,\n email: result.user.email,\n name: result.user.name,\n emailVerifiedAt: result.user.emailVerifiedAt ? new Date(result.user.emailVerifiedAt) : null,\n status: result.user.status,\n createdAt: new Date(result.user.createdAt),\n adminRole: result.user.adminRole,\n },\n correlationId: result.correlationId,\n sessionsRevoked: result.sessionsRevoked,\n };\n}\n\nasync function findCommandResult(\n tx: Tx,\n params: Pick<\n UserModerationCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {command: string},\n) {\n const result = await findAdminCommandResult(tx, params);\n if (!result) return undefined;\n if (!('userModeration' in result.result)) {\n throw new Error('Administrator command result has an unexpected shape');\n }\n return fromStoredResult(result.result.userModeration);\n}\n\nasync function storeCommandResult(\n tx: Tx,\n params: UserModerationCommandParams,\n result: StoredAdminUserModerationResult,\n): Promise<void> {\n await storeAdminCommandResult(\n tx,\n {...params, command: params.event.command},\n {userModeration: result},\n );\n}\n\nasync function readTargetUserForUpdate(tx: Tx, userId: string) {\n const rows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, userId))\n .limit(1)\n .for('update');\n const user = rows[0];\n if (!user || user.status === 'deleted') throw new UserNotFoundError(userId);\n return user;\n}\n\nexport async function requireActiveAdminOperator(tx: Tx, actorId: string): Promise<AdminRole> {\n const actorRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, actorId))\n .limit(1);\n const actor = actorRows[0];\n const grants = await tx\n .select({role: adminGrants.role})\n .from(adminGrants)\n .where(and(eq(adminGrants.userId, actorId), isNull(adminGrants.revokedAt)));\n const role = highestAdminRole(grants.map(({role}) => role));\n\n if (actor?.status !== 'active' || !role || !hasMinimumAdminRole(role, 'admin-operator')) {\n throw new AdminRoleRequiredError('admin-operator');\n }\n return role;\n}\n\nasync function revokeActiveSessions(tx: Tx, userId: string): Promise<number> {\n const revoked = await tx\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.userId, userId),\n isNull(refreshTokens.revokedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .returning({sessionId: refreshTokens.sessionId});\n\n return new Set(revoked.map(({sessionId}) => sessionId)).size;\n}\n\nasync function executeUserModerationCommand(\n params: UserModerationCommandParams & {\n operation: 'suspend' | 'reactivate' | 'revoke-sessions';\n },\n) {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n if (params.operation === 'suspend') await lockAdminOwnerGrants(tx);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n await lockUserSessionMutations(tx, params.userId);\n const user = await readTargetUserForUpdate(tx, params.userId);\n await requireActiveAdminOperator(tx, params.actorId);\n let sessionsRevoked = 0;\n\n if (params.operation === 'suspend') {\n if (user.status === 'active') {\n const activeOwners = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n )\n .limit(2);\n const targetIsActiveOwner = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .where(\n and(\n eq(adminGrants.userId, user.id),\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n ),\n )\n .limit(1);\n if (targetIsActiveOwner.length > 0 && activeOwners.length <= 1) {\n throw new LastAdminOwnerError();\n }\n\n await tx\n .update(users)\n .set({status: 'suspended', updatedAt: sql`now()`})\n .where(eq(users.id, user.id));\n }\n sessionsRevoked = await revokeActiveSessions(tx, user.id);\n } else if (params.operation === 'reactivate' && user.status === 'suspended') {\n await tx\n .update(users)\n .set({status: 'active', updatedAt: sql`now()`})\n .where(eq(users.id, user.id));\n } else if (params.operation === 'revoke-sessions') {\n sessionsRevoked = await revokeActiveSessions(tx, user.id);\n }\n\n const summary = await findUserSummary(tx, user.id);\n const result: StoredAdminUserModerationResult = {\n user: summary,\n correlationId: params.event.correlationId,\n sessionsRevoked,\n };\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, result);\n return fromStoredResult(result);\n });\n}\n\nexport type UserModerationResult = Awaited<ReturnType<typeof executeUserModerationCommand>>;\n\nexport async function suspendUserWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'suspend'});\n}\n\nexport async function reactivateUserWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'reactivate'});\n}\n\nexport async function revokeUserSessionsWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'revoke-sessions'});\n}\n"],"names":["and","eq","gt","isNull","sql","hasMinimumAdminRole","highestAdminRole","AdminRoleRequiredError","LastAdminOwnerError","UserNotFoundError","findAdminCommandResult","lockAdminCommand","lockAdminOwnerGrants","storeAdminCommandResult","writeAdminAction","findAdministratorUserSummary","db","lockUserSessionMutations","adminGrants","refreshTokens","users","toStoredUserSummary","user","id","email","name","emailVerifiedAt","toISOString","status","createdAt","adminRole","findUserSummary","tx","userId","Error","fromStoredResult","result","Date","correlationId","sessionsRevoked","findCommandResult","params","undefined","userModeration","storeCommandResult","command","event","readTargetUserForUpdate","rows","select","from","where","limit","for","requireActiveAdminOperator","actorId","actorRows","actor","grants","role","revokedAt","map","revokeActiveSessions","revoked","update","set","updatedAt","expiresAt","returning","sessionId","Set","size","executeUserModerationCommand","transaction","operation","existing","activeOwners","innerJoin","targetIsActiveOwner","length","summary","suspendUserWithAudit","reactivateUserWithAudit","revokeUserSessionsWithAudit"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/admin-user-moderation.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport type {AdministrationActionEvent} from '@shipfox/api-common-dto';\nimport {and, eq, gt, isNull, sql} from 'drizzle-orm';\nimport {hasMinimumAdminRole, highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport {AdminRoleRequiredError, LastAdminOwnerError, UserNotFoundError} from '#core/errors.js';\nimport {\n findAdminCommandResult,\n lockAdminCommand,\n lockAdminOwnerGrants,\n storeAdminCommandResult,\n type Tx,\n writeAdminAction,\n} from './admin-command.js';\nimport {findAdministratorUserSummary} from './admin-user-summary.js';\nimport {db} from './db.js';\nimport {lockUserSessionMutations} from './refresh-tokens.js';\nimport type {\n StoredAdministratorUserSummary,\n StoredAdminUserModerationResult,\n} from './schema/admin-command-results.js';\nimport {adminGrants} from './schema/admin-grants.js';\nimport {refreshTokens} from './schema/refresh-tokens.js';\nimport {users} from './schema/users.js';\n\ninterface UserModerationCommandParams {\n actorId: string;\n userId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: AdministrationActionEvent;\n}\n\nfunction toStoredUserSummary(user: AdministratorUserSummary): StoredAdministratorUserSummary {\n return {\n id: user.id,\n email: user.email,\n name: user.name,\n emailVerifiedAt: user.emailVerifiedAt?.toISOString() ?? null,\n status: user.status,\n createdAt: user.createdAt.toISOString(),\n adminRole: user.adminRole,\n };\n}\n\nasync function findUserSummary(tx: Tx, userId: string): Promise<StoredAdministratorUserSummary> {\n const user = await findAdministratorUserSummary(tx, {id: userId});\n if (!user) throw new Error('User summary query returned no rows');\n return toStoredUserSummary(user);\n}\n\nfunction fromStoredResult(result: StoredAdminUserModerationResult) {\n return {\n user: {\n id: result.user.id,\n email: result.user.email,\n name: result.user.name,\n emailVerifiedAt: result.user.emailVerifiedAt ? new Date(result.user.emailVerifiedAt) : null,\n status: result.user.status,\n createdAt: new Date(result.user.createdAt),\n adminRole: result.user.adminRole,\n },\n correlationId: result.correlationId,\n sessionsRevoked: result.sessionsRevoked,\n };\n}\n\nasync function findCommandResult(\n tx: Tx,\n params: Pick<\n UserModerationCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {command: string},\n) {\n const result = await findAdminCommandResult(tx, params);\n if (!result) return undefined;\n if (!('userModeration' in result.result)) {\n throw new Error('Administrator command result has an unexpected shape');\n }\n return fromStoredResult(result.result.userModeration);\n}\n\nasync function storeCommandResult(\n tx: Tx,\n params: UserModerationCommandParams,\n result: StoredAdminUserModerationResult,\n): Promise<void> {\n await storeAdminCommandResult(\n tx,\n {...params, command: params.event.command},\n {userModeration: result},\n );\n}\n\nasync function readTargetUserForUpdate(tx: Tx, userId: string) {\n const rows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, userId))\n .limit(1)\n .for('update');\n const user = rows[0];\n if (!user || user.status === 'deleted') throw new UserNotFoundError(userId);\n return user;\n}\n\nexport async function requireActiveAdminOperator(tx: Tx, actorId: string): Promise<AdminRole> {\n const actorRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, actorId))\n .limit(1);\n const actor = actorRows[0];\n const grants = await tx\n .select({role: adminGrants.role})\n .from(adminGrants)\n .where(and(eq(adminGrants.userId, actorId), isNull(adminGrants.revokedAt)));\n const role = highestAdminRole(grants.map(({role}) => role));\n\n if (actor?.status !== 'active' || !role || !hasMinimumAdminRole(role, 'admin-operator')) {\n throw new AdminRoleRequiredError('admin-operator');\n }\n return role;\n}\n\nasync function revokeActiveSessions(tx: Tx, userId: string): Promise<number> {\n const revoked = await tx\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.userId, userId),\n isNull(refreshTokens.revokedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .returning({sessionId: refreshTokens.sessionId});\n\n return new Set(revoked.map(({sessionId}) => sessionId)).size;\n}\n\nasync function suspendUser(tx: Tx, user: Awaited<ReturnType<typeof readTargetUserForUpdate>>) {\n if (user.status !== 'active') return;\n const activeOwners = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n )\n .limit(2);\n const targetIsActiveOwner = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .where(\n and(\n eq(adminGrants.userId, user.id),\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n ),\n )\n .limit(1);\n if (targetIsActiveOwner.length > 0 && activeOwners.length <= 1) throw new LastAdminOwnerError();\n await tx\n .update(users)\n .set({status: 'suspended', updatedAt: sql`now()`})\n .where(eq(users.id, user.id));\n}\n\nasync function applyModerationOperation(\n tx: Tx,\n user: Awaited<ReturnType<typeof readTargetUserForUpdate>>,\n operation: 'suspend' | 'reactivate' | 'revoke-sessions',\n): Promise<number> {\n if (operation === 'suspend') {\n await suspendUser(tx, user);\n return await revokeActiveSessions(tx, user.id);\n }\n if (operation === 'reactivate' && user.status === 'suspended') {\n await tx\n .update(users)\n .set({status: 'active', updatedAt: sql`now()`})\n .where(eq(users.id, user.id));\n return 0;\n }\n if (operation === 'revoke-sessions') return await revokeActiveSessions(tx, user.id);\n return 0;\n}\n\nasync function executeUserModerationCommand(\n params: UserModerationCommandParams & {\n operation: 'suspend' | 'reactivate' | 'revoke-sessions';\n },\n) {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n if (params.operation === 'suspend') await lockAdminOwnerGrants(tx);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n await lockUserSessionMutations(tx, params.userId);\n const user = await readTargetUserForUpdate(tx, params.userId);\n await requireActiveAdminOperator(tx, params.actorId);\n const sessionsRevoked = await applyModerationOperation(tx, user, params.operation);\n\n const summary = await findUserSummary(tx, user.id);\n const result: StoredAdminUserModerationResult = {\n user: summary,\n correlationId: params.event.correlationId,\n sessionsRevoked,\n };\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, result);\n return fromStoredResult(result);\n });\n}\n\nexport type UserModerationResult = Awaited<ReturnType<typeof executeUserModerationCommand>>;\n\nexport async function suspendUserWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'suspend'});\n}\n\nexport async function reactivateUserWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'reactivate'});\n}\n\nexport async function revokeUserSessionsWithAudit(\n params: UserModerationCommandParams,\n): Promise<UserModerationResult> {\n return await executeUserModerationCommand({...params, operation: 'revoke-sessions'});\n}\n"],"names":["and","eq","gt","isNull","sql","hasMinimumAdminRole","highestAdminRole","AdminRoleRequiredError","LastAdminOwnerError","UserNotFoundError","findAdminCommandResult","lockAdminCommand","lockAdminOwnerGrants","storeAdminCommandResult","writeAdminAction","findAdministratorUserSummary","db","lockUserSessionMutations","adminGrants","refreshTokens","users","toStoredUserSummary","user","id","email","name","emailVerifiedAt","toISOString","status","createdAt","adminRole","findUserSummary","tx","userId","Error","fromStoredResult","result","Date","correlationId","sessionsRevoked","findCommandResult","params","undefined","userModeration","storeCommandResult","command","event","readTargetUserForUpdate","rows","select","from","where","limit","for","requireActiveAdminOperator","actorId","actorRows","actor","grants","role","revokedAt","map","revokeActiveSessions","revoked","update","set","updatedAt","expiresAt","returning","sessionId","Set","size","suspendUser","activeOwners","innerJoin","targetIsActiveOwner","length","applyModerationOperation","operation","executeUserModerationCommand","transaction","existing","summary","suspendUserWithAudit","reactivateUserWithAudit","revokeUserSessionsWithAudit"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
+ import { type TimestampIdCursor } from '@shipfox/node-drizzle';
1
2
  import type { AdministratorUserSummary } from '#core/entities/administrator-read-model.js';
3
+ import type { UserStatus } from '#core/entities/user.js';
2
4
  import type { db } from './db.js';
3
5
  type Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];
4
6
  export type AdministratorUserSummaryExecutor = ReturnType<typeof db> | Tx;
@@ -9,6 +11,26 @@ type AdministratorUserLookup = {
9
11
  email: string;
10
12
  id?: never;
11
13
  };
14
+ export interface ListAdministratorUserSummariesParams {
15
+ actorId: string;
16
+ limit: number;
17
+ cursor?: TimestampIdCursor | undefined;
18
+ search?: string | undefined;
19
+ status?: UserStatus | undefined;
20
+ eligible?: boolean | undefined;
21
+ }
22
+ export interface ListAdministratorUserSummariesResult {
23
+ rows: AdministratorUserSummary[];
24
+ nextCursor: TimestampIdCursor | null;
25
+ }
12
26
  export declare function findAdministratorUserSummary(executor: AdministratorUserSummaryExecutor, params: AdministratorUserLookup): Promise<AdministratorUserSummary | undefined>;
27
+ /**
28
+ * Lists a live directory view using keyset pagination.
29
+ *
30
+ * Pages are not a consistent snapshot. Each page reads current account and grant state, so
31
+ * inserts and account or grant changes can make consecutive pages differ. Re-run the listing
32
+ * before acting on a row.
33
+ */
34
+ export declare function listAdministratorUserSummaries(executor: AdministratorUserSummaryExecutor, params: ListAdministratorUserSummariesParams): Promise<ListAdministratorUserSummariesResult>;
13
35
  export {};
14
36
  //# sourceMappingURL=admin-user-summary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-user-summary.d.ts","sourceRoot":"","sources":["../../src/db/admin-user-summary.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,4CAA4C,CAAC;AACzF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAIhC,KAAK,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7E,MAAM,MAAM,gCAAgC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC;AAE1E,KAAK,uBAAuB,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC;AAEzF,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,gCAAgC,EAC1C,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAmC/C"}
1
+ {"version":3,"file":"admin-user-summary.d.ts","sourceRoot":"","sources":["../../src/db/admin-user-summary.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAe/B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,4CAA4C,CAAC;AACzF,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAIhC,KAAK,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7E,MAAM,MAAM,gCAAgC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC;AAE1E,KAAK,uBAAuB,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC;AAKzF,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,oCAAoC;IACnD,IAAI,EAAE,wBAAwB,EAAE,CAAC;IACjC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC;AA2CD,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,gCAAgC,EAC1C,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAmC/C;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAClD,QAAQ,EAAE,gCAAgC,EAC1C,MAAM,EAAE,oCAAoC,GAC3C,OAAO,CAAC,oCAAoC,CAAC,CAqD/C"}
@@ -1,7 +1,36 @@
1
- import { and, eq, isNull } from 'drizzle-orm';
1
+ import { paginateTimestampIdRows, timestampIdCursorWhere } from '@shipfox/node-drizzle';
2
+ import { and, desc, eq, isNotNull, isNull, ne, not, or, sql } from 'drizzle-orm';
2
3
  import { highestAdminRole } from '#core/admin-role-model.js';
3
4
  import { adminGrants } from './schema/admin-grants.js';
4
5
  import { users } from './schema/users.js';
6
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7
+ const SEARCH_TERM_SEPARATOR = /\s+/;
8
+ function escapeLikeTerm(term) {
9
+ return term.replace(/[\\%_]/g, (character)=>`\\${character}`);
10
+ }
11
+ function isUuid(value) {
12
+ return UUID_PATTERN.test(value);
13
+ }
14
+ function containsTerm(column, term) {
15
+ return sql`${column} ILIKE ${`%${escapeLikeTerm(term)}%`}`;
16
+ }
17
+ function addEligibilityCondition(conditions, params, activeAdminRoleUserId) {
18
+ const eligibleCondition = and(eq(users.status, 'active'), isNotNull(users.emailVerifiedAt), isNull(activeAdminRoleUserId), ne(users.id, params.actorId));
19
+ if (eligibleCondition && params.eligible === true) conditions.push(eligibleCondition);
20
+ if (eligibleCondition && params.eligible === false) conditions.push(not(eligibleCondition));
21
+ }
22
+ function addSearchConditions(conditions, search) {
23
+ const normalizedSearch = search?.trim() ?? '';
24
+ if (!normalizedSearch) return;
25
+ if (isUuid(normalizedSearch)) {
26
+ conditions.push(eq(users.id, normalizedSearch));
27
+ return;
28
+ }
29
+ for (const term of normalizedSearch.split(SEARCH_TERM_SEPARATOR)){
30
+ const termCondition = or(containsTerm(users.name, term), containsTerm(users.email, term));
31
+ if (termCondition) conditions.push(termCondition);
32
+ }
33
+ }
5
34
  export async function findAdministratorUserSummary(executor, params) {
6
35
  const identifier = 'id' in params ? eq(users.id, params.id) : eq(users.email, params.email);
7
36
  const rows = await executor.select({
@@ -27,5 +56,50 @@ export async function findAdministratorUserSummary(executor, params) {
27
56
  ] : []))
28
57
  };
29
58
  }
59
+ /**
60
+ * Lists a live directory view using keyset pagination.
61
+ *
62
+ * Pages are not a consistent snapshot. Each page reads current account and grant state, so
63
+ * inserts and account or grant changes can make consecutive pages differ. Re-run the listing
64
+ * before acting on a row.
65
+ */ export async function listAdministratorUserSummaries(executor, params) {
66
+ // Aggregate grants in a subquery so pagination is over users, not grants.
67
+ const activeAdminRoles = executor.select({
68
+ userId: adminGrants.userId,
69
+ roles: sql`json_agg(${adminGrants.role})`.as('roles')
70
+ }).from(adminGrants).where(isNull(adminGrants.revokedAt)).groupBy(adminGrants.userId).as('active_admin_roles');
71
+ const conditions = [];
72
+ const cursorCondition = timestampIdCursorWhere({
73
+ timestampColumn: users.createdAt,
74
+ idColumn: users.id,
75
+ cursor: params.cursor
76
+ });
77
+ if (cursorCondition) conditions.push(cursorCondition);
78
+ if (params.status) conditions.push(eq(users.status, params.status));
79
+ addEligibilityCondition(conditions, params, activeAdminRoles.userId);
80
+ addSearchConditions(conditions, params.search);
81
+ const rows = await executor.select({
82
+ id: users.id,
83
+ email: users.email,
84
+ name: users.name,
85
+ emailVerifiedAt: users.emailVerifiedAt,
86
+ status: users.status,
87
+ createdAt: users.createdAt,
88
+ adminRoles: activeAdminRoles.roles
89
+ }).from(users).leftJoin(activeAdminRoles, eq(activeAdminRoles.userId, users.id)).where(and(...conditions)).orderBy(desc(users.createdAt), desc(users.id)).limit(params.limit + 1);
90
+ const mappedRows = rows.map(({ adminRoles, ...row })=>({
91
+ ...row,
92
+ adminRole: row.status === 'active' ? highestAdminRole(adminRoles ?? []) : null
93
+ }));
94
+ const page = paginateTimestampIdRows({
95
+ rows: mappedRows,
96
+ limit: params.limit,
97
+ timestampKey: 'createdAt'
98
+ });
99
+ return {
100
+ rows: page.pageRows,
101
+ nextCursor: page.nextCursor
102
+ };
103
+ }
30
104
 
31
105
  //# sourceMappingURL=admin-user-summary.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/admin-user-summary.ts"],"sourcesContent":["import {and, eq, isNull} from 'drizzle-orm';\nimport {highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport type {db} from './db.js';\nimport {adminGrants} from './schema/admin-grants.js';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\nexport type AdministratorUserSummaryExecutor = ReturnType<typeof db> | Tx;\n\ntype AdministratorUserLookup = {id: string; email?: never} | {email: string; id?: never};\n\nexport async function findAdministratorUserSummary(\n executor: AdministratorUserSummaryExecutor,\n params: AdministratorUserLookup,\n): Promise<AdministratorUserSummary | undefined> {\n const identifier = 'id' in params ? eq(users.id, params.id) : eq(users.email, params.email);\n const rows = await executor\n .select({\n id: users.id,\n email: users.email,\n name: users.name,\n emailVerifiedAt: users.emailVerifiedAt,\n status: users.status,\n createdAt: users.createdAt,\n adminRole: adminGrants.role,\n })\n .from(users)\n .leftJoin(\n adminGrants,\n and(\n eq(adminGrants.userId, users.id),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n )\n .where(identifier);\n\n const first = rows[0];\n if (!first) return undefined;\n\n return {\n id: first.id,\n email: first.email,\n name: first.name,\n emailVerifiedAt: first.emailVerifiedAt,\n status: first.status,\n createdAt: first.createdAt,\n adminRole: highestAdminRole(rows.flatMap(({adminRole}) => (adminRole ? [adminRole] : []))),\n } satisfies AdministratorUserSummary;\n}\n"],"names":["and","eq","isNull","highestAdminRole","adminGrants","users","findAdministratorUserSummary","executor","params","identifier","id","email","rows","select","name","emailVerifiedAt","status","createdAt","adminRole","role","from","leftJoin","userId","revokedAt","where","first","undefined","flatMap"],"mappings":"AAAA,SAAQA,GAAG,EAAEC,EAAE,EAAEC,MAAM,QAAO,cAAc;AAC5C,SAAQC,gBAAgB,QAAO,4BAA4B;AAG3D,SAAQC,WAAW,QAAO,2BAA2B;AACrD,SAAQC,KAAK,QAAO,oBAAoB;AAOxC,OAAO,eAAeC,6BACpBC,QAA0C,EAC1CC,MAA+B;IAE/B,MAAMC,aAAa,QAAQD,SAASP,GAAGI,MAAMK,EAAE,EAAEF,OAAOE,EAAE,IAAIT,GAAGI,MAAMM,KAAK,EAAEH,OAAOG,KAAK;IAC1F,MAAMC,OAAO,MAAML,SAChBM,MAAM,CAAC;QACNH,IAAIL,MAAMK,EAAE;QACZC,OAAON,MAAMM,KAAK;QAClBG,MAAMT,MAAMS,IAAI;QAChBC,iBAAiBV,MAAMU,eAAe;QACtCC,QAAQX,MAAMW,MAAM;QACpBC,WAAWZ,MAAMY,SAAS;QAC1BC,WAAWd,YAAYe,IAAI;IAC7B,GACCC,IAAI,CAACf,OACLgB,QAAQ,CACPjB,aACAJ,IACEC,GAAGG,YAAYkB,MAAM,EAAEjB,MAAMK,EAAE,GAC/BR,OAAOE,YAAYmB,SAAS,GAC5BtB,GAAGI,MAAMW,MAAM,EAAE,YAGpBQ,KAAK,CAACf;IAET,MAAMgB,QAAQb,IAAI,CAAC,EAAE;IACrB,IAAI,CAACa,OAAO,OAAOC;IAEnB,OAAO;QACLhB,IAAIe,MAAMf,EAAE;QACZC,OAAOc,MAAMd,KAAK;QAClBG,MAAMW,MAAMX,IAAI;QAChBC,iBAAiBU,MAAMV,eAAe;QACtCC,QAAQS,MAAMT,MAAM;QACpBC,WAAWQ,MAAMR,SAAS;QAC1BC,WAAWf,iBAAiBS,KAAKe,OAAO,CAAC,CAAC,EAACT,SAAS,EAAC,GAAMA,YAAY;gBAACA;aAAU,GAAG,EAAE;IACzF;AACF"}
1
+ {"version":3,"sources":["../../src/db/admin-user-summary.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport {\n paginateTimestampIdRows,\n type TimestampIdCursor,\n timestampIdCursorWhere,\n} from '@shipfox/node-drizzle';\nimport {\n and,\n desc,\n eq,\n isNotNull,\n isNull,\n ne,\n not,\n or,\n type SQL,\n type SQLWrapper,\n sql,\n} from 'drizzle-orm';\nimport {highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport type {UserStatus} from '#core/entities/user.js';\nimport type {db} from './db.js';\nimport {adminGrants} from './schema/admin-grants.js';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\nexport type AdministratorUserSummaryExecutor = ReturnType<typeof db> | Tx;\n\ntype AdministratorUserLookup = {id: string; email?: never} | {email: string; id?: never};\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst SEARCH_TERM_SEPARATOR = /\\s+/;\n\nexport interface ListAdministratorUserSummariesParams {\n actorId: string;\n limit: number;\n cursor?: TimestampIdCursor | undefined;\n search?: string | undefined;\n status?: UserStatus | undefined;\n eligible?: boolean | undefined;\n}\n\nexport interface ListAdministratorUserSummariesResult {\n rows: AdministratorUserSummary[];\n nextCursor: TimestampIdCursor | null;\n}\n\nfunction escapeLikeTerm(term: string): string {\n return term.replace(/[\\\\%_]/g, (character) => `\\\\${character}`);\n}\n\nfunction isUuid(value: string): boolean {\n return UUID_PATTERN.test(value);\n}\n\nfunction containsTerm(column: SQLWrapper, term: string): SQLWrapper {\n return sql`${column} ILIKE ${`%${escapeLikeTerm(term)}%`}`;\n}\n\nfunction addEligibilityCondition(\n conditions: SQL[],\n params: Pick<ListAdministratorUserSummariesParams, 'actorId' | 'eligible'>,\n activeAdminRoleUserId: SQLWrapper,\n): void {\n const eligibleCondition = and(\n eq(users.status, 'active'),\n isNotNull(users.emailVerifiedAt),\n isNull(activeAdminRoleUserId),\n ne(users.id, params.actorId),\n );\n if (eligibleCondition && params.eligible === true) conditions.push(eligibleCondition);\n if (eligibleCondition && params.eligible === false) conditions.push(not(eligibleCondition));\n}\n\nfunction addSearchConditions(conditions: SQL[], search: string | undefined): void {\n const normalizedSearch = search?.trim() ?? '';\n if (!normalizedSearch) return;\n if (isUuid(normalizedSearch)) {\n conditions.push(eq(users.id, normalizedSearch));\n return;\n }\n\n for (const term of normalizedSearch.split(SEARCH_TERM_SEPARATOR)) {\n const termCondition = or(containsTerm(users.name, term), containsTerm(users.email, term));\n if (termCondition) conditions.push(termCondition);\n }\n}\n\nexport async function findAdministratorUserSummary(\n executor: AdministratorUserSummaryExecutor,\n params: AdministratorUserLookup,\n): Promise<AdministratorUserSummary | undefined> {\n const identifier = 'id' in params ? eq(users.id, params.id) : eq(users.email, params.email);\n const rows = await executor\n .select({\n id: users.id,\n email: users.email,\n name: users.name,\n emailVerifiedAt: users.emailVerifiedAt,\n status: users.status,\n createdAt: users.createdAt,\n adminRole: adminGrants.role,\n })\n .from(users)\n .leftJoin(\n adminGrants,\n and(\n eq(adminGrants.userId, users.id),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n )\n .where(identifier);\n\n const first = rows[0];\n if (!first) return undefined;\n\n return {\n id: first.id,\n email: first.email,\n name: first.name,\n emailVerifiedAt: first.emailVerifiedAt,\n status: first.status,\n createdAt: first.createdAt,\n adminRole: highestAdminRole(rows.flatMap(({adminRole}) => (adminRole ? [adminRole] : []))),\n } satisfies AdministratorUserSummary;\n}\n\n/**\n * Lists a live directory view using keyset pagination.\n *\n * Pages are not a consistent snapshot. Each page reads current account and grant state, so\n * inserts and account or grant changes can make consecutive pages differ. Re-run the listing\n * before acting on a row.\n */\nexport async function listAdministratorUserSummaries(\n executor: AdministratorUserSummaryExecutor,\n params: ListAdministratorUserSummariesParams,\n): Promise<ListAdministratorUserSummariesResult> {\n // Aggregate grants in a subquery so pagination is over users, not grants.\n const activeAdminRoles = executor\n .select({\n userId: adminGrants.userId,\n roles: sql<AdminRole[]>`json_agg(${adminGrants.role})`.as('roles'),\n })\n .from(adminGrants)\n .where(isNull(adminGrants.revokedAt))\n .groupBy(adminGrants.userId)\n .as('active_admin_roles');\n\n const conditions: SQL[] = [];\n const cursorCondition = timestampIdCursorWhere({\n timestampColumn: users.createdAt,\n idColumn: users.id,\n cursor: params.cursor,\n });\n if (cursorCondition) conditions.push(cursorCondition);\n if (params.status) conditions.push(eq(users.status, params.status));\n\n addEligibilityCondition(conditions, params, activeAdminRoles.userId);\n addSearchConditions(conditions, params.search);\n\n const rows = await executor\n .select({\n id: users.id,\n email: users.email,\n name: users.name,\n emailVerifiedAt: users.emailVerifiedAt,\n status: users.status,\n createdAt: users.createdAt,\n adminRoles: activeAdminRoles.roles,\n })\n .from(users)\n .leftJoin(activeAdminRoles, eq(activeAdminRoles.userId, users.id))\n .where(and(...conditions))\n .orderBy(desc(users.createdAt), desc(users.id))\n .limit(params.limit + 1);\n\n const mappedRows = rows.map(\n ({adminRoles, ...row}): AdministratorUserSummary => ({\n ...row,\n adminRole: row.status === 'active' ? highestAdminRole(adminRoles ?? []) : null,\n }),\n );\n const page = paginateTimestampIdRows({\n rows: mappedRows,\n limit: params.limit,\n timestampKey: 'createdAt',\n });\n\n return {rows: page.pageRows, nextCursor: page.nextCursor};\n}\n"],"names":["paginateTimestampIdRows","timestampIdCursorWhere","and","desc","eq","isNotNull","isNull","ne","not","or","sql","highestAdminRole","adminGrants","users","UUID_PATTERN","SEARCH_TERM_SEPARATOR","escapeLikeTerm","term","replace","character","isUuid","value","test","containsTerm","column","addEligibilityCondition","conditions","params","activeAdminRoleUserId","eligibleCondition","status","emailVerifiedAt","id","actorId","eligible","push","addSearchConditions","search","normalizedSearch","trim","split","termCondition","name","email","findAdministratorUserSummary","executor","identifier","rows","select","createdAt","adminRole","role","from","leftJoin","userId","revokedAt","where","first","undefined","flatMap","listAdministratorUserSummaries","activeAdminRoles","roles","as","groupBy","cursorCondition","timestampColumn","idColumn","cursor","adminRoles","orderBy","limit","mappedRows","map","row","page","timestampKey","pageRows","nextCursor"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
+ import type { TimestampIdCursor } from '@shipfox/node-drizzle';
1
2
  import type { AdministratorUserSummary } from '#core/entities/administrator-read-model.js';
3
+ import { type ListAdministratorUserSummariesResult } from './admin-user-summary.js';
2
4
  export type AdministratorUserRecord = AdministratorUserSummary;
3
5
  type AdministratorUserLookup = {
4
6
  id: string;
@@ -8,5 +10,14 @@ type AdministratorUserLookup = {
8
10
  id?: never;
9
11
  };
10
12
  export declare function findAdministratorUser(params: AdministratorUserLookup): Promise<AdministratorUserRecord | undefined>;
13
+ export interface ListAdministratorUsersParams {
14
+ actorId: string;
15
+ limit: number;
16
+ cursor?: TimestampIdCursor | undefined;
17
+ search?: string | undefined;
18
+ status?: AdministratorUserSummary['status'] | undefined;
19
+ eligible?: boolean | undefined;
20
+ }
21
+ export declare function listAdministratorUsers(params: ListAdministratorUsersParams): Promise<ListAdministratorUserSummariesResult>;
11
22
  export {};
12
23
  //# sourceMappingURL=admin-users.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-users.d.ts","sourceRoot":"","sources":["../../src/db/admin-users.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,4CAA4C,CAAC;AAIzF,MAAM,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAE/D,KAAK,uBAAuB,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC;AAEzF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAE9C"}
1
+ {"version":3,"file":"admin-users.d.ts","sourceRoot":"","sources":["../../src/db/admin-users.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAEL,KAAK,oCAAoC,EAE1C,MAAM,yBAAyB,CAAC;AAGjC,MAAM,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAE/D,KAAK,uBAAuB,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC;AAEzF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAE9C;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,oCAAoC,CAAC,CAE/C"}
@@ -1,7 +1,10 @@
1
- import { findAdministratorUserSummary } from './admin-user-summary.js';
1
+ import { findAdministratorUserSummary, listAdministratorUserSummaries } from './admin-user-summary.js';
2
2
  import { db } from './db.js';
3
3
  export async function findAdministratorUser(params) {
4
4
  return await findAdministratorUserSummary(db(), params);
5
5
  }
6
+ export async function listAdministratorUsers(params) {
7
+ return await listAdministratorUserSummaries(db(), params);
8
+ }
6
9
 
7
10
  //# sourceMappingURL=admin-users.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/admin-users.ts"],"sourcesContent":["import type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport {findAdministratorUserSummary} from './admin-user-summary.js';\nimport {db} from './db.js';\n\nexport type AdministratorUserRecord = AdministratorUserSummary;\n\ntype AdministratorUserLookup = {id: string; email?: never} | {email: string; id?: never};\n\nexport async function findAdministratorUser(\n params: AdministratorUserLookup,\n): Promise<AdministratorUserRecord | undefined> {\n return await findAdministratorUserSummary(db(), params);\n}\n"],"names":["findAdministratorUserSummary","db","findAdministratorUser","params"],"mappings":"AACA,SAAQA,4BAA4B,QAAO,0BAA0B;AACrE,SAAQC,EAAE,QAAO,UAAU;AAM3B,OAAO,eAAeC,sBACpBC,MAA+B;IAE/B,OAAO,MAAMH,6BAA6BC,MAAME;AAClD"}
1
+ {"version":3,"sources":["../../src/db/admin-users.ts"],"sourcesContent":["import type {TimestampIdCursor} from '@shipfox/node-drizzle';\nimport type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';\nimport {\n findAdministratorUserSummary,\n type ListAdministratorUserSummariesResult,\n listAdministratorUserSummaries,\n} from './admin-user-summary.js';\nimport {db} from './db.js';\n\nexport type AdministratorUserRecord = AdministratorUserSummary;\n\ntype AdministratorUserLookup = {id: string; email?: never} | {email: string; id?: never};\n\nexport async function findAdministratorUser(\n params: AdministratorUserLookup,\n): Promise<AdministratorUserRecord | undefined> {\n return await findAdministratorUserSummary(db(), params);\n}\n\nexport interface ListAdministratorUsersParams {\n actorId: string;\n limit: number;\n cursor?: TimestampIdCursor | undefined;\n search?: string | undefined;\n status?: AdministratorUserSummary['status'] | undefined;\n eligible?: boolean | undefined;\n}\n\nexport async function listAdministratorUsers(\n params: ListAdministratorUsersParams,\n): Promise<ListAdministratorUserSummariesResult> {\n return await listAdministratorUserSummaries(db(), params);\n}\n"],"names":["findAdministratorUserSummary","listAdministratorUserSummaries","db","findAdministratorUser","params","listAdministratorUsers"],"mappings":"AAEA,SACEA,4BAA4B,EAE5BC,8BAA8B,QACzB,0BAA0B;AACjC,SAAQC,EAAE,QAAO,UAAU;AAM3B,OAAO,eAAeC,sBACpBC,MAA+B;IAE/B,OAAO,MAAMJ,6BAA6BE,MAAME;AAClD;AAWA,OAAO,eAAeC,uBACpBD,MAAoC;IAEpC,OAAO,MAAMH,+BAA+BC,MAAME;AACpD"}
@@ -0,0 +1,10 @@
1
+ export declare const AGENT_AUTHORIZATION_RETENTION_DAYS = 1;
2
+ export declare const AGENT_REFRESH_TOKEN_RETENTION_DAYS = 30;
3
+ export declare const AGENT_GRANT_RETENTION_DAYS = 90;
4
+ export declare const AGENT_PAT_RETENTION_DAYS = 90;
5
+ export declare const AGENT_CLIENT_RETENTION_DAYS = 30;
6
+ export declare const AGENT_ACCESS_RETENTION_BATCH_LIMIT = 200;
7
+ export declare const AGENT_ACCESS_RETENTION_MAX_ITERATIONS = 1000;
8
+ export declare const AGENT_ACCESS_RETENTION_TIME_BUDGET_MS: number;
9
+ export declare const AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS = 1000;
10
+ //# sourceMappingURL=agent-access-retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-access-retention.d.ts","sourceRoot":"","sources":["../../src/db/agent-access-retention.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,IAAI,CAAC;AACpD,eAAO,MAAM,kCAAkC,KAAK,CAAC;AACrD,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,kCAAkC,MAAM,CAAC;AACtD,eAAO,MAAM,qCAAqC,OAAQ,CAAC;AAC3D,eAAO,MAAM,qCAAqC,QAAa,CAAC;AAChE,eAAO,MAAM,wCAAwC,OAAQ,CAAC"}
@@ -0,0 +1,11 @@
1
+ export const AGENT_AUTHORIZATION_RETENTION_DAYS = 1;
2
+ export const AGENT_REFRESH_TOKEN_RETENTION_DAYS = 30;
3
+ export const AGENT_GRANT_RETENTION_DAYS = 90;
4
+ export const AGENT_PAT_RETENTION_DAYS = 90;
5
+ export const AGENT_CLIENT_RETENTION_DAYS = 30;
6
+ export const AGENT_ACCESS_RETENTION_BATCH_LIMIT = 200;
7
+ export const AGENT_ACCESS_RETENTION_MAX_ITERATIONS = 1_000;
8
+ export const AGENT_ACCESS_RETENTION_TIME_BUDGET_MS = 4 * 60_000;
9
+ export const AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS = 1_000;
10
+
11
+ //# sourceMappingURL=agent-access-retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/db/agent-access-retention.ts"],"sourcesContent":["export const AGENT_AUTHORIZATION_RETENTION_DAYS = 1;\nexport const AGENT_REFRESH_TOKEN_RETENTION_DAYS = 30;\nexport const AGENT_GRANT_RETENTION_DAYS = 90;\nexport const AGENT_PAT_RETENTION_DAYS = 90;\nexport const AGENT_CLIENT_RETENTION_DAYS = 30;\n\nexport const AGENT_ACCESS_RETENTION_BATCH_LIMIT = 200;\nexport const AGENT_ACCESS_RETENTION_MAX_ITERATIONS = 1_000;\nexport const AGENT_ACCESS_RETENTION_TIME_BUDGET_MS = 4 * 60_000;\nexport const AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS = 1_000;\n"],"names":["AGENT_AUTHORIZATION_RETENTION_DAYS","AGENT_REFRESH_TOKEN_RETENTION_DAYS","AGENT_GRANT_RETENTION_DAYS","AGENT_PAT_RETENTION_DAYS","AGENT_CLIENT_RETENTION_DAYS","AGENT_ACCESS_RETENTION_BATCH_LIMIT","AGENT_ACCESS_RETENTION_MAX_ITERATIONS","AGENT_ACCESS_RETENTION_TIME_BUDGET_MS","AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS"],"mappings":"AAAA,OAAO,MAAMA,qCAAqC,EAAE;AACpD,OAAO,MAAMC,qCAAqC,GAAG;AACrD,OAAO,MAAMC,6BAA6B,GAAG;AAC7C,OAAO,MAAMC,2BAA2B,GAAG;AAC3C,OAAO,MAAMC,8BAA8B,GAAG;AAE9C,OAAO,MAAMC,qCAAqC,IAAI;AACtD,OAAO,MAAMC,wCAAwC,MAAM;AAC3D,OAAO,MAAMC,wCAAwC,IAAI,OAAO;AAChE,OAAO,MAAMC,2CAA2C,MAAM"}