@glinr/theauth 0.5.0 → 0.6.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.
- package/README.md +54 -20
- package/dist/a2a/index.d.ts +3 -3
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +48 -48
- package/dist/agent/index.js.map +1 -1
- package/dist/audit/index.d.ts +2 -2
- package/dist/audit/index.js +48 -48
- package/dist/audit/index.js.map +1 -1
- package/dist/auth/index.d.ts +71 -51
- package/dist/auth/index.js +1156 -241
- package/dist/auth/index.js.map +1 -1
- package/dist/index.d.ts +1204 -1224
- package/dist/index.js +4467 -3520
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/permission/index.d.ts +3 -3
- package/dist/permission/index.js +51 -50
- package/dist/permission/index.js.map +1 -1
- package/dist/redirect/index.d.ts +1 -1
- package/dist/redirect/index.js +1 -1
- package/dist/redirect/index.js.map +1 -1
- package/dist/standards/index.d.ts +1 -1
- package/dist/standards/index.js.map +1 -1
- package/dist/{types-D1dTo8-m.d.ts → types-JOx1R8-Q.d.ts} +494 -499
- package/dist/{types-IkWVkOVv.d.ts → types-XoCxTUZj.d.ts} +2 -6
- package/dist/vc/index.d.ts +6 -12
- package/dist/vc/index.js +4 -7
- package/dist/vc/index.js.map +1 -1
- package/package.json +10 -4
package/dist/auth/index.js
CHANGED
|
@@ -93,7 +93,7 @@ function headerAuth(options) {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
-
var users = sqliteTable("
|
|
96
|
+
var users = sqliteTable("theauth_users", {
|
|
97
97
|
id: text("id").primaryKey(),
|
|
98
98
|
email: text("email").notNull().unique(),
|
|
99
99
|
name: text("name"),
|
|
@@ -109,14 +109,14 @@ var users = sqliteTable("kavach_users", {
|
|
|
109
109
|
banExpiresAt: integer("ban_expires_at", { mode: "timestamp" }),
|
|
110
110
|
forcePasswordReset: integer("force_password_reset").notNull().default(0),
|
|
111
111
|
emailVerified: integer("email_verified").notNull().default(0),
|
|
112
|
-
// Stripe integration fields (populated by
|
|
112
|
+
// Stripe integration fields (populated by theauth-stripe plugin)
|
|
113
113
|
stripeCustomerId: text("stripe_customer_id").unique(),
|
|
114
114
|
stripeSubscriptionId: text("stripe_subscription_id"),
|
|
115
115
|
stripeSubscriptionStatus: text("stripe_subscription_status"),
|
|
116
116
|
stripePriceId: text("stripe_price_id"),
|
|
117
117
|
stripeCurrentPeriodEnd: integer("stripe_current_period_end", { mode: "timestamp" }),
|
|
118
118
|
stripeCancelAtPeriodEnd: integer("stripe_cancel_at_period_end", { mode: "boolean" }).notNull().default(false),
|
|
119
|
-
// Polar integration fields (populated by
|
|
119
|
+
// Polar integration fields (populated by theauth-polar plugin)
|
|
120
120
|
polarCustomerId: text("polar_customer_id").unique(),
|
|
121
121
|
polarSubscriptionId: text("polar_subscription_id"),
|
|
122
122
|
polarSubscriptionStatus: text("polar_subscription_status"),
|
|
@@ -126,7 +126,7 @@ var users = sqliteTable("kavach_users", {
|
|
|
126
126
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
127
127
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
128
128
|
});
|
|
129
|
-
var tenants = sqliteTable("
|
|
129
|
+
var tenants = sqliteTable("theauth_tenants", {
|
|
130
130
|
id: text("id").primaryKey(),
|
|
131
131
|
name: text("name").notNull(),
|
|
132
132
|
slug: text("slug").notNull().unique(),
|
|
@@ -135,7 +135,7 @@ var tenants = sqliteTable("kavach_tenants", {
|
|
|
135
135
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
136
136
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
137
137
|
});
|
|
138
|
-
var agents = sqliteTable("
|
|
138
|
+
var agents = sqliteTable("theauth_agents", {
|
|
139
139
|
id: text("id").primaryKey(),
|
|
140
140
|
ownerId: text("owner_id").notNull().references(() => users.id),
|
|
141
141
|
tenantId: text("tenant_id").references(() => tenants.id),
|
|
@@ -153,7 +153,7 @@ var agents = sqliteTable("kavach_agents", {
|
|
|
153
153
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
154
154
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
155
155
|
});
|
|
156
|
-
var permissions = sqliteTable("
|
|
156
|
+
var permissions = sqliteTable("theauth_permissions", {
|
|
157
157
|
id: text("id").primaryKey(),
|
|
158
158
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
159
159
|
resource: text("resource").notNull(),
|
|
@@ -165,7 +165,7 @@ var permissions = sqliteTable("kavach_permissions", {
|
|
|
165
165
|
relation: text("relation"),
|
|
166
166
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
167
167
|
});
|
|
168
|
-
var delegationChains = sqliteTable("
|
|
168
|
+
var delegationChains = sqliteTable("theauth_delegation_chains", {
|
|
169
169
|
id: text("id").primaryKey(),
|
|
170
170
|
fromAgentId: text("from_agent_id").notNull().references(() => agents.id),
|
|
171
171
|
toAgentId: text("to_agent_id").notNull().references(() => agents.id),
|
|
@@ -176,7 +176,7 @@ var delegationChains = sqliteTable("kavach_delegation_chains", {
|
|
|
176
176
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
177
177
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
178
178
|
});
|
|
179
|
-
var auditLogs = sqliteTable("
|
|
179
|
+
var auditLogs = sqliteTable("theauth_audit_logs", {
|
|
180
180
|
id: text("id").primaryKey(),
|
|
181
181
|
agentId: text("agent_id").notNull().references(() => agents.id),
|
|
182
182
|
userId: text("user_id").notNull().references(() => users.id),
|
|
@@ -196,14 +196,14 @@ var auditLogs = sqliteTable("kavach_audit_logs", {
|
|
|
196
196
|
cacheHit: integer("cache_hit", { mode: "boolean" }).notNull().default(false),
|
|
197
197
|
timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
|
|
198
198
|
});
|
|
199
|
-
sqliteTable("
|
|
199
|
+
sqliteTable("theauth_rate_limits", {
|
|
200
200
|
id: text("id").primaryKey(),
|
|
201
201
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
202
202
|
resource: text("resource").notNull(),
|
|
203
203
|
windowStart: integer("window_start", { mode: "timestamp" }).notNull(),
|
|
204
204
|
count: integer("count").notNull().default(0)
|
|
205
205
|
});
|
|
206
|
-
sqliteTable("
|
|
206
|
+
sqliteTable("theauth_mcp_servers", {
|
|
207
207
|
id: text("id").primaryKey(),
|
|
208
208
|
name: text("name").notNull(),
|
|
209
209
|
endpoint: text("endpoint").notNull().unique(),
|
|
@@ -214,14 +214,14 @@ sqliteTable("kavach_mcp_servers", {
|
|
|
214
214
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
215
215
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
216
216
|
});
|
|
217
|
-
var sessions = sqliteTable("
|
|
217
|
+
var sessions = sqliteTable("theauth_sessions", {
|
|
218
218
|
id: text("id").primaryKey(),
|
|
219
219
|
userId: text("user_id").notNull().references(() => users.id),
|
|
220
220
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
221
221
|
metadata: text("metadata", { mode: "json" }).$type(),
|
|
222
222
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
223
223
|
});
|
|
224
|
-
var oauthClients = sqliteTable("
|
|
224
|
+
var oauthClients = sqliteTable("theauth_oauth_clients", {
|
|
225
225
|
id: text("id").primaryKey(),
|
|
226
226
|
clientId: text("client_id").notNull().unique(),
|
|
227
227
|
clientSecret: text("client_secret"),
|
|
@@ -238,7 +238,7 @@ var oauthClients = sqliteTable("kavach_oauth_clients", {
|
|
|
238
238
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
239
239
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
240
240
|
});
|
|
241
|
-
var oauthAccessTokens = sqliteTable("
|
|
241
|
+
var oauthAccessTokens = sqliteTable("theauth_oauth_access_tokens", {
|
|
242
242
|
id: text("id").primaryKey(),
|
|
243
243
|
accessToken: text("access_token").notNull().unique(),
|
|
244
244
|
refreshToken: text("refresh_token").unique(),
|
|
@@ -252,7 +252,7 @@ var oauthAccessTokens = sqliteTable("kavach_oauth_access_tokens", {
|
|
|
252
252
|
refreshTokenExpiresAt: integer("refresh_token_expires_at", { mode: "timestamp" }),
|
|
253
253
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
254
254
|
});
|
|
255
|
-
var oauthAuthorizationCodes = sqliteTable("
|
|
255
|
+
var oauthAuthorizationCodes = sqliteTable("theauth_oauth_authorization_codes", {
|
|
256
256
|
id: text("id").primaryKey(),
|
|
257
257
|
code: text("code").notNull().unique(),
|
|
258
258
|
clientId: text("client_id").notNull().references(() => oauthClients.clientId),
|
|
@@ -268,7 +268,7 @@ var oauthAuthorizationCodes = sqliteTable("kavach_oauth_authorization_codes", {
|
|
|
268
268
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
269
269
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
270
270
|
});
|
|
271
|
-
var budgetPolicies = sqliteTable("
|
|
271
|
+
var budgetPolicies = sqliteTable("theauth_budget_policies", {
|
|
272
272
|
id: text("id").primaryKey(),
|
|
273
273
|
agentId: text("agent_id").references(() => agents.id, { onDelete: "cascade" }),
|
|
274
274
|
// nullable
|
|
@@ -282,7 +282,7 @@ var budgetPolicies = sqliteTable("kavach_budget_policies", {
|
|
|
282
282
|
status: text("status", { enum: ["active", "triggered", "disabled"] }).notNull().default("active"),
|
|
283
283
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
284
284
|
});
|
|
285
|
-
sqliteTable("
|
|
285
|
+
sqliteTable("theauth_agent_cards", {
|
|
286
286
|
id: text("id").primaryKey(),
|
|
287
287
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
288
288
|
name: text("name").notNull(),
|
|
@@ -296,7 +296,7 @@ sqliteTable("kavach_agent_cards", {
|
|
|
296
296
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
297
297
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
298
298
|
});
|
|
299
|
-
var approvalRequests = sqliteTable("
|
|
299
|
+
var approvalRequests = sqliteTable("theauth_approval_requests", {
|
|
300
300
|
id: text("id").primaryKey(),
|
|
301
301
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
302
302
|
userId: text("user_id").notNull().references(() => users.id),
|
|
@@ -309,7 +309,7 @@ var approvalRequests = sqliteTable("kavach_approval_requests", {
|
|
|
309
309
|
respondedBy: text("responded_by"),
|
|
310
310
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
311
311
|
});
|
|
312
|
-
sqliteTable("
|
|
312
|
+
sqliteTable("theauth_trust_scores", {
|
|
313
313
|
agentId: text("agent_id").primaryKey().references(() => agents.id, { onDelete: "cascade" }),
|
|
314
314
|
score: integer("score").notNull(),
|
|
315
315
|
level: text("level", {
|
|
@@ -318,7 +318,7 @@ sqliteTable("kavach_trust_scores", {
|
|
|
318
318
|
factors: text("factors", { mode: "json" }).notNull().$type(),
|
|
319
319
|
computedAt: integer("computed_at", { mode: "timestamp" }).notNull()
|
|
320
320
|
});
|
|
321
|
-
var magicLinks = sqliteTable("
|
|
321
|
+
var magicLinks = sqliteTable("theauth_magic_links", {
|
|
322
322
|
id: text("id").primaryKey(),
|
|
323
323
|
email: text("email").notNull(),
|
|
324
324
|
token: text("token").notNull().unique(),
|
|
@@ -326,7 +326,7 @@ var magicLinks = sqliteTable("kavach_magic_links", {
|
|
|
326
326
|
used: integer("used", { mode: "boolean" }).notNull().default(false),
|
|
327
327
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
328
328
|
});
|
|
329
|
-
var emailOtps = sqliteTable("
|
|
329
|
+
var emailOtps = sqliteTable("theauth_email_otps", {
|
|
330
330
|
id: text("id").primaryKey(),
|
|
331
331
|
email: text("email").notNull(),
|
|
332
332
|
codeHash: text("code_hash").notNull(),
|
|
@@ -334,7 +334,7 @@ var emailOtps = sqliteTable("kavach_email_otps", {
|
|
|
334
334
|
attempts: integer("attempts").notNull().default(0),
|
|
335
335
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
336
336
|
});
|
|
337
|
-
var totpRecords = sqliteTable("
|
|
337
|
+
var totpRecords = sqliteTable("theauth_totp", {
|
|
338
338
|
userId: text("user_id").primaryKey().references(() => users.id),
|
|
339
339
|
secret: text("secret").notNull(),
|
|
340
340
|
// base32-encoded TOTP secret
|
|
@@ -343,7 +343,7 @@ var totpRecords = sqliteTable("kavach_totp", {
|
|
|
343
343
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
344
344
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
345
345
|
});
|
|
346
|
-
var organizations = sqliteTable("
|
|
346
|
+
var organizations = sqliteTable("theauth_organizations", {
|
|
347
347
|
id: text("id").primaryKey(),
|
|
348
348
|
name: text("name").notNull(),
|
|
349
349
|
slug: text("slug").notNull().unique(),
|
|
@@ -352,14 +352,14 @@ var organizations = sqliteTable("kavach_organizations", {
|
|
|
352
352
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
353
353
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
354
354
|
});
|
|
355
|
-
var orgMembers = sqliteTable("
|
|
355
|
+
var orgMembers = sqliteTable("theauth_org_members", {
|
|
356
356
|
id: text("id").primaryKey(),
|
|
357
357
|
orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
358
358
|
userId: text("user_id").notNull().references(() => users.id),
|
|
359
359
|
role: text("role").notNull().default("member"),
|
|
360
360
|
joinedAt: integer("joined_at", { mode: "timestamp" }).notNull()
|
|
361
361
|
});
|
|
362
|
-
var orgInvitations = sqliteTable("
|
|
362
|
+
var orgInvitations = sqliteTable("theauth_org_invitations", {
|
|
363
363
|
id: text("id").primaryKey(),
|
|
364
364
|
orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
365
365
|
email: text("email").notNull(),
|
|
@@ -369,13 +369,13 @@ var orgInvitations = sqliteTable("kavach_org_invitations", {
|
|
|
369
369
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
370
370
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
371
371
|
});
|
|
372
|
-
var orgRoles = sqliteTable("
|
|
372
|
+
var orgRoles = sqliteTable("theauth_org_roles", {
|
|
373
373
|
id: text("id").primaryKey(),
|
|
374
374
|
orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
375
375
|
name: text("name").notNull(),
|
|
376
376
|
permissions: text("permissions", { mode: "json" }).notNull().$type()
|
|
377
377
|
});
|
|
378
|
-
var passkeyCredentials = sqliteTable("
|
|
378
|
+
var passkeyCredentials = sqliteTable("theauth_passkey_credentials", {
|
|
379
379
|
id: text("id").primaryKey(),
|
|
380
380
|
userId: text("user_id").notNull().references(() => users.id),
|
|
381
381
|
credentialId: text("credential_id").notNull().unique(),
|
|
@@ -388,7 +388,7 @@ var passkeyCredentials = sqliteTable("kavach_passkey_credentials", {
|
|
|
388
388
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
389
389
|
lastUsedAt: integer("last_used_at", { mode: "timestamp" }).notNull()
|
|
390
390
|
});
|
|
391
|
-
var ssoConnections = sqliteTable("
|
|
391
|
+
var ssoConnections = sqliteTable("theauth_sso_connections", {
|
|
392
392
|
id: text("id").primaryKey(),
|
|
393
393
|
orgId: text("org_id").notNull(),
|
|
394
394
|
providerId: text("provider_id").notNull(),
|
|
@@ -397,7 +397,7 @@ var ssoConnections = sqliteTable("kavach_sso_connections", {
|
|
|
397
397
|
enabled: integer("enabled").notNull().default(1),
|
|
398
398
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
399
399
|
});
|
|
400
|
-
var apiKeys = sqliteTable("
|
|
400
|
+
var apiKeys = sqliteTable("theauth_api_keys", {
|
|
401
401
|
id: text("id").primaryKey(),
|
|
402
402
|
userId: text("user_id").notNull().references(() => users.id),
|
|
403
403
|
name: text("name").notNull(),
|
|
@@ -408,7 +408,7 @@ var apiKeys = sqliteTable("kavach_api_keys", {
|
|
|
408
408
|
lastUsedAt: integer("last_used_at", { mode: "timestamp" }),
|
|
409
409
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
410
410
|
});
|
|
411
|
-
var passkeyChallenges = sqliteTable("
|
|
411
|
+
var passkeyChallenges = sqliteTable("theauth_passkey_challenges", {
|
|
412
412
|
id: text("id").primaryKey(),
|
|
413
413
|
challenge: text("challenge").notNull().unique(),
|
|
414
414
|
userId: text("user_id"),
|
|
@@ -417,7 +417,7 @@ var passkeyChallenges = sqliteTable("kavach_passkey_challenges", {
|
|
|
417
417
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
418
418
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
419
419
|
});
|
|
420
|
-
var usernameAccounts = sqliteTable("
|
|
420
|
+
var usernameAccounts = sqliteTable("theauth_username_accounts", {
|
|
421
421
|
id: text("id").primaryKey(),
|
|
422
422
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
423
423
|
username: text("username").notNull().unique(),
|
|
@@ -425,7 +425,7 @@ var usernameAccounts = sqliteTable("kavach_username_accounts", {
|
|
|
425
425
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
426
426
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
427
427
|
});
|
|
428
|
-
var phoneVerifications = sqliteTable("
|
|
428
|
+
var phoneVerifications = sqliteTable("theauth_phone_verifications", {
|
|
429
429
|
id: text("id").primaryKey(),
|
|
430
430
|
phoneNumber: text("phone_number").notNull(),
|
|
431
431
|
codeHash: text("code_hash").notNull(),
|
|
@@ -433,7 +433,7 @@ var phoneVerifications = sqliteTable("kavach_phone_verifications", {
|
|
|
433
433
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
434
434
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
435
435
|
});
|
|
436
|
-
var trustedDevices = sqliteTable("
|
|
436
|
+
var trustedDevices = sqliteTable("theauth_trusted_devices", {
|
|
437
437
|
id: text("id").primaryKey(),
|
|
438
438
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
439
439
|
fingerprint: text("fingerprint").notNull(),
|
|
@@ -443,7 +443,7 @@ var trustedDevices = sqliteTable("kavach_trusted_devices", {
|
|
|
443
443
|
trustedAt: integer("trusted_at", { mode: "timestamp" }).notNull(),
|
|
444
444
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull()
|
|
445
445
|
});
|
|
446
|
-
var oneTimeTokens = sqliteTable("
|
|
446
|
+
var oneTimeTokens = sqliteTable("theauth_one_time_tokens", {
|
|
447
447
|
id: text("id").primaryKey(),
|
|
448
448
|
tokenHash: text("token_hash").notNull().unique(),
|
|
449
449
|
// SHA-256 hex of the raw token
|
|
@@ -457,7 +457,7 @@ var oneTimeTokens = sqliteTable("kavach_one_time_tokens", {
|
|
|
457
457
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
458
458
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
459
459
|
});
|
|
460
|
-
var loginHistory = sqliteTable("
|
|
460
|
+
var loginHistory = sqliteTable("theauth_login_history", {
|
|
461
461
|
id: text("id").primaryKey(),
|
|
462
462
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
463
463
|
method: text("method").notNull(),
|
|
@@ -466,7 +466,7 @@ var loginHistory = sqliteTable("kavach_login_history", {
|
|
|
466
466
|
userAgent: text("user_agent"),
|
|
467
467
|
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull()
|
|
468
468
|
});
|
|
469
|
-
sqliteTable("
|
|
469
|
+
sqliteTable("theauth_agent_dids", {
|
|
470
470
|
agentId: text("agent_id").primaryKey().references(() => agents.id, { onDelete: "cascade" }),
|
|
471
471
|
did: text("did").notNull().unique(),
|
|
472
472
|
method: text("method", { enum: ["key", "web"] }).notNull(),
|
|
@@ -476,7 +476,7 @@ sqliteTable("kavach_agent_dids", {
|
|
|
476
476
|
// JSON-serialised DID Document
|
|
477
477
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
478
478
|
});
|
|
479
|
-
var oidcClients = sqliteTable("
|
|
479
|
+
var oidcClients = sqliteTable("theauth_oidc_clients", {
|
|
480
480
|
id: text("id").primaryKey(),
|
|
481
481
|
clientId: text("client_id").notNull().unique(),
|
|
482
482
|
clientSecretHash: text("client_secret_hash").notNull(),
|
|
@@ -490,7 +490,7 @@ var oidcClients = sqliteTable("kavach_oidc_clients", {
|
|
|
490
490
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
491
491
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
492
492
|
});
|
|
493
|
-
var oidcAuthCodes = sqliteTable("
|
|
493
|
+
var oidcAuthCodes = sqliteTable("theauth_oidc_auth_codes", {
|
|
494
494
|
id: text("id").primaryKey(),
|
|
495
495
|
codeHash: text("code_hash").notNull().unique(),
|
|
496
496
|
// SHA-256 hex of the raw code
|
|
@@ -507,7 +507,7 @@ var oidcAuthCodes = sqliteTable("kavach_oidc_auth_codes", {
|
|
|
507
507
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
508
508
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
509
509
|
});
|
|
510
|
-
var oidcRefreshTokens = sqliteTable("
|
|
510
|
+
var oidcRefreshTokens = sqliteTable("theauth_oidc_refresh_tokens", {
|
|
511
511
|
id: text("id").primaryKey(),
|
|
512
512
|
tokenHash: text("token_hash").notNull().unique(),
|
|
513
513
|
// SHA-256 hex of the raw token
|
|
@@ -519,7 +519,7 @@ var oidcRefreshTokens = sqliteTable("kavach_oidc_refresh_tokens", {
|
|
|
519
519
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
520
520
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
521
521
|
});
|
|
522
|
-
var costEvents = sqliteTable("
|
|
522
|
+
var costEvents = sqliteTable("theauth_cost_events", {
|
|
523
523
|
id: text("id").primaryKey(),
|
|
524
524
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
525
525
|
tool: text("tool").notNull(),
|
|
@@ -534,7 +534,7 @@ var costEvents = sqliteTable("kavach_cost_events", {
|
|
|
534
534
|
// null when not part of a chain
|
|
535
535
|
recordedAt: integer("recorded_at", { mode: "timestamp" }).notNull()
|
|
536
536
|
});
|
|
537
|
-
var ephemeralSessions = sqliteTable("
|
|
537
|
+
var ephemeralSessions = sqliteTable("theauth_ephemeral_sessions", {
|
|
538
538
|
id: text("id").primaryKey(),
|
|
539
539
|
agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
540
540
|
ownerId: text("owner_id").notNull().references(() => users.id),
|
|
@@ -548,7 +548,7 @@ var ephemeralSessions = sqliteTable("kavach_ephemeral_sessions", {
|
|
|
548
548
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
549
549
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
550
550
|
});
|
|
551
|
-
var streamEvents = sqliteTable("
|
|
551
|
+
var streamEvents = sqliteTable("theauth_stream_events", {
|
|
552
552
|
id: text("id").primaryKey(),
|
|
553
553
|
type: text("type").notNull(),
|
|
554
554
|
timestamp: integer("timestamp", { mode: "timestamp" }).notNull(),
|
|
@@ -556,7 +556,7 @@ var streamEvents = sqliteTable("kavach_stream_events", {
|
|
|
556
556
|
agentId: text("agent_id"),
|
|
557
557
|
userId: text("user_id")
|
|
558
558
|
});
|
|
559
|
-
var jwtRefreshTokens = sqliteTable("
|
|
559
|
+
var jwtRefreshTokens = sqliteTable("theauth_jwt_refresh_tokens", {
|
|
560
560
|
id: text("id").primaryKey(),
|
|
561
561
|
/** SHA-256 hex of the raw refresh token. The raw token is never stored. */
|
|
562
562
|
tokenHash: text("token_hash").notNull().unique(),
|
|
@@ -567,7 +567,7 @@ var jwtRefreshTokens = sqliteTable("kavach_jwt_refresh_tokens", {
|
|
|
567
567
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
568
568
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
569
569
|
});
|
|
570
|
-
var rebacResources = sqliteTable("
|
|
570
|
+
var rebacResources = sqliteTable("theauth_rebac_resources", {
|
|
571
571
|
id: text("id").notNull().primaryKey(),
|
|
572
572
|
type: text("type").notNull(),
|
|
573
573
|
// 'org', 'workspace', 'project', 'document', etc.
|
|
@@ -575,7 +575,7 @@ var rebacResources = sqliteTable("kavach_rebac_resources", {
|
|
|
575
575
|
parentType: text("parent_type"),
|
|
576
576
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
577
577
|
});
|
|
578
|
-
var rebacRelationships = sqliteTable("
|
|
578
|
+
var rebacRelationships = sqliteTable("theauth_rebac_relationships", {
|
|
579
579
|
id: text("id").primaryKey(),
|
|
580
580
|
subjectType: text("subject_type").notNull(),
|
|
581
581
|
// 'user', 'agent', 'team', 'role'
|
|
@@ -586,7 +586,7 @@ var rebacRelationships = sqliteTable("kavach_rebac_relationships", {
|
|
|
586
586
|
objectId: text("object_id").notNull(),
|
|
587
587
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
588
588
|
});
|
|
589
|
-
sqliteTable("
|
|
589
|
+
sqliteTable("theauth_federation_instances", {
|
|
590
590
|
id: text("id").primaryKey(),
|
|
591
591
|
instanceId: text("instance_id").notNull().unique(),
|
|
592
592
|
instanceUrl: text("instance_url").notNull(),
|
|
@@ -597,7 +597,7 @@ sqliteTable("kavach_federation_instances", {
|
|
|
597
597
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
598
598
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
599
599
|
});
|
|
600
|
-
sqliteTable("
|
|
600
|
+
sqliteTable("theauth_federation_tokens", {
|
|
601
601
|
id: text("id").primaryKey(),
|
|
602
602
|
tokenJti: text("token_jti").notNull().unique(),
|
|
603
603
|
// JWT ID for dedup
|
|
@@ -611,7 +611,7 @@ sqliteTable("kavach_federation_tokens", {
|
|
|
611
611
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
|
612
612
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
613
613
|
});
|
|
614
|
-
var refreshTokenFamilies = sqliteTable("
|
|
614
|
+
var refreshTokenFamilies = sqliteTable("theauth_refresh_token_families", {
|
|
615
615
|
id: text("id").primaryKey(),
|
|
616
616
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
617
617
|
/** Absolute session expiry — no rotation can extend beyond this date. */
|
|
@@ -620,7 +620,7 @@ var refreshTokenFamilies = sqliteTable("kavach_refresh_token_families", {
|
|
|
620
620
|
revoked: integer("revoked").notNull().default(0),
|
|
621
621
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull()
|
|
622
622
|
});
|
|
623
|
-
sqliteTable("
|
|
623
|
+
sqliteTable("theauth_refresh_tokens", {
|
|
624
624
|
id: text("id").primaryKey(),
|
|
625
625
|
familyId: text("family_id").notNull().references(() => refreshTokenFamilies.id, { onDelete: "cascade" }),
|
|
626
626
|
/** SHA-256 hash of the opaque token — never store the raw token. */
|
|
@@ -728,7 +728,7 @@ function createAdditionalFieldsModule(config, db) {
|
|
|
728
728
|
}
|
|
729
729
|
function additionalFields(config = {}) {
|
|
730
730
|
return {
|
|
731
|
-
id: "
|
|
731
|
+
id: "theauth-additional-fields",
|
|
732
732
|
async init(ctx) {
|
|
733
733
|
const mod = createAdditionalFieldsModule(config, ctx.db);
|
|
734
734
|
ctx.addEndpoint({
|
|
@@ -1081,7 +1081,7 @@ function buildSetCookie(name, value, maxAge, path = "/", secure = true) {
|
|
|
1081
1081
|
// src/auth/admin-plugin.ts
|
|
1082
1082
|
function admin(config) {
|
|
1083
1083
|
return {
|
|
1084
|
-
id: "
|
|
1084
|
+
id: "theauth-admin",
|
|
1085
1085
|
async init(ctx) {
|
|
1086
1086
|
const module = createAdminModule(config ?? {}, ctx.db, ctx.sessionManager ?? null);
|
|
1087
1087
|
ctx.addEndpoint({
|
|
@@ -1510,7 +1510,7 @@ function createAnonymousAuthModule(config, db, sessionManager) {
|
|
|
1510
1510
|
// src/auth/anonymous-plugin.ts
|
|
1511
1511
|
function anonymousAuth(config) {
|
|
1512
1512
|
return {
|
|
1513
|
-
id: "
|
|
1513
|
+
id: "theauth-anonymous",
|
|
1514
1514
|
async init(ctx) {
|
|
1515
1515
|
if (!ctx.sessionManager) {
|
|
1516
1516
|
throw new Error("anonymousAuth plugin requires auth.session.secret to be configured");
|
|
@@ -1521,7 +1521,7 @@ function anonymousAuth(config) {
|
|
|
1521
1521
|
path: "/auth/anonymous",
|
|
1522
1522
|
metadata: {
|
|
1523
1523
|
description: "Create an anonymous guest user and return a session token",
|
|
1524
|
-
rateLimit: { window:
|
|
1524
|
+
rateLimit: { window: 60, max: 20 }
|
|
1525
1525
|
},
|
|
1526
1526
|
async handler(_request, _endpointCtx) {
|
|
1527
1527
|
try {
|
|
@@ -1745,7 +1745,7 @@ function createApiKeyManagerModule(config, db) {
|
|
|
1745
1745
|
// src/auth/api-key-plugin.ts
|
|
1746
1746
|
function apiKeys2(config) {
|
|
1747
1747
|
return {
|
|
1748
|
-
id: "
|
|
1748
|
+
id: "theauth-api-key",
|
|
1749
1749
|
async init(ctx) {
|
|
1750
1750
|
const module = createApiKeyManagerModule(config ?? {}, ctx.db);
|
|
1751
1751
|
ctx.addEndpoint({
|
|
@@ -2214,7 +2214,7 @@ function createCustomSessionModule(_config, db) {
|
|
|
2214
2214
|
}
|
|
2215
2215
|
function customSession(config = {}) {
|
|
2216
2216
|
return {
|
|
2217
|
-
id: "
|
|
2217
|
+
id: "theauth-custom-session",
|
|
2218
2218
|
hooks: {
|
|
2219
2219
|
/**
|
|
2220
2220
|
* Fired by the plugin runner after a session record is inserted.
|
|
@@ -2531,7 +2531,7 @@ function createDeviceAuthModule(config) {
|
|
|
2531
2531
|
}
|
|
2532
2532
|
function deviceAuth(config) {
|
|
2533
2533
|
return {
|
|
2534
|
-
id: "
|
|
2534
|
+
id: "theauth-device-auth",
|
|
2535
2535
|
async init(ctx) {
|
|
2536
2536
|
const mod = createDeviceAuthModule(config);
|
|
2537
2537
|
ctx.addEndpoint({
|
|
@@ -2539,7 +2539,7 @@ function deviceAuth(config) {
|
|
|
2539
2539
|
path: "/auth/device/code",
|
|
2540
2540
|
metadata: {
|
|
2541
2541
|
description: "Request a device code and user code for the device authorization flow",
|
|
2542
|
-
rateLimit: { window:
|
|
2542
|
+
rateLimit: { window: 60, max: 30 }
|
|
2543
2543
|
},
|
|
2544
2544
|
async handler(_request, _endpointCtx) {
|
|
2545
2545
|
const response = await mod.requestCode();
|
|
@@ -2561,7 +2561,7 @@ function deviceAuth(config) {
|
|
|
2561
2561
|
path: "/auth/device/token",
|
|
2562
2562
|
metadata: {
|
|
2563
2563
|
description: "Poll for device authorization status (RFC 8628)",
|
|
2564
|
-
rateLimit: { window:
|
|
2564
|
+
rateLimit: { window: 10, max: 5 }
|
|
2565
2565
|
},
|
|
2566
2566
|
async handler(request, _endpointCtx) {
|
|
2567
2567
|
let body;
|
|
@@ -2799,11 +2799,11 @@ function createEmailOtpModule(config, db, sessionManager) {
|
|
|
2799
2799
|
// src/auth/email-otp-plugin.ts
|
|
2800
2800
|
function emailOtp(config) {
|
|
2801
2801
|
return {
|
|
2802
|
-
id: "
|
|
2802
|
+
id: "theauth-email-otp",
|
|
2803
2803
|
async init(ctx) {
|
|
2804
2804
|
if (!ctx.sessionManager) {
|
|
2805
2805
|
throw new Error(
|
|
2806
|
-
"
|
|
2806
|
+
"theauth-email-otp plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
2807
2807
|
);
|
|
2808
2808
|
}
|
|
2809
2809
|
const module = createEmailOtpModule(config, ctx.db, ctx.sessionManager);
|
|
@@ -3492,8 +3492,8 @@ data: ${JSON.stringify({ clientId, timestamp: (/* @__PURE__ */ new Date()).toISO
|
|
|
3492
3492
|
return { emit, handleRequest, getConnectionCount, replay, close };
|
|
3493
3493
|
}
|
|
3494
3494
|
var DEFAULT_TOKEN_TTL_SECONDS2 = 300;
|
|
3495
|
-
var WELL_KNOWN_PATH = "/.well-known/
|
|
3496
|
-
var FEDERATION_TOKEN_TYPE = "
|
|
3495
|
+
var WELL_KNOWN_PATH = "/.well-known/theauth-federation.json";
|
|
3496
|
+
var FEDERATION_TOKEN_TYPE = "theauth-federation+jwt";
|
|
3497
3497
|
var TrustLevelSchema = z.enum(["full", "limited", "verify-only"]);
|
|
3498
3498
|
var TrustedInstanceSchema = z.object({
|
|
3499
3499
|
instanceId: z.string().min(1),
|
|
@@ -3569,8 +3569,8 @@ function createFederationModule(config) {
|
|
|
3569
3569
|
const now = Math.floor(Date.now() / 1e3);
|
|
3570
3570
|
const exp = now + tokenTtlSeconds;
|
|
3571
3571
|
const payload = {
|
|
3572
|
-
|
|
3573
|
-
|
|
3572
|
+
theauth_instance: instanceId,
|
|
3573
|
+
theauth_instance_url: instanceUrl,
|
|
3574
3574
|
permissions: permissions2,
|
|
3575
3575
|
trust_score: trustScore,
|
|
3576
3576
|
delegation_scope: delegationScope,
|
|
@@ -3693,7 +3693,7 @@ function createFederationModule(config) {
|
|
|
3693
3693
|
data: {
|
|
3694
3694
|
agentId,
|
|
3695
3695
|
sourceInstance: sourceInstanceId,
|
|
3696
|
-
sourceInstanceUrl: claims.
|
|
3696
|
+
sourceInstanceUrl: claims.theauth_instance_url ?? "",
|
|
3697
3697
|
permissions: permissions2,
|
|
3698
3698
|
trustScore: effectiveTrustScore,
|
|
3699
3699
|
delegationScope,
|
|
@@ -4009,7 +4009,7 @@ function createGdprModule(db) {
|
|
|
4009
4009
|
// src/auth/gdpr-plugin.ts
|
|
4010
4010
|
function gdpr() {
|
|
4011
4011
|
return {
|
|
4012
|
-
id: "
|
|
4012
|
+
id: "theauth-gdpr",
|
|
4013
4013
|
async init(ctx) {
|
|
4014
4014
|
const module = createGdprModule(ctx.db);
|
|
4015
4015
|
ctx.addEndpoint({
|
|
@@ -4800,11 +4800,11 @@ function createRateLimiter(config) {
|
|
|
4800
4800
|
// src/auth/magic-link-plugin.ts
|
|
4801
4801
|
function magicLink(config) {
|
|
4802
4802
|
return {
|
|
4803
|
-
id: "
|
|
4803
|
+
id: "theauth-magic-link",
|
|
4804
4804
|
async init(ctx) {
|
|
4805
4805
|
if (!ctx.sessionManager) {
|
|
4806
4806
|
throw new Error(
|
|
4807
|
-
"
|
|
4807
|
+
"theauth-magic-link plugin requires auth.session to be configured so that sessions can be issued on successful verification."
|
|
4808
4808
|
);
|
|
4809
4809
|
}
|
|
4810
4810
|
const module = createMagicLinkModule(config, ctx.db, ctx.sessionManager);
|
|
@@ -4876,7 +4876,7 @@ function base64url(bytes) {
|
|
|
4876
4876
|
}
|
|
4877
4877
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
4878
4878
|
}
|
|
4879
|
-
var oauthAccounts = sqliteTable("
|
|
4879
|
+
var oauthAccounts = sqliteTable("theauth_oauth_accounts", {
|
|
4880
4880
|
id: text("id").primaryKey(),
|
|
4881
4881
|
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
4882
4882
|
/** Provider machine ID, e.g. `'google'`, `'github'`. */
|
|
@@ -4892,7 +4892,7 @@ var oauthAccounts = sqliteTable("kavach_oauth_accounts", {
|
|
|
4892
4892
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
4893
4893
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
|
|
4894
4894
|
});
|
|
4895
|
-
var oauthStates = sqliteTable("
|
|
4895
|
+
var oauthStates = sqliteTable("theauth_oauth_states", {
|
|
4896
4896
|
/** Random, opaque state value sent as the `state` query parameter. */
|
|
4897
4897
|
state: text("state").primaryKey(),
|
|
4898
4898
|
/** PKCE code verifier (plain text — never sent to the provider). */
|
|
@@ -4994,7 +4994,7 @@ function createOAuthModule(db, config) {
|
|
|
4994
4994
|
await db.insert(oauthAccounts).values({
|
|
4995
4995
|
id: accountId,
|
|
4996
4996
|
// userId will be set by the caller via linkAccount once they know the
|
|
4997
|
-
//
|
|
4997
|
+
// theauth user ID. We use a placeholder that foreign-key-checks will
|
|
4998
4998
|
// reject on real databases — callers MUST call linkAccount.
|
|
4999
4999
|
userId: "__pending__",
|
|
5000
5000
|
provider: providerId,
|
|
@@ -5089,14 +5089,14 @@ function redirectResponse(url) {
|
|
|
5089
5089
|
}
|
|
5090
5090
|
function oauth(config) {
|
|
5091
5091
|
return {
|
|
5092
|
-
id: "
|
|
5092
|
+
id: "theauth-oauth",
|
|
5093
5093
|
async init(ctx) {
|
|
5094
5094
|
const module = createOAuthModule(ctx.db, config);
|
|
5095
5095
|
const baseUrl = ctx.config.baseUrl ?? "";
|
|
5096
5096
|
const sessionManager = ctx.sessionManager;
|
|
5097
5097
|
if (!sessionManager) {
|
|
5098
5098
|
throw new Error(
|
|
5099
|
-
"
|
|
5099
|
+
"theauth-oauth plugin requires auth.session to be configured so that sessions can be issued on successful OAuth callback."
|
|
5100
5100
|
);
|
|
5101
5101
|
}
|
|
5102
5102
|
const authorizeLimiter = createRateLimiter({ max: 20, window: 60 });
|
|
@@ -5180,7 +5180,7 @@ function oauth(config) {
|
|
|
5180
5180
|
const { session, token } = await sessionManager.create(userId);
|
|
5181
5181
|
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
|
|
5182
5182
|
const isSecure = baseUrl.startsWith("https://");
|
|
5183
|
-
const cookie = buildSetCookie("
|
|
5183
|
+
const cookie = buildSetCookie("theauth_session", token, maxAge, "/", isSecure);
|
|
5184
5184
|
const userInfo = encodeURIComponent(JSON.stringify({ id: userId, email }));
|
|
5185
5185
|
const callbackUrl = `${baseUrl}/?auth_user=${userInfo}`;
|
|
5186
5186
|
return new Response(null, {
|
|
@@ -7755,7 +7755,7 @@ var OAuthProxyError = class extends Error {
|
|
|
7755
7755
|
// src/auth/oauth-proxy-plugin.ts
|
|
7756
7756
|
function oauthProxy(config) {
|
|
7757
7757
|
return {
|
|
7758
|
-
id: "
|
|
7758
|
+
id: "theauth-oauth-proxy",
|
|
7759
7759
|
async init(ctx) {
|
|
7760
7760
|
const baseUrl = ctx.config.baseUrl;
|
|
7761
7761
|
if (!baseUrl) {
|
|
@@ -7770,7 +7770,7 @@ function oauthProxy(config) {
|
|
|
7770
7770
|
metadata: {
|
|
7771
7771
|
description: "Start an OAuth proxy flow for a mobile app. Returns the provider authorization URL.",
|
|
7772
7772
|
rateLimit: {
|
|
7773
|
-
window:
|
|
7773
|
+
window: config.rateLimit?.windowSeconds ?? 60,
|
|
7774
7774
|
max: config.rateLimit?.max ?? 20
|
|
7775
7775
|
}
|
|
7776
7776
|
},
|
|
@@ -7886,7 +7886,7 @@ function createOidcProviderModule(config, db, getUserClaims) {
|
|
|
7886
7886
|
jwk = { ...config.signingKey };
|
|
7887
7887
|
}
|
|
7888
7888
|
const { d, p, q, dp, dq, qi, k, ...publicComponents } = jwk;
|
|
7889
|
-
return { ...publicComponents, alg: signingAlg, use: "sig", kid: "
|
|
7889
|
+
return { ...publicComponents, alg: signingAlg, use: "sig", kid: "theauth-oidc-1" };
|
|
7890
7890
|
})();
|
|
7891
7891
|
}
|
|
7892
7892
|
return publicJwkPromise;
|
|
@@ -8209,7 +8209,7 @@ function createOidcProviderModule(config, db, getUserClaims) {
|
|
|
8209
8209
|
const accessTokenBuilder = new jose2.SignJWT({
|
|
8210
8210
|
scope: scopes.join(" "),
|
|
8211
8211
|
client_id: clientId
|
|
8212
|
-
}).setProtectedHeader({ alg: signingAlg, kid: "
|
|
8212
|
+
}).setProtectedHeader({ alg: signingAlg, kid: "theauth-oidc-1" }).setIssuer(issuer).setSubject(userId).setAudience(clientId).setIssuedAt(now).setExpirationTime(now + accessTokenTtl).setJti(jti);
|
|
8213
8213
|
const accessToken = await accessTokenBuilder.sign(key);
|
|
8214
8214
|
const userClaims = await getUserClaims(userId, scopes);
|
|
8215
8215
|
const idTokenPayload = {
|
|
@@ -8222,7 +8222,7 @@ function createOidcProviderModule(config, db, getUserClaims) {
|
|
|
8222
8222
|
const atHashBuffer = await sha256Raw(accessToken);
|
|
8223
8223
|
const atHashHalf = atHashBuffer.subarray(0, atHashBuffer.length / 2);
|
|
8224
8224
|
idTokenPayload.at_hash = jose2.base64url.encode(atHashHalf);
|
|
8225
|
-
const idToken = await new jose2.SignJWT(idTokenPayload).setProtectedHeader({ alg: signingAlg, kid: "
|
|
8225
|
+
const idToken = await new jose2.SignJWT(idTokenPayload).setProtectedHeader({ alg: signingAlg, kid: "theauth-oidc-1" }).setIssuer(issuer).setSubject(userId).setAudience(clientId).setIssuedAt(now).setExpirationTime(now + idTokenTtl).sign(key);
|
|
8226
8226
|
const rawRefreshToken = generateRandomHex(REFRESH_TOKEN_BYTE_LENGTH);
|
|
8227
8227
|
const refreshTokenHash = await hashSecret(rawRefreshToken);
|
|
8228
8228
|
const refreshExpiresAt = new Date((now + refreshTokenTtl) * 1e3);
|
|
@@ -8577,12 +8577,12 @@ function createSessionManager(config, db) {
|
|
|
8577
8577
|
// src/auth/one-tap-plugin.ts
|
|
8578
8578
|
function oneTap(config) {
|
|
8579
8579
|
return {
|
|
8580
|
-
id: "
|
|
8580
|
+
id: "theauth-one-tap",
|
|
8581
8581
|
async init(ctx) {
|
|
8582
8582
|
const sessionConfig = ctx.config.auth?.session;
|
|
8583
8583
|
if (!sessionConfig) {
|
|
8584
8584
|
throw new Error(
|
|
8585
|
-
"
|
|
8585
|
+
"theauth-one-tap plugin requires auth.session to be configured so that sessions can be issued on successful sign-in."
|
|
8586
8586
|
);
|
|
8587
8587
|
}
|
|
8588
8588
|
const sessionManager = createSessionManager(sessionConfig, ctx.db);
|
|
@@ -8766,7 +8766,7 @@ function createOneTimeTokenModule(config, db) {
|
|
|
8766
8766
|
var DEFAULT_TITLE = "TheAuth API";
|
|
8767
8767
|
var DEFAULT_VERSION = "0.0.1";
|
|
8768
8768
|
var DEFAULT_SERVER_URL = "/";
|
|
8769
|
-
var DEFAULT_BASE_PATH = "/api/
|
|
8769
|
+
var DEFAULT_BASE_PATH = "/api/theauth";
|
|
8770
8770
|
var ERROR_SCHEMA = {
|
|
8771
8771
|
type: "object",
|
|
8772
8772
|
properties: {
|
|
@@ -10351,7 +10351,7 @@ async function parseBody3(request) {
|
|
|
10351
10351
|
var ADMIN_ROLES = /* @__PURE__ */ new Set(["owner", "admin"]);
|
|
10352
10352
|
function organization(config) {
|
|
10353
10353
|
return {
|
|
10354
|
-
id: "
|
|
10354
|
+
id: "theauth-organization",
|
|
10355
10355
|
async init(ctx) {
|
|
10356
10356
|
const module = createOrgModule(config ?? {}, ctx.db);
|
|
10357
10357
|
ctx.addEndpoint({
|
|
@@ -11394,7 +11394,7 @@ async function parseBody5(request) {
|
|
|
11394
11394
|
}
|
|
11395
11395
|
function passkey(config) {
|
|
11396
11396
|
return {
|
|
11397
|
-
id: "
|
|
11397
|
+
id: "theauth-passkey",
|
|
11398
11398
|
async init(ctx) {
|
|
11399
11399
|
const module = createPasskeyModule(config, ctx.db);
|
|
11400
11400
|
ctx.addEndpoint({
|
|
@@ -11503,7 +11503,7 @@ function passkey(config) {
|
|
|
11503
11503
|
headers: {
|
|
11504
11504
|
"Content-Type": "application/json",
|
|
11505
11505
|
"Set-Cookie": buildSetCookie(
|
|
11506
|
-
"
|
|
11506
|
+
"theauth_session",
|
|
11507
11507
|
token,
|
|
11508
11508
|
maxAge,
|
|
11509
11509
|
"/",
|
|
@@ -11954,8 +11954,8 @@ function createPolarModule(config, db) {
|
|
|
11954
11954
|
const sub = obj;
|
|
11955
11955
|
let userId = null;
|
|
11956
11956
|
const metadata = sub.metadata;
|
|
11957
|
-
if (metadata && typeof metadata === "object" && typeof metadata.
|
|
11958
|
-
userId = metadata.
|
|
11957
|
+
if (metadata && typeof metadata === "object" && typeof metadata.theauth_user_id === "string") {
|
|
11958
|
+
userId = metadata.theauth_user_id;
|
|
11959
11959
|
}
|
|
11960
11960
|
if (!userId && sub.customer_id) {
|
|
11961
11961
|
const user = await findUserByPolarCustomerId(sub.customer_id);
|
|
@@ -11972,8 +11972,8 @@ function createPolarModule(config, db) {
|
|
|
11972
11972
|
const sub = obj;
|
|
11973
11973
|
let userId = null;
|
|
11974
11974
|
const metadata = sub.metadata;
|
|
11975
|
-
if (metadata && typeof metadata === "object" && typeof metadata.
|
|
11976
|
-
userId = metadata.
|
|
11975
|
+
if (metadata && typeof metadata === "object" && typeof metadata.theauth_user_id === "string") {
|
|
11976
|
+
userId = metadata.theauth_user_id;
|
|
11977
11977
|
}
|
|
11978
11978
|
if (!userId && sub.customer_id) {
|
|
11979
11979
|
const user = await findUserByPolarCustomerId(sub.customer_id);
|
|
@@ -11998,7 +11998,7 @@ function createPolarModule(config, db) {
|
|
|
11998
11998
|
}
|
|
11999
11999
|
const body = {
|
|
12000
12000
|
product_id: productId,
|
|
12001
|
-
metadata: {
|
|
12001
|
+
metadata: { theauth_user_id: userId }
|
|
12002
12002
|
};
|
|
12003
12003
|
if (options.successUrl) {
|
|
12004
12004
|
body.success_url = options.successUrl;
|
|
@@ -12095,7 +12095,7 @@ function createPolarModule(config, db) {
|
|
|
12095
12095
|
// src/auth/polar-plugin.ts
|
|
12096
12096
|
function polar(config) {
|
|
12097
12097
|
return {
|
|
12098
|
-
id: "
|
|
12098
|
+
id: "theauth-polar",
|
|
12099
12099
|
async init(ctx) {
|
|
12100
12100
|
const module = createPolarModule(config, ctx.db);
|
|
12101
12101
|
ctx.addEndpoint({
|
|
@@ -12248,7 +12248,7 @@ function rateLimit(config = {}) {
|
|
|
12248
12248
|
const extractKey = config.keyExtractor ?? defaultKeyExtractor2;
|
|
12249
12249
|
const onLimitFn = config.onLimit ?? defaultOnLimit;
|
|
12250
12250
|
return {
|
|
12251
|
-
id: "
|
|
12251
|
+
id: "theauth-rate-limit",
|
|
12252
12252
|
hooks: {
|
|
12253
12253
|
async onRequest(request) {
|
|
12254
12254
|
const url = new URL(request.url);
|
|
@@ -12640,6 +12640,618 @@ function createReBACModule(config, db) {
|
|
|
12640
12640
|
expand
|
|
12641
12641
|
};
|
|
12642
12642
|
}
|
|
12643
|
+
|
|
12644
|
+
// src/auth/scim-filter.ts
|
|
12645
|
+
var ScimFilterError = class extends Error {
|
|
12646
|
+
scimType = "invalidFilter";
|
|
12647
|
+
constructor(message) {
|
|
12648
|
+
super(message);
|
|
12649
|
+
this.name = "ScimFilterError";
|
|
12650
|
+
}
|
|
12651
|
+
};
|
|
12652
|
+
var WORD_RE = /[A-Za-z0-9_\-:.]/;
|
|
12653
|
+
function tokenize(input) {
|
|
12654
|
+
const tokens = [];
|
|
12655
|
+
let i = 0;
|
|
12656
|
+
const n = input.length;
|
|
12657
|
+
while (i < n) {
|
|
12658
|
+
const c = input[i];
|
|
12659
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
12660
|
+
i += 1;
|
|
12661
|
+
continue;
|
|
12662
|
+
}
|
|
12663
|
+
if (c === "(") {
|
|
12664
|
+
tokens.push({ kind: "lparen", value: "(", start: i });
|
|
12665
|
+
i += 1;
|
|
12666
|
+
continue;
|
|
12667
|
+
}
|
|
12668
|
+
if (c === ")") {
|
|
12669
|
+
tokens.push({ kind: "rparen", value: ")", start: i });
|
|
12670
|
+
i += 1;
|
|
12671
|
+
continue;
|
|
12672
|
+
}
|
|
12673
|
+
if (c === "[") {
|
|
12674
|
+
tokens.push({ kind: "lbracket", value: "[", start: i });
|
|
12675
|
+
i += 1;
|
|
12676
|
+
continue;
|
|
12677
|
+
}
|
|
12678
|
+
if (c === "]") {
|
|
12679
|
+
tokens.push({ kind: "rbracket", value: "]", start: i });
|
|
12680
|
+
i += 1;
|
|
12681
|
+
continue;
|
|
12682
|
+
}
|
|
12683
|
+
if (c === '"') {
|
|
12684
|
+
const start = i;
|
|
12685
|
+
i += 1;
|
|
12686
|
+
let value = "";
|
|
12687
|
+
while (i < n && input[i] !== '"') {
|
|
12688
|
+
if (input[i] === "\\" && i + 1 < n) {
|
|
12689
|
+
const next = input[i + 1];
|
|
12690
|
+
if (next === '"' || next === "\\" || next === "/") {
|
|
12691
|
+
value += next;
|
|
12692
|
+
i += 2;
|
|
12693
|
+
continue;
|
|
12694
|
+
}
|
|
12695
|
+
if (next === "t") {
|
|
12696
|
+
value += " ";
|
|
12697
|
+
i += 2;
|
|
12698
|
+
continue;
|
|
12699
|
+
}
|
|
12700
|
+
if (next === "n") {
|
|
12701
|
+
value += "\n";
|
|
12702
|
+
i += 2;
|
|
12703
|
+
continue;
|
|
12704
|
+
}
|
|
12705
|
+
if (next === "r") {
|
|
12706
|
+
value += "\r";
|
|
12707
|
+
i += 2;
|
|
12708
|
+
continue;
|
|
12709
|
+
}
|
|
12710
|
+
value += input[i];
|
|
12711
|
+
i += 1;
|
|
12712
|
+
continue;
|
|
12713
|
+
}
|
|
12714
|
+
value += input[i];
|
|
12715
|
+
i += 1;
|
|
12716
|
+
}
|
|
12717
|
+
if (i >= n) {
|
|
12718
|
+
throw new ScimFilterError(`Unterminated string literal at position ${start}`);
|
|
12719
|
+
}
|
|
12720
|
+
i += 1;
|
|
12721
|
+
tokens.push({ kind: "string", value, start });
|
|
12722
|
+
continue;
|
|
12723
|
+
}
|
|
12724
|
+
if (c === "-" || c >= "0" && c <= "9") {
|
|
12725
|
+
const start = i;
|
|
12726
|
+
let raw = "";
|
|
12727
|
+
if (c === "-") {
|
|
12728
|
+
raw += "-";
|
|
12729
|
+
i += 1;
|
|
12730
|
+
}
|
|
12731
|
+
while (i < n && /[0-9.eE+-]/.test(input[i])) {
|
|
12732
|
+
raw += input[i];
|
|
12733
|
+
i += 1;
|
|
12734
|
+
}
|
|
12735
|
+
if (raw === "" || raw === "-" || Number.isNaN(Number(raw))) {
|
|
12736
|
+
throw new ScimFilterError(`Invalid number literal at position ${start}`);
|
|
12737
|
+
}
|
|
12738
|
+
tokens.push({ kind: "number", value: raw, start });
|
|
12739
|
+
continue;
|
|
12740
|
+
}
|
|
12741
|
+
if (WORD_RE.test(c)) {
|
|
12742
|
+
const start = i;
|
|
12743
|
+
let raw = "";
|
|
12744
|
+
while (i < n && WORD_RE.test(input[i])) {
|
|
12745
|
+
raw += input[i];
|
|
12746
|
+
i += 1;
|
|
12747
|
+
}
|
|
12748
|
+
const lower = raw.toLowerCase();
|
|
12749
|
+
if (lower === "true" || lower === "false") {
|
|
12750
|
+
tokens.push({ kind: "boolean", value: lower, start });
|
|
12751
|
+
continue;
|
|
12752
|
+
}
|
|
12753
|
+
if (lower === "null") {
|
|
12754
|
+
tokens.push({ kind: "null", value: "null", start });
|
|
12755
|
+
continue;
|
|
12756
|
+
}
|
|
12757
|
+
tokens.push({ kind: "word", value: raw, start });
|
|
12758
|
+
continue;
|
|
12759
|
+
}
|
|
12760
|
+
throw new ScimFilterError(`Unexpected character "${c}" at position ${i}`);
|
|
12761
|
+
}
|
|
12762
|
+
tokens.push({ kind: "eof", value: "", start: n });
|
|
12763
|
+
return tokens;
|
|
12764
|
+
}
|
|
12765
|
+
var COMP_OPS = /* @__PURE__ */ new Set(["eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le"]);
|
|
12766
|
+
function parseFilter(input) {
|
|
12767
|
+
const tokens = tokenize(input);
|
|
12768
|
+
let pos = 0;
|
|
12769
|
+
function peek() {
|
|
12770
|
+
return tokens[pos];
|
|
12771
|
+
}
|
|
12772
|
+
function eat() {
|
|
12773
|
+
const t = tokens[pos];
|
|
12774
|
+
pos += 1;
|
|
12775
|
+
return t;
|
|
12776
|
+
}
|
|
12777
|
+
function expectKind(kind) {
|
|
12778
|
+
const t = peek();
|
|
12779
|
+
if (t.kind !== kind) {
|
|
12780
|
+
throw new ScimFilterError(`Expected ${kind} at position ${t.start}, got ${t.kind}`);
|
|
12781
|
+
}
|
|
12782
|
+
return eat();
|
|
12783
|
+
}
|
|
12784
|
+
function matchWord(word) {
|
|
12785
|
+
const t = peek();
|
|
12786
|
+
return t.kind === "word" && t.value.toLowerCase() === word;
|
|
12787
|
+
}
|
|
12788
|
+
function parseAttrPath() {
|
|
12789
|
+
const t = peek();
|
|
12790
|
+
if (t.kind !== "word") {
|
|
12791
|
+
throw new ScimFilterError(`Expected attribute path at position ${t.start}`);
|
|
12792
|
+
}
|
|
12793
|
+
eat();
|
|
12794
|
+
const raw = t.value;
|
|
12795
|
+
const colonIndex = raw.lastIndexOf(":");
|
|
12796
|
+
let schemaUrn;
|
|
12797
|
+
let tail = raw;
|
|
12798
|
+
if (colonIndex > 0 && raw.startsWith("urn:")) {
|
|
12799
|
+
schemaUrn = raw.slice(0, colonIndex);
|
|
12800
|
+
tail = raw.slice(colonIndex + 1);
|
|
12801
|
+
}
|
|
12802
|
+
const parts = tail.split(".").filter((part) => part.length > 0);
|
|
12803
|
+
if (parts.length === 0) {
|
|
12804
|
+
throw new ScimFilterError(`Empty attribute path at position ${t.start}`);
|
|
12805
|
+
}
|
|
12806
|
+
return { schemaUrn, path: parts };
|
|
12807
|
+
}
|
|
12808
|
+
function parseCompValue() {
|
|
12809
|
+
const t = eat();
|
|
12810
|
+
if (t.kind === "string") return t.value;
|
|
12811
|
+
if (t.kind === "number") return Number(t.value);
|
|
12812
|
+
if (t.kind === "boolean") return t.value === "true";
|
|
12813
|
+
if (t.kind === "null") return null;
|
|
12814
|
+
throw new ScimFilterError(`Expected comparison value at position ${t.start}, got ${t.kind}`);
|
|
12815
|
+
}
|
|
12816
|
+
function parsePrimary() {
|
|
12817
|
+
if (matchWord("not")) {
|
|
12818
|
+
eat();
|
|
12819
|
+
expectKind("lparen");
|
|
12820
|
+
const inner = parseOr();
|
|
12821
|
+
expectKind("rparen");
|
|
12822
|
+
return { kind: "not", inner };
|
|
12823
|
+
}
|
|
12824
|
+
if (peek().kind === "lparen") {
|
|
12825
|
+
eat();
|
|
12826
|
+
const inner = parseOr();
|
|
12827
|
+
expectKind("rparen");
|
|
12828
|
+
return inner;
|
|
12829
|
+
}
|
|
12830
|
+
const attr = parseAttrPath();
|
|
12831
|
+
if (peek().kind === "lbracket") {
|
|
12832
|
+
eat();
|
|
12833
|
+
const inner = parseOr();
|
|
12834
|
+
expectKind("rbracket");
|
|
12835
|
+
return { kind: "valuePath", attr, inner };
|
|
12836
|
+
}
|
|
12837
|
+
const opTok = peek();
|
|
12838
|
+
if (opTok.kind !== "word") {
|
|
12839
|
+
throw new ScimFilterError(`Expected operator at position ${opTok.start}`);
|
|
12840
|
+
}
|
|
12841
|
+
const op = opTok.value.toLowerCase();
|
|
12842
|
+
if (op === "pr") {
|
|
12843
|
+
eat();
|
|
12844
|
+
return { kind: "pr", attr };
|
|
12845
|
+
}
|
|
12846
|
+
if (COMP_OPS.has(op)) {
|
|
12847
|
+
eat();
|
|
12848
|
+
const value = parseCompValue();
|
|
12849
|
+
return { kind: "cmp", attr, op, value };
|
|
12850
|
+
}
|
|
12851
|
+
throw new ScimFilterError(`Unknown operator "${opTok.value}" at position ${opTok.start}`);
|
|
12852
|
+
}
|
|
12853
|
+
function parseAnd() {
|
|
12854
|
+
let left = parsePrimary();
|
|
12855
|
+
while (matchWord("and")) {
|
|
12856
|
+
eat();
|
|
12857
|
+
const right = parsePrimary();
|
|
12858
|
+
left = { kind: "and", left, right };
|
|
12859
|
+
}
|
|
12860
|
+
return left;
|
|
12861
|
+
}
|
|
12862
|
+
function parseOr() {
|
|
12863
|
+
let left = parseAnd();
|
|
12864
|
+
while (matchWord("or")) {
|
|
12865
|
+
eat();
|
|
12866
|
+
const right = parseAnd();
|
|
12867
|
+
left = { kind: "or", left, right };
|
|
12868
|
+
}
|
|
12869
|
+
return left;
|
|
12870
|
+
}
|
|
12871
|
+
const ast = parseOr();
|
|
12872
|
+
if (peek().kind !== "eof") {
|
|
12873
|
+
throw new ScimFilterError(`Unexpected trailing input at position ${peek().start}`);
|
|
12874
|
+
}
|
|
12875
|
+
return ast;
|
|
12876
|
+
}
|
|
12877
|
+
function evaluateFilter(ast, resource) {
|
|
12878
|
+
switch (ast.kind) {
|
|
12879
|
+
case "and":
|
|
12880
|
+
return evaluateFilter(ast.left, resource) && evaluateFilter(ast.right, resource);
|
|
12881
|
+
case "or":
|
|
12882
|
+
return evaluateFilter(ast.left, resource) || evaluateFilter(ast.right, resource);
|
|
12883
|
+
case "not":
|
|
12884
|
+
return !evaluateFilter(ast.inner, resource);
|
|
12885
|
+
case "pr":
|
|
12886
|
+
return hasPresent(readPath(resource, ast.attr));
|
|
12887
|
+
case "cmp":
|
|
12888
|
+
return compareAny(readPath(resource, ast.attr), ast.op, ast.value);
|
|
12889
|
+
case "valuePath":
|
|
12890
|
+
return evaluateValuePath(ast, resource);
|
|
12891
|
+
}
|
|
12892
|
+
}
|
|
12893
|
+
function evaluateValuePath(node, resource) {
|
|
12894
|
+
const value = readPath(resource, node.attr);
|
|
12895
|
+
if (value === void 0 || value === null) return false;
|
|
12896
|
+
const items = Array.isArray(value) ? value : [value];
|
|
12897
|
+
for (const item of items) {
|
|
12898
|
+
if (item && typeof item === "object") {
|
|
12899
|
+
if (evaluateFilter(node.inner, item)) return true;
|
|
12900
|
+
}
|
|
12901
|
+
}
|
|
12902
|
+
return false;
|
|
12903
|
+
}
|
|
12904
|
+
function readPath(resource, attr) {
|
|
12905
|
+
let current = resource;
|
|
12906
|
+
if (attr.schemaUrn && attr.schemaUrn !== "urn:ietf:params:scim:schemas:core:2.0:User") {
|
|
12907
|
+
if (current && typeof current === "object") {
|
|
12908
|
+
const record = current;
|
|
12909
|
+
current = record[attr.schemaUrn];
|
|
12910
|
+
}
|
|
12911
|
+
}
|
|
12912
|
+
for (const segment of attr.path) {
|
|
12913
|
+
if (current === void 0 || current === null) return void 0;
|
|
12914
|
+
if (Array.isArray(current)) {
|
|
12915
|
+
const collected = [];
|
|
12916
|
+
for (const item of current) {
|
|
12917
|
+
if (item && typeof item === "object") {
|
|
12918
|
+
const sub = item[segment];
|
|
12919
|
+
if (sub !== void 0) collected.push(sub);
|
|
12920
|
+
}
|
|
12921
|
+
}
|
|
12922
|
+
current = collected.length > 0 ? collected : void 0;
|
|
12923
|
+
continue;
|
|
12924
|
+
}
|
|
12925
|
+
if (typeof current !== "object") return void 0;
|
|
12926
|
+
current = current[segment];
|
|
12927
|
+
}
|
|
12928
|
+
return current;
|
|
12929
|
+
}
|
|
12930
|
+
function hasPresent(value) {
|
|
12931
|
+
if (value === void 0 || value === null) return false;
|
|
12932
|
+
if (typeof value === "string") return value.length > 0;
|
|
12933
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
12934
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
12935
|
+
return true;
|
|
12936
|
+
}
|
|
12937
|
+
function compareAny(raw, op, target) {
|
|
12938
|
+
if (Array.isArray(raw)) {
|
|
12939
|
+
return raw.some((item) => compareScalar(item, op, target));
|
|
12940
|
+
}
|
|
12941
|
+
return compareScalar(raw, op, target);
|
|
12942
|
+
}
|
|
12943
|
+
function compareScalar(raw, op, target) {
|
|
12944
|
+
if (raw === void 0) return false;
|
|
12945
|
+
if (typeof raw === "string" && typeof target === "string") {
|
|
12946
|
+
return compareString(raw, op, target);
|
|
12947
|
+
}
|
|
12948
|
+
if (typeof raw === "number" && typeof target === "number") {
|
|
12949
|
+
return compareNumber(raw, op, target);
|
|
12950
|
+
}
|
|
12951
|
+
if (typeof raw === "boolean" && typeof target === "boolean") {
|
|
12952
|
+
if (op === "eq") return raw === target;
|
|
12953
|
+
if (op === "ne") return raw !== target;
|
|
12954
|
+
return false;
|
|
12955
|
+
}
|
|
12956
|
+
if (raw === null) {
|
|
12957
|
+
if (op === "eq") return target === null;
|
|
12958
|
+
if (op === "ne") return target !== null;
|
|
12959
|
+
return false;
|
|
12960
|
+
}
|
|
12961
|
+
if (op === "ne") return true;
|
|
12962
|
+
return false;
|
|
12963
|
+
}
|
|
12964
|
+
function compareString(raw, op, target) {
|
|
12965
|
+
const a = raw.toLowerCase();
|
|
12966
|
+
const b = target.toLowerCase();
|
|
12967
|
+
switch (op) {
|
|
12968
|
+
case "eq":
|
|
12969
|
+
return a === b;
|
|
12970
|
+
case "ne":
|
|
12971
|
+
return a !== b;
|
|
12972
|
+
case "co":
|
|
12973
|
+
return a.includes(b);
|
|
12974
|
+
case "sw":
|
|
12975
|
+
return a.startsWith(b);
|
|
12976
|
+
case "ew":
|
|
12977
|
+
return a.endsWith(b);
|
|
12978
|
+
case "gt":
|
|
12979
|
+
return a > b;
|
|
12980
|
+
case "ge":
|
|
12981
|
+
return a >= b;
|
|
12982
|
+
case "lt":
|
|
12983
|
+
return a < b;
|
|
12984
|
+
case "le":
|
|
12985
|
+
return a <= b;
|
|
12986
|
+
}
|
|
12987
|
+
}
|
|
12988
|
+
function compareNumber(raw, op, target) {
|
|
12989
|
+
switch (op) {
|
|
12990
|
+
case "eq":
|
|
12991
|
+
return raw === target;
|
|
12992
|
+
case "ne":
|
|
12993
|
+
return raw !== target;
|
|
12994
|
+
case "gt":
|
|
12995
|
+
return raw > target;
|
|
12996
|
+
case "ge":
|
|
12997
|
+
return raw >= target;
|
|
12998
|
+
case "lt":
|
|
12999
|
+
return raw < target;
|
|
13000
|
+
case "le":
|
|
13001
|
+
return raw <= target;
|
|
13002
|
+
case "co":
|
|
13003
|
+
case "sw":
|
|
13004
|
+
case "ew":
|
|
13005
|
+
return false;
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
|
|
13009
|
+
// src/auth/scim-patch.ts
|
|
13010
|
+
var ScimPatchError = class extends Error {
|
|
13011
|
+
scimType;
|
|
13012
|
+
status;
|
|
13013
|
+
constructor(message, scimType, status = 400) {
|
|
13014
|
+
super(message);
|
|
13015
|
+
this.name = "ScimPatchError";
|
|
13016
|
+
this.scimType = scimType;
|
|
13017
|
+
this.status = status;
|
|
13018
|
+
}
|
|
13019
|
+
};
|
|
13020
|
+
var IMMUTABLE_PATHS = /* @__PURE__ */ new Set([
|
|
13021
|
+
"id",
|
|
13022
|
+
"schemas",
|
|
13023
|
+
"meta",
|
|
13024
|
+
"meta.created",
|
|
13025
|
+
"meta.lastmodified",
|
|
13026
|
+
"meta.location",
|
|
13027
|
+
"meta.resourcetype"
|
|
13028
|
+
]);
|
|
13029
|
+
function parsePatchPath(raw) {
|
|
13030
|
+
if (raw.length === 0) {
|
|
13031
|
+
throw new ScimPatchError("Empty PATCH path", "invalidPath");
|
|
13032
|
+
}
|
|
13033
|
+
const bracketIdx = raw.indexOf("[");
|
|
13034
|
+
if (bracketIdx === -1) {
|
|
13035
|
+
const { schemaUrn: schemaUrn2, parts } = splitSchemaUrn(raw);
|
|
13036
|
+
if (parts.length === 0) throw new ScimPatchError("Empty PATCH path", "invalidPath");
|
|
13037
|
+
return {
|
|
13038
|
+
schemaUrn: schemaUrn2,
|
|
13039
|
+
base: parts[0],
|
|
13040
|
+
subPath: parts.length > 1 ? parts.slice(1) : void 0
|
|
13041
|
+
};
|
|
13042
|
+
}
|
|
13043
|
+
const closeIdx = raw.lastIndexOf("]");
|
|
13044
|
+
if (closeIdx < bracketIdx) {
|
|
13045
|
+
throw new ScimPatchError("Unbalanced brackets in PATCH path", "invalidPath");
|
|
13046
|
+
}
|
|
13047
|
+
const headRaw = raw.slice(0, bracketIdx);
|
|
13048
|
+
const filterRaw = raw.slice(bracketIdx + 1, closeIdx);
|
|
13049
|
+
const tailRaw = raw.slice(closeIdx + 1);
|
|
13050
|
+
const { schemaUrn, parts: headParts } = splitSchemaUrn(headRaw);
|
|
13051
|
+
if (headParts.length === 0) {
|
|
13052
|
+
throw new ScimPatchError("PATCH path missing attribute before '['", "invalidPath");
|
|
13053
|
+
}
|
|
13054
|
+
if (headParts.length > 1) {
|
|
13055
|
+
throw new ScimPatchError(
|
|
13056
|
+
"PATCH value-filter must attach to a top-level attribute",
|
|
13057
|
+
"invalidPath"
|
|
13058
|
+
);
|
|
13059
|
+
}
|
|
13060
|
+
let valueFilter;
|
|
13061
|
+
try {
|
|
13062
|
+
valueFilter = parseFilter(filterRaw);
|
|
13063
|
+
} catch (err2) {
|
|
13064
|
+
if (err2 instanceof ScimFilterError) {
|
|
13065
|
+
throw new ScimPatchError(`Invalid value filter: ${err2.message}`, "invalidPath");
|
|
13066
|
+
}
|
|
13067
|
+
throw err2;
|
|
13068
|
+
}
|
|
13069
|
+
let subPath;
|
|
13070
|
+
if (tailRaw.length > 0) {
|
|
13071
|
+
if (!tailRaw.startsWith(".")) {
|
|
13072
|
+
throw new ScimPatchError(
|
|
13073
|
+
`Unexpected characters after value filter: "${tailRaw}"`,
|
|
13074
|
+
"invalidPath"
|
|
13075
|
+
);
|
|
13076
|
+
}
|
|
13077
|
+
subPath = tailRaw.slice(1).split(".").filter((p) => p.length > 0);
|
|
13078
|
+
if (subPath.length === 0) {
|
|
13079
|
+
throw new ScimPatchError("Trailing '.' in PATCH path", "invalidPath");
|
|
13080
|
+
}
|
|
13081
|
+
}
|
|
13082
|
+
return { schemaUrn, base: headParts[0], valueFilter, subPath };
|
|
13083
|
+
}
|
|
13084
|
+
function splitSchemaUrn(raw) {
|
|
13085
|
+
if (!raw.startsWith("urn:")) {
|
|
13086
|
+
return { parts: raw.split(".").filter((p) => p.length > 0) };
|
|
13087
|
+
}
|
|
13088
|
+
const colonIndex = raw.lastIndexOf(":");
|
|
13089
|
+
if (colonIndex < 4) return { parts: raw.split(".").filter((p) => p.length > 0) };
|
|
13090
|
+
const schemaUrn = raw.slice(0, colonIndex);
|
|
13091
|
+
const tail = raw.slice(colonIndex + 1);
|
|
13092
|
+
return { schemaUrn, parts: tail.split(".").filter((p) => p.length > 0) };
|
|
13093
|
+
}
|
|
13094
|
+
function applyPatchOps(resource, operations, options = {}) {
|
|
13095
|
+
const cap = options.maxOperations ?? 1e3;
|
|
13096
|
+
if (operations.length > cap) {
|
|
13097
|
+
throw new ScimPatchError(
|
|
13098
|
+
`PATCH has ${operations.length} operations, limit is ${cap}`,
|
|
13099
|
+
"tooMany",
|
|
13100
|
+
413
|
|
13101
|
+
);
|
|
13102
|
+
}
|
|
13103
|
+
const extraReadonly = new Set((options.readonlyPaths ?? []).map((p) => p.toLowerCase()));
|
|
13104
|
+
for (const rawOp of operations) {
|
|
13105
|
+
const op = rawOp.op?.toLowerCase();
|
|
13106
|
+
if (op !== "add" && op !== "replace" && op !== "remove") {
|
|
13107
|
+
throw new ScimPatchError(`Unknown PATCH op "${rawOp.op}"`, "invalidValue");
|
|
13108
|
+
}
|
|
13109
|
+
if (!rawOp.path) {
|
|
13110
|
+
if (op === "remove") {
|
|
13111
|
+
throw new ScimPatchError("PATCH remove requires a path", "noTarget");
|
|
13112
|
+
}
|
|
13113
|
+
if (rawOp.value === void 0 || rawOp.value === null) continue;
|
|
13114
|
+
if (typeof rawOp.value !== "object") {
|
|
13115
|
+
throw new ScimPatchError(
|
|
13116
|
+
"PATCH value must be an object when path is absent",
|
|
13117
|
+
"invalidValue"
|
|
13118
|
+
);
|
|
13119
|
+
}
|
|
13120
|
+
mergeInto(resource, rawOp.value, op, extraReadonly);
|
|
13121
|
+
continue;
|
|
13122
|
+
}
|
|
13123
|
+
let path;
|
|
13124
|
+
try {
|
|
13125
|
+
path = parsePatchPath(rawOp.path);
|
|
13126
|
+
} catch (err2) {
|
|
13127
|
+
if (err2 instanceof ScimPatchError) throw err2;
|
|
13128
|
+
throw new ScimPatchError(`Invalid PATCH path "${rawOp.path}"`, "invalidPath");
|
|
13129
|
+
}
|
|
13130
|
+
const joinedPath = joinPath(path);
|
|
13131
|
+
if (IMMUTABLE_PATHS.has(joinedPath) || extraReadonly.has(joinedPath)) {
|
|
13132
|
+
throw new ScimPatchError(`Attribute "${joinedPath}" is immutable`, "mutability");
|
|
13133
|
+
}
|
|
13134
|
+
applyToResource(resource, path, op, rawOp.value);
|
|
13135
|
+
}
|
|
13136
|
+
return resource;
|
|
13137
|
+
}
|
|
13138
|
+
function joinPath(path) {
|
|
13139
|
+
const head = path.schemaUrn ? `${path.schemaUrn}:${path.base}` : path.base;
|
|
13140
|
+
const sub = path.subPath && path.subPath.length > 0 ? `.${path.subPath.join(".")}` : "";
|
|
13141
|
+
return `${head}${sub}`.toLowerCase();
|
|
13142
|
+
}
|
|
13143
|
+
function containerFor(resource, schemaUrn) {
|
|
13144
|
+
if (!schemaUrn) return resource;
|
|
13145
|
+
const existing = resource[schemaUrn];
|
|
13146
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
13147
|
+
return existing;
|
|
13148
|
+
}
|
|
13149
|
+
const next = {};
|
|
13150
|
+
resource[schemaUrn] = next;
|
|
13151
|
+
return next;
|
|
13152
|
+
}
|
|
13153
|
+
function mergeInto(target, source, op, readonlyPaths) {
|
|
13154
|
+
for (const [key, value] of Object.entries(source)) {
|
|
13155
|
+
const lower = key.toLowerCase();
|
|
13156
|
+
if (IMMUTABLE_PATHS.has(lower) || readonlyPaths.has(lower)) {
|
|
13157
|
+
throw new ScimPatchError(`Attribute "${key}" is immutable`, "mutability");
|
|
13158
|
+
}
|
|
13159
|
+
if (op === "add" && Array.isArray(target[key]) && Array.isArray(value)) {
|
|
13160
|
+
target[key] = target[key].concat(value);
|
|
13161
|
+
continue;
|
|
13162
|
+
}
|
|
13163
|
+
target[key] = value;
|
|
13164
|
+
}
|
|
13165
|
+
}
|
|
13166
|
+
function applyToResource(resource, path, op, value) {
|
|
13167
|
+
const container = containerFor(resource, path.schemaUrn);
|
|
13168
|
+
const currentBase = container[path.base];
|
|
13169
|
+
if (path.valueFilter) {
|
|
13170
|
+
if (!Array.isArray(currentBase)) {
|
|
13171
|
+
throw new ScimPatchError(
|
|
13172
|
+
`Value filter requires "${path.base}" to be an array`,
|
|
13173
|
+
"invalidPath"
|
|
13174
|
+
);
|
|
13175
|
+
}
|
|
13176
|
+
const arr = currentBase;
|
|
13177
|
+
if (op === "remove") {
|
|
13178
|
+
container[path.base] = arr.filter(
|
|
13179
|
+
(item) => !evaluateFilter(path.valueFilter, item)
|
|
13180
|
+
);
|
|
13181
|
+
return;
|
|
13182
|
+
}
|
|
13183
|
+
for (const item of arr) {
|
|
13184
|
+
if (!evaluateFilter(path.valueFilter, item)) continue;
|
|
13185
|
+
if (path.subPath && path.subPath.length > 0) {
|
|
13186
|
+
setDeep(item, path.subPath, value);
|
|
13187
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
13188
|
+
mergeInto(item, value, op, /* @__PURE__ */ new Set());
|
|
13189
|
+
} else {
|
|
13190
|
+
throw new ScimPatchError(
|
|
13191
|
+
"Value filter without subPath requires object value",
|
|
13192
|
+
"invalidValue"
|
|
13193
|
+
);
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
return;
|
|
13197
|
+
}
|
|
13198
|
+
if (op === "remove") {
|
|
13199
|
+
if (path.subPath && path.subPath.length > 0) {
|
|
13200
|
+
deleteDeep(currentBase, path.subPath);
|
|
13201
|
+
} else {
|
|
13202
|
+
container[path.base] = void 0;
|
|
13203
|
+
}
|
|
13204
|
+
return;
|
|
13205
|
+
}
|
|
13206
|
+
if (path.subPath && path.subPath.length > 0) {
|
|
13207
|
+
const host = ensureObject(container, path.base);
|
|
13208
|
+
setDeep(host, path.subPath, value);
|
|
13209
|
+
return;
|
|
13210
|
+
}
|
|
13211
|
+
if (op === "add" && Array.isArray(currentBase) && Array.isArray(value)) {
|
|
13212
|
+
container[path.base] = currentBase.concat(value);
|
|
13213
|
+
return;
|
|
13214
|
+
}
|
|
13215
|
+
container[path.base] = value;
|
|
13216
|
+
}
|
|
13217
|
+
function ensureObject(container, key) {
|
|
13218
|
+
const current = container[key];
|
|
13219
|
+
if (current && typeof current === "object" && !Array.isArray(current)) {
|
|
13220
|
+
return current;
|
|
13221
|
+
}
|
|
13222
|
+
const next = {};
|
|
13223
|
+
container[key] = next;
|
|
13224
|
+
return next;
|
|
13225
|
+
}
|
|
13226
|
+
function setDeep(target, path, value) {
|
|
13227
|
+
if (!target || typeof target !== "object") return;
|
|
13228
|
+
let cursor = target;
|
|
13229
|
+
for (let i = 0; i < path.length - 1; i += 1) {
|
|
13230
|
+
const segment = path[i];
|
|
13231
|
+
const next = cursor[segment];
|
|
13232
|
+
if (next && typeof next === "object" && !Array.isArray(next)) {
|
|
13233
|
+
cursor = next;
|
|
13234
|
+
} else {
|
|
13235
|
+
const fresh = {};
|
|
13236
|
+
cursor[segment] = fresh;
|
|
13237
|
+
cursor = fresh;
|
|
13238
|
+
}
|
|
13239
|
+
}
|
|
13240
|
+
cursor[path[path.length - 1]] = value;
|
|
13241
|
+
}
|
|
13242
|
+
function deleteDeep(target, path) {
|
|
13243
|
+
if (!target || typeof target !== "object") return;
|
|
13244
|
+
let cursor = target;
|
|
13245
|
+
for (let i = 0; i < path.length - 1; i += 1) {
|
|
13246
|
+
const segment = path[i];
|
|
13247
|
+
const next = cursor[segment];
|
|
13248
|
+
if (!next || typeof next !== "object") return;
|
|
13249
|
+
cursor = next;
|
|
13250
|
+
}
|
|
13251
|
+
cursor[path[path.length - 1]] = void 0;
|
|
13252
|
+
}
|
|
13253
|
+
|
|
13254
|
+
// src/auth/scim.ts
|
|
12643
13255
|
var SCIM_CONTENT_TYPE = "application/scim+json";
|
|
12644
13256
|
var SCHEMA_USER = "urn:ietf:params:scim:schemas:core:2.0:User";
|
|
12645
13257
|
var SCHEMA_GROUP = "urn:ietf:params:scim:schemas:core:2.0:Group";
|
|
@@ -12648,6 +13260,7 @@ var SCHEMA_PATCH = "urn:ietf:params:scim:api:messages:2.0:PatchOp";
|
|
|
12648
13260
|
var SCHEMA_ERROR = "urn:ietf:params:scim:api:messages:2.0:Error";
|
|
12649
13261
|
var SCHEMA_SP_CONFIG = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig";
|
|
12650
13262
|
var SCHEMA_RESOURCE_TYPE = "urn:ietf:params:scim:schemas:core:2.0:ResourceType";
|
|
13263
|
+
var SCHEMA_ENTERPRISE = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
|
|
12651
13264
|
function scimResponse(body, status = 200) {
|
|
12652
13265
|
return new Response(JSON.stringify(body), {
|
|
12653
13266
|
status,
|
|
@@ -12682,8 +13295,13 @@ function userRowToScim(row, baseUrl) {
|
|
|
12682
13295
|
const primaryEmail = row.email;
|
|
12683
13296
|
const meta = row.metadata ?? {};
|
|
12684
13297
|
const active = meta["scim:active"] !== false && row.banned === 0;
|
|
12685
|
-
|
|
12686
|
-
|
|
13298
|
+
const enterprise = meta["scim:enterprise"];
|
|
13299
|
+
const schemas = [SCHEMA_USER];
|
|
13300
|
+
if (enterprise && typeof enterprise === "object" && Object.keys(enterprise).length > 0) {
|
|
13301
|
+
schemas.push("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User");
|
|
13302
|
+
}
|
|
13303
|
+
const body = {
|
|
13304
|
+
schemas,
|
|
12687
13305
|
id: row.id,
|
|
12688
13306
|
externalId: row.externalId ?? void 0,
|
|
12689
13307
|
userName: row.username ?? row.email,
|
|
@@ -12702,6 +13320,10 @@ function userRowToScim(row, baseUrl) {
|
|
|
12702
13320
|
location: `${baseUrl}/scim/v2/Users/${row.id}`
|
|
12703
13321
|
}
|
|
12704
13322
|
};
|
|
13323
|
+
if (enterprise && typeof enterprise === "object") {
|
|
13324
|
+
body["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"] = enterprise;
|
|
13325
|
+
}
|
|
13326
|
+
return body;
|
|
12705
13327
|
}
|
|
12706
13328
|
function orgRowToScim(row, members, memberEmails, baseUrl) {
|
|
12707
13329
|
const meta = row.metadata ?? {};
|
|
@@ -12723,35 +13345,71 @@ function orgRowToScim(row, members, memberEmails, baseUrl) {
|
|
|
12723
13345
|
}
|
|
12724
13346
|
};
|
|
12725
13347
|
}
|
|
12726
|
-
function
|
|
13348
|
+
function flattenToLegacyClauses(ast) {
|
|
12727
13349
|
const clauses = [];
|
|
12728
|
-
const
|
|
12729
|
-
|
|
12730
|
-
const
|
|
12731
|
-
|
|
12732
|
-
|
|
13350
|
+
const stack = [ast];
|
|
13351
|
+
while (stack.length > 0) {
|
|
13352
|
+
const node = stack.pop();
|
|
13353
|
+
if (node.kind === "and") {
|
|
13354
|
+
stack.push(node.right, node.left);
|
|
13355
|
+
continue;
|
|
13356
|
+
}
|
|
13357
|
+
if (node.kind === "pr") {
|
|
13358
|
+
clauses.push({ attribute: attrKey(node.attr.path), op: "pr" });
|
|
13359
|
+
continue;
|
|
13360
|
+
}
|
|
13361
|
+
if (node.kind === "cmp") {
|
|
13362
|
+
if (node.op !== "eq" && node.op !== "co" && node.op !== "sw") return null;
|
|
13363
|
+
if (typeof node.value !== "string" && typeof node.value !== "boolean") return null;
|
|
12733
13364
|
clauses.push({
|
|
12734
|
-
attribute:
|
|
12735
|
-
op:
|
|
12736
|
-
value:
|
|
13365
|
+
attribute: attrKey(node.attr.path),
|
|
13366
|
+
op: node.op,
|
|
13367
|
+
value: typeof node.value === "boolean" ? String(node.value) : node.value
|
|
12737
13368
|
});
|
|
12738
13369
|
continue;
|
|
12739
13370
|
}
|
|
12740
|
-
|
|
12741
|
-
if (matchPr) {
|
|
12742
|
-
clauses.push({ attribute: matchPr[1].toLowerCase(), op: "pr" });
|
|
12743
|
-
}
|
|
13371
|
+
return null;
|
|
12744
13372
|
}
|
|
12745
13373
|
return clauses;
|
|
12746
13374
|
}
|
|
13375
|
+
function attrKey(path) {
|
|
13376
|
+
return path.join(".").toLowerCase();
|
|
13377
|
+
}
|
|
13378
|
+
function parseScimFilter(filter) {
|
|
13379
|
+
const ast = parseFilter(filter);
|
|
13380
|
+
const clauses = flattenToLegacyClauses(ast);
|
|
13381
|
+
return { ast, clauses };
|
|
13382
|
+
}
|
|
12747
13383
|
function createScimModule(config, db) {
|
|
12748
13384
|
const {
|
|
12749
13385
|
bearerToken,
|
|
12750
13386
|
autoCreateUsers = true,
|
|
12751
13387
|
autoDeactivateUsers = true,
|
|
12752
13388
|
onProvision,
|
|
12753
|
-
onDeprovision
|
|
13389
|
+
onDeprovision,
|
|
13390
|
+
resolveSelf,
|
|
13391
|
+
audit
|
|
12754
13392
|
} = config;
|
|
13393
|
+
async function writeScimAudit(action, resource, subjectUserId, durationMs, result = "allowed", reason) {
|
|
13394
|
+
if (!audit || audit.enabled === false) return;
|
|
13395
|
+
try {
|
|
13396
|
+
await db.insert(auditLogs).values({
|
|
13397
|
+
id: generateId2(),
|
|
13398
|
+
agentId: audit.agentId,
|
|
13399
|
+
userId: subjectUserId,
|
|
13400
|
+
action,
|
|
13401
|
+
resource,
|
|
13402
|
+
parameters: {},
|
|
13403
|
+
result,
|
|
13404
|
+
reason: reason ?? null,
|
|
13405
|
+
durationMs,
|
|
13406
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
13407
|
+
ip: null,
|
|
13408
|
+
userAgent: null
|
|
13409
|
+
});
|
|
13410
|
+
} catch {
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
12755
13413
|
function isAuthorized(request) {
|
|
12756
13414
|
const authHeader = request.headers.get("Authorization");
|
|
12757
13415
|
if (!authHeader) return false;
|
|
@@ -12762,64 +13420,144 @@ function createScimModule(config, db) {
|
|
|
12762
13420
|
const url = new URL(request.url);
|
|
12763
13421
|
return `${url.protocol}//${url.host}`;
|
|
12764
13422
|
}
|
|
13423
|
+
function parseSortSpec(url) {
|
|
13424
|
+
const by = url.searchParams.get("sortBy");
|
|
13425
|
+
if (!by) return null;
|
|
13426
|
+
const order = url.searchParams.get("sortOrder")?.toLowerCase();
|
|
13427
|
+
return {
|
|
13428
|
+
by: by.split(".").filter((p) => p.length > 0),
|
|
13429
|
+
order: order === "descending" ? "descending" : "ascending"
|
|
13430
|
+
};
|
|
13431
|
+
}
|
|
13432
|
+
function sortResources(items, spec) {
|
|
13433
|
+
const direction = spec.order === "descending" ? -1 : 1;
|
|
13434
|
+
return [...items].sort((a, b) => {
|
|
13435
|
+
const av = readAttr(a.scim, spec.by);
|
|
13436
|
+
const bv = readAttr(b.scim, spec.by);
|
|
13437
|
+
const primary = compareSortValues(av, bv);
|
|
13438
|
+
if (primary !== 0) return primary * direction;
|
|
13439
|
+
const ida = a.scim.id ?? "";
|
|
13440
|
+
const idb = b.scim.id ?? "";
|
|
13441
|
+
return ida < idb ? -1 : ida > idb ? 1 : 0;
|
|
13442
|
+
});
|
|
13443
|
+
}
|
|
13444
|
+
function readAttr(resource, path) {
|
|
13445
|
+
let current = resource;
|
|
13446
|
+
for (const segment of path) {
|
|
13447
|
+
if (current === void 0 || current === null) return void 0;
|
|
13448
|
+
if (typeof current !== "object") return void 0;
|
|
13449
|
+
current = current[segment];
|
|
13450
|
+
}
|
|
13451
|
+
return current;
|
|
13452
|
+
}
|
|
13453
|
+
function compareSortValues(a, b) {
|
|
13454
|
+
if (a === void 0 && b === void 0) return 0;
|
|
13455
|
+
if (a === void 0) return 1;
|
|
13456
|
+
if (b === void 0) return -1;
|
|
13457
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
13458
|
+
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
13459
|
+
}
|
|
13460
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
13461
|
+
return a - b;
|
|
13462
|
+
}
|
|
13463
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
13464
|
+
return a === b ? 0 : a ? 1 : -1;
|
|
13465
|
+
}
|
|
13466
|
+
return String(a).localeCompare(String(b));
|
|
13467
|
+
}
|
|
12765
13468
|
async function handleListUsers(request) {
|
|
12766
13469
|
const url = new URL(request.url);
|
|
12767
13470
|
const filterParam = url.searchParams.get("filter") ?? "";
|
|
12768
13471
|
const startIndex = Math.max(1, parseInt(url.searchParams.get("startIndex") ?? "1", 10));
|
|
12769
13472
|
const count = Math.min(200, parseInt(url.searchParams.get("count") ?? "100", 10));
|
|
12770
13473
|
const baseUrl = getBaseUrl(request);
|
|
12771
|
-
const
|
|
13474
|
+
const sortSpec = parseSortSpec(url);
|
|
13475
|
+
let ast = null;
|
|
13476
|
+
let clauses = null;
|
|
12772
13477
|
if (filterParam) {
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
} else if (clause.op === "co") {
|
|
12781
|
-
conditions.push(
|
|
12782
|
-
sql`(${users.username} LIKE ${`%${clause.value}%`} OR ${users.email} LIKE ${`%${clause.value}%`})`
|
|
12783
|
-
);
|
|
12784
|
-
} else if (clause.op === "sw") {
|
|
12785
|
-
conditions.push(
|
|
12786
|
-
sql`(${users.username} LIKE ${`${clause.value}%`} OR ${users.email} LIKE ${`${clause.value}%`})`
|
|
12787
|
-
);
|
|
12788
|
-
}
|
|
12789
|
-
} else if (clause.attribute === "emails.value" && clause.value !== void 0) {
|
|
12790
|
-
if (clause.op === "eq") {
|
|
12791
|
-
conditions.push(eq(users.email, clause.value));
|
|
12792
|
-
} else if (clause.op === "co") {
|
|
12793
|
-
conditions.push(like(users.email, `%${clause.value}%`));
|
|
12794
|
-
} else if (clause.op === "sw") {
|
|
12795
|
-
conditions.push(like(users.email, `${clause.value}%`));
|
|
12796
|
-
}
|
|
12797
|
-
} else if (clause.attribute === "externalid" && clause.value !== void 0) {
|
|
12798
|
-
if (clause.op === "eq") {
|
|
12799
|
-
conditions.push(eq(users.externalId, clause.value));
|
|
12800
|
-
}
|
|
12801
|
-
} else if (clause.attribute === "active") {
|
|
12802
|
-
if (clause.op === "eq" && clause.value === "false") {
|
|
12803
|
-
conditions.push(eq(users.banned, 1));
|
|
12804
|
-
} else if (clause.op === "eq" && clause.value === "true") {
|
|
12805
|
-
conditions.push(eq(users.banned, 0));
|
|
12806
|
-
}
|
|
13478
|
+
try {
|
|
13479
|
+
const parsed = parseScimFilter(filterParam);
|
|
13480
|
+
ast = parsed.ast;
|
|
13481
|
+
clauses = parsed.clauses;
|
|
13482
|
+
} catch (err2) {
|
|
13483
|
+
if (err2 instanceof ScimFilterError) {
|
|
13484
|
+
return scimError(err2.message, 400, "invalidFilter");
|
|
12807
13485
|
}
|
|
13486
|
+
throw err2;
|
|
12808
13487
|
}
|
|
12809
13488
|
}
|
|
12810
|
-
const
|
|
12811
|
-
const
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
13489
|
+
const conditions = clauses ? buildUserSqlConditions(clauses) : null;
|
|
13490
|
+
const whereClause = conditions && conditions.length > 0 ? and(...conditions) : void 0;
|
|
13491
|
+
if (conditions !== null && !sortSpec) {
|
|
13492
|
+
const countRows = await db.select({ count: sql`count(*)` }).from(users).where(whereClause);
|
|
13493
|
+
const totalResults2 = Number(countRows[0]?.count ?? 0);
|
|
13494
|
+
const offset = startIndex - 1;
|
|
13495
|
+
const rows = await db.select().from(users).where(whereClause).limit(count).offset(offset);
|
|
13496
|
+
return scimResponse({
|
|
13497
|
+
schemas: [SCHEMA_LIST],
|
|
13498
|
+
totalResults: totalResults2,
|
|
13499
|
+
startIndex,
|
|
13500
|
+
itemsPerPage: rows.length,
|
|
13501
|
+
Resources: rows.map((r) => userRowToScim(r, baseUrl))
|
|
13502
|
+
});
|
|
13503
|
+
}
|
|
13504
|
+
const allRows = await db.select().from(users);
|
|
13505
|
+
const mapped = allRows.map((r) => ({ row: r, scim: userRowToScim(r, baseUrl) }));
|
|
13506
|
+
const matched = ast ? mapped.filter(({ scim: scim2 }) => evaluateFilter(ast, scim2)) : mapped;
|
|
13507
|
+
const sorted = sortSpec ? sortResources(matched, sortSpec) : matched;
|
|
13508
|
+
const totalResults = sorted.length;
|
|
13509
|
+
const page = sorted.slice(startIndex - 1, startIndex - 1 + count);
|
|
12815
13510
|
return scimResponse({
|
|
12816
13511
|
schemas: [SCHEMA_LIST],
|
|
12817
13512
|
totalResults,
|
|
12818
13513
|
startIndex,
|
|
12819
|
-
itemsPerPage:
|
|
12820
|
-
Resources:
|
|
13514
|
+
itemsPerPage: page.length,
|
|
13515
|
+
Resources: page.map((p) => p.scim)
|
|
12821
13516
|
});
|
|
12822
13517
|
}
|
|
13518
|
+
function buildUserSqlConditions(list) {
|
|
13519
|
+
const conds = [];
|
|
13520
|
+
for (const clause of list) {
|
|
13521
|
+
if (clause.attribute === "username" && clause.value !== void 0) {
|
|
13522
|
+
if (clause.op === "eq") {
|
|
13523
|
+
conds.push(
|
|
13524
|
+
sql`(${users.username} = ${clause.value} OR ${users.email} = ${clause.value})`
|
|
13525
|
+
);
|
|
13526
|
+
} else if (clause.op === "co") {
|
|
13527
|
+
conds.push(
|
|
13528
|
+
sql`(${users.username} LIKE ${`%${clause.value}%`} OR ${users.email} LIKE ${`%${clause.value}%`})`
|
|
13529
|
+
);
|
|
13530
|
+
} else if (clause.op === "sw") {
|
|
13531
|
+
conds.push(
|
|
13532
|
+
sql`(${users.username} LIKE ${`${clause.value}%`} OR ${users.email} LIKE ${`${clause.value}%`})`
|
|
13533
|
+
);
|
|
13534
|
+
} else {
|
|
13535
|
+
return null;
|
|
13536
|
+
}
|
|
13537
|
+
continue;
|
|
13538
|
+
}
|
|
13539
|
+
if (clause.attribute === "emails.value" && clause.value !== void 0) {
|
|
13540
|
+
if (clause.op === "eq") conds.push(eq(users.email, clause.value));
|
|
13541
|
+
else if (clause.op === "co") conds.push(like(users.email, `%${clause.value}%`));
|
|
13542
|
+
else if (clause.op === "sw") conds.push(like(users.email, `${clause.value}%`));
|
|
13543
|
+
else return null;
|
|
13544
|
+
continue;
|
|
13545
|
+
}
|
|
13546
|
+
if (clause.attribute === "externalid" && clause.value !== void 0) {
|
|
13547
|
+
if (clause.op === "eq") conds.push(eq(users.externalId, clause.value));
|
|
13548
|
+
else return null;
|
|
13549
|
+
continue;
|
|
13550
|
+
}
|
|
13551
|
+
if (clause.attribute === "active") {
|
|
13552
|
+
if (clause.op === "eq" && clause.value === "false") conds.push(eq(users.banned, 1));
|
|
13553
|
+
else if (clause.op === "eq" && clause.value === "true") conds.push(eq(users.banned, 0));
|
|
13554
|
+
else return null;
|
|
13555
|
+
continue;
|
|
13556
|
+
}
|
|
13557
|
+
return null;
|
|
13558
|
+
}
|
|
13559
|
+
return conds;
|
|
13560
|
+
}
|
|
12823
13561
|
async function handleGetUser(request, userId) {
|
|
12824
13562
|
const baseUrl = getBaseUrl(request);
|
|
12825
13563
|
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
@@ -12828,6 +13566,7 @@ function createScimModule(config, db) {
|
|
|
12828
13566
|
return scimResponse(userRowToScim(row, baseUrl));
|
|
12829
13567
|
}
|
|
12830
13568
|
async function handleCreateUser(request) {
|
|
13569
|
+
const auditStart = performance.now();
|
|
12831
13570
|
if (!autoCreateUsers) {
|
|
12832
13571
|
return scimError("User provisioning is disabled", 403);
|
|
12833
13572
|
}
|
|
@@ -12862,6 +13601,10 @@ function createScimModule(config, db) {
|
|
|
12862
13601
|
};
|
|
12863
13602
|
if (givenName) metadata["scim:givenName"] = givenName;
|
|
12864
13603
|
if (familyName) metadata["scim:familyName"] = familyName;
|
|
13604
|
+
const enterpriseExt = body[SCHEMA_ENTERPRISE];
|
|
13605
|
+
if (enterpriseExt && typeof enterpriseExt === "object") {
|
|
13606
|
+
metadata["scim:enterprise"] = enterpriseExt;
|
|
13607
|
+
}
|
|
12865
13608
|
await db.insert(users).values({
|
|
12866
13609
|
id,
|
|
12867
13610
|
email: primaryEmail,
|
|
@@ -12888,9 +13631,16 @@ function createScimModule(config, db) {
|
|
|
12888
13631
|
await onProvision(scimUser);
|
|
12889
13632
|
}
|
|
12890
13633
|
const baseUrl = getBaseUrl(request);
|
|
13634
|
+
await writeScimAudit(
|
|
13635
|
+
"scim.user.create",
|
|
13636
|
+
`scim:users:${id}`,
|
|
13637
|
+
id,
|
|
13638
|
+
Math.round(performance.now() - auditStart)
|
|
13639
|
+
);
|
|
12891
13640
|
return scimResponse(userRowToScim(created, baseUrl), 201);
|
|
12892
13641
|
}
|
|
12893
13642
|
async function handleReplaceUser(request, userId) {
|
|
13643
|
+
const auditStart = performance.now();
|
|
12894
13644
|
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
12895
13645
|
const existing = rows[0];
|
|
12896
13646
|
if (!existing) return scimError("User not found", 404, "noTarget");
|
|
@@ -12917,6 +13667,10 @@ function createScimModule(config, db) {
|
|
|
12917
13667
|
};
|
|
12918
13668
|
if (givenName !== void 0) metadata["scim:givenName"] = givenName;
|
|
12919
13669
|
if (familyName !== void 0) metadata["scim:familyName"] = familyName;
|
|
13670
|
+
const enterpriseExt = body[SCHEMA_ENTERPRISE];
|
|
13671
|
+
if (enterpriseExt && typeof enterpriseExt === "object") {
|
|
13672
|
+
metadata["scim:enterprise"] = enterpriseExt;
|
|
13673
|
+
}
|
|
12920
13674
|
const now = /* @__PURE__ */ new Date();
|
|
12921
13675
|
await db.update(users).set({
|
|
12922
13676
|
email: primaryEmail,
|
|
@@ -12929,9 +13683,16 @@ function createScimModule(config, db) {
|
|
|
12929
13683
|
}).where(eq(users.id, userId));
|
|
12930
13684
|
const updatedRows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
12931
13685
|
const baseUrl = getBaseUrl(request);
|
|
13686
|
+
await writeScimAudit(
|
|
13687
|
+
"scim.user.replace",
|
|
13688
|
+
`scim:users:${userId}`,
|
|
13689
|
+
userId,
|
|
13690
|
+
Math.round(performance.now() - auditStart)
|
|
13691
|
+
);
|
|
12932
13692
|
return scimResponse(userRowToScim(updatedRows[0], baseUrl));
|
|
12933
13693
|
}
|
|
12934
13694
|
async function handlePatchUser(request, userId) {
|
|
13695
|
+
const auditStart = performance.now();
|
|
12935
13696
|
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
12936
13697
|
const current = rows[0];
|
|
12937
13698
|
if (!current) return scimError("User not found", 404, "noTarget");
|
|
@@ -12944,76 +13705,68 @@ function createScimModule(config, db) {
|
|
|
12944
13705
|
if (!body.schemas?.includes(SCHEMA_PATCH) || !Array.isArray(body.Operations) || body.Operations.length === 0) {
|
|
12945
13706
|
return scimError("Invalid PATCH body: missing Operations", 400, "invalidValue");
|
|
12946
13707
|
}
|
|
13708
|
+
const baseUrlForPatch = getBaseUrl(request);
|
|
13709
|
+
const scimView = userRowToScim(current, baseUrlForPatch);
|
|
13710
|
+
try {
|
|
13711
|
+
applyPatchOps(scimView, body.Operations, {
|
|
13712
|
+
readonlyPaths: ["id", "externalid"]
|
|
13713
|
+
});
|
|
13714
|
+
} catch (err2) {
|
|
13715
|
+
if (err2 instanceof ScimPatchError) {
|
|
13716
|
+
return scimError(err2.message, err2.status, err2.scimType);
|
|
13717
|
+
}
|
|
13718
|
+
throw err2;
|
|
13719
|
+
}
|
|
12947
13720
|
const updatedMeta = { ...current.metadata ?? {} };
|
|
12948
13721
|
let email = current.email;
|
|
12949
13722
|
let name = current.name;
|
|
12950
13723
|
let username = current.username;
|
|
12951
13724
|
let banned = current.banned;
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
externalId = typeof op.value === "string" ? op.value : externalId;
|
|
12978
|
-
} else if (path === "emails" || path?.startsWith("emails[")) {
|
|
12979
|
-
const emailsVal = op.value;
|
|
12980
|
-
if (Array.isArray(emailsVal)) {
|
|
12981
|
-
const primary = emailsVal.find((e) => e.primary)?.value ?? emailsVal[0]?.value;
|
|
12982
|
-
if (primary) email = primary;
|
|
12983
|
-
}
|
|
12984
|
-
} else if (!path) {
|
|
12985
|
-
const val = op.value;
|
|
12986
|
-
if (val && typeof val === "object") {
|
|
12987
|
-
if ("active" in val) {
|
|
12988
|
-
const active = val.active === true || val.active === "true";
|
|
12989
|
-
updatedMeta["scim:active"] = active;
|
|
12990
|
-
banned = active ? 0 : autoDeactivateUsers ? 1 : 0;
|
|
12991
|
-
}
|
|
12992
|
-
if ("displayName" in val && typeof val.displayName === "string") {
|
|
12993
|
-
name = val.displayName;
|
|
12994
|
-
}
|
|
12995
|
-
if ("userName" in val && typeof val.userName === "string") {
|
|
12996
|
-
username = val.userName;
|
|
12997
|
-
}
|
|
12998
|
-
if ("externalId" in val && typeof val.externalId === "string") {
|
|
12999
|
-
externalId = val.externalId;
|
|
13000
|
-
}
|
|
13001
|
-
const nameVal = val.name;
|
|
13002
|
-
if (nameVal) {
|
|
13003
|
-
if (nameVal.givenName !== void 0) updatedMeta["scim:givenName"] = nameVal.givenName;
|
|
13004
|
-
if (nameVal.familyName !== void 0)
|
|
13005
|
-
updatedMeta["scim:familyName"] = nameVal.familyName;
|
|
13006
|
-
}
|
|
13007
|
-
}
|
|
13725
|
+
const externalId = current.externalId;
|
|
13726
|
+
const scimUserName = scimView.userName;
|
|
13727
|
+
if (typeof scimUserName === "string") username = scimUserName;
|
|
13728
|
+
const scimDisplay = scimView.displayName;
|
|
13729
|
+
if (typeof scimDisplay === "string") name = scimDisplay;
|
|
13730
|
+
const scimName = scimView.name;
|
|
13731
|
+
if (scimName) {
|
|
13732
|
+
if (scimName.givenName !== void 0) updatedMeta["scim:givenName"] = scimName.givenName;
|
|
13733
|
+
if (scimName.familyName !== void 0) updatedMeta["scim:familyName"] = scimName.familyName;
|
|
13734
|
+
}
|
|
13735
|
+
const scimActive = scimView.active;
|
|
13736
|
+
if (typeof scimActive === "boolean") {
|
|
13737
|
+
updatedMeta["scim:active"] = scimActive;
|
|
13738
|
+
banned = scimActive ? 0 : autoDeactivateUsers ? 1 : 0;
|
|
13739
|
+
} else if (scimActive === void 0 && scimView.active === void 0) {
|
|
13740
|
+
updatedMeta["scim:active"] = false;
|
|
13741
|
+
banned = autoDeactivateUsers ? 1 : 0;
|
|
13742
|
+
}
|
|
13743
|
+
const scimEmails = scimView.emails;
|
|
13744
|
+
if (Array.isArray(scimEmails)) {
|
|
13745
|
+
const primary = scimEmails.find(
|
|
13746
|
+
(entry) => typeof entry === "object" && entry !== null && entry.primary === true
|
|
13747
|
+
)?.value ?? scimEmails[0]?.value;
|
|
13748
|
+
if (typeof primary === "string" && primary.length > 0) {
|
|
13749
|
+
email = primary;
|
|
13008
13750
|
}
|
|
13009
13751
|
}
|
|
13752
|
+
const extKey = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
|
|
13753
|
+
const enterpriseExt = scimView[extKey];
|
|
13754
|
+
if (enterpriseExt && typeof enterpriseExt === "object") {
|
|
13755
|
+
updatedMeta["scim:enterprise"] = enterpriseExt;
|
|
13756
|
+
}
|
|
13010
13757
|
const now = /* @__PURE__ */ new Date();
|
|
13011
13758
|
await db.update(users).set({ email, name, username, banned, externalId, metadata: updatedMeta, updatedAt: now }).where(eq(users.id, userId));
|
|
13012
13759
|
const updatedRows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
13013
|
-
|
|
13014
|
-
|
|
13760
|
+
await writeScimAudit(
|
|
13761
|
+
"scim.user.update",
|
|
13762
|
+
`scim:users:${userId}`,
|
|
13763
|
+
userId,
|
|
13764
|
+
Math.round(performance.now() - auditStart)
|
|
13765
|
+
);
|
|
13766
|
+
return scimResponse(userRowToScim(updatedRows[0], baseUrlForPatch));
|
|
13015
13767
|
}
|
|
13016
13768
|
async function handleDeleteUser(_request, userId) {
|
|
13769
|
+
const auditStart = performance.now();
|
|
13017
13770
|
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
13018
13771
|
const row = rows[0];
|
|
13019
13772
|
if (!row) return scimError("User not found", 404, "noTarget");
|
|
@@ -13027,6 +13780,12 @@ function createScimModule(config, db) {
|
|
|
13027
13780
|
if (onDeprovision) {
|
|
13028
13781
|
await onDeprovision(userId);
|
|
13029
13782
|
}
|
|
13783
|
+
await writeScimAudit(
|
|
13784
|
+
"scim.user.delete",
|
|
13785
|
+
`scim:users:${userId}`,
|
|
13786
|
+
userId,
|
|
13787
|
+
Math.round(performance.now() - auditStart)
|
|
13788
|
+
);
|
|
13030
13789
|
return new Response(null, { status: 204 });
|
|
13031
13790
|
}
|
|
13032
13791
|
async function getMembersForOrg(orgId) {
|
|
@@ -13044,41 +13803,74 @@ function createScimModule(config, db) {
|
|
|
13044
13803
|
const startIndex = Math.max(1, parseInt(url.searchParams.get("startIndex") ?? "1", 10));
|
|
13045
13804
|
const count = Math.min(200, parseInt(url.searchParams.get("count") ?? "100", 10));
|
|
13046
13805
|
const baseUrl = getBaseUrl(request);
|
|
13047
|
-
const
|
|
13806
|
+
const sortSpec = parseSortSpec(url);
|
|
13807
|
+
let ast = null;
|
|
13808
|
+
let clauses = null;
|
|
13048
13809
|
if (filterParam) {
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
} else if (clause.op === "sw") {
|
|
13057
|
-
conditions.push(like(organizations.name, `${clause.value}%`));
|
|
13058
|
-
}
|
|
13810
|
+
try {
|
|
13811
|
+
const parsed = parseScimFilter(filterParam);
|
|
13812
|
+
ast = parsed.ast;
|
|
13813
|
+
clauses = parsed.clauses;
|
|
13814
|
+
} catch (err2) {
|
|
13815
|
+
if (err2 instanceof ScimFilterError) {
|
|
13816
|
+
return scimError(err2.message, 400, "invalidFilter");
|
|
13059
13817
|
}
|
|
13818
|
+
throw err2;
|
|
13060
13819
|
}
|
|
13061
13820
|
}
|
|
13062
|
-
const
|
|
13063
|
-
const
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13821
|
+
const conditions = clauses ? buildGroupSqlConditions(clauses) : null;
|
|
13822
|
+
const whereClause = conditions && conditions.length > 0 ? and(...conditions) : void 0;
|
|
13823
|
+
if (conditions !== null && !sortSpec) {
|
|
13824
|
+
const countRows = await db.select({ count: sql`count(*)` }).from(organizations).where(whereClause);
|
|
13825
|
+
const totalResults2 = Number(countRows[0]?.count ?? 0);
|
|
13826
|
+
const offset = startIndex - 1;
|
|
13827
|
+
const orgRows = await db.select().from(organizations).where(whereClause).limit(count).offset(offset);
|
|
13828
|
+
const resources2 = await Promise.all(
|
|
13829
|
+
orgRows.map(async (org) => {
|
|
13830
|
+
const members = await getMembersForOrg(org.id);
|
|
13831
|
+
const memberEmails = await getMemberEmails(members);
|
|
13832
|
+
return orgRowToScim(org, members, memberEmails, baseUrl);
|
|
13833
|
+
})
|
|
13834
|
+
);
|
|
13835
|
+
return scimResponse({
|
|
13836
|
+
schemas: [SCHEMA_LIST],
|
|
13837
|
+
totalResults: totalResults2,
|
|
13838
|
+
startIndex,
|
|
13839
|
+
itemsPerPage: orgRows.length,
|
|
13840
|
+
Resources: resources2
|
|
13841
|
+
});
|
|
13842
|
+
}
|
|
13843
|
+
const allOrgs = await db.select().from(organizations);
|
|
13067
13844
|
const resources = await Promise.all(
|
|
13068
|
-
|
|
13845
|
+
allOrgs.map(async (org) => {
|
|
13069
13846
|
const members = await getMembersForOrg(org.id);
|
|
13070
13847
|
const memberEmails = await getMemberEmails(members);
|
|
13071
|
-
return orgRowToScim(org, members, memberEmails, baseUrl);
|
|
13848
|
+
return { org, scim: orgRowToScim(org, members, memberEmails, baseUrl) };
|
|
13072
13849
|
})
|
|
13073
13850
|
);
|
|
13851
|
+
const matched = ast ? resources.filter(({ scim: scim2 }) => evaluateFilter(ast, scim2)) : resources;
|
|
13852
|
+
const sorted = sortSpec ? sortResources(matched, sortSpec) : matched;
|
|
13853
|
+
const totalResults = sorted.length;
|
|
13854
|
+
const page = sorted.slice(startIndex - 1, startIndex - 1 + count);
|
|
13074
13855
|
return scimResponse({
|
|
13075
13856
|
schemas: [SCHEMA_LIST],
|
|
13076
13857
|
totalResults,
|
|
13077
13858
|
startIndex,
|
|
13078
|
-
itemsPerPage:
|
|
13079
|
-
Resources:
|
|
13859
|
+
itemsPerPage: page.length,
|
|
13860
|
+
Resources: page.map((p) => p.scim)
|
|
13080
13861
|
});
|
|
13081
13862
|
}
|
|
13863
|
+
function buildGroupSqlConditions(list) {
|
|
13864
|
+
const conds = [];
|
|
13865
|
+
for (const clause of list) {
|
|
13866
|
+
if (clause.attribute !== "displayname" || clause.value === void 0) return null;
|
|
13867
|
+
if (clause.op === "eq") conds.push(eq(organizations.name, clause.value));
|
|
13868
|
+
else if (clause.op === "co") conds.push(like(organizations.name, `%${clause.value}%`));
|
|
13869
|
+
else if (clause.op === "sw") conds.push(like(organizations.name, `${clause.value}%`));
|
|
13870
|
+
else return null;
|
|
13871
|
+
}
|
|
13872
|
+
return conds;
|
|
13873
|
+
}
|
|
13082
13874
|
async function handleGetGroup(request, groupId) {
|
|
13083
13875
|
const baseUrl = getBaseUrl(request);
|
|
13084
13876
|
const orgRows = await db.select().from(organizations).where(eq(organizations.id, groupId)).limit(1);
|
|
@@ -13089,6 +13881,7 @@ function createScimModule(config, db) {
|
|
|
13089
13881
|
return scimResponse(orgRowToScim(orgRow, members, memberEmails, baseUrl));
|
|
13090
13882
|
}
|
|
13091
13883
|
async function handleCreateGroup(request) {
|
|
13884
|
+
const auditStart = performance.now();
|
|
13092
13885
|
let body;
|
|
13093
13886
|
try {
|
|
13094
13887
|
body = await request.json();
|
|
@@ -13147,12 +13940,19 @@ function createScimModule(config, db) {
|
|
|
13147
13940
|
const createdMembers = await getMembersForOrg(id);
|
|
13148
13941
|
const memberEmails = await getMemberEmails(createdMembers);
|
|
13149
13942
|
const baseUrl = getBaseUrl(request);
|
|
13943
|
+
await writeScimAudit(
|
|
13944
|
+
"scim.group.create",
|
|
13945
|
+
`scim:groups:${id}`,
|
|
13946
|
+
ownerId,
|
|
13947
|
+
Math.round(performance.now() - auditStart)
|
|
13948
|
+
);
|
|
13150
13949
|
return scimResponse(
|
|
13151
13950
|
orgRowToScim(orgRows[0], createdMembers, memberEmails, baseUrl),
|
|
13152
13951
|
201
|
|
13153
13952
|
);
|
|
13154
13953
|
}
|
|
13155
13954
|
async function handleReplaceGroup(request, groupId) {
|
|
13955
|
+
const auditStart = performance.now();
|
|
13156
13956
|
const orgRows = await db.select().from(organizations).where(eq(organizations.id, groupId)).limit(1);
|
|
13157
13957
|
const existingOrg = orgRows[0];
|
|
13158
13958
|
if (!existingOrg) return scimError("Group not found", 404, "noTarget");
|
|
@@ -13188,9 +13988,16 @@ function createScimModule(config, db) {
|
|
|
13188
13988
|
const members = await getMembersForOrg(groupId);
|
|
13189
13989
|
const memberEmails = await getMemberEmails(members);
|
|
13190
13990
|
const baseUrl = getBaseUrl(request);
|
|
13991
|
+
await writeScimAudit(
|
|
13992
|
+
"scim.group.replace",
|
|
13993
|
+
`scim:groups:${groupId}`,
|
|
13994
|
+
updatedOrg[0].ownerId,
|
|
13995
|
+
Math.round(performance.now() - auditStart)
|
|
13996
|
+
);
|
|
13191
13997
|
return scimResponse(orgRowToScim(updatedOrg[0], members, memberEmails, baseUrl));
|
|
13192
13998
|
}
|
|
13193
13999
|
async function handlePatchGroup(request, groupId) {
|
|
14000
|
+
const auditStart = performance.now();
|
|
13194
14001
|
const orgRows = await db.select().from(organizations).where(eq(organizations.id, groupId)).limit(1);
|
|
13195
14002
|
const current = orgRows[0];
|
|
13196
14003
|
if (!current) return scimError("Group not found", 404, "noTarget");
|
|
@@ -13258,13 +14065,27 @@ function createScimModule(config, db) {
|
|
|
13258
14065
|
const members = await getMembersForOrg(groupId);
|
|
13259
14066
|
const memberEmails = await getMemberEmails(members);
|
|
13260
14067
|
const baseUrl = getBaseUrl(request);
|
|
14068
|
+
await writeScimAudit(
|
|
14069
|
+
"scim.group.update",
|
|
14070
|
+
`scim:groups:${groupId}`,
|
|
14071
|
+
updatedOrg[0].ownerId,
|
|
14072
|
+
Math.round(performance.now() - auditStart)
|
|
14073
|
+
);
|
|
13261
14074
|
return scimResponse(orgRowToScim(updatedOrg[0], members, memberEmails, baseUrl));
|
|
13262
14075
|
}
|
|
13263
14076
|
async function handleDeleteGroup(_request, groupId) {
|
|
14077
|
+
const auditStart = performance.now();
|
|
13264
14078
|
const orgRows = await db.select().from(organizations).where(eq(organizations.id, groupId)).limit(1);
|
|
13265
|
-
|
|
14079
|
+
const existing = orgRows[0];
|
|
14080
|
+
if (!existing) return scimError("Group not found", 404, "noTarget");
|
|
13266
14081
|
await db.delete(orgMembers).where(eq(orgMembers.orgId, groupId));
|
|
13267
14082
|
await db.delete(organizations).where(eq(organizations.id, groupId));
|
|
14083
|
+
await writeScimAudit(
|
|
14084
|
+
"scim.group.delete",
|
|
14085
|
+
`scim:groups:${groupId}`,
|
|
14086
|
+
existing.ownerId,
|
|
14087
|
+
Math.round(performance.now() - auditStart)
|
|
14088
|
+
);
|
|
13268
14089
|
return new Response(null, { status: 204 });
|
|
13269
14090
|
}
|
|
13270
14091
|
function handleServiceProviderConfig(request) {
|
|
@@ -13276,7 +14097,7 @@ function createScimModule(config, db) {
|
|
|
13276
14097
|
bulk: { supported: false, maxOperations: 0, maxPayloadSize: 0 },
|
|
13277
14098
|
filter: { supported: true, maxResults: 200 },
|
|
13278
14099
|
changePassword: { supported: false },
|
|
13279
|
-
sort: { supported:
|
|
14100
|
+
sort: { supported: true },
|
|
13280
14101
|
etag: { supported: false },
|
|
13281
14102
|
authenticationSchemes: [
|
|
13282
14103
|
{
|
|
@@ -13297,9 +14118,9 @@ function createScimModule(config, db) {
|
|
|
13297
14118
|
const baseUrl = getBaseUrl(request);
|
|
13298
14119
|
return scimResponse({
|
|
13299
14120
|
schemas: [SCHEMA_LIST],
|
|
13300
|
-
totalResults:
|
|
14121
|
+
totalResults: 3,
|
|
13301
14122
|
startIndex: 1,
|
|
13302
|
-
itemsPerPage:
|
|
14123
|
+
itemsPerPage: 3,
|
|
13303
14124
|
Resources: [
|
|
13304
14125
|
{
|
|
13305
14126
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
|
|
@@ -13342,6 +14163,39 @@ function createScimModule(config, db) {
|
|
|
13342
14163
|
resourceType: "Schema",
|
|
13343
14164
|
location: `${baseUrl}/scim/v2/Schemas/${SCHEMA_GROUP}`
|
|
13344
14165
|
}
|
|
14166
|
+
},
|
|
14167
|
+
{
|
|
14168
|
+
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
|
|
14169
|
+
id: SCHEMA_ENTERPRISE,
|
|
14170
|
+
name: "EnterpriseUser",
|
|
14171
|
+
description: "Enterprise User extension (RFC 7643 \xA74.3). Carries employeeNumber, department, and manager.",
|
|
14172
|
+
attributes: [
|
|
14173
|
+
{
|
|
14174
|
+
name: "employeeNumber",
|
|
14175
|
+
type: "string",
|
|
14176
|
+
required: false,
|
|
14177
|
+
mutability: "readWrite"
|
|
14178
|
+
},
|
|
14179
|
+
{ name: "department", type: "string", required: false, mutability: "readWrite" },
|
|
14180
|
+
{ name: "costCenter", type: "string", required: false, mutability: "readWrite" },
|
|
14181
|
+
{ name: "organization", type: "string", required: false, mutability: "readWrite" },
|
|
14182
|
+
{ name: "division", type: "string", required: false, mutability: "readWrite" },
|
|
14183
|
+
{
|
|
14184
|
+
name: "manager",
|
|
14185
|
+
type: "complex",
|
|
14186
|
+
required: false,
|
|
14187
|
+
mutability: "readWrite",
|
|
14188
|
+
subAttributes: [
|
|
14189
|
+
{ name: "value", type: "string" },
|
|
14190
|
+
{ name: "$ref", type: "reference" },
|
|
14191
|
+
{ name: "displayName", type: "string" }
|
|
14192
|
+
]
|
|
14193
|
+
}
|
|
14194
|
+
],
|
|
14195
|
+
meta: {
|
|
14196
|
+
resourceType: "Schema",
|
|
14197
|
+
location: `${baseUrl}/scim/v2/Schemas/${SCHEMA_ENTERPRISE}`
|
|
14198
|
+
}
|
|
13345
14199
|
}
|
|
13346
14200
|
]
|
|
13347
14201
|
});
|
|
@@ -13381,6 +14235,45 @@ function createScimModule(config, db) {
|
|
|
13381
14235
|
]
|
|
13382
14236
|
});
|
|
13383
14237
|
}
|
|
14238
|
+
const SCHEMA_BULK_REQUEST = "urn:ietf:params:scim:api:messages:2.0:BulkRequest";
|
|
14239
|
+
function handleBulk(request) {
|
|
14240
|
+
const contentType = request.headers.get("Content-Type") ?? "";
|
|
14241
|
+
if (!contentType.toLowerCase().startsWith(SCIM_CONTENT_TYPE)) {
|
|
14242
|
+
return scimError(`Bulk requires Content-Type "${SCIM_CONTENT_TYPE}"`, 415, "invalidValue");
|
|
14243
|
+
}
|
|
14244
|
+
return scimResponse(
|
|
14245
|
+
{
|
|
14246
|
+
schemas: [SCHEMA_ERROR],
|
|
14247
|
+
status: "501",
|
|
14248
|
+
detail: `Bulk operations are not supported by this server. See /scim/v2/ServiceProviderConfig for supported features. Requested schema: ${SCHEMA_BULK_REQUEST}`
|
|
14249
|
+
},
|
|
14250
|
+
501
|
|
14251
|
+
);
|
|
14252
|
+
}
|
|
14253
|
+
async function handleMe(request) {
|
|
14254
|
+
if (!resolveSelf) {
|
|
14255
|
+
return scimError(
|
|
14256
|
+
"Me endpoint requires a resolveSelf configuration callback",
|
|
14257
|
+
501,
|
|
14258
|
+
"invalidValue"
|
|
14259
|
+
);
|
|
14260
|
+
}
|
|
14261
|
+
let userId;
|
|
14262
|
+
try {
|
|
14263
|
+
userId = await resolveSelf(request);
|
|
14264
|
+
} catch {
|
|
14265
|
+
return scimError("resolveSelf threw while resolving the current user", 500);
|
|
14266
|
+
}
|
|
14267
|
+
if (!userId) {
|
|
14268
|
+
return scimError("No user is associated with this bearer token", 404, "noTarget");
|
|
14269
|
+
}
|
|
14270
|
+
const rows = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
|
14271
|
+
const row = rows[0];
|
|
14272
|
+
if (!row) {
|
|
14273
|
+
return scimError("Resolved user does not exist", 404, "noTarget");
|
|
14274
|
+
}
|
|
14275
|
+
return scimResponse(userRowToScim(row, getBaseUrl(request)));
|
|
14276
|
+
}
|
|
13384
14277
|
async function handleRequest(request) {
|
|
13385
14278
|
const url = new URL(request.url);
|
|
13386
14279
|
const pathname = url.pathname;
|
|
@@ -13398,6 +14291,12 @@ function createScimModule(config, db) {
|
|
|
13398
14291
|
if (method === "GET" && pathname.endsWith("/scim/v2/ResourceTypes")) {
|
|
13399
14292
|
return handleResourceTypes(request);
|
|
13400
14293
|
}
|
|
14294
|
+
if (method === "GET" && pathname.endsWith("/scim/v2/Me")) {
|
|
14295
|
+
return handleMe(request);
|
|
14296
|
+
}
|
|
14297
|
+
if (method === "POST" && pathname.endsWith("/scim/v2/Bulk")) {
|
|
14298
|
+
return handleBulk(request);
|
|
14299
|
+
}
|
|
13401
14300
|
if (method === "GET" && /\/scim\/v2\/Users\/?$/.test(pathname)) {
|
|
13402
14301
|
return handleListUsers(request);
|
|
13403
14302
|
}
|
|
@@ -13432,7 +14331,7 @@ function createScimModule(config, db) {
|
|
|
13432
14331
|
// src/auth/scim-plugin.ts
|
|
13433
14332
|
function scim(config) {
|
|
13434
14333
|
return {
|
|
13435
|
-
id: "
|
|
14334
|
+
id: "theauth-scim",
|
|
13436
14335
|
async init(ctx) {
|
|
13437
14336
|
const module = createScimModule(config, ctx.db);
|
|
13438
14337
|
ctx.addEndpoint({
|
|
@@ -13459,6 +14358,22 @@ function scim(config) {
|
|
|
13459
14358
|
return await module.handleRequest(request);
|
|
13460
14359
|
}
|
|
13461
14360
|
});
|
|
14361
|
+
ctx.addEndpoint({
|
|
14362
|
+
method: "GET",
|
|
14363
|
+
path: "/scim/v2/Me",
|
|
14364
|
+
metadata: { description: "SCIM /Me: current user for the bearer token" },
|
|
14365
|
+
async handler(request) {
|
|
14366
|
+
return await module.handleRequest(request);
|
|
14367
|
+
}
|
|
14368
|
+
});
|
|
14369
|
+
ctx.addEndpoint({
|
|
14370
|
+
method: "POST",
|
|
14371
|
+
path: "/scim/v2/Bulk",
|
|
14372
|
+
metadata: { description: "SCIM /Bulk: returns 501 (bulk ops not supported)" },
|
|
14373
|
+
async handler(request) {
|
|
14374
|
+
return await module.handleRequest(request);
|
|
14375
|
+
}
|
|
14376
|
+
});
|
|
13462
14377
|
ctx.addEndpoint({
|
|
13463
14378
|
method: "GET",
|
|
13464
14379
|
path: "/scim/v2/Users",
|
|
@@ -13728,7 +14643,7 @@ function createSiweModule(config) {
|
|
|
13728
14643
|
}
|
|
13729
14644
|
function siwe(config) {
|
|
13730
14645
|
return {
|
|
13731
|
-
id: "
|
|
14646
|
+
id: "theauth-siwe",
|
|
13732
14647
|
async init(ctx) {
|
|
13733
14648
|
const mod = createSiweModule(config);
|
|
13734
14649
|
ctx.addEndpoint({
|
|
@@ -13736,7 +14651,7 @@ function siwe(config) {
|
|
|
13736
14651
|
path: "/auth/siwe/nonce",
|
|
13737
14652
|
metadata: {
|
|
13738
14653
|
description: "Generate a SIWE nonce for wallet signing",
|
|
13739
|
-
rateLimit: { window:
|
|
14654
|
+
rateLimit: { window: 60, max: 60 }
|
|
13740
14655
|
},
|
|
13741
14656
|
async handler(_request, _endpointCtx) {
|
|
13742
14657
|
const nonce = await mod.generateNonce();
|
|
@@ -13751,7 +14666,7 @@ function siwe(config) {
|
|
|
13751
14666
|
path: "/auth/siwe/verify",
|
|
13752
14667
|
metadata: {
|
|
13753
14668
|
description: "Verify a SIWE-signed message and return the Ethereum address",
|
|
13754
|
-
rateLimit: { window:
|
|
14669
|
+
rateLimit: { window: 60, max: 20 }
|
|
13755
14670
|
},
|
|
13756
14671
|
async handler(request, _endpointCtx) {
|
|
13757
14672
|
let body;
|
|
@@ -15114,7 +16029,7 @@ function createStripeModule(config, db) {
|
|
|
15114
16029
|
const customer = await api("POST", "/customers", {
|
|
15115
16030
|
email,
|
|
15116
16031
|
...name ? { name } : {},
|
|
15117
|
-
metadata: {
|
|
16032
|
+
metadata: { theauth_user_id: userId }
|
|
15118
16033
|
});
|
|
15119
16034
|
await db.update(users).set({ stripeCustomerId: customer.id, updatedAt: /* @__PURE__ */ new Date() }).where(eq(users.id, userId));
|
|
15120
16035
|
return customer.id;
|
|
@@ -15268,7 +16183,7 @@ function createStripeModule(config, db) {
|
|
|
15268
16183
|
// src/auth/stripe-plugin.ts
|
|
15269
16184
|
function stripe(config) {
|
|
15270
16185
|
return {
|
|
15271
|
-
id: "
|
|
16186
|
+
id: "theauth-stripe",
|
|
15272
16187
|
async init(ctx) {
|
|
15273
16188
|
const module = createStripeModule(config, ctx.db);
|
|
15274
16189
|
ctx.addEndpoint({
|
|
@@ -15639,7 +16554,7 @@ async function parseBody8(request) {
|
|
|
15639
16554
|
}
|
|
15640
16555
|
function twoFactor(config) {
|
|
15641
16556
|
return {
|
|
15642
|
-
id: "
|
|
16557
|
+
id: "theauth-2fa",
|
|
15643
16558
|
async init(ctx) {
|
|
15644
16559
|
const module = createTotpModule(config ?? {}, ctx.db);
|
|
15645
16560
|
ctx.addEndpoint({
|
|
@@ -16089,9 +17004,9 @@ async function deliverOnce(endpoint, event, body, timestamp) {
|
|
|
16089
17004
|
method: "POST",
|
|
16090
17005
|
headers: {
|
|
16091
17006
|
"Content-Type": "application/json",
|
|
16092
|
-
"X-
|
|
16093
|
-
"X-
|
|
16094
|
-
"X-
|
|
17007
|
+
"X-TheAuth-Event": event,
|
|
17008
|
+
"X-TheAuth-Signature": `sha256=${signature}`,
|
|
17009
|
+
"X-TheAuth-Timestamp": timestamp
|
|
16095
17010
|
},
|
|
16096
17011
|
body,
|
|
16097
17012
|
signal: controller.signal
|