@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.
- package/README.md +3 -5
- package/dist/bin.js +6488 -1571
- package/dist/browser/index.js +10 -7
- package/dist/browser/locks.js +3 -5
- package/dist/browser/navigation.js +7 -10
- package/dist/browser/runtime.js +35 -33
- package/dist/client/core/types.js +17 -0
- package/dist/client/factors/device.js +26 -19
- package/dist/client/index.js +151 -163
- package/dist/client/runtime/proxy.js +6 -6
- package/dist/client/services/adapters.js +3 -7
- package/dist/client/services/http.js +2 -5
- package/dist/client/services/resolve.js +5 -11
- package/dist/client/services/runtime.js +2 -5
- package/dist/component/_generated/component.d.ts +46 -0
- package/dist/component/index.d.ts +3 -3
- package/dist/component/model.d.ts +25 -25
- package/dist/component/public/identity/sessions.js +38 -1
- package/dist/component/public/identity/tokens.js +81 -3
- package/dist/component/public/identity/verifiers.js +9 -3
- package/dist/component/public.js +3 -3
- package/dist/component/schema.d.ts +320 -320
- package/dist/core/index.d.ts +380 -0
- package/dist/core/index.js +83 -0
- package/dist/otel.d.ts +13 -17
- package/dist/otel.js +39 -49
- package/dist/providers/email.d.ts +2 -2
- package/dist/providers/password.js +8 -16
- package/dist/providers/phone.js +2 -9
- package/dist/server/auth-context.d.ts +204 -0
- package/dist/server/auth-context.js +76 -0
- package/dist/server/auth.d.ts +25 -187
- package/dist/server/auth.js +5 -96
- package/dist/server/componentContext.d.ts +12 -0
- package/dist/server/componentContext.js +1 -0
- package/dist/server/config.js +1 -12
- package/dist/server/constants.js +6 -0
- package/dist/server/contract.d.ts +1 -1
- package/dist/server/core.js +5 -14
- package/dist/server/crypto.js +26 -18
- package/dist/server/db.js +6 -1
- package/dist/server/device.js +88 -78
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.js +74 -86
- package/dist/server/index.d.ts +2 -1
- package/dist/server/limits.js +22 -15
- package/dist/server/mounts.d.ts +103 -103
- package/dist/server/mutations/account.js +6 -4
- package/dist/server/mutations/invalidate.js +3 -6
- package/dist/server/mutations/oauth.js +86 -88
- package/dist/server/mutations/refresh.js +45 -87
- package/dist/server/mutations/register.js +19 -19
- package/dist/server/mutations/retrieve.js +17 -15
- package/dist/server/mutations/signature.js +9 -13
- package/dist/server/mutations/signin.js +7 -3
- package/dist/server/mutations/signout.js +10 -15
- package/dist/server/mutations/store.js +22 -12
- package/dist/server/mutations/verifier.js +11 -6
- package/dist/server/mutations/verify.js +55 -46
- package/dist/server/oauth/runtime.js +27 -25
- package/dist/server/passkey.js +299 -250
- package/dist/server/prefetch.js +283 -281
- package/dist/server/refresh.js +7 -60
- package/dist/server/runtime.d.ts +82 -206
- package/dist/server/runtime.js +63 -56
- package/dist/server/services/config.js +5 -3
- package/dist/server/services/logger.js +2 -4
- package/dist/server/services/providers.js +2 -4
- package/dist/server/services/refresh.js +2 -4
- package/dist/server/services/resolve.js +15 -14
- package/dist/server/services/signin.js +2 -4
- package/dist/server/sessions.js +32 -33
- package/dist/server/signin.js +177 -142
- package/dist/server/sso/domain.d.ts +20 -68
- package/dist/server/sso/domain.js +444 -413
- package/dist/server/sso/http.js +53 -59
- package/dist/server/sso/oidc.js +94 -80
- package/dist/server/tokens.js +13 -3
- package/dist/server/totp.js +153 -116
- package/dist/server/types.d.ts +2 -2
- package/dist/server/users.js +18 -23
- package/dist/server/utils/cache.js +51 -0
- package/dist/server/utils/dispatch.js +36 -0
- package/dist/server/utils/retry.js +24 -0
- package/dist/server/utils/span.js +32 -0
- package/dist/shared/errors.js +9 -3
- package/dist/shared/log.js +20 -22
- 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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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) {
|
package/dist/providers/phone.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
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
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
*
|
|
290
|
+
* Create an auth API object.
|
|
399
291
|
*
|
|
400
|
-
*
|
|
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
|
-
* @
|
|
403
|
-
*
|
|
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
|
|
408
|
-
*
|
|
409
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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,
|
|
320
|
+
export { AuthApi, AuthApiBase, ConvexAuthResult, InferClientApi, createAuth };
|
|
483
321
|
//# sourceMappingURL=auth.d.ts.map
|