@robelest/convex-auth 0.0.4-preview.16 → 0.0.4-preview.18
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/dist/authorization/index.d.ts +17 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +17 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/client/index.js +73 -0
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +52 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/index.js +2 -2
- package/dist/component/model.d.ts +75 -75
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +2 -0
- package/dist/component/model.js.map +1 -1
- package/dist/component/public/factors.d.ts.map +1 -1
- package/dist/component/public/factors.js +1 -1
- package/dist/component/public/factors.js.map +1 -1
- package/dist/component/public/groups.d.ts +10 -2
- package/dist/component/public/groups.d.ts.map +1 -1
- package/dist/component/public/groups.js +92 -8
- package/dist/component/public/groups.js.map +1 -1
- package/dist/component/public/identity.d.ts.map +1 -1
- package/dist/component/public/identity.js +3 -3
- package/dist/component/public/identity.js.map +1 -1
- package/dist/component/public/shared.d.ts +4 -4
- package/dist/component/public/shared.d.ts.map +1 -1
- package/dist/component/public.d.ts +3 -3
- package/dist/component/public.js +2 -2
- package/dist/component/schema.d.ts +13 -2
- package/dist/component/schema.js +7 -5
- package/dist/component/schema.js.map +1 -1
- package/dist/component/server/auth.d.ts +4 -25
- package/dist/component/server/auth.d.ts.map +1 -1
- package/dist/component/server/auth.js +4 -10
- package/dist/component/server/auth.js.map +1 -1
- package/dist/component/server/domains/core.js +196 -131
- package/dist/component/server/domains/core.js.map +1 -1
- package/dist/component/server/factory.d.ts +3 -3
- package/dist/component/server/signin.js +20 -1
- package/dist/component/server/signin.js.map +1 -1
- package/dist/server/auth.d.ts +4 -25
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +4 -10
- package/dist/server/auth.js.map +1 -1
- package/dist/server/domains/core.d.ts +73 -58
- package/dist/server/domains/core.d.ts.map +1 -1
- package/dist/server/domains/core.js +196 -131
- package/dist/server/domains/core.js.map +1 -1
- package/dist/server/http.d.ts +2 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/index.d.ts +108 -69
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +138 -54
- package/dist/server/index.js.map +1 -1
- package/dist/server/mutations/code.d.ts +9 -9
- package/dist/server/mutations/index.d.ts +69 -69
- package/dist/server/mutations/invalidate.d.ts +4 -4
- package/dist/server/mutations/invalidate.d.ts.map +1 -1
- package/dist/server/mutations/oauth.d.ts +7 -7
- package/dist/server/mutations/register.d.ts +9 -9
- package/dist/server/mutations/retrieve.d.ts +6 -6
- package/dist/server/mutations/retrieve.d.ts.map +1 -1
- package/dist/server/mutations/signature.d.ts +4 -4
- package/dist/server/mutations/signature.d.ts.map +1 -1
- package/dist/server/mutations/signin.d.ts +5 -5
- package/dist/server/mutations/signin.d.ts.map +1 -1
- package/dist/server/mutations/verify.d.ts +7 -7
- package/dist/server/signin.js +20 -1
- package/dist/server/signin.js.map +1 -1
- package/dist/server/version.d.ts +1 -1
- package/dist/server/version.js +1 -1
- package/dist/server/version.js.map +1 -1
- package/package.json +9 -5
- package/src/authorization/index.ts +37 -0
- package/src/client/index.ts +122 -0
- package/src/component/_generated/component.ts +64 -0
- package/src/component/index.ts +1 -1
- package/src/component/model.ts +2 -0
- package/src/component/public/factors.ts +3 -2
- package/src/component/public/groups.ts +142 -8
- package/src/component/public/identity.ts +9 -6
- package/src/component/schema.ts +14 -2
- package/src/server/auth.ts +9 -61
- package/src/server/domains/core.ts +235 -210
- package/src/server/index.ts +158 -75
- package/src/server/signin.ts +52 -0
- package/src/server/version.ts +1 -1
package/src/server/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type EnterpriseAdminAuthorizationInput = {
|
|
|
53
53
|
export type EnterpriseAuthorizer = (
|
|
54
54
|
ctx: { auth: import("convex/server").Auth },
|
|
55
55
|
input: EnterpriseAdminAuthorizationInput,
|
|
56
|
-
) => Promise<void>;
|
|
56
|
+
) => Promise<void | { ok: false }>;
|
|
57
57
|
|
|
58
58
|
type RoleRef<TRoleId extends string> = { id: TRoleId };
|
|
59
59
|
|
|
@@ -78,8 +78,10 @@ type MountedEnterpriseTarget = {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
function requireSignedInUser(auth: Pick<AuthApi, "user">) {
|
|
81
|
-
return async (ctx: {
|
|
82
|
-
|
|
81
|
+
return async (ctx: {
|
|
82
|
+
auth: import("convex/server").Auth;
|
|
83
|
+
}): Promise<string | null> => {
|
|
84
|
+
return await auth.user.id(ctx as never);
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -157,22 +159,24 @@ function createMountedAdminAuthorizer(
|
|
|
157
159
|
target: MountedEnterpriseTarget = {},
|
|
158
160
|
) => {
|
|
159
161
|
const userId = await requireUserId(ctx);
|
|
162
|
+
if (userId === null) {
|
|
163
|
+
return { ok: false as const, code: "NOT_SIGNED_IN" as const };
|
|
164
|
+
}
|
|
160
165
|
if (!options?.admin?.authorized) {
|
|
161
|
-
|
|
162
|
-
code: "FORBIDDEN",
|
|
163
|
-
message:
|
|
164
|
-
"Mounted enterprise admin APIs require an authorized callback.",
|
|
165
|
-
});
|
|
166
|
+
return { ok: false as const, code: "FORBIDDEN" as const };
|
|
166
167
|
}
|
|
167
168
|
const resolved = await resolveMountedEnterpriseTarget(auth, ctx, target);
|
|
168
|
-
await options.admin.authorized(ctx, {
|
|
169
|
+
const authResult = await options.admin.authorized(ctx, {
|
|
169
170
|
userId,
|
|
170
171
|
permission,
|
|
171
172
|
enterpriseId: resolved.enterpriseId,
|
|
172
173
|
groupId: resolved.groupId,
|
|
173
174
|
resolvedGroupId: resolved.resolvedGroupId,
|
|
174
175
|
});
|
|
175
|
-
|
|
176
|
+
if (authResult && !authResult.ok) {
|
|
177
|
+
return { ok: false as const, code: "FORBIDDEN" as const };
|
|
178
|
+
}
|
|
179
|
+
return { ok: true as const, userId, ...resolved };
|
|
176
180
|
};
|
|
177
181
|
}
|
|
178
182
|
|
|
@@ -205,9 +209,12 @@ export function sso<
|
|
|
205
209
|
domain: v.optional(v.string()),
|
|
206
210
|
},
|
|
207
211
|
handler: async (ctx, args) => {
|
|
208
|
-
const
|
|
212
|
+
const authResult = await authorize(ctx, "sso.connection.create", {
|
|
209
213
|
groupId: args.groupId,
|
|
210
214
|
});
|
|
215
|
+
if (!authResult.ok)
|
|
216
|
+
return { ok: false as const, code: authResult.code };
|
|
217
|
+
const { userId } = authResult;
|
|
211
218
|
const createsGroup = args.groupId === undefined;
|
|
212
219
|
const groupId =
|
|
213
220
|
args.groupId ??
|
|
@@ -251,9 +258,10 @@ export function sso<
|
|
|
251
258
|
get: queryGeneric({
|
|
252
259
|
args: { enterpriseId: v.string() },
|
|
253
260
|
handler: async (ctx, args) => {
|
|
254
|
-
await authorize(ctx, "sso.connection.read", {
|
|
261
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
255
262
|
enterpriseId: args.enterpriseId,
|
|
256
263
|
});
|
|
264
|
+
if (!_auth.ok) return null;
|
|
257
265
|
return await auth.sso.admin.connection.get(
|
|
258
266
|
ctx as never,
|
|
259
267
|
args.enterpriseId,
|
|
@@ -263,9 +271,10 @@ export function sso<
|
|
|
263
271
|
getByGroup: queryGeneric({
|
|
264
272
|
args: { groupId: v.string() },
|
|
265
273
|
handler: async (ctx, args) => {
|
|
266
|
-
await authorize(ctx, "sso.connection.read", {
|
|
274
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
267
275
|
groupId: args.groupId,
|
|
268
276
|
});
|
|
277
|
+
if (!_auth.ok) return null;
|
|
269
278
|
return await auth.sso.admin.connection.getByGroup(
|
|
270
279
|
ctx as never,
|
|
271
280
|
args.groupId,
|
|
@@ -275,9 +284,10 @@ export function sso<
|
|
|
275
284
|
getByDomain: queryGeneric({
|
|
276
285
|
args: { domain: v.string() },
|
|
277
286
|
handler: async (ctx, args) => {
|
|
278
|
-
await authorize(ctx, "sso.connection.read", {
|
|
287
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
279
288
|
domain: args.domain,
|
|
280
289
|
});
|
|
290
|
+
if (!_auth.ok) return null;
|
|
281
291
|
return await auth.sso.admin.connection.getByDomain(
|
|
282
292
|
ctx as never,
|
|
283
293
|
args.domain,
|
|
@@ -293,9 +303,10 @@ export function sso<
|
|
|
293
303
|
order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
|
|
294
304
|
},
|
|
295
305
|
handler: async (ctx, args) => {
|
|
296
|
-
await authorize(ctx, "sso.connection.read", {
|
|
306
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
297
307
|
groupId: args.where?.groupId,
|
|
298
308
|
});
|
|
309
|
+
if (!_auth.ok) return null;
|
|
299
310
|
return await auth.sso.admin.connection.list(
|
|
300
311
|
ctx as never,
|
|
301
312
|
args as never,
|
|
@@ -312,9 +323,10 @@ export function sso<
|
|
|
312
323
|
}),
|
|
313
324
|
},
|
|
314
325
|
handler: async (ctx, args) => {
|
|
315
|
-
await authorize(ctx, "sso.connection.manage", {
|
|
326
|
+
const _auth = await authorize(ctx, "sso.connection.manage", {
|
|
316
327
|
enterpriseId: args.enterpriseId,
|
|
317
328
|
});
|
|
329
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
318
330
|
await auth.sso.admin.connection.update(
|
|
319
331
|
ctx as never,
|
|
320
332
|
args.enterpriseId,
|
|
@@ -326,9 +338,10 @@ export function sso<
|
|
|
326
338
|
delete: mutationGeneric({
|
|
327
339
|
args: { enterpriseId: v.string() },
|
|
328
340
|
handler: async (ctx, args) => {
|
|
329
|
-
await authorize(ctx, "sso.connection.manage", {
|
|
341
|
+
const _auth = await authorize(ctx, "sso.connection.manage", {
|
|
330
342
|
enterpriseId: args.enterpriseId,
|
|
331
343
|
});
|
|
344
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
332
345
|
return await auth.sso.admin.connection.delete(
|
|
333
346
|
ctx as never,
|
|
334
347
|
args.enterpriseId,
|
|
@@ -338,9 +351,10 @@ export function sso<
|
|
|
338
351
|
status: queryGeneric({
|
|
339
352
|
args: { enterpriseId: v.string() },
|
|
340
353
|
handler: async (ctx, args) => {
|
|
341
|
-
await authorize(ctx, "sso.connection.read", {
|
|
354
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
342
355
|
enterpriseId: args.enterpriseId,
|
|
343
356
|
});
|
|
357
|
+
if (!_auth.ok) return null;
|
|
344
358
|
return await auth.sso.admin.connection.status(
|
|
345
359
|
ctx as never,
|
|
346
360
|
args.enterpriseId,
|
|
@@ -351,9 +365,10 @@ export function sso<
|
|
|
351
365
|
list: queryGeneric({
|
|
352
366
|
args: { enterpriseId: v.string() },
|
|
353
367
|
handler: async (ctx, args) => {
|
|
354
|
-
await authorize(ctx, "sso.connection.read", {
|
|
368
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
355
369
|
enterpriseId: args.enterpriseId,
|
|
356
370
|
});
|
|
371
|
+
if (!_auth.ok) return null;
|
|
357
372
|
return await auth.sso.admin.connection.domain.list(
|
|
358
373
|
ctx as never,
|
|
359
374
|
args.enterpriseId,
|
|
@@ -363,9 +378,10 @@ export function sso<
|
|
|
363
378
|
validate: queryGeneric({
|
|
364
379
|
args: { enterpriseId: v.string() },
|
|
365
380
|
handler: async (ctx, args) => {
|
|
366
|
-
await authorize(ctx, "sso.domain.manage", {
|
|
381
|
+
const _auth = await authorize(ctx, "sso.domain.manage", {
|
|
367
382
|
enterpriseId: args.enterpriseId,
|
|
368
383
|
});
|
|
384
|
+
if (!_auth.ok) return null;
|
|
369
385
|
return await auth.sso.admin.connection.domain.validate(
|
|
370
386
|
ctx as never,
|
|
371
387
|
args.enterpriseId,
|
|
@@ -378,9 +394,10 @@ export function sso<
|
|
|
378
394
|
domains: v.array(enterpriseDomainInputValidator),
|
|
379
395
|
},
|
|
380
396
|
handler: async (ctx, args) => {
|
|
381
|
-
await authorize(ctx, "sso.domain.manage", {
|
|
397
|
+
const _auth = await authorize(ctx, "sso.domain.manage", {
|
|
382
398
|
enterpriseId: args.enterpriseId,
|
|
383
399
|
});
|
|
400
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
384
401
|
return await auth.sso.admin.connection.domain.set(
|
|
385
402
|
ctx as never,
|
|
386
403
|
args.enterpriseId,
|
|
@@ -392,9 +409,10 @@ export function sso<
|
|
|
392
409
|
request: mutationGeneric({
|
|
393
410
|
args: enterpriseDomainVerificationInputValidator,
|
|
394
411
|
handler: async (ctx, args) => {
|
|
395
|
-
await authorize(ctx, "sso.domain.manage", {
|
|
412
|
+
const _auth = await authorize(ctx, "sso.domain.manage", {
|
|
396
413
|
enterpriseId: args.enterpriseId,
|
|
397
414
|
});
|
|
415
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
398
416
|
return await auth.sso.admin.connection.domain.verification.request(
|
|
399
417
|
ctx as never,
|
|
400
418
|
args,
|
|
@@ -404,9 +422,10 @@ export function sso<
|
|
|
404
422
|
confirm: actionGeneric({
|
|
405
423
|
args: enterpriseDomainVerificationInputValidator,
|
|
406
424
|
handler: async (ctx, args) => {
|
|
407
|
-
await authorize(ctx, "sso.domain.manage", {
|
|
425
|
+
const _auth = await authorize(ctx, "sso.domain.manage", {
|
|
408
426
|
enterpriseId: args.enterpriseId,
|
|
409
427
|
});
|
|
428
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
410
429
|
return await auth.sso.admin.connection.domain.verification.confirm(
|
|
411
430
|
ctx as never,
|
|
412
431
|
args,
|
|
@@ -431,18 +450,20 @@ export function sso<
|
|
|
431
450
|
extraFields: v.optional(v.record(v.string(), v.string())),
|
|
432
451
|
},
|
|
433
452
|
handler: async (ctx, args) => {
|
|
434
|
-
await authorize(ctx, "sso.protocol.manage", {
|
|
453
|
+
const _auth = await authorize(ctx, "sso.protocol.manage", {
|
|
435
454
|
enterpriseId: args.enterpriseId,
|
|
436
455
|
});
|
|
456
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
437
457
|
return await auth.sso.admin.oidc.configure(ctx as never, args);
|
|
438
458
|
},
|
|
439
459
|
}),
|
|
440
460
|
get: queryGeneric({
|
|
441
461
|
args: { enterpriseId: v.string() },
|
|
442
462
|
handler: async (ctx, args) => {
|
|
443
|
-
await authorize(ctx, "sso.connection.read", {
|
|
463
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
444
464
|
enterpriseId: args.enterpriseId,
|
|
445
465
|
});
|
|
466
|
+
if (!_auth.ok) return null;
|
|
446
467
|
return await auth.sso.admin.oidc.get(
|
|
447
468
|
ctx as never,
|
|
448
469
|
args.enterpriseId,
|
|
@@ -452,9 +473,10 @@ export function sso<
|
|
|
452
473
|
validate: actionGeneric({
|
|
453
474
|
args: { enterpriseId: v.string() },
|
|
454
475
|
handler: async (ctx, args) => {
|
|
455
|
-
await authorize(ctx, "sso.protocol.manage", {
|
|
476
|
+
const _auth = await authorize(ctx, "sso.protocol.manage", {
|
|
456
477
|
enterpriseId: args.enterpriseId,
|
|
457
478
|
});
|
|
479
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
458
480
|
return await auth.sso.admin.oidc.validate(
|
|
459
481
|
ctx as never,
|
|
460
482
|
args.enterpriseId,
|
|
@@ -476,18 +498,20 @@ export function sso<
|
|
|
476
498
|
sp: v.optional(enterpriseSamlSpValidator),
|
|
477
499
|
},
|
|
478
500
|
handler: async (ctx, args) => {
|
|
479
|
-
await authorize(ctx, "sso.protocol.manage", {
|
|
501
|
+
const _auth = await authorize(ctx, "sso.protocol.manage", {
|
|
480
502
|
enterpriseId: args.enterpriseId,
|
|
481
503
|
});
|
|
504
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
482
505
|
return await auth.sso.admin.saml.configure(ctx as never, args);
|
|
483
506
|
},
|
|
484
507
|
}),
|
|
485
508
|
validate: queryGeneric({
|
|
486
509
|
args: { enterpriseId: v.string() },
|
|
487
510
|
handler: async (ctx, args) => {
|
|
488
|
-
await authorize(ctx, "sso.protocol.manage", {
|
|
511
|
+
const _auth = await authorize(ctx, "sso.protocol.manage", {
|
|
489
512
|
enterpriseId: args.enterpriseId,
|
|
490
513
|
});
|
|
514
|
+
if (!_auth.ok) return null;
|
|
491
515
|
return await auth.sso.admin.saml.validate(
|
|
492
516
|
ctx as never,
|
|
493
517
|
args.enterpriseId,
|
|
@@ -499,9 +523,10 @@ export function sso<
|
|
|
499
523
|
get: queryGeneric({
|
|
500
524
|
args: { enterpriseId: v.string() },
|
|
501
525
|
handler: async (ctx, args) => {
|
|
502
|
-
await authorize(ctx, "sso.connection.read", {
|
|
526
|
+
const _auth = await authorize(ctx, "sso.connection.read", {
|
|
503
527
|
enterpriseId: args.enterpriseId,
|
|
504
528
|
});
|
|
529
|
+
if (!_auth.ok) return null;
|
|
505
530
|
return await auth.sso.admin.policy.get(
|
|
506
531
|
ctx as never,
|
|
507
532
|
args.enterpriseId,
|
|
@@ -514,9 +539,10 @@ export function sso<
|
|
|
514
539
|
patch: enterprisePolicyPatchValidator,
|
|
515
540
|
},
|
|
516
541
|
handler: async (ctx, args) => {
|
|
517
|
-
await authorize(ctx, "sso.policy.manage", {
|
|
542
|
+
const _auth = await authorize(ctx, "sso.policy.manage", {
|
|
518
543
|
enterpriseId: args.enterpriseId,
|
|
519
544
|
});
|
|
545
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
520
546
|
return await auth.sso.admin.policy.update(
|
|
521
547
|
ctx as never,
|
|
522
548
|
args.enterpriseId,
|
|
@@ -527,9 +553,10 @@ export function sso<
|
|
|
527
553
|
validate: queryGeneric({
|
|
528
554
|
args: { enterpriseId: v.string() },
|
|
529
555
|
handler: async (ctx, args) => {
|
|
530
|
-
await authorize(ctx, "sso.policy.manage", {
|
|
556
|
+
const _auth = await authorize(ctx, "sso.policy.manage", {
|
|
531
557
|
enterpriseId: args.enterpriseId,
|
|
532
558
|
});
|
|
559
|
+
if (!_auth.ok) return null;
|
|
533
560
|
return await auth.sso.admin.policy.validate(
|
|
534
561
|
ctx as never,
|
|
535
562
|
args.enterpriseId,
|
|
@@ -545,10 +572,11 @@ export function sso<
|
|
|
545
572
|
limit: v.optional(v.number()),
|
|
546
573
|
},
|
|
547
574
|
handler: async (ctx, args) => {
|
|
548
|
-
await authorize(ctx, "sso.audit.read", {
|
|
575
|
+
const _auth = await authorize(ctx, "sso.audit.read", {
|
|
549
576
|
enterpriseId: args.enterpriseId,
|
|
550
577
|
groupId: args.groupId,
|
|
551
578
|
});
|
|
579
|
+
if (!_auth.ok) return null;
|
|
552
580
|
return await auth.sso.admin.audit.list(ctx as never, args);
|
|
553
581
|
},
|
|
554
582
|
}),
|
|
@@ -561,9 +589,10 @@ export function sso<
|
|
|
561
589
|
limit: v.optional(v.number()),
|
|
562
590
|
},
|
|
563
591
|
handler: async (ctx, args) => {
|
|
564
|
-
await authorize(ctx, "sso.webhook.manage", {
|
|
592
|
+
const _auth = await authorize(ctx, "sso.webhook.manage", {
|
|
565
593
|
enterpriseId: args.enterpriseId,
|
|
566
594
|
});
|
|
595
|
+
if (!_auth.ok) return null;
|
|
567
596
|
return await (auth.sso.admin.webhook as any).delivery.list(
|
|
568
597
|
ctx as never,
|
|
569
598
|
args,
|
|
@@ -581,9 +610,12 @@ export function sso<
|
|
|
581
610
|
createdByUserId: v.optional(v.string()),
|
|
582
611
|
},
|
|
583
612
|
handler: async (ctx, args) => {
|
|
584
|
-
const
|
|
613
|
+
const authResult = await authorize(ctx, "sso.webhook.manage", {
|
|
585
614
|
enterpriseId: args.enterpriseId,
|
|
586
615
|
});
|
|
616
|
+
if (!authResult.ok)
|
|
617
|
+
return { ok: false as const, code: authResult.code };
|
|
618
|
+
const { userId } = authResult;
|
|
587
619
|
const result = await auth.sso.admin.webhook.endpoint.create(
|
|
588
620
|
ctx as never,
|
|
589
621
|
{
|
|
@@ -605,9 +637,10 @@ export function sso<
|
|
|
605
637
|
list: queryGeneric({
|
|
606
638
|
args: { enterpriseId: v.string() },
|
|
607
639
|
handler: async (ctx, args) => {
|
|
608
|
-
await authorize(ctx, "sso.webhook.manage", {
|
|
640
|
+
const _auth = await authorize(ctx, "sso.webhook.manage", {
|
|
609
641
|
enterpriseId: args.enterpriseId,
|
|
610
642
|
});
|
|
643
|
+
if (!_auth.ok) return null;
|
|
611
644
|
const endpoints = await auth.sso.admin.webhook.endpoint.list(
|
|
612
645
|
ctx as never,
|
|
613
646
|
args.enterpriseId,
|
|
@@ -626,15 +659,16 @@ export function sso<
|
|
|
626
659
|
args.endpointId,
|
|
627
660
|
);
|
|
628
661
|
if (!endpoint) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
662
|
+
return {
|
|
663
|
+
ok: false as const,
|
|
664
|
+
code: "INVALID_PARAMETERS" as const,
|
|
665
|
+
};
|
|
633
666
|
}
|
|
634
|
-
await authorize(ctx, "sso.webhook.manage", {
|
|
667
|
+
const _auth = await authorize(ctx, "sso.webhook.manage", {
|
|
635
668
|
enterpriseId: endpoint.enterpriseId,
|
|
636
669
|
groupId: endpoint.groupId,
|
|
637
670
|
});
|
|
671
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
638
672
|
return await auth.sso.admin.webhook.endpoint.disable(
|
|
639
673
|
ctx as never,
|
|
640
674
|
args.endpointId,
|
|
@@ -692,27 +726,30 @@ export function scim<
|
|
|
692
726
|
status: v.optional(enterpriseStatusValidator),
|
|
693
727
|
},
|
|
694
728
|
handler: async (ctx, args) => {
|
|
695
|
-
await authorize(ctx, "scim.manage", {
|
|
729
|
+
const _auth = await authorize(ctx, "scim.manage", {
|
|
696
730
|
enterpriseId: args.enterpriseId,
|
|
697
731
|
});
|
|
732
|
+
if (!_auth.ok) return { ok: false as const, code: _auth.code };
|
|
698
733
|
return await auth.scim.admin.configure(ctx as never, args);
|
|
699
734
|
},
|
|
700
735
|
}),
|
|
701
736
|
get: queryGeneric({
|
|
702
737
|
args: { enterpriseId: v.string() },
|
|
703
738
|
handler: async (ctx, args) => {
|
|
704
|
-
await authorize(ctx, "scim.manage", {
|
|
739
|
+
const _auth = await authorize(ctx, "scim.manage", {
|
|
705
740
|
enterpriseId: args.enterpriseId,
|
|
706
741
|
});
|
|
742
|
+
if (!_auth.ok) return null;
|
|
707
743
|
return await auth.scim.admin.get(ctx as never, args.enterpriseId);
|
|
708
744
|
},
|
|
709
745
|
}),
|
|
710
746
|
validate: queryGeneric({
|
|
711
747
|
args: { enterpriseId: v.string() },
|
|
712
748
|
handler: async (ctx, args) => {
|
|
713
|
-
await authorize(ctx, "scim.manage", {
|
|
749
|
+
const _auth = await authorize(ctx, "scim.manage", {
|
|
714
750
|
enterpriseId: args.enterpriseId,
|
|
715
751
|
});
|
|
752
|
+
if (!_auth.ok) return null;
|
|
716
753
|
return await auth.scim.admin.validate(
|
|
717
754
|
ctx as never,
|
|
718
755
|
args.enterpriseId,
|
|
@@ -854,14 +891,21 @@ export type ServerOptions = {
|
|
|
854
891
|
| boolean;
|
|
855
892
|
};
|
|
856
893
|
|
|
857
|
-
export type RefreshResult =
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
894
|
+
export type RefreshResult =
|
|
895
|
+
| {
|
|
896
|
+
/** Code exchange occurred — return the pre-built redirect `Response`. */
|
|
897
|
+
redirect: true;
|
|
898
|
+
/** 302 redirect with Set-Cookie headers already serialized. */
|
|
899
|
+
response: Response;
|
|
900
|
+
}
|
|
901
|
+
| {
|
|
902
|
+
/** No redirect — apply cookies and read the token. */
|
|
903
|
+
redirect: false;
|
|
904
|
+
/** Structured cookies to set on the response. */
|
|
905
|
+
cookies: AuthCookie[];
|
|
906
|
+
/** JWT for SSR hydration, or `null` if not authenticated. */
|
|
907
|
+
token: string | null;
|
|
908
|
+
};
|
|
865
909
|
|
|
866
910
|
const TOKEN_COOKIE_BASE_NAME = "__convexAuthJWT";
|
|
867
911
|
const REFRESH_COOKIE_BASE_NAME = "__convexAuthRefreshToken";
|
|
@@ -1126,6 +1170,33 @@ function canParseUrl(value: string): boolean {
|
|
|
1126
1170
|
}
|
|
1127
1171
|
}
|
|
1128
1172
|
|
|
1173
|
+
function serializeAuthCookie(cookie: AuthCookie): string {
|
|
1174
|
+
const parts = [
|
|
1175
|
+
`${cookie.name}=${cookie.value}`,
|
|
1176
|
+
`Path=${cookie.options.path}`,
|
|
1177
|
+
];
|
|
1178
|
+
if (cookie.options.httpOnly) parts.push("HttpOnly");
|
|
1179
|
+
if (cookie.options.secure) parts.push("Secure");
|
|
1180
|
+
if (cookie.options.sameSite)
|
|
1181
|
+
parts.push(`SameSite=${cookie.options.sameSite}`);
|
|
1182
|
+
if (cookie.options.maxAge !== undefined)
|
|
1183
|
+
parts.push(`Max-Age=${cookie.options.maxAge}`);
|
|
1184
|
+
if (cookie.options.expires)
|
|
1185
|
+
parts.push(`Expires=${cookie.options.expires.toUTCString()}`);
|
|
1186
|
+
return parts.join("; ");
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function buildRedirectResponse(
|
|
1190
|
+
location: string,
|
|
1191
|
+
cookies: AuthCookie[],
|
|
1192
|
+
): Response {
|
|
1193
|
+
const headers = new Headers({ Location: location });
|
|
1194
|
+
for (const cookie of cookies) {
|
|
1195
|
+
headers.append("Set-Cookie", serializeAuthCookie(cookie));
|
|
1196
|
+
}
|
|
1197
|
+
return new Response(null, { status: 302, headers });
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1129
1200
|
function deriveCookieNamespaceFromUrl(url: string) {
|
|
1130
1201
|
if (!canParseUrl(url)) return DERIVED_COOKIE_NAMESPACE_FALLBACK;
|
|
1131
1202
|
const parsed = new URL(url);
|
|
@@ -1866,7 +1937,11 @@ export function server(options: ServerOptions) {
|
|
|
1866
1937
|
const corsRefreshResult = await Fx.run(
|
|
1867
1938
|
Fx.match(corsDispatch, corsDispatch.kind, {
|
|
1868
1939
|
crossOrigin: () =>
|
|
1869
|
-
({
|
|
1940
|
+
({
|
|
1941
|
+
redirect: false,
|
|
1942
|
+
cookies: [],
|
|
1943
|
+
token: null,
|
|
1944
|
+
}) satisfies RefreshResult,
|
|
1870
1945
|
sameOrigin: () => null,
|
|
1871
1946
|
}),
|
|
1872
1947
|
);
|
|
@@ -1954,19 +2029,22 @@ export function server(options: ServerOptions) {
|
|
|
1954
2029
|
Fx.fold({
|
|
1955
2030
|
ok: (result): RefreshResult => {
|
|
1956
2031
|
redirectUrl.searchParams.delete("code");
|
|
2032
|
+
const cookies = structuredAuthCookies(
|
|
2033
|
+
{
|
|
2034
|
+
token: result.tokens?.token ?? null,
|
|
2035
|
+
refreshToken: result.tokens?.refreshToken ?? null,
|
|
2036
|
+
verifier: null,
|
|
2037
|
+
},
|
|
2038
|
+
host,
|
|
2039
|
+
cookieConfig,
|
|
2040
|
+
cookieNamespace,
|
|
2041
|
+
);
|
|
1957
2042
|
return {
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
verifier: null,
|
|
1963
|
-
},
|
|
1964
|
-
host,
|
|
1965
|
-
cookieConfig,
|
|
1966
|
-
cookieNamespace,
|
|
2043
|
+
redirect: true,
|
|
2044
|
+
response: buildRedirectResponse(
|
|
2045
|
+
redirectUrl.toString(),
|
|
2046
|
+
cookies,
|
|
1967
2047
|
),
|
|
1968
|
-
redirect: redirectUrl.toString(),
|
|
1969
|
-
token: result.tokens?.token ?? null,
|
|
1970
2048
|
};
|
|
1971
2049
|
},
|
|
1972
2050
|
err: (error: unknown): RefreshResult => {
|
|
@@ -1993,24 +2071,28 @@ export function server(options: ServerOptions) {
|
|
|
1993
2071
|
errorCode === "INVALID_VERIFICATION_CODE";
|
|
1994
2072
|
if (!terminalCodeExchangeError) {
|
|
1995
2073
|
return {
|
|
2074
|
+
redirect: false,
|
|
1996
2075
|
cookies: [],
|
|
1997
2076
|
token: currentCookies.token,
|
|
1998
2077
|
};
|
|
1999
2078
|
}
|
|
2000
2079
|
redirectUrl.searchParams.delete("code");
|
|
2080
|
+
const cookies = structuredAuthCookies(
|
|
2081
|
+
{
|
|
2082
|
+
token: currentCookies.token,
|
|
2083
|
+
refreshToken: currentCookies.refreshToken,
|
|
2084
|
+
verifier: null,
|
|
2085
|
+
},
|
|
2086
|
+
host,
|
|
2087
|
+
cookieConfig,
|
|
2088
|
+
cookieNamespace,
|
|
2089
|
+
);
|
|
2001
2090
|
return {
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
verifier: null,
|
|
2007
|
-
},
|
|
2008
|
-
host,
|
|
2009
|
-
cookieConfig,
|
|
2010
|
-
cookieNamespace,
|
|
2091
|
+
redirect: true,
|
|
2092
|
+
response: buildRedirectResponse(
|
|
2093
|
+
redirectUrl.toString(),
|
|
2094
|
+
cookies,
|
|
2011
2095
|
),
|
|
2012
|
-
redirect: redirectUrl.toString(),
|
|
2013
|
-
token: currentCookies.token,
|
|
2014
2096
|
};
|
|
2015
2097
|
},
|
|
2016
2098
|
}),
|
|
@@ -2501,10 +2583,11 @@ export function server(options: ServerOptions) {
|
|
|
2501
2583
|
}),
|
|
2502
2584
|
);
|
|
2503
2585
|
if (tokens === undefined) {
|
|
2504
|
-
return { cookies: [], token: currentToken };
|
|
2586
|
+
return { redirect: false, cookies: [], token: currentToken };
|
|
2505
2587
|
}
|
|
2506
2588
|
|
|
2507
2589
|
return {
|
|
2590
|
+
redirect: false,
|
|
2508
2591
|
cookies: structuredAuthCookies(
|
|
2509
2592
|
{
|
|
2510
2593
|
token: tokens?.token ?? null,
|
package/src/server/signin.ts
CHANGED
|
@@ -142,6 +142,7 @@ function signInFx(
|
|
|
142
142
|
passkey: (p) => handlePasskeyFx(ctx, p, args),
|
|
143
143
|
totp: (p) => handleTotp(ctx, p, args),
|
|
144
144
|
device: (p) => handleDevice(ctx, p, args),
|
|
145
|
+
sso: (_p) => handleSsoProviderFx(ctx, args),
|
|
145
146
|
});
|
|
146
147
|
});
|
|
147
148
|
}
|
|
@@ -382,3 +383,54 @@ function handleOAuthProviderFx(
|
|
|
382
383
|
};
|
|
383
384
|
});
|
|
384
385
|
}
|
|
386
|
+
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// SSO (Enterprise OIDC / SAML)
|
|
389
|
+
// ============================================================================
|
|
390
|
+
|
|
391
|
+
function handleSsoProviderFx(
|
|
392
|
+
ctx: EnrichedActionCtx,
|
|
393
|
+
args: {
|
|
394
|
+
params?: Record<string, any>;
|
|
395
|
+
},
|
|
396
|
+
): FxType<{ kind: "redirect"; redirect: string; verifier: string }, AuthError> {
|
|
397
|
+
return Fx.gen(function* () {
|
|
398
|
+
const enterpriseId = args.params?.enterpriseId;
|
|
399
|
+
if (!enterpriseId || typeof enterpriseId !== "string") {
|
|
400
|
+
return yield* Fx.fail(
|
|
401
|
+
new AuthError(
|
|
402
|
+
"SIGN_IN_MISSING_PARAMS",
|
|
403
|
+
"enterpriseId is required for SSO sign-in.",
|
|
404
|
+
),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const protocol: "oidc" | "saml" = args.params?.protocol ?? "oidc";
|
|
409
|
+
if (protocol !== "oidc" && protocol !== "saml") {
|
|
410
|
+
return yield* Fx.fail(
|
|
411
|
+
new AuthError(
|
|
412
|
+
"SIGN_IN_MISSING_PARAMS",
|
|
413
|
+
`Invalid SSO protocol: ${protocol as string}. Expected "oidc" or "saml".`,
|
|
414
|
+
),
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const verifier = yield* Fx.promise(() => callVerifier(ctx));
|
|
419
|
+
const siteUrl =
|
|
420
|
+
process.env.CUSTOM_AUTH_SITE_URL ?? requireEnv("CONVEX_SITE_URL");
|
|
421
|
+
const redirect = new URL(
|
|
422
|
+
`${siteUrl}/api/auth/sso/${enterpriseId}/${protocol}/signin`,
|
|
423
|
+
);
|
|
424
|
+
redirect.searchParams.set("code", verifier);
|
|
425
|
+
|
|
426
|
+
if (typeof args.params?.redirectTo === "string") {
|
|
427
|
+
redirect.searchParams.set("redirectTo", args.params.redirectTo);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
kind: "redirect" as const,
|
|
432
|
+
redirect: redirect.toString(),
|
|
433
|
+
verifier,
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
}
|
package/src/server/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-version.js — do not edit.
|
|
2
|
-
export const AUTH_VERSION = "0.0.4-preview.
|
|
2
|
+
export const AUTH_VERSION = "0.0.4-preview.18";
|