@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
package/src/db/db.ts CHANGED
@@ -2,6 +2,14 @@ import {drizzle, type NodePgDatabase} from '@shipfox/node-drizzle';
2
2
  import {pgClient} from '@shipfox/node-postgres';
3
3
  import {adminCommandResults} from './schema/admin-command-results.js';
4
4
  import {adminGrants} from './schema/admin-grants.js';
5
+ import {
6
+ agentAuthorizationCodes,
7
+ agentAuthorizationRequests,
8
+ agentClients,
9
+ agentGrants,
10
+ agentPersonalAccessTokens,
11
+ agentRefreshTokens,
12
+ } from './schema/agent-access.js';
5
13
  import {authOutbox} from './schema/outbox.js';
6
14
  import {passwordResets} from './schema/password-resets.js';
7
15
  import {authRateLimits} from './schema/rate-limits.js';
@@ -10,6 +18,12 @@ import {users} from './schema/users.js';
10
18
 
11
19
  export const schema = {
12
20
  adminGrants,
21
+ agentClients,
22
+ agentAuthorizationRequests,
23
+ agentGrants,
24
+ agentAuthorizationCodes,
25
+ agentRefreshTokens,
26
+ agentPersonalAccessTokens,
13
27
  adminCommandResults,
14
28
  users,
15
29
  passwordResets,
@@ -0,0 +1,273 @@
1
+ import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';
2
+ import {sql} from 'drizzle-orm';
3
+ import {index, pgEnum, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';
4
+ import type {
5
+ AgentAuthorizationCode,
6
+ AgentAuthorizationRequest,
7
+ AgentClient,
8
+ AgentClientKind,
9
+ AgentGrant,
10
+ AgentPersonalAccessToken,
11
+ AgentRefreshToken,
12
+ } from '#core/entities/agent-access.js';
13
+ import {pgTable} from './common.js';
14
+ import {users} from './users.js';
15
+
16
+ export const agentClientKindEnum = pgEnum('auth_agent_client_kind', ['registered', 'cimd']);
17
+
18
+ export const agentClients = pgTable(
19
+ 'agent_clients',
20
+ {
21
+ id: uuidv7PrimaryKey(),
22
+ clientId: text('client_id').notNull(),
23
+ name: text('name').notNull(),
24
+ redirectUris: text('redirect_uris').array().notNull(),
25
+ kind: agentClientKindEnum('kind').notNull(),
26
+ lastSeenAt: timestamp('last_seen_at', {withTimezone: true}),
27
+ unreferencedAt: timestamp('unreferenced_at', {withTimezone: true}),
28
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
29
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
30
+ },
31
+ (table) => [
32
+ uniqueIndex('auth_agent_clients_client_id_unique').on(table.clientId),
33
+ index('auth_agent_clients_unreferenced_at_idx').on(table.unreferencedAt),
34
+ index('auth_agent_clients_created_at_idx').on(table.createdAt),
35
+ ],
36
+ );
37
+
38
+ export const agentAuthorizationRequests = pgTable(
39
+ 'agent_authorization_requests',
40
+ {
41
+ id: uuidv7PrimaryKey(),
42
+ clientId: uuid('client_id')
43
+ .notNull()
44
+ .references(() => agentClients.id, {onDelete: 'cascade'}),
45
+ redirectUri: text('redirect_uri').notNull(),
46
+ resource: text('resource').notNull(),
47
+ scopes: text('scopes').array().notNull(),
48
+ codeChallenge: text('code_challenge').notNull(),
49
+ state: text('state'),
50
+ expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),
51
+ consumedAt: timestamp('consumed_at', {withTimezone: true}),
52
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
53
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
54
+ },
55
+ (table) => [
56
+ index('auth_agent_authorization_requests_client_id_idx').on(table.clientId),
57
+ index('auth_agent_authorization_requests_expires_at_idx').on(table.expiresAt),
58
+ ],
59
+ );
60
+
61
+ export const agentGrants = pgTable(
62
+ 'agent_grants',
63
+ {
64
+ id: uuidv7PrimaryKey(),
65
+ userId: uuid('user_id')
66
+ .notNull()
67
+ .references(() => users.id, {onDelete: 'cascade'}),
68
+ workspaceId: uuid('workspace_id').notNull(),
69
+ clientId: uuid('client_id')
70
+ .notNull()
71
+ .references(() => agentClients.id, {onDelete: 'cascade'}),
72
+ scopes: text('scopes').array().notNull(),
73
+ lastUsedAt: timestamp('last_used_at', {withTimezone: true}),
74
+ revokedAt: timestamp('revoked_at', {withTimezone: true}),
75
+ terminalAt: timestamp('terminal_at', {withTimezone: true}),
76
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
77
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
78
+ },
79
+ (table) => [
80
+ index('auth_agent_grants_user_id_idx').on(table.userId),
81
+ index('auth_agent_grants_workspace_id_idx').on(table.workspaceId),
82
+ index('auth_agent_grants_client_id_idx').on(table.clientId),
83
+ index('auth_agent_grants_terminal_at_idx').on(table.terminalAt),
84
+ uniqueIndex('auth_agent_grants_active_user_workspace_client_unique')
85
+ .on(table.userId, table.workspaceId, table.clientId)
86
+ .where(sql`${table.revokedAt} IS NULL AND ${table.terminalAt} IS NULL`),
87
+ ],
88
+ );
89
+
90
+ export const agentAuthorizationCodes = pgTable(
91
+ 'agent_authorization_codes',
92
+ {
93
+ id: uuidv7PrimaryKey(),
94
+ grantId: uuid('grant_id')
95
+ .notNull()
96
+ .references(() => agentGrants.id, {onDelete: 'cascade'}),
97
+ hashedCode: text('hashed_code').notNull(),
98
+ codeChallenge: text('code_challenge').notNull(),
99
+ redirectUri: text('redirect_uri').notNull(),
100
+ resource: text('resource').notNull(),
101
+ expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),
102
+ consumedAt: timestamp('consumed_at', {withTimezone: true}),
103
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
104
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
105
+ },
106
+ (table) => [
107
+ uniqueIndex('auth_agent_authorization_codes_hashed_code_unique').on(table.hashedCode),
108
+ index('auth_agent_authorization_codes_grant_id_idx').on(table.grantId),
109
+ index('auth_agent_authorization_codes_expires_at_idx').on(table.expiresAt),
110
+ ],
111
+ );
112
+
113
+ export const agentRefreshTokens = pgTable(
114
+ 'agent_refresh_tokens',
115
+ {
116
+ id: uuidv7PrimaryKey(),
117
+ grantId: uuid('grant_id')
118
+ .notNull()
119
+ .references(() => agentGrants.id, {onDelete: 'cascade'}),
120
+ hashedToken: text('hashed_token').notNull(),
121
+ expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),
122
+ rotatedAt: timestamp('rotated_at', {withTimezone: true}),
123
+ revokedAt: timestamp('revoked_at', {withTimezone: true}),
124
+ lastUsedAt: timestamp('last_used_at', {withTimezone: true}),
125
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
126
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
127
+ },
128
+ (table) => [
129
+ uniqueIndex('auth_agent_refresh_tokens_hashed_token_unique').on(table.hashedToken),
130
+ uniqueIndex('auth_agent_refresh_tokens_live_grant_unique')
131
+ .on(table.grantId)
132
+ .where(sql`${table.rotatedAt} IS NULL AND ${table.revokedAt} IS NULL`),
133
+ index('auth_agent_refresh_tokens_grant_id_idx').on(table.grantId),
134
+ index('auth_agent_refresh_tokens_expires_at_idx').on(table.expiresAt),
135
+ index('auth_agent_refresh_tokens_rotated_at_idx').on(table.rotatedAt),
136
+ index('auth_agent_refresh_tokens_revoked_at_idx').on(table.revokedAt),
137
+ ],
138
+ );
139
+
140
+ export const agentPersonalAccessTokens = pgTable(
141
+ 'agent_pats',
142
+ {
143
+ id: uuidv7PrimaryKey(),
144
+ userId: uuid('user_id')
145
+ .notNull()
146
+ .references(() => users.id, {onDelete: 'cascade'}),
147
+ workspaceId: uuid('workspace_id').notNull(),
148
+ hashedToken: text('hashed_token').notNull(),
149
+ prefix: text('prefix').notNull(),
150
+ name: text('name').notNull(),
151
+ scopes: text('scopes').array().notNull(),
152
+ expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),
153
+ lastUsedAt: timestamp('last_used_at', {withTimezone: true}),
154
+ revokedAt: timestamp('revoked_at', {withTimezone: true}),
155
+ createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
156
+ updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
157
+ },
158
+ (table) => [
159
+ uniqueIndex('auth_agent_pats_hashed_token_unique').on(table.hashedToken),
160
+ index('auth_agent_pats_user_id_idx').on(table.userId),
161
+ index('auth_agent_pats_workspace_id_idx').on(table.workspaceId),
162
+ index('auth_agent_pats_expires_at_idx').on(table.expiresAt),
163
+ index('auth_agent_pats_revoked_at_idx').on(table.revokedAt),
164
+ ],
165
+ );
166
+
167
+ export type AgentClientDb = typeof agentClients.$inferSelect;
168
+ export type AgentClientCreateDb = typeof agentClients.$inferInsert;
169
+ export type AgentAuthorizationRequestDb = typeof agentAuthorizationRequests.$inferSelect;
170
+ export type AgentAuthorizationRequestCreateDb = typeof agentAuthorizationRequests.$inferInsert;
171
+ export type AgentGrantDb = typeof agentGrants.$inferSelect;
172
+ export type AgentGrantCreateDb = typeof agentGrants.$inferInsert;
173
+ export type AgentAuthorizationCodeDb = typeof agentAuthorizationCodes.$inferSelect;
174
+ export type AgentAuthorizationCodeCreateDb = typeof agentAuthorizationCodes.$inferInsert;
175
+ export type AgentRefreshTokenDb = typeof agentRefreshTokens.$inferSelect;
176
+ export type AgentRefreshTokenCreateDb = typeof agentRefreshTokens.$inferInsert;
177
+ export type AgentPersonalAccessTokenDb = typeof agentPersonalAccessTokens.$inferSelect;
178
+ export type AgentPersonalAccessTokenCreateDb = typeof agentPersonalAccessTokens.$inferInsert;
179
+
180
+ export function toAgentClient(row: AgentClientDb): AgentClient {
181
+ return {
182
+ id: row.id,
183
+ clientId: row.clientId,
184
+ name: row.name,
185
+ redirectUris: row.redirectUris,
186
+ kind: row.kind as AgentClientKind,
187
+ lastSeenAt: row.lastSeenAt,
188
+ unreferencedAt: row.unreferencedAt,
189
+ createdAt: row.createdAt,
190
+ updatedAt: row.updatedAt,
191
+ };
192
+ }
193
+
194
+ export function toAgentAuthorizationRequest(
195
+ row: AgentAuthorizationRequestDb,
196
+ ): AgentAuthorizationRequest {
197
+ return {
198
+ id: row.id,
199
+ clientId: row.clientId,
200
+ redirectUri: row.redirectUri,
201
+ resource: row.resource,
202
+ scopes: row.scopes,
203
+ codeChallenge: row.codeChallenge,
204
+ state: row.state,
205
+ expiresAt: row.expiresAt,
206
+ consumedAt: row.consumedAt,
207
+ createdAt: row.createdAt,
208
+ updatedAt: row.updatedAt,
209
+ };
210
+ }
211
+
212
+ export function toAgentGrant(row: AgentGrantDb): AgentGrant {
213
+ return {
214
+ id: row.id,
215
+ userId: row.userId,
216
+ workspaceId: row.workspaceId,
217
+ clientId: row.clientId,
218
+ scopes: row.scopes,
219
+ lastUsedAt: row.lastUsedAt,
220
+ revokedAt: row.revokedAt,
221
+ terminalAt: row.terminalAt,
222
+ createdAt: row.createdAt,
223
+ updatedAt: row.updatedAt,
224
+ };
225
+ }
226
+
227
+ export function toAgentAuthorizationCode(row: AgentAuthorizationCodeDb): AgentAuthorizationCode {
228
+ return {
229
+ id: row.id,
230
+ grantId: row.grantId,
231
+ hashedCode: row.hashedCode,
232
+ codeChallenge: row.codeChallenge,
233
+ redirectUri: row.redirectUri,
234
+ resource: row.resource,
235
+ expiresAt: row.expiresAt,
236
+ consumedAt: row.consumedAt,
237
+ createdAt: row.createdAt,
238
+ updatedAt: row.updatedAt,
239
+ };
240
+ }
241
+
242
+ export function toAgentRefreshToken(row: AgentRefreshTokenDb): AgentRefreshToken {
243
+ return {
244
+ id: row.id,
245
+ grantId: row.grantId,
246
+ hashedToken: row.hashedToken,
247
+ expiresAt: row.expiresAt,
248
+ rotatedAt: row.rotatedAt,
249
+ revokedAt: row.revokedAt,
250
+ lastUsedAt: row.lastUsedAt,
251
+ createdAt: row.createdAt,
252
+ updatedAt: row.updatedAt,
253
+ };
254
+ }
255
+
256
+ export function toAgentPersonalAccessToken(
257
+ row: AgentPersonalAccessTokenDb,
258
+ ): AgentPersonalAccessToken {
259
+ return {
260
+ id: row.id,
261
+ userId: row.userId,
262
+ workspaceId: row.workspaceId,
263
+ hashedToken: row.hashedToken,
264
+ prefix: row.prefix,
265
+ name: row.name,
266
+ scopes: row.scopes,
267
+ expiresAt: row.expiresAt,
268
+ lastUsedAt: row.lastUsedAt,
269
+ revokedAt: row.revokedAt,
270
+ createdAt: row.createdAt,
271
+ updatedAt: row.updatedAt,
272
+ };
273
+ }
@@ -1,5 +1,6 @@
1
1
  import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';
2
- import {pgEnum, text, timestamp, uniqueIndex} from 'drizzle-orm/pg-core';
2
+ import {desc} from 'drizzle-orm';
3
+ import {index, pgEnum, text, timestamp, uniqueIndex} from 'drizzle-orm/pg-core';
3
4
  import type {User, UserStatus} from '#core/entities/user.js';
4
5
  import {pgTable} from './common.js';
5
6
 
@@ -14,10 +15,15 @@ export const users = pgTable(
14
15
  name: text('name'),
15
16
  emailVerifiedAt: timestamp('email_verified_at', {withTimezone: true}),
16
17
  status: userStatusEnum('status').notNull().default('active'),
17
- createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
18
+ createdAt: timestamp('created_at', {withTimezone: true, precision: 3}).notNull().defaultNow(),
18
19
  updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),
19
20
  },
20
- (table) => [uniqueIndex('auth_users_email_unique').on(table.email)],
21
+ (table) => [
22
+ uniqueIndex('auth_users_email_unique').on(table.email),
23
+ index('auth_users_created_at_id_index').on(desc(table.createdAt), desc(table.id)),
24
+ index('auth_users_name_trgm_index').using('gin', table.name.op('gin_trgm_ops')),
25
+ index('auth_users_email_trgm_index').using('gin', table.email.op('gin_trgm_ops')),
26
+ ],
21
27
  );
22
28
 
23
29
  export type UserDb = typeof users.$inferSelect;
package/src/index.test.ts CHANGED
@@ -1,12 +1,21 @@
1
+ import {dirname, resolve} from 'node:path';
2
+ import {fileURLToPath} from 'node:url';
1
3
  import {
2
4
  AUTH_PASSWORD_RESET_SEND_REQUESTED,
3
5
  AUTH_USER_SIGNED_UP,
4
6
  authEventSchemas,
5
7
  } from '@shipfox/api-auth-dto';
6
8
  import {ADMINISTRATION_ACTION_PERFORMED} from '@shipfox/api-common-dto';
9
+ import {createAuthMaintenanceActivities} from '#temporal/activities/index.js';
7
10
  import {createAuthModule} from './index.js';
8
11
  import {passwordLoginMethods} from './login-methods.js';
9
12
 
13
+ const expectedWorkflowsPath = resolve(
14
+ dirname(fileURLToPath(import.meta.url)),
15
+ '..',
16
+ 'dist/temporal/workflows/index.js',
17
+ );
18
+
10
19
  type SignupPolicyLike = {
11
20
  isSignupAllowed(params: {
12
21
  email: string;
@@ -110,4 +119,22 @@ describe('authModule', () => {
110
119
  ]);
111
120
  expect(events).toEqual(expect.arrayContaining([AUTH_PASSWORD_RESET_SEND_REQUESTED]));
112
121
  });
122
+
123
+ test('registers the agent-access retention worker', () => {
124
+ const worker = authModule.workers?.[0];
125
+ expect(worker).toEqual(
126
+ expect.objectContaining({
127
+ taskQueue: 'auth-agent-access-maintenance',
128
+ workflowsPath: expectedWorkflowsPath,
129
+ workflows: [
130
+ {
131
+ name: 'agentAccessRetentionCron',
132
+ id: 'auth-agent-access-retention',
133
+ cronSchedule: '10 * * * *',
134
+ },
135
+ ],
136
+ }),
137
+ );
138
+ expect(worker?.activities).toBe(createAuthMaintenanceActivities);
139
+ });
113
140
  });
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import {dirname, resolve} from 'node:path';
2
+ import {fileURLToPath} from 'node:url';
1
3
  import {
2
4
  AUTH_PASSWORD_RESET_SEND_REQUESTED,
3
5
  type AuthEventMap,
@@ -24,9 +26,13 @@ import {
24
26
  } from '#presentation/routes/administration.js';
25
27
  import {buildAuthRoutes} from '#presentation/routes/index.js';
26
28
  import {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';
29
+ import {createAuthMaintenanceActivities} from '#temporal/activities/index.js';
30
+ import {AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE} from '#temporal/constants.js';
27
31
  import {passwordLoginMethods} from './login-methods.js';
28
32
 
29
33
  const authPublisherEventSchemas = {...authEventSchemas, ...administrationActionEventSchemas};
34
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
35
+ const workflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');
30
36
 
31
37
  export type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';
32
38
  export {
@@ -37,10 +43,15 @@ export {
37
43
  requireAdminRole,
38
44
  revokeAdminGrant,
39
45
  } from '#core/admin-role.js';
46
+ export type {
47
+ ListAdministratorUsersParams,
48
+ ListAdministratorUsersResult,
49
+ } from '#core/administration.js';
40
50
  export {
41
51
  bootstrapFirstAdminOwner,
42
52
  grantAdministratorRole,
43
53
  impersonateUser,
54
+ listAdministratorUsers,
44
55
  reactivateAdministratorUser,
45
56
  revokeAdministratorGrant,
46
57
  revokeAdministratorUserSessions,
@@ -55,6 +66,20 @@ export type {
55
66
  ProvisionUserParams,
56
67
  } from '#core/auth.js';
57
68
  export {createImpersonatedSessionToken, createSessionForUser, provisionUser} from '#core/auth.js';
69
+ export type {
70
+ CimdAddress,
71
+ CimdAddressResolver,
72
+ CimdHttpRequester,
73
+ CimdHttpResponse,
74
+ FetchCimdMetadataOptions,
75
+ FetchedCimdMetadata,
76
+ } from '#core/cimd.js';
77
+ export {
78
+ fetchClientIdMetadata,
79
+ isPublicUnicastAddress,
80
+ OAUTH_CIMD_FETCH_TIMEOUT_MS,
81
+ OAUTH_CIMD_MAX_BODY_BYTES,
82
+ } from '#core/cimd.js';
58
83
  export type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';
59
84
  export {findUserByEmail} from '#core/email-owner.js';
60
85
  export type {AdminGrant} from '#core/entities/admin-grant.js';
@@ -73,8 +98,13 @@ export {
73
98
  ImpersonationExpiredError,
74
99
  ImpersonationTargetNotActiveError,
75
100
  InvalidAdminBootstrapTokenError,
101
+ InvalidAdministratorUserDirectoryFilterError,
76
102
  InvalidCredentialsError,
103
+ InvalidOAuthClientMetadataError,
104
+ InvalidOAuthConfigurationError,
77
105
  LastAdminOwnerError,
106
+ OAuthMetadataFetchError,
107
+ OAuthRedirectUriNotRegisteredError,
78
108
  SignupNotAllowedError,
79
109
  UserNotFoundError,
80
110
  } from '#core/errors.js';
@@ -83,6 +113,38 @@ export {
83
113
  jobLeaseParamsFrom,
84
114
  verifyJobLeaseToken,
85
115
  } from '#core/job-lease-token.js';
116
+ export type {OAuthGrantType, ValidatedOAuthClientMetadata} from '#core/oauth-client.js';
117
+ export {
118
+ assertOAuthClientMetadataMatchesRequest,
119
+ assertOAuthRedirectUriRegistered,
120
+ isOAuthLoopbackRedirectUri,
121
+ metadataForAgentClient,
122
+ OAUTH_CIMD_CACHE_MAX_AGE_SECONDS,
123
+ OAUTH_CLIENT_ID_MAX_BYTES,
124
+ OAUTH_CLIENT_NAME_MAX_BYTES,
125
+ OAUTH_REDIRECT_URI_MAX_BYTES,
126
+ OAUTH_REDIRECT_URI_MAX_COUNT,
127
+ oauthRedirectUriMatches,
128
+ validateOAuthClientId,
129
+ validateOAuthClientMetadataDocument,
130
+ validateOAuthDynamicClientRegistration,
131
+ validateOAuthPublicOrigin,
132
+ validateOAuthRedirectUri,
133
+ } from '#core/oauth-client.js';
134
+ export type {
135
+ OAuthClientResolver,
136
+ OAuthClientResolverOptions,
137
+ RegisteredOAuthClient,
138
+ RegisterOAuthClientParams,
139
+ ResolvedOAuthClient,
140
+ ResolveOAuthClientParams,
141
+ } from '#core/oauth-client-resolver.js';
142
+ export {
143
+ createOAuthClientResolver,
144
+ OAUTH_CIMD_CACHE_MAX_ENTRIES,
145
+ registerOAuthClient,
146
+ resolveOAuthClient,
147
+ } from '#core/oauth-client-resolver.js';
86
148
  export type {SignupDenialMessageFormat, SignupPolicy} from '#core/ports.js';
87
149
  export {
88
150
  issueRunnerSessionToken,
@@ -108,6 +170,12 @@ export {
108
170
  setRefreshTokenCookie,
109
171
  } from '#presentation/auth/refresh-cookie.js';
110
172
  export {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';
173
+ export type {CreateOAuthRoutesOptions} from '#presentation/routes/oauth.js';
174
+ export {
175
+ createOAuthClientIdentificationRoutes,
176
+ createOAuthMetadataRoutes,
177
+ createOAuthRoutes,
178
+ } from '#presentation/routes/oauth.js';
111
179
 
112
180
  const subscriber = subscriberFactory<AuthEventMap>();
113
181
 
@@ -134,6 +202,20 @@ export function createAuthModule({
134
202
  e2eRoutes: [createAuthE2eRoutes(workspaces)],
135
203
  publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authPublisherEventSchemas}],
136
204
  subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],
205
+ workers: [
206
+ {
207
+ taskQueue: AUTH_AGENT_ACCESS_MAINTENANCE_TASK_QUEUE,
208
+ workflowsPath,
209
+ activities: createAuthMaintenanceActivities,
210
+ workflows: [
211
+ {
212
+ name: 'agentAccessRetentionCron',
213
+ id: 'auth-agent-access-retention',
214
+ cronSchedule: '10 * * * *',
215
+ },
216
+ ],
217
+ },
218
+ ],
137
219
  interModulePresentations: [createAuthInterModulePresentation()],
138
220
  };
139
221
  }
@@ -9,7 +9,12 @@ export type AuthRateLimitAction =
9
9
  | 'bootstrap'
10
10
  | 'bootstrap-state'
11
11
  | 'lookup'
12
- | 'impersonate';
12
+ | 'directory'
13
+ | 'impersonate'
14
+ | 'oauth-register'
15
+ | 'oauth-cimd'
16
+ | 'oauth-authorize'
17
+ | 'oauth-token';
13
18
  export type AuthRateLimitScope = 'ip' | 'email' | 'actor';
14
19
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
15
20
  export type AuthImpersonationOutcome = 'succeeded' | 'failed';