@robelest/convex-auth 0.0.4-preview.21 → 0.0.4-preview.22

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 (39) hide show
  1. package/dist/component/convex.config.d.ts +2 -2
  2. package/dist/component/convex.config.d.ts.map +1 -1
  3. package/dist/component/model.d.ts +10 -10
  4. package/dist/component/model.d.ts.map +1 -1
  5. package/dist/component/schema.d.ts +51 -51
  6. package/dist/component/server/auth.d.ts +71 -25
  7. package/dist/component/server/auth.d.ts.map +1 -1
  8. package/dist/component/server/auth.js +13 -27
  9. package/dist/component/server/auth.js.map +1 -1
  10. package/dist/component/server/runtime.d.ts +2 -2
  11. package/dist/server/auth.d.ts +70 -24
  12. package/dist/server/auth.d.ts.map +1 -1
  13. package/dist/server/auth.js +13 -27
  14. package/dist/server/auth.js.map +1 -1
  15. package/dist/server/index.d.ts +2 -2
  16. package/dist/server/mounts.d.ts +12 -12
  17. package/dist/server/mutations/account.d.ts +6 -6
  18. package/dist/server/mutations/account.d.ts.map +1 -1
  19. package/dist/server/mutations/code.d.ts +13 -13
  20. package/dist/server/mutations/invalidate.d.ts +4 -4
  21. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  22. package/dist/server/mutations/oauth.d.ts +9 -9
  23. package/dist/server/mutations/oauth.d.ts.map +1 -1
  24. package/dist/server/mutations/refresh.d.ts +3 -3
  25. package/dist/server/mutations/refresh.d.ts.map +1 -1
  26. package/dist/server/mutations/register.d.ts +11 -11
  27. package/dist/server/mutations/retrieve.d.ts +6 -6
  28. package/dist/server/mutations/retrieve.d.ts.map +1 -1
  29. package/dist/server/mutations/signature.d.ts +4 -4
  30. package/dist/server/mutations/signin.d.ts +5 -5
  31. package/dist/server/mutations/store.d.ts +100 -100
  32. package/dist/server/mutations/store.d.ts.map +1 -1
  33. package/dist/server/mutations/verify.d.ts +10 -10
  34. package/dist/server/runtime.d.ts +15 -15
  35. package/package.json +2 -2
  36. package/src/cli/index.ts +1 -1
  37. package/src/component/index.ts +1 -0
  38. package/src/server/auth.ts +103 -59
  39. package/src/server/index.ts +2 -0
@@ -9,7 +9,6 @@ import type { GenericId } from "convex/values";
9
9
 
10
10
  import type { AuthApiRefs } from "../client/index";
11
11
  import { Auth as AuthFactory } from "./runtime";
12
- import { Fx } from "@robelest/fx";
13
12
  import { AuthError } from "./authError";
14
13
  import type { Doc } from "./types";
15
14
  import type {
@@ -35,6 +34,9 @@ import type {
35
34
  */
36
35
  export type AuthConfig = Omit<ConvexAuthConfig, "component">;
37
36
 
37
+ /** Canonical user document type exposed by Convex Auth. */
38
+ export type UserDoc = Doc<"User">;
39
+
38
40
  type MemberApiWithAuthorization<
39
41
  TAuthorization extends AuthAuthorizationConfig | undefined,
40
42
  > = Omit<
@@ -197,17 +199,32 @@ export type AuthApiBase<
197
199
  };
198
200
 
199
201
  /**
200
- * Resolved auth context injected into `ctx.auth` by `auth.ctx()`.
202
+ * Resolved auth context injected into `ctx.auth` by `auth.ctx()` and
203
+ * {@link AuthCtx}. Also the expected return shape for custom
204
+ * {@link AuthCtxConfig.authResolve | authResolve} hooks.
201
205
  *
202
206
  * - `null` when unauthenticated.
203
207
  * - `groupId` is `null` when the user has no active group set.
204
208
  * - `role` / `grants` are `null` / `[]` when no active group or no membership.
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * import type { AuthResolvedContext } from "@robelest/convex-auth/server";
213
+ *
214
+ * const mockAuth: AuthResolvedContext = {
215
+ * userId: "user123" as Id<"User">,
216
+ * user: { _id: "user123", email: "test@example.com" },
217
+ * groupId: "group456",
218
+ * role: "admin",
219
+ * grants: ["read", "write"],
220
+ * };
221
+ * ```
205
222
  */
206
223
  export type AuthResolvedContext = {
207
224
  /** The authenticated user's document ID. */
208
- userId: string;
225
+ userId: GenericId<"User">;
209
226
  /** The authenticated user's full document. */
210
- user: any;
227
+ user: UserDoc;
211
228
  /** The user's active group ID, or `null` if none set. */
212
229
  groupId: string | null;
213
230
  /** The user's primary role in the active group, or `null`. */
@@ -216,6 +233,20 @@ export type AuthResolvedContext = {
216
233
  grants: string[];
217
234
  };
218
235
 
236
+ type AuthCtxBase = {
237
+ getUserIdentity: () => Promise<UserIdentity | null>;
238
+ };
239
+
240
+ type RequiredAuthCtxState = AuthCtxBase & AuthResolvedContext;
241
+
242
+ type OptionalAuthCtxState = AuthCtxBase & {
243
+ userId: GenericId<"User"> | null;
244
+ user: UserDoc | null;
245
+ groupId: string | null;
246
+ role: string | null;
247
+ grants: string[];
248
+ };
249
+
219
250
  type InternalSsoApi = ReturnType<typeof AuthFactory>["auth"]["sso"];
220
251
 
221
252
  type PublicSsoAdminApi = {
@@ -370,7 +401,7 @@ export type InferClientApi<T> =
370
401
  : AuthApiRefs;
371
402
 
372
403
  /** @internal */
373
- export type AuthLike = Pick<AuthApiBase, "user">;
404
+ export type AuthLike = Pick<AuthApiBase, "user" | "member">;
374
405
 
375
406
  // ============================================================================
376
407
  // Auth setup APIs
@@ -417,7 +448,7 @@ export type AuthLike = Pick<AuthApiBase, "user">;
417
448
  * 3. `user.getActiveGroup(ctx, { userId })` → groupId or null
418
449
  * 4. If groupId → `member.resolve(ctx, { userId, groupId })` → role + grants
419
450
  */
420
- async function resolveAuthContext(auth: any, ctx: any) {
451
+ async function resolveAuthContext(auth: AuthLike, ctx: any) {
421
452
  const userId = await auth.user.id(ctx);
422
453
  if (!userId) return null;
423
454
  const user = await auth.user.get(ctx, userId);
@@ -645,9 +676,6 @@ export function createAuth<
645
676
  // AuthCtx — ctx enrichment for customQuery / customMutation
646
677
  // ============================================================================
647
678
 
648
- /** Canonical user document type exposed by Convex Auth. */
649
- export type UserDoc = Doc<"User">;
650
-
651
679
  /**
652
680
  * Configuration for {@link AuthCtx} context enrichment.
653
681
  *
@@ -660,17 +688,54 @@ export type AuthCtxConfig<
660
688
  /** Allow unauthenticated callers and return `userId: null` / `user: null`. */
661
689
  optional?: boolean;
662
690
  /**
663
- * Attach additional derived fields to the auth context after the user is resolved.
691
+ * Attach additional derived fields to the auth context after the base auth
692
+ * context is resolved.
664
693
  */
665
- resolve?: (ctx: any, user: UserDoc) => Promise<TResolve> | TResolve;
694
+ resolve?: (
695
+ ctx: any,
696
+ user: UserDoc,
697
+ auth: AuthResolvedContext,
698
+ ) => Promise<TResolve> | TResolve;
699
+ /**
700
+ * Override or wrap the base auth resolution used by {@link AuthCtx}.
701
+ *
702
+ * Return `undefined` to fall back to the built-in resolver,
703
+ * `null` for an explicit unauthenticated state, or an
704
+ * {@link AuthResolvedContext} object to provide a pre-resolved auth state.
705
+ * This is useful for tests, proxy auth, impersonation flows, or any
706
+ * environment that needs to inject auth without depending on the standard
707
+ * Convex auth tables.
708
+ *
709
+ * @param ctx - The Convex function context.
710
+ * @param fallback - The built-in auth resolver used by {@link AuthCtx}.
711
+ * @returns Resolved auth state, `null`, or `undefined` to use the fallback.
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * const authCtx = AuthCtx(auth, {
716
+ * authResolve: async (ctx, fallback) => {
717
+ * const injected = getInjectedAuth(ctx);
718
+ * return injected ?? (await fallback());
719
+ * },
720
+ * });
721
+ * ```
722
+ */
723
+ authResolve?: (
724
+ ctx: any,
725
+ fallback: () => Promise<AuthResolvedContext | null>,
726
+ ) =>
727
+ | Promise<AuthResolvedContext | null | undefined>
728
+ | AuthResolvedContext
729
+ | null
730
+ | undefined;
666
731
  };
667
732
 
668
733
  /**
669
734
  * Create a context enrichment for `customQuery` / `customMutation` — optional auth.
670
735
  *
671
736
  * When `optional: true` is set, unauthenticated requests are allowed.
672
- * The enriched `ctx.auth` will have `userId: null` and `user: null`
673
- * for unauthenticated callers.
737
+ * The enriched `ctx.auth` will have `userId: null`, `user: null`,
738
+ * `groupId: null`, `role: null`, and `grants: []` for unauthenticated callers.
674
739
  *
675
740
  * @param auth - The auth API object returned by {@link createAuth}.
676
741
  * @param config - Configuration with `optional: true` and an optional
@@ -701,11 +766,7 @@ export function AuthCtx<
701
766
  _extra?: any,
702
767
  ) => Promise<{
703
768
  ctx: {
704
- auth: {
705
- getUserIdentity: () => Promise<UserIdentity | null>;
706
- userId: GenericId<"User"> | null;
707
- user: UserDoc | null;
708
- } & TResolve;
769
+ auth: OptionalAuthCtxState & TResolve;
709
770
  };
710
771
  args: {};
711
772
  }>;
@@ -715,8 +776,9 @@ export function AuthCtx<
715
776
  *
716
777
  * When `optional` is omitted or `false`, the inferred type is the authenticated
717
778
  * auth shape. At runtime this helper still resolves instead of throwing, so if
718
- * no user is signed in the returned `ctx.auth.userId` and `ctx.auth.user` are
719
- * `null`.
779
+ * no user is signed in the returned `ctx.auth.userId` / `ctx.auth.user` are
780
+ * `null`, `ctx.auth.groupId` / `ctx.auth.role` are `null`, and
781
+ * `ctx.auth.grants` is `[]`.
720
782
  *
721
783
  * @param auth - The auth API object returned by {@link createAuth}.
722
784
  * @param config - Optional configuration with a `resolve` callback
@@ -746,11 +808,7 @@ export function AuthCtx<
746
808
  _extra?: any,
747
809
  ) => Promise<{
748
810
  ctx: {
749
- auth: {
750
- getUserIdentity: () => Promise<UserIdentity | null>;
751
- userId: GenericId<"User">;
752
- user: UserDoc;
753
- } & TResolve;
811
+ auth: RequiredAuthCtxState & TResolve;
754
812
  };
755
813
  args: {};
756
814
  }>;
@@ -761,39 +819,25 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
761
819
  args: {},
762
820
  input: async (ctx: any, _args: any, _extra?: any) => {
763
821
  const nativeAuth = ctx.auth;
764
- const modeDispatch =
765
- config?.optional === true
766
- ? { mode: "optional" as const }
767
- : { mode: "required" as const };
768
-
769
- const userContext = await Fx.run(
770
- Fx.match(modeDispatch, modeDispatch.mode, {
771
- optional: async () => {
772
- const userId = await auth.user.id(ctx);
773
- if (!userId) {
774
- return null;
775
- }
776
- const user = await auth.user.get(ctx, userId);
777
- return { userId, user };
778
- },
779
- required: async () => {
780
- const userId = await auth.user.id(ctx);
781
- if (!userId) {
782
- return null;
783
- }
784
- const user = await auth.user.get(ctx, userId);
785
- return { userId, user };
786
- },
787
- }),
788
- );
822
+ const getUserIdentity = nativeAuth.getUserIdentity.bind(nativeAuth);
823
+ const fallback = () => resolveAuthContext(auth, ctx);
824
+
825
+ const authOverride = config?.authResolve
826
+ ? await config.authResolve(ctx, fallback)
827
+ : undefined;
828
+ const resolved =
829
+ authOverride === undefined ? await fallback() : authOverride;
789
830
 
790
- if (userContext === null) {
831
+ if (resolved === null) {
791
832
  return {
792
833
  ctx: {
793
834
  auth: {
794
- getUserIdentity: nativeAuth.getUserIdentity.bind(nativeAuth),
835
+ getUserIdentity,
795
836
  userId: null,
796
837
  user: null,
838
+ groupId: null,
839
+ role: null,
840
+ grants: [],
797
841
  },
798
842
  },
799
843
  args: {},
@@ -801,15 +845,14 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
801
845
  }
802
846
 
803
847
  const extra = config?.resolve
804
- ? await config.resolve(ctx, userContext.user)
848
+ ? await config.resolve(ctx, resolved.user, resolved)
805
849
  : {};
806
850
 
807
851
  return {
808
852
  ctx: {
809
853
  auth: {
810
- getUserIdentity: nativeAuth.getUserIdentity.bind(nativeAuth),
811
- userId: userContext.userId,
812
- user: userContext.user,
854
+ getUserIdentity,
855
+ ...resolved,
813
856
  ...extra,
814
857
  },
815
858
  },
@@ -824,9 +867,10 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
824
867
  *
825
868
  * Use this to type function parameters or variables that receive the
826
869
  * enriched auth context produced by `AuthCtx`. The inferred type includes
827
- * `userId`, `user`, `getUserIdentity`, and any additional fields added
828
- * by the `resolve` callback. This is the generic utility for reusing the
829
- * enriched auth shape without manually duplicating conditional auth types.
870
+ * `userId`, `user`, `groupId`, `role`, `grants`, `getUserIdentity`, and any
871
+ * additional fields added by the `resolve` callback. This is the generic
872
+ * utility for reusing the enriched auth shape without manually duplicating
873
+ * conditional auth types.
830
874
  *
831
875
  * @typeParam T - An `AuthCtx` return value (must have an `input` method
832
876
  * that returns `{ ctx: { auth: ... } }`).
@@ -3,6 +3,8 @@ export type {
3
3
  AuthApi,
4
4
  AuthApiBase,
5
5
  AuthConfig,
6
+ AuthCtxConfig,
7
+ AuthResolvedContext,
6
8
  ConvexAuthResult,
7
9
  InferAuth,
8
10
  InferClientApi,