@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.
Files changed (88) hide show
  1. package/dist/authorization/index.d.ts +17 -0
  2. package/dist/authorization/index.d.ts.map +1 -0
  3. package/dist/authorization/index.js +17 -0
  4. package/dist/authorization/index.js.map +1 -0
  5. package/dist/client/index.js +73 -0
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/component/_generated/component.d.ts +52 -0
  8. package/dist/component/_generated/component.d.ts.map +1 -1
  9. package/dist/component/convex.config.d.ts +2 -2
  10. package/dist/component/convex.config.d.ts.map +1 -1
  11. package/dist/component/index.js +2 -2
  12. package/dist/component/model.d.ts +75 -75
  13. package/dist/component/model.d.ts.map +1 -1
  14. package/dist/component/model.js +2 -0
  15. package/dist/component/model.js.map +1 -1
  16. package/dist/component/public/factors.d.ts.map +1 -1
  17. package/dist/component/public/factors.js +1 -1
  18. package/dist/component/public/factors.js.map +1 -1
  19. package/dist/component/public/groups.d.ts +10 -2
  20. package/dist/component/public/groups.d.ts.map +1 -1
  21. package/dist/component/public/groups.js +92 -8
  22. package/dist/component/public/groups.js.map +1 -1
  23. package/dist/component/public/identity.d.ts.map +1 -1
  24. package/dist/component/public/identity.js +3 -3
  25. package/dist/component/public/identity.js.map +1 -1
  26. package/dist/component/public/shared.d.ts +4 -4
  27. package/dist/component/public/shared.d.ts.map +1 -1
  28. package/dist/component/public.d.ts +3 -3
  29. package/dist/component/public.js +2 -2
  30. package/dist/component/schema.d.ts +13 -2
  31. package/dist/component/schema.js +7 -5
  32. package/dist/component/schema.js.map +1 -1
  33. package/dist/component/server/auth.d.ts +4 -25
  34. package/dist/component/server/auth.d.ts.map +1 -1
  35. package/dist/component/server/auth.js +4 -10
  36. package/dist/component/server/auth.js.map +1 -1
  37. package/dist/component/server/domains/core.js +196 -131
  38. package/dist/component/server/domains/core.js.map +1 -1
  39. package/dist/component/server/factory.d.ts +3 -3
  40. package/dist/component/server/signin.js +20 -1
  41. package/dist/component/server/signin.js.map +1 -1
  42. package/dist/server/auth.d.ts +4 -25
  43. package/dist/server/auth.d.ts.map +1 -1
  44. package/dist/server/auth.js +4 -10
  45. package/dist/server/auth.js.map +1 -1
  46. package/dist/server/domains/core.d.ts +73 -58
  47. package/dist/server/domains/core.d.ts.map +1 -1
  48. package/dist/server/domains/core.js +196 -131
  49. package/dist/server/domains/core.js.map +1 -1
  50. package/dist/server/http.d.ts +2 -2
  51. package/dist/server/http.d.ts.map +1 -1
  52. package/dist/server/index.d.ts +108 -69
  53. package/dist/server/index.d.ts.map +1 -1
  54. package/dist/server/index.js +138 -54
  55. package/dist/server/index.js.map +1 -1
  56. package/dist/server/mutations/code.d.ts +9 -9
  57. package/dist/server/mutations/index.d.ts +69 -69
  58. package/dist/server/mutations/invalidate.d.ts +4 -4
  59. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  60. package/dist/server/mutations/oauth.d.ts +7 -7
  61. package/dist/server/mutations/register.d.ts +9 -9
  62. package/dist/server/mutations/retrieve.d.ts +6 -6
  63. package/dist/server/mutations/retrieve.d.ts.map +1 -1
  64. package/dist/server/mutations/signature.d.ts +4 -4
  65. package/dist/server/mutations/signature.d.ts.map +1 -1
  66. package/dist/server/mutations/signin.d.ts +5 -5
  67. package/dist/server/mutations/signin.d.ts.map +1 -1
  68. package/dist/server/mutations/verify.d.ts +7 -7
  69. package/dist/server/signin.js +20 -1
  70. package/dist/server/signin.js.map +1 -1
  71. package/dist/server/version.d.ts +1 -1
  72. package/dist/server/version.js +1 -1
  73. package/dist/server/version.js.map +1 -1
  74. package/package.json +9 -5
  75. package/src/authorization/index.ts +37 -0
  76. package/src/client/index.ts +122 -0
  77. package/src/component/_generated/component.ts +64 -0
  78. package/src/component/index.ts +1 -1
  79. package/src/component/model.ts +2 -0
  80. package/src/component/public/factors.ts +3 -2
  81. package/src/component/public/groups.ts +142 -8
  82. package/src/component/public/identity.ts +9 -6
  83. package/src/component/schema.ts +14 -2
  84. package/src/server/auth.ts +9 -61
  85. package/src/server/domains/core.ts +235 -210
  86. package/src/server/index.ts +158 -75
  87. package/src/server/signin.ts +52 -0
  88. package/src/server/version.ts +1 -1
@@ -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: { auth: import("convex/server").Auth }) => {
82
- return await auth.user.require(ctx as never);
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
- throw new ConvexError({
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
- return { userId, ...resolved };
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 { userId } = await authorize(ctx, "sso.connection.create", {
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 { userId } = await authorize(ctx, "sso.webhook.manage", {
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
- throw new ConvexError({
630
- code: "INVALID_PARAMETERS",
631
- message: "Webhook endpoint not found.",
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
- /** Structured cookies to set on the response. */
859
- cookies: AuthCookie[];
860
- /** URL to redirect to (set after OAuth code exchange). */
861
- redirect?: string;
862
- /** JWT for SSR hydration, or `null` if not authenticated. */
863
- token: string | null;
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
- ({ cookies: [], token: null }) satisfies RefreshResult,
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
- cookies: structuredAuthCookies(
1959
- {
1960
- token: result.tokens?.token ?? null,
1961
- refreshToken: result.tokens?.refreshToken ?? null,
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
- cookies: structuredAuthCookies(
2003
- {
2004
- token: currentCookies.token,
2005
- refreshToken: currentCookies.refreshToken,
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,
@@ -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
+ }
@@ -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.16";
2
+ export const AUTH_VERSION = "0.0.4-preview.18";