@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
@@ -1,6 +1,25 @@
1
- import {and, eq, isNull} from 'drizzle-orm';
1
+ import type {AdminRole} from '@shipfox/api-auth-dto';
2
+ import {
3
+ paginateTimestampIdRows,
4
+ type TimestampIdCursor,
5
+ timestampIdCursorWhere,
6
+ } from '@shipfox/node-drizzle';
7
+ import {
8
+ and,
9
+ desc,
10
+ eq,
11
+ isNotNull,
12
+ isNull,
13
+ ne,
14
+ not,
15
+ or,
16
+ type SQL,
17
+ type SQLWrapper,
18
+ sql,
19
+ } from 'drizzle-orm';
2
20
  import {highestAdminRole} from '#core/admin-role-model.js';
3
21
  import type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';
22
+ import type {UserStatus} from '#core/entities/user.js';
4
23
  import type {db} from './db.js';
5
24
  import {adminGrants} from './schema/admin-grants.js';
6
25
  import {users} from './schema/users.js';
@@ -10,6 +29,64 @@ export type AdministratorUserSummaryExecutor = ReturnType<typeof db> | Tx;
10
29
 
11
30
  type AdministratorUserLookup = {id: string; email?: never} | {email: string; id?: never};
12
31
 
32
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33
+ const SEARCH_TERM_SEPARATOR = /\s+/;
34
+
35
+ export interface ListAdministratorUserSummariesParams {
36
+ actorId: string;
37
+ limit: number;
38
+ cursor?: TimestampIdCursor | undefined;
39
+ search?: string | undefined;
40
+ status?: UserStatus | undefined;
41
+ eligible?: boolean | undefined;
42
+ }
43
+
44
+ export interface ListAdministratorUserSummariesResult {
45
+ rows: AdministratorUserSummary[];
46
+ nextCursor: TimestampIdCursor | null;
47
+ }
48
+
49
+ function escapeLikeTerm(term: string): string {
50
+ return term.replace(/[\\%_]/g, (character) => `\\${character}`);
51
+ }
52
+
53
+ function isUuid(value: string): boolean {
54
+ return UUID_PATTERN.test(value);
55
+ }
56
+
57
+ function containsTerm(column: SQLWrapper, term: string): SQLWrapper {
58
+ return sql`${column} ILIKE ${`%${escapeLikeTerm(term)}%`}`;
59
+ }
60
+
61
+ function addEligibilityCondition(
62
+ conditions: SQL[],
63
+ params: Pick<ListAdministratorUserSummariesParams, 'actorId' | 'eligible'>,
64
+ activeAdminRoleUserId: SQLWrapper,
65
+ ): void {
66
+ const eligibleCondition = and(
67
+ eq(users.status, 'active'),
68
+ isNotNull(users.emailVerifiedAt),
69
+ isNull(activeAdminRoleUserId),
70
+ ne(users.id, params.actorId),
71
+ );
72
+ if (eligibleCondition && params.eligible === true) conditions.push(eligibleCondition);
73
+ if (eligibleCondition && params.eligible === false) conditions.push(not(eligibleCondition));
74
+ }
75
+
76
+ function addSearchConditions(conditions: SQL[], search: string | undefined): void {
77
+ const normalizedSearch = search?.trim() ?? '';
78
+ if (!normalizedSearch) return;
79
+ if (isUuid(normalizedSearch)) {
80
+ conditions.push(eq(users.id, normalizedSearch));
81
+ return;
82
+ }
83
+
84
+ for (const term of normalizedSearch.split(SEARCH_TERM_SEPARATOR)) {
85
+ const termCondition = or(containsTerm(users.name, term), containsTerm(users.email, term));
86
+ if (termCondition) conditions.push(termCondition);
87
+ }
88
+ }
89
+
13
90
  export async function findAdministratorUserSummary(
14
91
  executor: AdministratorUserSummaryExecutor,
15
92
  params: AdministratorUserLookup,
@@ -49,3 +126,68 @@ export async function findAdministratorUserSummary(
49
126
  adminRole: highestAdminRole(rows.flatMap(({adminRole}) => (adminRole ? [adminRole] : []))),
50
127
  } satisfies AdministratorUserSummary;
51
128
  }
129
+
130
+ /**
131
+ * Lists a live directory view using keyset pagination.
132
+ *
133
+ * Pages are not a consistent snapshot. Each page reads current account and grant state, so
134
+ * inserts and account or grant changes can make consecutive pages differ. Re-run the listing
135
+ * before acting on a row.
136
+ */
137
+ export async function listAdministratorUserSummaries(
138
+ executor: AdministratorUserSummaryExecutor,
139
+ params: ListAdministratorUserSummariesParams,
140
+ ): Promise<ListAdministratorUserSummariesResult> {
141
+ // Aggregate grants in a subquery so pagination is over users, not grants.
142
+ const activeAdminRoles = executor
143
+ .select({
144
+ userId: adminGrants.userId,
145
+ roles: sql<AdminRole[]>`json_agg(${adminGrants.role})`.as('roles'),
146
+ })
147
+ .from(adminGrants)
148
+ .where(isNull(adminGrants.revokedAt))
149
+ .groupBy(adminGrants.userId)
150
+ .as('active_admin_roles');
151
+
152
+ const conditions: SQL[] = [];
153
+ const cursorCondition = timestampIdCursorWhere({
154
+ timestampColumn: users.createdAt,
155
+ idColumn: users.id,
156
+ cursor: params.cursor,
157
+ });
158
+ if (cursorCondition) conditions.push(cursorCondition);
159
+ if (params.status) conditions.push(eq(users.status, params.status));
160
+
161
+ addEligibilityCondition(conditions, params, activeAdminRoles.userId);
162
+ addSearchConditions(conditions, params.search);
163
+
164
+ const rows = await executor
165
+ .select({
166
+ id: users.id,
167
+ email: users.email,
168
+ name: users.name,
169
+ emailVerifiedAt: users.emailVerifiedAt,
170
+ status: users.status,
171
+ createdAt: users.createdAt,
172
+ adminRoles: activeAdminRoles.roles,
173
+ })
174
+ .from(users)
175
+ .leftJoin(activeAdminRoles, eq(activeAdminRoles.userId, users.id))
176
+ .where(and(...conditions))
177
+ .orderBy(desc(users.createdAt), desc(users.id))
178
+ .limit(params.limit + 1);
179
+
180
+ const mappedRows = rows.map(
181
+ ({adminRoles, ...row}): AdministratorUserSummary => ({
182
+ ...row,
183
+ adminRole: row.status === 'active' ? highestAdminRole(adminRoles ?? []) : null,
184
+ }),
185
+ );
186
+ const page = paginateTimestampIdRows({
187
+ rows: mappedRows,
188
+ limit: params.limit,
189
+ timestampKey: 'createdAt',
190
+ });
191
+
192
+ return {rows: page.pageRows, nextCursor: page.nextCursor};
193
+ }
@@ -1,5 +1,10 @@
1
+ import type {TimestampIdCursor} from '@shipfox/node-drizzle';
1
2
  import type {AdministratorUserSummary} from '#core/entities/administrator-read-model.js';
2
- import {findAdministratorUserSummary} from './admin-user-summary.js';
3
+ import {
4
+ findAdministratorUserSummary,
5
+ type ListAdministratorUserSummariesResult,
6
+ listAdministratorUserSummaries,
7
+ } from './admin-user-summary.js';
3
8
  import {db} from './db.js';
4
9
 
5
10
  export type AdministratorUserRecord = AdministratorUserSummary;
@@ -11,3 +16,18 @@ export async function findAdministratorUser(
11
16
  ): Promise<AdministratorUserRecord | undefined> {
12
17
  return await findAdministratorUserSummary(db(), params);
13
18
  }
19
+
20
+ export interface ListAdministratorUsersParams {
21
+ actorId: string;
22
+ limit: number;
23
+ cursor?: TimestampIdCursor | undefined;
24
+ search?: string | undefined;
25
+ status?: AdministratorUserSummary['status'] | undefined;
26
+ eligible?: boolean | undefined;
27
+ }
28
+
29
+ export async function listAdministratorUsers(
30
+ params: ListAdministratorUsersParams,
31
+ ): Promise<ListAdministratorUserSummariesResult> {
32
+ return await listAdministratorUserSummaries(db(), params);
33
+ }
@@ -0,0 +1,10 @@
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
+
7
+ export const AGENT_ACCESS_RETENTION_BATCH_LIMIT = 200;
8
+ export const AGENT_ACCESS_RETENTION_MAX_ITERATIONS = 1_000;
9
+ export const AGENT_ACCESS_RETENTION_TIME_BUDGET_MS = 4 * 60_000;
10
+ export const AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS = 1_000;