@robelest/convex-auth 0.0.4-preview.27 → 0.0.4-preview.28

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/README.md +3 -5
  2. package/dist/bin.js +6488 -1571
  3. package/dist/browser/index.js +10 -7
  4. package/dist/browser/locks.js +3 -5
  5. package/dist/browser/navigation.js +7 -10
  6. package/dist/browser/runtime.js +35 -33
  7. package/dist/client/core/types.js +17 -0
  8. package/dist/client/factors/device.js +26 -19
  9. package/dist/client/index.js +151 -163
  10. package/dist/client/runtime/proxy.js +6 -6
  11. package/dist/client/services/adapters.js +3 -7
  12. package/dist/client/services/http.js +2 -5
  13. package/dist/client/services/resolve.js +5 -11
  14. package/dist/client/services/runtime.js +2 -5
  15. package/dist/component/_generated/component.d.ts +46 -0
  16. package/dist/component/index.d.ts +3 -3
  17. package/dist/component/model.d.ts +25 -25
  18. package/dist/component/public/identity/sessions.js +38 -1
  19. package/dist/component/public/identity/tokens.js +81 -3
  20. package/dist/component/public/identity/verifiers.js +9 -3
  21. package/dist/component/public.js +3 -3
  22. package/dist/component/schema.d.ts +320 -320
  23. package/dist/core/index.d.ts +380 -0
  24. package/dist/core/index.js +83 -0
  25. package/dist/otel.d.ts +13 -17
  26. package/dist/otel.js +39 -49
  27. package/dist/providers/email.d.ts +2 -2
  28. package/dist/providers/password.js +8 -16
  29. package/dist/providers/phone.js +2 -9
  30. package/dist/server/auth-context.d.ts +204 -0
  31. package/dist/server/auth-context.js +76 -0
  32. package/dist/server/auth.d.ts +25 -187
  33. package/dist/server/auth.js +5 -96
  34. package/dist/server/componentContext.d.ts +12 -0
  35. package/dist/server/componentContext.js +1 -0
  36. package/dist/server/config.js +1 -12
  37. package/dist/server/constants.js +6 -0
  38. package/dist/server/contract.d.ts +1 -1
  39. package/dist/server/core.js +5 -14
  40. package/dist/server/crypto.js +26 -18
  41. package/dist/server/db.js +6 -1
  42. package/dist/server/device.js +88 -78
  43. package/dist/server/http.d.ts +4 -3
  44. package/dist/server/http.js +74 -86
  45. package/dist/server/index.d.ts +2 -1
  46. package/dist/server/limits.js +22 -15
  47. package/dist/server/mounts.d.ts +103 -103
  48. package/dist/server/mutations/account.js +6 -4
  49. package/dist/server/mutations/invalidate.js +3 -6
  50. package/dist/server/mutations/oauth.js +86 -88
  51. package/dist/server/mutations/refresh.js +45 -87
  52. package/dist/server/mutations/register.js +19 -19
  53. package/dist/server/mutations/retrieve.js +17 -15
  54. package/dist/server/mutations/signature.js +9 -13
  55. package/dist/server/mutations/signin.js +7 -3
  56. package/dist/server/mutations/signout.js +10 -15
  57. package/dist/server/mutations/store.js +22 -12
  58. package/dist/server/mutations/verifier.js +11 -6
  59. package/dist/server/mutations/verify.js +55 -46
  60. package/dist/server/oauth/runtime.js +27 -25
  61. package/dist/server/passkey.js +299 -250
  62. package/dist/server/prefetch.js +283 -281
  63. package/dist/server/refresh.js +7 -60
  64. package/dist/server/runtime.d.ts +82 -206
  65. package/dist/server/runtime.js +63 -56
  66. package/dist/server/services/config.js +5 -3
  67. package/dist/server/services/logger.js +2 -4
  68. package/dist/server/services/providers.js +2 -4
  69. package/dist/server/services/refresh.js +2 -4
  70. package/dist/server/services/resolve.js +15 -14
  71. package/dist/server/services/signin.js +2 -4
  72. package/dist/server/sessions.js +32 -33
  73. package/dist/server/signin.js +177 -142
  74. package/dist/server/sso/domain.d.ts +20 -68
  75. package/dist/server/sso/domain.js +444 -413
  76. package/dist/server/sso/http.js +53 -59
  77. package/dist/server/sso/oidc.js +94 -80
  78. package/dist/server/tokens.js +13 -3
  79. package/dist/server/totp.js +153 -116
  80. package/dist/server/types.d.ts +2 -2
  81. package/dist/server/users.js +18 -23
  82. package/dist/server/utils/cache.js +51 -0
  83. package/dist/server/utils/dispatch.js +36 -0
  84. package/dist/server/utils/retry.js +24 -0
  85. package/dist/server/utils/span.js +32 -0
  86. package/dist/shared/errors.js +9 -3
  87. package/dist/shared/log.js +20 -22
  88. package/package.json +41 -33
@@ -1,5 +1,4 @@
1
1
  import { credentials } from "./credentials.js";
2
- import { Effect, Match } from "effect";
3
2
  import { scryptAsync } from "@noble/hashes/scrypt.js";
4
3
  import { bytesToHex } from "@noble/hashes/utils.js";
5
4
 
@@ -80,11 +79,8 @@ function password(config = {}) {
80
79
  }
81
80
  validateDefaultPasswordRequirements(password);
82
81
  };
83
- await Effect.runPromise(Match.value(flowDispatch).pipe(Match.when({ tag: "signUp" }, () => Effect.sync(() => {
84
- validatePasswordRequirements(params.password);
85
- })), Match.when({ tag: "resetVerification" }, () => Effect.sync(() => {
86
- validatePasswordRequirements(params.newPassword);
87
- })), Match.orElse(() => Effect.void)));
82
+ if (flowDispatch.tag === "signUp") validatePasswordRequirements(params.password);
83
+ else if (flowDispatch.tag === "resetVerification") validatePasswordRequirements(params.newPassword);
88
84
  const profile = config.profile?.(params, ctx) ?? defaultProfile(params);
89
85
  const { email } = profile;
90
86
  const requirePasswordParam = (value, flow) => {
@@ -98,11 +94,7 @@ function password(config = {}) {
98
94
  });
99
95
  return { userId: user._id };
100
96
  };
101
- const tryPasswordFlow = (try_) => Effect.tryPromise({
102
- try: try_,
103
- catch: (error) => error instanceof Error ? error : new Error(String(error))
104
- });
105
- return await Effect.runPromise(Match.value(flowDispatch).pipe(Match.when({ tag: "signUp" }, () => tryPasswordFlow(async () => {
97
+ if (flowDispatch.tag === "signUp") {
106
98
  const secret = requirePasswordParam(params.password, "signUp");
107
99
  const created = await ctx.auth.account.create(ctx, {
108
100
  provider,
@@ -115,7 +107,7 @@ function password(config = {}) {
115
107
  shouldLinkViaPhone: false
116
108
  });
117
109
  return await finalizeCredentialsResult(created.account, created.user);
118
- })), Match.when({ tag: "signIn" }, () => tryPasswordFlow(async () => {
110
+ } else if (flowDispatch.tag === "signIn") {
119
111
  const secret = requirePasswordParam(params.password, "signIn");
120
112
  const retrieved = await ctx.auth.account.get(ctx, {
121
113
  provider,
@@ -126,7 +118,7 @@ function password(config = {}) {
126
118
  });
127
119
  if (retrieved === null) throw new Error("Invalid credentials");
128
120
  return await finalizeCredentialsResult(retrieved.account, retrieved.user);
129
- })), Match.when({ tag: "reset" }, () => tryPasswordFlow(async () => {
121
+ } else if (flowDispatch.tag === "reset") {
130
122
  if (!resetProvider) throw new Error(`Password reset is not enabled for ${provider}`);
131
123
  const { account } = await ctx.auth.account.get(ctx, {
132
124
  provider,
@@ -136,7 +128,7 @@ function password(config = {}) {
136
128
  accountId: account._id,
137
129
  params
138
130
  });
139
- })), Match.when({ tag: "resetVerification" }, () => tryPasswordFlow(async () => {
131
+ } else if (flowDispatch.tag === "resetVerification") {
140
132
  if (!resetProvider) throw new Error(`Password reset is not enabled for ${provider}`);
141
133
  if (params.newPassword === void 0) throw new Error("Missing `newPassword` param for `reset-verification` flow");
142
134
  const result = await ctx.auth.provider.signIn(ctx, resetProvider, { params });
@@ -158,7 +150,7 @@ function password(config = {}) {
158
150
  userId,
159
151
  sessionId
160
152
  };
161
- })), Match.when({ tag: "emailVerification" }, () => tryPasswordFlow(async () => {
153
+ } else if (flowDispatch.tag === "emailVerification") {
162
154
  if (!verifyProvider) throw new Error(`Email verification is not enabled for ${provider}`);
163
155
  const { account } = await ctx.auth.account.get(ctx, {
164
156
  provider,
@@ -168,7 +160,7 @@ function password(config = {}) {
168
160
  accountId: account._id,
169
161
  params
170
162
  });
171
- })), Match.when({ tag: "invalid" }, () => Effect.die(/* @__PURE__ */ new Error("Missing `flow` param, it must be one of \"signUp\", \"signIn\", \"reset\", \"reset-verification\" or \"email-verification\"!"))), Match.exhaustive));
163
+ } else throw new Error("Missing `flow` param, it must be one of \"signUp\", \"signIn\", \"reset\", \"reset-verification\" or \"email-verification\"!");
172
164
  },
173
165
  crypto: config.crypto ?? {
174
166
  async hashSecret(password) {
@@ -1,12 +1,5 @@
1
- import { Effect, Match } from "effect";
2
-
3
1
  //#region src/providers/phone.ts
4
2
  /**
5
- * Phone / SMS authentication provider.
6
- *
7
- * @module
8
- */
9
- /**
10
3
  * Create a phone or SMS verification provider.
11
4
  *
12
5
  * @param config - SMS delivery hook and optional provider settings.
@@ -29,8 +22,8 @@ function phone(config) {
29
22
  type: "phone",
30
23
  maxAge: config.maxAge ?? 1200,
31
24
  authorize: async (params, account) => {
32
- const dispatch = typeof params.phone !== "string" ? { tag: "missingPhone" } : account.providerAccountId !== params.phone ? { tag: "mismatch" } : { tag: "ok" };
33
- return await Effect.runPromise(Match.value(dispatch).pipe(Match.when({ tag: "missingPhone" }, () => Effect.die(/* @__PURE__ */ new Error("Token verification requires a `phone` in params of `signIn`."))), Match.when({ tag: "mismatch" }, () => Effect.die(/* @__PURE__ */ new Error("Short verification code requires a matching `phone` in params of `signIn`."))), Match.when({ tag: "ok" }, () => Effect.succeed(void 0)), Match.exhaustive));
25
+ if (typeof params.phone !== "string") throw new Error("Token verification requires a `phone` in params of `signIn`.");
26
+ if (account.providerAccountId !== params.phone) throw new Error("Short verification code requires a matching `phone` in params of `signIn`.");
34
27
  },
35
28
  sendVerificationRequest: config.send,
36
29
  options: {}
@@ -0,0 +1,204 @@
1
+ import { ConvexAuthConfig, Doc } from "./types.js";
2
+ import { GenericId } from "convex/values";
3
+ import { UserIdentity } from "convex/server";
4
+
5
+ //#region src/server/auth-context.d.ts
6
+ /**
7
+ * Config for auth setup. Extends the standard auth config
8
+ * minus `component` (which is passed as the first constructor argument).
9
+ */
10
+ type AuthConfig = Omit<ConvexAuthConfig, "component">;
11
+ /** Canonical user document type exposed by Convex Auth. */
12
+ type UserDoc = Doc<"User">;
13
+ type AuthIdentityCtx = {
14
+ auth: {
15
+ getUserIdentity: () => Promise<UserIdentity | null>;
16
+ };
17
+ };
18
+ type AuthQueryCtx = {
19
+ runQuery: (...args: never[]) => Promise<unknown>;
20
+ };
21
+ type CustomFunctionInputResult<TAuth extends Record<string, unknown>> = Promise<{
22
+ ctx: {
23
+ auth: TAuth;
24
+ };
25
+ }>;
26
+ /**
27
+ * Current request auth context injected into `ctx.auth` by `auth.ctx()`. This
28
+ * is the authenticated auth shape returned by {@link createAuth().context}.
29
+ * Optional context builders may still surface nullable fields when
30
+ * `optional: true` is used.
31
+ *
32
+ * - `groupId` is `null` when the user has no active group set.
33
+ * - `role` is `null` when no active group or no membership is resolved.
34
+ * - `grants` is `[]` when no active group or no membership is resolved.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import type { AuthContext } from "@robelest/convex-auth/server";
39
+ *
40
+ * const mockAuth: AuthContext = {
41
+ * userId: "user123" as Id<"User">,
42
+ * user: { _id: "user123", email: "test@example.com" },
43
+ * groupId: "group456",
44
+ * role: "admin",
45
+ * grants: ["read", "write"],
46
+ * };
47
+ * ```
48
+ */
49
+ type AuthContext = {
50
+ /** The authenticated user's document ID. */userId: GenericId<"User">; /** The authenticated user's full document. */
51
+ user: UserDoc; /** The user's active group ID, or `null` if none set. */
52
+ groupId: string | null; /** The user's primary role in the active group, or `null`. */
53
+ role: string | null; /** Resolved grant strings from the user's role definitions. */
54
+ grants: string[];
55
+ };
56
+ /**
57
+ * Nullable auth context returned by `auth.context(ctx, { optional: true })`
58
+ * and injected by `auth.ctx({ optional: true })`.
59
+ *
60
+ * Use this when callers may be unauthenticated but you still want a stable
61
+ * auth-shaped object.
62
+ *
63
+ * - `userId` and `user` are `null` when unauthenticated.
64
+ * - `groupId` and `role` are `null` when no active group is resolved.
65
+ * - `grants` is `[]` when no membership is resolved.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const authContext = await auth.context(ctx, { optional: true });
70
+ * if (authContext.userId === null) {
71
+ * return null;
72
+ * }
73
+ * ```
74
+ */
75
+ type OptionalAuthContext = {
76
+ /** The authenticated user's document ID, or `null` when unauthenticated. */userId: GenericId<"User"> | null; /** The authenticated user's full document, or `null` when unauthenticated. */
77
+ user: UserDoc | null; /** The user's active group ID, or `null` if none is set. */
78
+ groupId: string | null; /** The user's primary role in the active group, or `null`. */
79
+ role: string | null; /** Resolved grant strings for the active membership, or `[]`. */
80
+ grants: string[];
81
+ };
82
+ type AuthContextBase = {
83
+ getUserIdentity: () => Promise<UserIdentity | null>;
84
+ };
85
+ type RequiredAuthContextState = AuthContextBase & AuthContext;
86
+ type OptionalAuthContextState = AuthContextBase & OptionalAuthContext;
87
+ type ResolvedAuthContext<TResolve> = AuthContext & TResolve;
88
+ type ResolvedOptionalAuthContext<TResolve> = OptionalAuthContext & TResolve;
89
+ type AuthResolverCtx = AuthIdentityCtx & AuthQueryCtx;
90
+ type PublicAuthContextConfig<TResolve extends Record<string, unknown>, TCtx> = AuthContextConfig<TResolve, TCtx & AuthResolverCtx>;
91
+ type AuthContextResolver = {
92
+ <TCtx, TResolve extends Record<string, unknown> = Record<string, never>>(ctx: TCtx, config: PublicAuthContextConfig<TResolve, TCtx> & {
93
+ optional: true;
94
+ }): Promise<ResolvedOptionalAuthContext<TResolve>>;
95
+ <TCtx, TResolve extends Record<string, unknown> = Record<string, never>>(ctx: TCtx, config?: PublicAuthContextConfig<TResolve, TCtx>): Promise<ResolvedAuthContext<TResolve>>;
96
+ };
97
+ type AuthContextCustomization<TAuth> = {
98
+ args: {};
99
+ input: (ctx: AuthResolverCtx, _args: Record<string, never>, _extra?: unknown) => Promise<{
100
+ ctx: {
101
+ auth: TAuth;
102
+ };
103
+ args: {};
104
+ }>;
105
+ };
106
+ type AuthContextFactory = {
107
+ <TResolve extends Record<string, unknown> = Record<string, never>>(config: AuthContextConfig<TResolve> & {
108
+ optional: true;
109
+ }): AuthContextCustomization<OptionalAuthContextState & TResolve>;
110
+ <TResolve extends Record<string, unknown> = Record<string, never>>(config?: AuthContextConfig<TResolve>): AuthContextCustomization<RequiredAuthContextState & TResolve>;
111
+ };
112
+ /**
113
+ * Minimal auth helper surface required by the context resolvers.
114
+ *
115
+ * This stays exported because `auth.ts` re-exports it for compatibility with
116
+ * existing consumers that reference the low-level context helpers.
117
+ */
118
+ /**
119
+ * Configuration for {@link createAuth().ctx} context enrichment.
120
+ *
121
+ * The same config shape is also used by {@link createAuth().context}.
122
+ *
123
+ * @typeParam TResolve - Extra fields returned from `resolve()` and merged into
124
+ * the resulting `ctx.auth` object.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * const authContext = await auth.context(ctx, {
129
+ * resolve: async (_ctx, user, authState) => ({
130
+ * email: user.email,
131
+ * canWrite: authState.grants.includes("posts.write"),
132
+ * }),
133
+ * });
134
+ * ```
135
+ */
136
+ type AuthContextConfig<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends AuthIdentityCtx = AuthIdentityCtx> = {
137
+ /**
138
+ * Allow unauthenticated callers and return a null-shaped auth object instead
139
+ * of throwing `NOT_SIGNED_IN`.
140
+ */
141
+ optional?: boolean;
142
+ /**
143
+ * Attach additional derived fields to the auth context after the base auth
144
+ * context is resolved.
145
+ *
146
+ * This callback runs only when a user is authenticated.
147
+ */
148
+ resolve?: (ctx: TCtx, user: UserDoc, auth: AuthContext) => Promise<TResolve> | TResolve;
149
+ /**
150
+ * Override or wrap the base auth resolution used by {@link createAuth().ctx}.
151
+ *
152
+ * Return `undefined` to fall back to the built-in resolver,
153
+ * `null` for an explicit unauthenticated state, or an
154
+ * {@link AuthContext} object to provide a pre-resolved auth state.
155
+ * This is useful for tests, proxy auth, impersonation flows, or any
156
+ * environment that needs to inject auth without depending on the standard
157
+ * Convex auth tables.
158
+ *
159
+ * @param ctx - The Convex function context.
160
+ * @param fallback - The built-in auth resolver used by {@link createAuth().ctx}.
161
+ * @returns Resolved auth state, `null`, or `undefined` to use the fallback.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * const authCtx = auth.ctx({
166
+ * authResolve: async (ctx, fallback) => {
167
+ * const injected = getInjectedAuth(ctx);
168
+ * return injected ?? (await fallback());
169
+ * },
170
+ * });
171
+ * ```
172
+ */
173
+ authResolve?: (ctx: TCtx, fallback: () => Promise<AuthContext | null>) => Promise<AuthContext | null | undefined> | AuthContext | null | undefined;
174
+ };
175
+ /**
176
+ * Extract the resolved `auth` context type from an `auth.ctx()` customization.
177
+ *
178
+ * Use this to type function parameters or variables that receive the
179
+ * enriched auth context produced by `auth.ctx()`. The inferred type includes
180
+ * `userId`, `user`, `groupId`, `role`, `grants`, `getUserIdentity`, and any
181
+ * additional fields added by the `resolve` callback. This is the generic
182
+ * utility for reusing the enriched auth shape without manually duplicating
183
+ * conditional auth types.
184
+ *
185
+ * @typeParam T - An `auth.ctx()` return value (must have an `input` method
186
+ * that returns `{ ctx: { auth: ... } }`).
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const authCtx = auth.ctx({
191
+ * resolve: async (ctx, user) => ({ orgId: user.orgId }),
192
+ * });
193
+ * type Auth = InferAuth<typeof authCtx>;
194
+ * // Auth = { userId: Id<"User">; user: UserDoc; getUserIdentity: ...; orgId: string }
195
+ * ```
196
+ *
197
+ * @see {@link createAuth}
198
+ */
199
+ type InferAuth<T extends {
200
+ input: (...args: never[]) => CustomFunctionInputResult<Record<string, unknown>>;
201
+ }> = Awaited<ReturnType<T["input"]>>["ctx"]["auth"];
202
+ //#endregion
203
+ export { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory, AuthContextResolver, InferAuth, OptionalAuthContext, UserDoc };
204
+ //# sourceMappingURL=auth-context.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { createUnauthenticatedAuthContext, getAuthContext } from "./context.js";
2
+ import { ConvexError } from "convex/values";
3
+
4
+ //#region src/server/auth-context.ts
5
+ async function resolveConfiguredAuthContext(auth, ctx, config) {
6
+ const fallback = () => getAuthContext(auth, ctx);
7
+ const authOverride = config?.authResolve ? await config.authResolve(ctx, fallback) : void 0;
8
+ return authOverride === void 0 ? await fallback() : authOverride;
9
+ }
10
+ function createNotSignedInError() {
11
+ return new ConvexError({
12
+ code: "NOT_SIGNED_IN",
13
+ message: "Authentication required."
14
+ });
15
+ }
16
+ function assertAuthResolverContext(ctx) {
17
+ const candidate = ctx;
18
+ if (candidate === null || typeof candidate !== "object" || candidate.auth === void 0 || candidate.auth === null || typeof candidate.auth !== "object" || typeof candidate.auth.getUserIdentity !== "function" || typeof candidate.runQuery !== "function") throw new TypeError("auth.context(ctx) requires a Convex function context with auth.getUserIdentity() and runQuery().");
19
+ }
20
+ /**
21
+ * Resolve the public auth context for a Convex handler context.
22
+ *
23
+ * This low-level helper underpins `auth.context(...)` and remains exported for
24
+ * compatibility with existing consumers using the server entrypoint.
25
+ */
26
+ async function createPublicAuthContext(auth, ctx, config) {
27
+ const resolved = await resolveConfiguredAuthContext(auth, ctx, config);
28
+ if (resolved === null) {
29
+ if (config?.optional !== true) throw createNotSignedInError();
30
+ return createUnauthenticatedAuthContext();
31
+ }
32
+ const extra = config?.resolve ? await config.resolve(ctx, resolved.user, resolved) : {};
33
+ return {
34
+ ...resolved,
35
+ ...extra
36
+ };
37
+ }
38
+ /**
39
+ * Create a convex-helpers customization that injects `ctx.auth`.
40
+ *
41
+ * This low-level helper underpins `auth.ctx(...)` and remains exported for
42
+ * compatibility with existing consumers using the server entrypoint.
43
+ */
44
+ function createAuthContextCustomization(auth, config) {
45
+ return {
46
+ args: {},
47
+ input: async (ctx, _args, _extra) => {
48
+ const nativeAuth = ctx.auth;
49
+ const getUserIdentity = nativeAuth.getUserIdentity.bind(nativeAuth);
50
+ const resolved = await resolveConfiguredAuthContext(auth, ctx, config);
51
+ if (resolved === null) {
52
+ if (config?.optional !== true) throw createNotSignedInError();
53
+ return {
54
+ ctx: { auth: {
55
+ getUserIdentity,
56
+ ...createUnauthenticatedAuthContext()
57
+ } },
58
+ args: {}
59
+ };
60
+ }
61
+ const extra = config?.resolve ? await config.resolve(ctx, resolved.user, resolved) : {};
62
+ return {
63
+ ctx: { auth: {
64
+ getUserIdentity,
65
+ ...resolved,
66
+ ...extra
67
+ } },
68
+ args: {}
69
+ };
70
+ }
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ export { assertAuthResolverContext, createAuthContextCustomization, createPublicAuthContext };
76
+ //# sourceMappingURL=auth-context.js.map
@@ -1,31 +1,12 @@
1
1
  import { AuthApiRefs } from "../client/core/types.js";
2
2
  import "../client/index.js";
3
- import { AuthAuthorizationConfig, AuthGrant, AuthProviderConfig, AuthRoleId, ConvexAuthConfig, Doc, HasDeviceProvider, HasPasskeyProvider, HasSSO, HasTotpProvider } from "./types.js";
3
+ import { AuthAuthorizationConfig, AuthGrant, AuthProviderConfig, AuthRoleId, ConvexAuthConfig, HasDeviceProvider, HasPasskeyProvider, HasSSO, HasTotpProvider } from "./types.js";
4
+ import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory as AuthContextFactory$1, AuthContextResolver as AuthContextResolver$1, InferAuth, OptionalAuthContext, UserDoc } from "./auth-context.js";
4
5
  import { Auth } from "./runtime.js";
5
- import { GenericId } from "convex/values";
6
- import { UserIdentity } from "convex/server";
7
6
 
8
7
  //#region src/server/auth.d.ts
9
- /**
10
- * Config for auth setup. Extends the standard auth config
11
- * minus `component` (which is passed as the first constructor argument).
12
- */
13
- type AuthConfig = Omit<ConvexAuthConfig, "component">;
14
- /** Canonical user document type exposed by Convex Auth. */
15
- type UserDoc = Doc<"User">;
16
- type AuthIdentityCtx = {
17
- auth: {
18
- getUserIdentity: () => Promise<UserIdentity | null>;
19
- };
20
- };
21
- type AuthQueryCtx = {
22
- runQuery: (...args: never[]) => Promise<unknown>;
23
- };
24
- type CustomFunctionInputResult<TAuth extends Record<string, unknown>> = Promise<{
25
- ctx: {
26
- auth: TAuth;
27
- };
28
- }>;
8
+ type AuthContextResolver = AuthContextResolver$1;
9
+ type AuthContextFactory = AuthContextFactory$1;
29
10
  type MemberApiWithAuthorization<TAuthorization extends AuthAuthorizationConfig | undefined> = Omit<ReturnType<typeof Auth>["auth"]["member"], "create" | "list" | "update" | "inspect" | "require"> & {
30
11
  create: (ctx: Parameters<ReturnType<typeof Auth>["auth"]["member"]["create"]>[0], data: {
31
12
  groupId: string;
@@ -171,91 +152,6 @@ type AuthApiBase<TAuthorization extends AuthAuthorizationConfig | undefined = un
171
152
  */
172
153
  ctx: AuthContextFactory;
173
154
  };
174
- /**
175
- * Current request auth context injected into `ctx.auth` by `auth.ctx()`. This
176
- * is the authenticated auth shape returned by {@link createAuth().context}.
177
- * Optional context builders may still surface nullable fields when
178
- * `optional: true` is used.
179
- *
180
- * - `groupId` is `null` when the user has no active group set.
181
- * - `role` is `null` when no active group or no membership is resolved.
182
- * - `grants` is `[]` when no active group or no membership is resolved.
183
- *
184
- * @example
185
- * ```ts
186
- * import type { AuthContext } from "@robelest/convex-auth/server";
187
- *
188
- * const mockAuth: AuthContext = {
189
- * userId: "user123" as Id<"User">,
190
- * user: { _id: "user123", email: "test@example.com" },
191
- * groupId: "group456",
192
- * role: "admin",
193
- * grants: ["read", "write"],
194
- * };
195
- * ```
196
- */
197
- type AuthContext = {
198
- /** The authenticated user's document ID. */userId: GenericId<"User">; /** The authenticated user's full document. */
199
- user: UserDoc; /** The user's active group ID, or `null` if none set. */
200
- groupId: string | null; /** The user's primary role in the active group, or `null`. */
201
- role: string | null; /** Resolved grant strings from the user's role definitions. */
202
- grants: string[];
203
- };
204
- /**
205
- * Nullable auth context returned by `auth.context(ctx, { optional: true })`
206
- * and injected by `auth.ctx({ optional: true })`.
207
- *
208
- * Use this when callers may be unauthenticated but you still want a stable
209
- * auth-shaped object.
210
- *
211
- * - `userId` and `user` are `null` when unauthenticated.
212
- * - `groupId` and `role` are `null` when no active group is resolved.
213
- * - `grants` is `[]` when no membership is resolved.
214
- *
215
- * @example
216
- * ```ts
217
- * const authContext = await auth.context(ctx, { optional: true });
218
- * if (authContext.userId === null) {
219
- * return null;
220
- * }
221
- * ```
222
- */
223
- type OptionalAuthContext = {
224
- /** The authenticated user's document ID, or `null` when unauthenticated. */userId: GenericId<"User"> | null; /** The authenticated user's full document, or `null` when unauthenticated. */
225
- user: UserDoc | null; /** The user's active group ID, or `null` if none is set. */
226
- groupId: string | null; /** The user's primary role in the active group, or `null`. */
227
- role: string | null; /** Resolved grant strings for the active membership, or `[]`. */
228
- grants: string[];
229
- };
230
- type AuthContextBase = {
231
- getUserIdentity: () => Promise<UserIdentity | null>;
232
- };
233
- type RequiredAuthContextState = AuthContextBase & AuthContext;
234
- type OptionalAuthContextState = AuthContextBase & OptionalAuthContext;
235
- type ResolvedAuthContext<TResolve> = AuthContext & TResolve;
236
- type ResolvedOptionalAuthContext<TResolve> = OptionalAuthContext & TResolve;
237
- type AuthResolverCtx = AuthIdentityCtx & AuthQueryCtx;
238
- type AuthContextResolver = {
239
- <TResolve extends Record<string, unknown> = Record<string, never>>(ctx: AuthResolverCtx, config: AuthContextConfig<TResolve> & {
240
- optional: true;
241
- }): Promise<ResolvedOptionalAuthContext<TResolve>>;
242
- <TResolve extends Record<string, unknown> = Record<string, never>>(ctx: AuthResolverCtx, config?: AuthContextConfig<TResolve>): Promise<ResolvedAuthContext<TResolve>>;
243
- };
244
- type AuthContextCustomization<TAuth> = {
245
- args: {};
246
- input: (ctx: AuthResolverCtx, _args: Record<string, never>, _extra?: unknown) => Promise<{
247
- ctx: {
248
- auth: TAuth;
249
- };
250
- args: {};
251
- }>;
252
- };
253
- type AuthContextFactory = {
254
- <TResolve extends Record<string, unknown> = Record<string, never>>(config: AuthContextConfig<TResolve> & {
255
- optional: true;
256
- }): AuthContextCustomization<OptionalAuthContextState & TResolve>;
257
- <TResolve extends Record<string, unknown> = Record<string, never>>(config?: AuthContextConfig<TResolve>): AuthContextCustomization<RequiredAuthContextState & TResolve>;
258
- };
259
155
  type InternalSsoApi = ReturnType<typeof Auth>["auth"]["sso"];
260
156
  type PublicGroupSsoApi = {
261
157
  signIn: (ctx: Parameters<InternalSsoApi["connection"]["create"]>[0], data: {
@@ -390,94 +286,36 @@ type ConvexAuthResult<P extends AuthProviderConfig[], TAuthorization extends Aut
390
286
  * @typeParam T - A ConvexAuthResult to extract the client API from.
391
287
  */
392
288
  type InferClientApi<T> = T extends ConvexAuthResult<infer P> ? AuthApiRefs<HasPasskeyProvider<P>, HasTotpProvider<P>, HasDeviceProvider<P>> : AuthApiRefs;
393
- declare function createAuth<P extends AuthProviderConfig[], TAuthorization extends AuthAuthorizationConfig | undefined = undefined>(component: ConvexAuthConfig["component"], config: Omit<AuthConfig, "providers" | "authorization"> & {
394
- providers: P;
395
- authorization?: TAuthorization;
396
- }): ConvexAuthResult<P, TAuthorization>;
397
289
  /**
398
- * Configuration for {@link createAuth().ctx} context enrichment.
290
+ * Create an auth API object.
399
291
  *
400
- * The same config shape is also used by {@link createAuth().context}.
292
+ * When `sso()` is included in providers, `auth.group.sso` is available
293
+ * on the returned object. Without it, that namespace is absent and
294
+ * accessing it is a TypeScript compile error.
401
295
  *
402
- * @typeParam TResolve - Extra fields returned from `resolve()` and merged into
403
- * the resulting `ctx.auth` object.
296
+ * @param component - The installed auth component reference from
297
+ * `components.auth` in your Convex app definition.
298
+ * @param config - Auth configuration including `providers` and optional
299
+ * `authorization`. All fields from {@link AuthConfig} are accepted
300
+ * except `component` (passed as the first argument).
301
+ * @returns A {@link ConvexAuthResult} object — either {@link AuthApi}
302
+ * (with `group.sso`) or {@link AuthApiBase}, depending on whether
303
+ * an SSO provider is present.
404
304
  *
405
305
  * @example
406
306
  * ```ts
407
- * const authContext = await auth.context(ctx, {
408
- * resolve: async (_ctx, user, authState) => ({
409
- * email: user.email,
410
- * canWrite: authState.grants.includes("posts.write"),
411
- * }),
307
+ * export const auth = createAuth(components.auth, {
308
+ * providers: [password(), google()],
309
+ * authorization: { roles },
412
310
  * });
413
311
  * ```
414
- */
415
- type AuthContextConfig<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends AuthIdentityCtx = AuthIdentityCtx> = {
416
- /**
417
- * Allow unauthenticated callers and return a null-shaped auth object instead
418
- * of throwing `NOT_SIGNED_IN`.
419
- */
420
- optional?: boolean;
421
- /**
422
- * Attach additional derived fields to the auth context after the base auth
423
- * context is resolved.
424
- *
425
- * This callback runs only when a user is authenticated.
426
- */
427
- resolve?: (ctx: TCtx, user: UserDoc, auth: AuthContext) => Promise<TResolve> | TResolve;
428
- /**
429
- * Override or wrap the base auth resolution used by {@link createAuth().ctx}.
430
- *
431
- * Return `undefined` to fall back to the built-in resolver,
432
- * `null` for an explicit unauthenticated state, or an
433
- * {@link AuthContext} object to provide a pre-resolved auth state.
434
- * This is useful for tests, proxy auth, impersonation flows, or any
435
- * environment that needs to inject auth without depending on the standard
436
- * Convex auth tables.
437
- *
438
- * @param ctx - The Convex function context.
439
- * @param fallback - The built-in auth resolver used by {@link createAuth().ctx}.
440
- * @returns Resolved auth state, `null`, or `undefined` to use the fallback.
441
- *
442
- * @example
443
- * ```ts
444
- * const authCtx = auth.ctx({
445
- * authResolve: async (ctx, fallback) => {
446
- * const injected = getInjectedAuth(ctx);
447
- * return injected ?? (await fallback());
448
- * },
449
- * });
450
- * ```
451
- */
452
- authResolve?: (ctx: TCtx, fallback: () => Promise<AuthContext | null>) => Promise<AuthContext | null | undefined> | AuthContext | null | undefined;
453
- };
454
- /**
455
- * Extract the resolved `auth` context type from an `auth.ctx()` customization.
456
312
  *
457
- * Use this to type function parameters or variables that receive the
458
- * enriched auth context produced by `auth.ctx()`. The inferred type includes
459
- * `userId`, `user`, `groupId`, `role`, `grants`, `getUserIdentity`, and any
460
- * additional fields added by the `resolve` callback. This is the generic
461
- * utility for reusing the enriched auth shape without manually duplicating
462
- * conditional auth types.
463
- *
464
- * @typeParam T - An `auth.ctx()` return value (must have an `input` method
465
- * that returns `{ ctx: { auth: ... } }`).
466
- *
467
- * @example
468
- * ```ts
469
- * const authCtx = auth.ctx({
470
- * resolve: async (ctx, user) => ({ orgId: user.orgId }),
471
- * });
472
- * type Auth = InferAuth<typeof authCtx>;
473
- * // Auth = { userId: Id<"User">; user: UserDoc; getUserIdentity: ...; orgId: string }
474
- * ```
475
- *
476
- * @see {@link createAuth}
313
+ * @see {@link AuthContextConfig}
477
314
  */
478
- type InferAuth<T extends {
479
- input: (...args: never[]) => CustomFunctionInputResult<Record<string, unknown>>;
480
- }> = Awaited<ReturnType<T["input"]>>["ctx"]["auth"];
315
+ declare function createAuth<P extends AuthProviderConfig[], TAuthorization extends AuthAuthorizationConfig | undefined = undefined>(component: ConvexAuthConfig["component"], config: Omit<AuthConfig, "providers" | "authorization"> & {
316
+ providers: P;
317
+ authorization?: TAuthorization;
318
+ }): ConvexAuthResult<P, TAuthorization>;
481
319
  //#endregion
482
- export { AuthApi, AuthApiBase, AuthConfig, AuthContext, AuthContextConfig, ConvexAuthResult, InferAuth, InferClientApi, OptionalAuthContext, UserDoc, createAuth };
320
+ export { AuthApi, AuthApiBase, ConvexAuthResult, InferClientApi, createAuth };
483
321
  //# sourceMappingURL=auth.d.ts.map