@shipfox/api-auth 18.0.0 → 19.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 (94) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -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/entities/agent-access.d.ts +75 -0
  12. package/dist/core/entities/agent-access.d.ts.map +1 -0
  13. package/dist/core/entities/agent-access.js +3 -0
  14. package/dist/core/entities/agent-access.js.map +1 -0
  15. package/dist/core/errors.d.ts +3 -0
  16. package/dist/core/errors.d.ts.map +1 -1
  17. package/dist/core/errors.js +6 -0
  18. package/dist/core/errors.js.map +1 -1
  19. package/dist/db/admin-user-moderation.d.ts.map +1 -1
  20. package/dist/db/admin-user-moderation.js +30 -26
  21. package/dist/db/admin-user-moderation.js.map +1 -1
  22. package/dist/db/admin-user-summary.d.ts +22 -0
  23. package/dist/db/admin-user-summary.d.ts.map +1 -1
  24. package/dist/db/admin-user-summary.js +75 -1
  25. package/dist/db/admin-user-summary.js.map +1 -1
  26. package/dist/db/admin-users.d.ts +11 -0
  27. package/dist/db/admin-users.d.ts.map +1 -1
  28. package/dist/db/admin-users.js +4 -1
  29. package/dist/db/admin-users.js.map +1 -1
  30. package/dist/db/agent-access.d.ts +159 -0
  31. package/dist/db/agent-access.d.ts.map +1 -0
  32. package/dist/db/agent-access.js +267 -0
  33. package/dist/db/agent-access.js.map +1 -0
  34. package/dist/db/db.d.ts +2604 -238
  35. package/dist/db/db.d.ts.map +1 -1
  36. package/dist/db/db.js +7 -0
  37. package/dist/db/db.js.map +1 -1
  38. package/dist/db/schema/agent-access.d.ts +1204 -0
  39. package/dist/db/schema/agent-access.d.ts.map +1 -0
  40. package/dist/db/schema/agent-access.js +267 -0
  41. package/dist/db/schema/agent-access.js.map +1 -0
  42. package/dist/db/schema/users.d.ts.map +1 -1
  43. package/dist/db/schema/users.js +8 -3
  44. package/dist/db/schema/users.js.map +1 -1
  45. package/dist/index.d.ts +3 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2 -2
  48. package/dist/index.js.map +1 -1
  49. package/dist/metrics/instance.d.ts +1 -1
  50. package/dist/metrics/instance.d.ts.map +1 -1
  51. package/dist/metrics/instance.js.map +1 -1
  52. package/dist/presentation/routes/administration.d.ts.map +1 -1
  53. package/dist/presentation/routes/administration.js +123 -20
  54. package/dist/presentation/routes/administration.js.map +1 -1
  55. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
  56. package/dist/presentation/routes/email-verification/verify-email-resend.js +6 -1
  57. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
  58. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  59. package/dist/presentation/routes/rate-limit.js +10 -0
  60. package/dist/presentation/routes/rate-limit.js.map +1 -1
  61. package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
  62. package/dist/presentation/routes/registration/signup.js +59 -50
  63. package/dist/presentation/routes/registration/signup.js.map +1 -1
  64. package/dist/tsconfig.test.tsbuildinfo +1 -1
  65. package/drizzle/0003_brainy_luckman.sql +6 -0
  66. package/drizzle/0004_yummy_runaways.sql +110 -0
  67. package/drizzle/meta/0003_snapshot.json +844 -0
  68. package/drizzle/meta/0004_snapshot.json +1751 -0
  69. package/drizzle/meta/_journal.json +14 -0
  70. package/package.json +7 -7
  71. package/src/core/admin-role.test.ts +70 -0
  72. package/src/core/administration.ts +78 -1
  73. package/src/core/auth.ts +3 -5
  74. package/src/core/entities/agent-access.ts +80 -0
  75. package/src/core/errors.ts +7 -0
  76. package/src/db/admin-user-moderation.ts +53 -45
  77. package/src/db/admin-user-summary.test.ts +228 -0
  78. package/src/db/admin-user-summary.ts +143 -1
  79. package/src/db/admin-users.ts +21 -1
  80. package/src/db/agent-access.test.ts +370 -0
  81. package/src/db/agent-access.ts +629 -0
  82. package/src/db/db.ts +14 -0
  83. package/src/db/schema/agent-access.ts +273 -0
  84. package/src/db/schema/users.ts +9 -3
  85. package/src/index.ts +6 -0
  86. package/src/metrics/instance.ts +1 -0
  87. package/src/presentation/routes/administration.test.ts +359 -2
  88. package/src/presentation/routes/administration.ts +142 -20
  89. package/src/presentation/routes/email-verification/verify-email-resend.ts +7 -1
  90. package/src/presentation/routes/rate-limit.ts +4 -0
  91. package/src/presentation/routes/registration/signup.ts +52 -41
  92. package/test/globalSetup.ts +1 -1
  93. package/test/routes.ts +3 -1
  94. package/tsconfig.build.tsbuildinfo +1 -1
@@ -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,159 @@
1
+ import type { AgentAuthorizationCode, AgentAuthorizationRequest, AgentClient, AgentGrant, AgentPersonalAccessToken, AgentRefreshToken } from '#core/entities/agent-access.js';
2
+ import { db } from './db.js';
3
+ export type AgentAccessTx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];
4
+ type AgentAccessExecutor = ReturnType<typeof db> | AgentAccessTx;
5
+ export interface CreateAgentClientParams {
6
+ clientId: string;
7
+ name: string;
8
+ redirectUris: string[];
9
+ kind: 'registered' | 'cimd';
10
+ }
11
+ export declare function createAgentClient(params: CreateAgentClientParams): Promise<AgentClient>;
12
+ export declare function createAgentClientTx(tx: AgentAccessTx, params: CreateAgentClientParams): Promise<AgentClient>;
13
+ export declare function findAgentClientByClientId(params: {
14
+ clientId: string;
15
+ executor?: AgentAccessExecutor;
16
+ }): Promise<AgentClient | undefined>;
17
+ export declare function markAgentClientReferenced(tx: AgentAccessTx, params: {
18
+ clientUuid: string;
19
+ }): Promise<void>;
20
+ export declare function markAgentClientUnreferenced(tx: AgentAccessTx, params: {
21
+ clientUuid: string;
22
+ }): Promise<void>;
23
+ export interface CreateAgentAuthorizationRequestParams {
24
+ /** Internal `auth_agent_clients.id`, not the public OAuth `client_id`. */
25
+ clientId: string;
26
+ redirectUri: string;
27
+ resource: string;
28
+ scopes: string[];
29
+ codeChallenge: string;
30
+ state: string | null;
31
+ expiresAt: Date;
32
+ }
33
+ export declare function createAgentAuthorizationRequest(params: CreateAgentAuthorizationRequestParams): Promise<AgentAuthorizationRequest>;
34
+ export declare function createAgentAuthorizationRequestTx(tx: AgentAccessTx, params: CreateAgentAuthorizationRequestParams): Promise<AgentAuthorizationRequest>;
35
+ export declare function findPendingAgentAuthorizationRequest(params: {
36
+ id: string;
37
+ executor?: AgentAccessExecutor;
38
+ }): Promise<AgentAuthorizationRequest | undefined>;
39
+ export declare function consumeAgentAuthorizationRequest(params: {
40
+ id: string;
41
+ }): Promise<AgentAuthorizationRequest | undefined>;
42
+ /**
43
+ * Claims a pending request in the caller's transaction. The expiry and
44
+ * consumed-at predicates are part of the update so concurrent approval and
45
+ * denial can have only one winner.
46
+ */
47
+ export declare function consumeAgentAuthorizationRequestTx(tx: AgentAccessTx, params: {
48
+ id: string;
49
+ }): Promise<AgentAuthorizationRequest | undefined>;
50
+ export interface CreateAgentGrantParams {
51
+ userId: string;
52
+ workspaceId: string;
53
+ /** Internal `auth_agent_clients.id`, not the public OAuth `client_id`. */
54
+ clientId: string;
55
+ scopes: string[];
56
+ }
57
+ export declare function createAgentGrant(params: CreateAgentGrantParams): Promise<AgentGrant>;
58
+ export declare function createAgentGrantTx(tx: AgentAccessTx, params: CreateAgentGrantParams): Promise<AgentGrant>;
59
+ export declare function findAgentGrant(params: {
60
+ id: string;
61
+ executor?: AgentAccessExecutor;
62
+ }): Promise<AgentGrant | undefined>;
63
+ /**
64
+ * Locks one grant row until the surrounding transaction ends. Code exchange
65
+ * and lifecycle transitions must use this same primitive before inspecting or
66
+ * changing grant state. Callers must keep the transaction database-only while
67
+ * this lock is held; external I/O must happen after commit.
68
+ */
69
+ export declare function lockAgentGrant(tx: AgentAccessTx, params: {
70
+ grantId: string;
71
+ }): Promise<AgentGrant | undefined>;
72
+ export interface CreateAgentAuthorizationCodeParams {
73
+ grantId: string;
74
+ hashedCode: string;
75
+ codeChallenge: string;
76
+ redirectUri: string;
77
+ resource: string;
78
+ expiresAt: Date;
79
+ }
80
+ export declare function createAgentAuthorizationCode(params: CreateAgentAuthorizationCodeParams): Promise<AgentAuthorizationCode>;
81
+ export declare function createAgentAuthorizationCodeTx(tx: AgentAccessTx, params: CreateAgentAuthorizationCodeParams): Promise<AgentAuthorizationCode>;
82
+ export declare function findAgentAuthorizationCodeByHash(params: {
83
+ hashedCode: string;
84
+ executor?: AgentAccessExecutor;
85
+ }): Promise<AgentAuthorizationCode | undefined>;
86
+ export declare function findActiveAgentAuthorizationCodeByHash(params: {
87
+ hashedCode: string;
88
+ executor?: AgentAccessExecutor;
89
+ }): Promise<AgentAuthorizationCode | undefined>;
90
+ export declare function consumeAgentAuthorizationCode(params: {
91
+ hashedCode: string;
92
+ }): Promise<AgentAuthorizationCode | undefined>;
93
+ /** Claims an authorization code exactly once, including its 60-second TTL. */
94
+ export declare function consumeAgentAuthorizationCodeTx(tx: AgentAccessTx, params: {
95
+ hashedCode: string;
96
+ }): Promise<AgentAuthorizationCode | undefined>;
97
+ export interface CreateAgentRefreshTokenParams {
98
+ grantId: string;
99
+ hashedToken: string;
100
+ expiresAt: Date;
101
+ }
102
+ export declare function createAgentRefreshToken(params: CreateAgentRefreshTokenParams): Promise<AgentRefreshToken>;
103
+ export declare function createAgentRefreshTokenTx(tx: AgentAccessTx, params: CreateAgentRefreshTokenParams): Promise<AgentRefreshToken>;
104
+ export declare function findAgentRefreshTokenByHash(params: {
105
+ hashedToken: string;
106
+ executor?: AgentAccessExecutor;
107
+ }): Promise<AgentRefreshToken | undefined>;
108
+ export declare function findActiveAgentRefreshTokenByHash(params: {
109
+ hashedToken: string;
110
+ executor?: AgentAccessExecutor;
111
+ }): Promise<AgentRefreshToken | undefined>;
112
+ /** Rotates a refresh token atomically; a concurrent rotation cannot win twice. */
113
+ export declare function rotateAgentRefreshToken(params: {
114
+ hashedToken: string;
115
+ replacementHashedToken: string;
116
+ replacementExpiresAt: Date;
117
+ }): Promise<AgentRefreshToken | undefined>;
118
+ export declare function rotateAgentRefreshTokenTx(tx: AgentAccessTx, params: {
119
+ hashedToken: string;
120
+ replacementHashedToken: string;
121
+ replacementExpiresAt: Date;
122
+ }): Promise<AgentRefreshToken | undefined>;
123
+ export declare function revokeAgentRefreshToken(params: {
124
+ id: string;
125
+ }): Promise<AgentRefreshToken | undefined>;
126
+ export interface CreateAgentPersonalAccessTokenParams {
127
+ userId: string;
128
+ workspaceId: string;
129
+ hashedToken: string;
130
+ prefix: string;
131
+ name: string;
132
+ scopes: string[];
133
+ expiresAt: Date;
134
+ }
135
+ export declare function createAgentPersonalAccessToken(params: CreateAgentPersonalAccessTokenParams): Promise<AgentPersonalAccessToken>;
136
+ export declare function createAgentPersonalAccessTokenTx(tx: AgentAccessTx, params: CreateAgentPersonalAccessTokenParams): Promise<AgentPersonalAccessToken>;
137
+ export declare function findAgentPersonalAccessTokenByHash(params: {
138
+ hashedToken: string;
139
+ executor?: AgentAccessExecutor;
140
+ }): Promise<AgentPersonalAccessToken | undefined>;
141
+ export declare function findActiveAgentPersonalAccessTokenByHash(params: {
142
+ hashedToken: string;
143
+ executor?: AgentAccessExecutor;
144
+ }): Promise<AgentPersonalAccessToken | undefined>;
145
+ export declare function markAgentPersonalAccessTokenUsed(params: {
146
+ id: string;
147
+ }): Promise<AgentPersonalAccessToken | undefined>;
148
+ export declare function revokeAgentPersonalAccessToken(params: {
149
+ id: string;
150
+ }): Promise<AgentPersonalAccessToken | undefined>;
151
+ export interface PruneAgentAccessParams {
152
+ retentionDays?: number;
153
+ limit?: number;
154
+ now?: Date;
155
+ }
156
+ /** Deletes terminal credentials and ephemeral authorization rows in bounded batches. */
157
+ export declare function pruneAgentAccess(params?: PruneAgentAccessParams): Promise<number>;
158
+ export {};
159
+ //# sourceMappingURL=agent-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-access.d.ts","sourceRoot":"","sources":["../../src/db/agent-access.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,UAAU,EACV,wBAAwB,EACxB,iBAAiB,EAClB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAgB3B,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/F,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC;AAEjE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC;CAC7B;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAE7F;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAgBtB;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CASnC;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,2BAA2B,CAC/C,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,WAAW,qCAAqC;IACpD,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,qCAAqC,GAC5C,OAAO,CAAC,yBAAyB,CAAC,CAEpC;AAED,wBAAsB,iCAAiC,CACrD,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,qCAAqC,GAC5C,OAAO,CAAC,yBAAyB,CAAC,CAKpC;AAED,wBAAsB,oCAAoC,CAAC,MAAM,EAAE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAejD;AAED,wBAAsB,gCAAgC,CAAC,MAAM,EAAE;IAC7D,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,kCAAkC,CACtD,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,GACnB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAchD;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAE1F;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,UAAU,CAAC,CA+BrB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CASlC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACxB,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CASjC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,sBAAsB,CAAC,CAEjC;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,sBAAsB,CAAC,CAKjC;AAED,wBAAsB,gCAAgC,CAAC,MAAM,EAAE;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAS9C;AAED,wBAAsB,sCAAsC,CAAC,MAAM,EAAE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAe9C;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAE9C;AAED,8EAA8E;AAC9E,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,GAC3B,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,CAc7C;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CASzC;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAgBzC;AAED,kFAAkF;AAClF,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,IAAI,CAAC;CAC5B,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAEzC;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,IAAI,CAAC;CAC5B,GACA,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAqBxC;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAUzC;AAED,MAAM,WAAW,oCAAoC;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,oCAAoC,GAC3C,OAAO,CAAC,wBAAwB,CAAC,CAEnC;AAED,wBAAsB,gCAAgC,CACpD,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE,oCAAoC,GAC3C,OAAO,CAAC,wBAAwB,CAAC,CAKnC;AAED,wBAAsB,kCAAkC,CAAC,MAAM,EAAE;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAShD;AAED,wBAAsB,wCAAwC,CAAC,MAAM,EAAE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAehD;AAED,wBAAsB,gCAAgC,CAAC,MAAM,EAAE;IAC7D,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAQhD;AAED,wBAAsB,8BAA8B,CAAC,MAAM,EAAE;IAC3D,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAUhD;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wFAAwF;AACxF,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,CAsF3F"}
@@ -0,0 +1,267 @@
1
+ import { and, asc, eq, gt, inArray, isNull, lt, or, sql } from 'drizzle-orm';
2
+ import { db } from './db.js';
3
+ import { agentAuthorizationCodes, agentAuthorizationRequests, agentClients, agentGrants, agentPersonalAccessTokens, agentRefreshTokens, toAgentAuthorizationCode, toAgentAuthorizationRequest, toAgentClient, toAgentGrant, toAgentPersonalAccessToken, toAgentRefreshToken } from './schema/agent-access.js';
4
+ export async function createAgentClient(params) {
5
+ return await db().transaction((tx)=>createAgentClientTx(tx, params));
6
+ }
7
+ export async function createAgentClientTx(tx, params) {
8
+ const rows = await tx.insert(agentClients).values(params).onConflictDoUpdate({
9
+ target: agentClients.clientId,
10
+ set: {
11
+ lastSeenAt: sql`now()`,
12
+ unreferencedAt: null,
13
+ updatedAt: sql`now()`
14
+ }
15
+ }).returning();
16
+ const row = rows[0];
17
+ if (!row) throw new Error('Upsert returned no rows');
18
+ return toAgentClient(row);
19
+ }
20
+ export async function findAgentClientByClientId(params) {
21
+ const executor = params.executor ?? db();
22
+ const rows = await executor.select().from(agentClients).where(eq(agentClients.clientId, params.clientId)).limit(1);
23
+ const row = rows[0];
24
+ return row ? toAgentClient(row) : undefined;
25
+ }
26
+ export async function markAgentClientReferenced(tx, params) {
27
+ await tx.update(agentClients).set({
28
+ unreferencedAt: null,
29
+ lastSeenAt: sql`now()`,
30
+ updatedAt: sql`now()`
31
+ }).where(eq(agentClients.id, params.clientUuid));
32
+ }
33
+ export async function markAgentClientUnreferenced(tx, params) {
34
+ await tx.update(agentClients).set({
35
+ unreferencedAt: sql`now()`,
36
+ updatedAt: sql`now()`
37
+ }).where(eq(agentClients.id, params.clientUuid));
38
+ }
39
+ export async function createAgentAuthorizationRequest(params) {
40
+ return await db().transaction((tx)=>createAgentAuthorizationRequestTx(tx, params));
41
+ }
42
+ export async function createAgentAuthorizationRequestTx(tx, params) {
43
+ const rows = await tx.insert(agentAuthorizationRequests).values(params).returning();
44
+ const row = rows[0];
45
+ if (!row) throw new Error('Insert returned no rows');
46
+ return toAgentAuthorizationRequest(row);
47
+ }
48
+ export async function findPendingAgentAuthorizationRequest(params) {
49
+ const executor = params.executor ?? db();
50
+ const rows = await executor.select().from(agentAuthorizationRequests).where(and(eq(agentAuthorizationRequests.id, params.id), isNull(agentAuthorizationRequests.consumedAt), gt(agentAuthorizationRequests.expiresAt, sql`now()`))).limit(1);
51
+ const row = rows[0];
52
+ return row ? toAgentAuthorizationRequest(row) : undefined;
53
+ }
54
+ export async function consumeAgentAuthorizationRequest(params) {
55
+ return await db().transaction((tx)=>consumeAgentAuthorizationRequestTx(tx, params));
56
+ }
57
+ /**
58
+ * Claims a pending request in the caller's transaction. The expiry and
59
+ * consumed-at predicates are part of the update so concurrent approval and
60
+ * denial can have only one winner.
61
+ */ export async function consumeAgentAuthorizationRequestTx(tx, params) {
62
+ const rows = await tx.update(agentAuthorizationRequests).set({
63
+ consumedAt: sql`now()`,
64
+ updatedAt: sql`now()`
65
+ }).where(and(eq(agentAuthorizationRequests.id, params.id), isNull(agentAuthorizationRequests.consumedAt), gt(agentAuthorizationRequests.expiresAt, sql`now()`))).returning();
66
+ const row = rows[0];
67
+ return row ? toAgentAuthorizationRequest(row) : undefined;
68
+ }
69
+ export async function createAgentGrant(params) {
70
+ return await db().transaction((tx)=>createAgentGrantTx(tx, params));
71
+ }
72
+ export async function createAgentGrantTx(tx, params) {
73
+ const rows = await tx.insert(agentGrants).values(params).onConflictDoUpdate({
74
+ target: [
75
+ agentGrants.userId,
76
+ agentGrants.workspaceId,
77
+ agentGrants.clientId
78
+ ],
79
+ targetWhere: sql`${agentGrants.revokedAt} IS NULL AND ${agentGrants.terminalAt} IS NULL`,
80
+ set: {
81
+ scopes: params.scopes,
82
+ updatedAt: sql`now()`
83
+ }
84
+ }).returning();
85
+ const row = rows[0];
86
+ if (!row) throw new Error('Upsert returned no rows');
87
+ // Refresh tokens derive their scopes from the grant. Reauthorization must
88
+ // invalidate existing tokens before the grant's scopes can be widened.
89
+ await tx.update(agentRefreshTokens).set({
90
+ revokedAt: sql`now()`,
91
+ updatedAt: sql`now()`
92
+ }).where(and(eq(agentRefreshTokens.grantId, row.id), isNull(agentRefreshTokens.rotatedAt), isNull(agentRefreshTokens.revokedAt)));
93
+ await markAgentClientReferenced(tx, {
94
+ clientUuid: params.clientId
95
+ });
96
+ return toAgentGrant(row);
97
+ }
98
+ export async function findAgentGrant(params) {
99
+ const executor = params.executor ?? db();
100
+ const rows = await executor.select().from(agentGrants).where(eq(agentGrants.id, params.id)).limit(1);
101
+ const row = rows[0];
102
+ return row ? toAgentGrant(row) : undefined;
103
+ }
104
+ /**
105
+ * Locks one grant row until the surrounding transaction ends. Code exchange
106
+ * and lifecycle transitions must use this same primitive before inspecting or
107
+ * changing grant state. Callers must keep the transaction database-only while
108
+ * this lock is held; external I/O must happen after commit.
109
+ */ export async function lockAgentGrant(tx, params) {
110
+ const rows = await tx.select().from(agentGrants).where(eq(agentGrants.id, params.grantId)).for('update').limit(1);
111
+ const row = rows[0];
112
+ return row ? toAgentGrant(row) : undefined;
113
+ }
114
+ export async function createAgentAuthorizationCode(params) {
115
+ return await db().transaction((tx)=>createAgentAuthorizationCodeTx(tx, params));
116
+ }
117
+ export async function createAgentAuthorizationCodeTx(tx, params) {
118
+ const rows = await tx.insert(agentAuthorizationCodes).values(params).returning();
119
+ const row = rows[0];
120
+ if (!row) throw new Error('Insert returned no rows');
121
+ return toAgentAuthorizationCode(row);
122
+ }
123
+ export async function findAgentAuthorizationCodeByHash(params) {
124
+ const executor = params.executor ?? db();
125
+ const rows = await executor.select().from(agentAuthorizationCodes).where(eq(agentAuthorizationCodes.hashedCode, params.hashedCode)).limit(1);
126
+ const row = rows[0];
127
+ return row ? toAgentAuthorizationCode(row) : undefined;
128
+ }
129
+ export async function findActiveAgentAuthorizationCodeByHash(params) {
130
+ const executor = params.executor ?? db();
131
+ const rows = await executor.select().from(agentAuthorizationCodes).where(and(eq(agentAuthorizationCodes.hashedCode, params.hashedCode), isNull(agentAuthorizationCodes.consumedAt), gt(agentAuthorizationCodes.expiresAt, sql`now()`))).limit(1);
132
+ const row = rows[0];
133
+ return row ? toAgentAuthorizationCode(row) : undefined;
134
+ }
135
+ export async function consumeAgentAuthorizationCode(params) {
136
+ return await db().transaction((tx)=>consumeAgentAuthorizationCodeTx(tx, params));
137
+ }
138
+ /** Claims an authorization code exactly once, including its 60-second TTL. */ export async function consumeAgentAuthorizationCodeTx(tx, params) {
139
+ const rows = await tx.update(agentAuthorizationCodes).set({
140
+ consumedAt: sql`now()`,
141
+ updatedAt: sql`now()`
142
+ }).where(and(eq(agentAuthorizationCodes.hashedCode, params.hashedCode), isNull(agentAuthorizationCodes.consumedAt), gt(agentAuthorizationCodes.expiresAt, sql`now()`))).returning();
143
+ const row = rows[0];
144
+ return row ? toAgentAuthorizationCode(row) : undefined;
145
+ }
146
+ export async function createAgentRefreshToken(params) {
147
+ return await db().transaction((tx)=>createAgentRefreshTokenTx(tx, params));
148
+ }
149
+ export async function createAgentRefreshTokenTx(tx, params) {
150
+ const rows = await tx.insert(agentRefreshTokens).values(params).returning();
151
+ const row = rows[0];
152
+ if (!row) throw new Error('Insert returned no rows');
153
+ return toAgentRefreshToken(row);
154
+ }
155
+ export async function findAgentRefreshTokenByHash(params) {
156
+ const executor = params.executor ?? db();
157
+ const rows = await executor.select().from(agentRefreshTokens).where(eq(agentRefreshTokens.hashedToken, params.hashedToken)).limit(1);
158
+ const row = rows[0];
159
+ return row ? toAgentRefreshToken(row) : undefined;
160
+ }
161
+ export async function findActiveAgentRefreshTokenByHash(params) {
162
+ const executor = params.executor ?? db();
163
+ const rows = await executor.select().from(agentRefreshTokens).where(and(eq(agentRefreshTokens.hashedToken, params.hashedToken), isNull(agentRefreshTokens.rotatedAt), isNull(agentRefreshTokens.revokedAt), gt(agentRefreshTokens.expiresAt, sql`now()`))).limit(1);
164
+ const row = rows[0];
165
+ return row ? toAgentRefreshToken(row) : undefined;
166
+ }
167
+ /** Rotates a refresh token atomically; a concurrent rotation cannot win twice. */ export async function rotateAgentRefreshToken(params) {
168
+ return await db().transaction((tx)=>rotateAgentRefreshTokenTx(tx, params));
169
+ }
170
+ export async function rotateAgentRefreshTokenTx(tx, params) {
171
+ const rows = await tx.update(agentRefreshTokens).set({
172
+ rotatedAt: sql`now()`,
173
+ lastUsedAt: sql`now()`,
174
+ updatedAt: sql`now()`
175
+ }).where(and(eq(agentRefreshTokens.hashedToken, params.hashedToken), isNull(agentRefreshTokens.rotatedAt), isNull(agentRefreshTokens.revokedAt), gt(agentRefreshTokens.expiresAt, sql`now()`))).returning();
176
+ const row = rows[0];
177
+ if (!row) return undefined;
178
+ return await createAgentRefreshTokenTx(tx, {
179
+ grantId: row.grantId,
180
+ hashedToken: params.replacementHashedToken,
181
+ expiresAt: params.replacementExpiresAt
182
+ });
183
+ }
184
+ export async function revokeAgentRefreshToken(params) {
185
+ return await db().transaction(async (tx)=>{
186
+ const rows = await tx.update(agentRefreshTokens).set({
187
+ revokedAt: sql`now()`,
188
+ updatedAt: sql`now()`
189
+ }).where(and(eq(agentRefreshTokens.id, params.id), isNull(agentRefreshTokens.revokedAt))).returning();
190
+ const row = rows[0];
191
+ return row ? toAgentRefreshToken(row) : undefined;
192
+ });
193
+ }
194
+ export async function createAgentPersonalAccessToken(params) {
195
+ return await db().transaction((tx)=>createAgentPersonalAccessTokenTx(tx, params));
196
+ }
197
+ export async function createAgentPersonalAccessTokenTx(tx, params) {
198
+ const rows = await tx.insert(agentPersonalAccessTokens).values(params).returning();
199
+ const row = rows[0];
200
+ if (!row) throw new Error('Insert returned no rows');
201
+ return toAgentPersonalAccessToken(row);
202
+ }
203
+ export async function findAgentPersonalAccessTokenByHash(params) {
204
+ const executor = params.executor ?? db();
205
+ const rows = await executor.select().from(agentPersonalAccessTokens).where(eq(agentPersonalAccessTokens.hashedToken, params.hashedToken)).limit(1);
206
+ const row = rows[0];
207
+ return row ? toAgentPersonalAccessToken(row) : undefined;
208
+ }
209
+ export async function findActiveAgentPersonalAccessTokenByHash(params) {
210
+ const executor = params.executor ?? db();
211
+ const rows = await executor.select().from(agentPersonalAccessTokens).where(and(eq(agentPersonalAccessTokens.hashedToken, params.hashedToken), isNull(agentPersonalAccessTokens.revokedAt), gt(agentPersonalAccessTokens.expiresAt, sql`now()`))).limit(1);
212
+ const row = rows[0];
213
+ return row ? toAgentPersonalAccessToken(row) : undefined;
214
+ }
215
+ export async function markAgentPersonalAccessTokenUsed(params) {
216
+ const rows = await db().update(agentPersonalAccessTokens).set({
217
+ lastUsedAt: sql`now()`,
218
+ updatedAt: sql`now()`
219
+ }).where(eq(agentPersonalAccessTokens.id, params.id)).returning();
220
+ const row = rows[0];
221
+ return row ? toAgentPersonalAccessToken(row) : undefined;
222
+ }
223
+ export async function revokeAgentPersonalAccessToken(params) {
224
+ const rows = await db().update(agentPersonalAccessTokens).set({
225
+ revokedAt: sql`now()`,
226
+ updatedAt: sql`now()`
227
+ }).where(and(eq(agentPersonalAccessTokens.id, params.id), isNull(agentPersonalAccessTokens.revokedAt))).returning();
228
+ const row = rows[0];
229
+ return row ? toAgentPersonalAccessToken(row) : undefined;
230
+ }
231
+ /** Deletes terminal credentials and ephemeral authorization rows in bounded batches. */ export async function pruneAgentAccess(params = {}) {
232
+ const now = params.now ?? new Date();
233
+ const retentionDays = params.retentionDays ?? 7;
234
+ const limit = params.limit ?? 1000;
235
+ const cutoff = new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1000);
236
+ return await db().transaction(async (tx)=>{
237
+ const requestIds = tx.select({
238
+ id: agentAuthorizationRequests.id
239
+ }).from(agentAuthorizationRequests).where(or(lt(agentAuthorizationRequests.consumedAt, cutoff), and(isNull(agentAuthorizationRequests.consumedAt), lt(agentAuthorizationRequests.expiresAt, cutoff)))).orderBy(asc(agentAuthorizationRequests.expiresAt), asc(agentAuthorizationRequests.id)).limit(limit);
240
+ const codeIds = tx.select({
241
+ id: agentAuthorizationCodes.id
242
+ }).from(agentAuthorizationCodes).where(or(lt(agentAuthorizationCodes.consumedAt, cutoff), and(isNull(agentAuthorizationCodes.consumedAt), lt(agentAuthorizationCodes.expiresAt, cutoff)))).orderBy(asc(agentAuthorizationCodes.expiresAt), asc(agentAuthorizationCodes.id)).limit(limit);
243
+ const refreshTokenIds = tx.select({
244
+ id: agentRefreshTokens.id
245
+ }).from(agentRefreshTokens).where(or(lt(agentRefreshTokens.rotatedAt, cutoff), and(isNull(agentRefreshTokens.rotatedAt), lt(agentRefreshTokens.revokedAt, cutoff)), and(isNull(agentRefreshTokens.rotatedAt), isNull(agentRefreshTokens.revokedAt), lt(agentRefreshTokens.expiresAt, cutoff)))).orderBy(asc(agentRefreshTokens.expiresAt), asc(agentRefreshTokens.id)).limit(limit);
246
+ const patIds = tx.select({
247
+ id: agentPersonalAccessTokens.id
248
+ }).from(agentPersonalAccessTokens).where(or(lt(agentPersonalAccessTokens.revokedAt, cutoff), and(isNull(agentPersonalAccessTokens.revokedAt), lt(agentPersonalAccessTokens.expiresAt, cutoff)))).orderBy(asc(agentPersonalAccessTokens.expiresAt), asc(agentPersonalAccessTokens.id)).limit(limit);
249
+ const [requests, codes, refreshTokens, pats] = await Promise.all([
250
+ tx.delete(agentAuthorizationRequests).where(inArray(agentAuthorizationRequests.id, requestIds)).returning({
251
+ id: agentAuthorizationRequests.id
252
+ }),
253
+ tx.delete(agentAuthorizationCodes).where(inArray(agentAuthorizationCodes.id, codeIds)).returning({
254
+ id: agentAuthorizationCodes.id
255
+ }),
256
+ tx.delete(agentRefreshTokens).where(inArray(agentRefreshTokens.id, refreshTokenIds)).returning({
257
+ id: agentRefreshTokens.id
258
+ }),
259
+ tx.delete(agentPersonalAccessTokens).where(inArray(agentPersonalAccessTokens.id, patIds)).returning({
260
+ id: agentPersonalAccessTokens.id
261
+ })
262
+ ]);
263
+ return requests.length + codes.length + refreshTokens.length + pats.length;
264
+ });
265
+ }
266
+
267
+ //# sourceMappingURL=agent-access.js.map