@shipfox/api-auth 17.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 (99) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +44 -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/core/runner-session-token.d.ts.map +1 -1
  20. package/dist/core/runner-session-token.js +2 -1
  21. package/dist/core/runner-session-token.js.map +1 -1
  22. package/dist/db/admin-user-moderation.d.ts.map +1 -1
  23. package/dist/db/admin-user-moderation.js +30 -26
  24. package/dist/db/admin-user-moderation.js.map +1 -1
  25. package/dist/db/admin-user-summary.d.ts +22 -0
  26. package/dist/db/admin-user-summary.d.ts.map +1 -1
  27. package/dist/db/admin-user-summary.js +75 -1
  28. package/dist/db/admin-user-summary.js.map +1 -1
  29. package/dist/db/admin-users.d.ts +11 -0
  30. package/dist/db/admin-users.d.ts.map +1 -1
  31. package/dist/db/admin-users.js +4 -1
  32. package/dist/db/admin-users.js.map +1 -1
  33. package/dist/db/agent-access.d.ts +159 -0
  34. package/dist/db/agent-access.d.ts.map +1 -0
  35. package/dist/db/agent-access.js +267 -0
  36. package/dist/db/agent-access.js.map +1 -0
  37. package/dist/db/db.d.ts +2604 -238
  38. package/dist/db/db.d.ts.map +1 -1
  39. package/dist/db/db.js +7 -0
  40. package/dist/db/db.js.map +1 -1
  41. package/dist/db/schema/agent-access.d.ts +1204 -0
  42. package/dist/db/schema/agent-access.d.ts.map +1 -0
  43. package/dist/db/schema/agent-access.js +267 -0
  44. package/dist/db/schema/agent-access.js.map +1 -0
  45. package/dist/db/schema/users.d.ts.map +1 -1
  46. package/dist/db/schema/users.js +8 -3
  47. package/dist/db/schema/users.js.map +1 -1
  48. package/dist/index.d.ts +3 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +2 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/metrics/instance.d.ts +1 -1
  53. package/dist/metrics/instance.d.ts.map +1 -1
  54. package/dist/metrics/instance.js.map +1 -1
  55. package/dist/presentation/routes/administration.d.ts.map +1 -1
  56. package/dist/presentation/routes/administration.js +123 -20
  57. package/dist/presentation/routes/administration.js.map +1 -1
  58. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
  59. package/dist/presentation/routes/email-verification/verify-email-resend.js +6 -1
  60. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
  61. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  62. package/dist/presentation/routes/rate-limit.js +10 -0
  63. package/dist/presentation/routes/rate-limit.js.map +1 -1
  64. package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
  65. package/dist/presentation/routes/registration/signup.js +59 -50
  66. package/dist/presentation/routes/registration/signup.js.map +1 -1
  67. package/dist/tsconfig.test.tsbuildinfo +1 -1
  68. package/drizzle/0003_brainy_luckman.sql +6 -0
  69. package/drizzle/0004_yummy_runaways.sql +110 -0
  70. package/drizzle/meta/0003_snapshot.json +844 -0
  71. package/drizzle/meta/0004_snapshot.json +1751 -0
  72. package/drizzle/meta/_journal.json +14 -0
  73. package/package.json +7 -7
  74. package/src/core/admin-role.test.ts +70 -0
  75. package/src/core/administration.ts +78 -1
  76. package/src/core/auth.ts +3 -5
  77. package/src/core/entities/agent-access.ts +80 -0
  78. package/src/core/errors.ts +7 -0
  79. package/src/core/runner-session-token.test.ts +2 -0
  80. package/src/core/runner-session-token.ts +1 -0
  81. package/src/db/admin-user-moderation.ts +53 -45
  82. package/src/db/admin-user-summary.test.ts +228 -0
  83. package/src/db/admin-user-summary.ts +143 -1
  84. package/src/db/admin-users.ts +21 -1
  85. package/src/db/agent-access.test.ts +370 -0
  86. package/src/db/agent-access.ts +629 -0
  87. package/src/db/db.ts +14 -0
  88. package/src/db/schema/agent-access.ts +273 -0
  89. package/src/db/schema/users.ts +9 -3
  90. package/src/index.ts +6 -0
  91. package/src/metrics/instance.ts +1 -0
  92. package/src/presentation/routes/administration.test.ts +359 -2
  93. package/src/presentation/routes/administration.ts +142 -20
  94. package/src/presentation/routes/email-verification/verify-email-resend.ts +7 -1
  95. package/src/presentation/routes/rate-limit.ts +4 -0
  96. package/src/presentation/routes/registration/signup.ts +52 -41
  97. package/test/globalSetup.ts +1 -1
  98. package/test/routes.ts +3 -1
  99. package/tsconfig.build.tsbuildinfo +1 -1
@@ -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.ts CHANGED
@@ -37,10 +37,15 @@ export {
37
37
  requireAdminRole,
38
38
  revokeAdminGrant,
39
39
  } from '#core/admin-role.js';
40
+ export type {
41
+ ListAdministratorUsersParams,
42
+ ListAdministratorUsersResult,
43
+ } from '#core/administration.js';
40
44
  export {
41
45
  bootstrapFirstAdminOwner,
42
46
  grantAdministratorRole,
43
47
  impersonateUser,
48
+ listAdministratorUsers,
44
49
  reactivateAdministratorUser,
45
50
  revokeAdministratorGrant,
46
51
  revokeAdministratorUserSessions,
@@ -73,6 +78,7 @@ export {
73
78
  ImpersonationExpiredError,
74
79
  ImpersonationTargetNotActiveError,
75
80
  InvalidAdminBootstrapTokenError,
81
+ InvalidAdministratorUserDirectoryFilterError,
76
82
  InvalidCredentialsError,
77
83
  LastAdminOwnerError,
78
84
  SignupNotAllowedError,
@@ -9,6 +9,7 @@ export type AuthRateLimitAction =
9
9
  | 'bootstrap'
10
10
  | 'bootstrap-state'
11
11
  | 'lookup'
12
+ | 'directory'
12
13
  | 'impersonate';
13
14
  export type AuthRateLimitScope = 'ip' | 'email' | 'actor';
14
15
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';