@robelest/convex-auth 0.0.4-preview.30 → 0.0.4-preview.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +125 -36
- package/dist/browser/index.d.ts +3 -13
- package/dist/browser/index.js +47 -12
- package/dist/browser/navigation.js +1 -1
- package/dist/browser/passkey.js +7 -7
- package/dist/browser/runtime.js +13 -15
- package/dist/client/core/types.d.ts +179 -63
- package/dist/client/core/types.js +6 -0
- package/dist/client/factors/totp.js +1 -1
- package/dist/client/index.d.ts +5 -4
- package/dist/client/index.js +115 -56
- package/dist/client/runtime/mutex.js +3 -2
- package/dist/component/_generated/component.d.ts +40 -0
- package/dist/component/http.js +9 -0
- package/dist/component/index.d.ts +1 -1
- package/dist/component/model.d.ts +27 -27
- package/dist/component/model.js +2 -1
- package/dist/component/modules.js +1 -0
- package/dist/component/public/factors/passkeys.js +31 -1
- package/dist/component/public/identity/codes.js +1 -1
- package/dist/component/public/identity/tokens.js +2 -1
- package/dist/component/public/identity/verifiers.js +15 -5
- package/dist/component/public.js +2 -2
- package/dist/component/schema.d.ts +287 -285
- package/dist/component/schema.js +2 -1
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.js +7 -2
- package/dist/expo/index.d.ts +21 -0
- package/dist/expo/index.js +148 -0
- package/dist/expo/passkey.js +174 -0
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.js +6 -8
- package/dist/providers/custom.d.ts +1 -1
- package/dist/providers/custom.js +4 -7
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/github.js +5 -8
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +5 -8
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/microsoft.js +5 -9
- package/dist/providers/password.d.ts +18 -37
- package/dist/providers/password.js +170 -115
- package/dist/providers/redirect.d.ts +1 -0
- package/dist/providers/redirect.js +20 -0
- package/dist/server/auth.d.ts +6 -7
- package/dist/server/auth.js +3 -2
- package/dist/server/{ctxCache.js → cache/context.js} +2 -2
- package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
- package/dist/server/context.js +3 -10
- package/dist/server/contract.d.ts +2 -87
- package/dist/server/contract.js +1 -1
- package/dist/server/cookies.js +25 -1
- package/dist/server/core.js +1 -1
- package/dist/server/errors.js +24 -1
- package/dist/server/{auth-context.d.ts → facade.d.ts} +3 -45
- package/dist/server/{auth-context.js → facade.js} +11 -12
- package/dist/server/http.d.ts +7 -7
- package/dist/server/http.js +47 -7
- package/dist/server/{convexIdentity.d.ts → identity/convex.d.ts} +3 -3
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.js +3 -2
- package/dist/server/mounts.d.ts +171 -18
- package/dist/server/mutations/code.js +7 -1
- package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
- package/dist/server/mutations/index.js +1 -1
- package/dist/server/mutations/invalidate.js +11 -1
- package/dist/server/mutations/oauth.js +25 -27
- package/dist/server/mutations/signin.js +6 -0
- package/dist/server/mutations/signout.js +5 -0
- package/dist/server/mutations/store.js +1 -1
- package/dist/server/oauth/factory.js +11 -3
- package/dist/server/passkey.js +126 -110
- package/dist/server/prefetch.js +8 -1
- package/dist/server/redirects.js +11 -3
- package/dist/server/refresh.js +6 -1
- package/dist/server/runtime.d.ts +68 -37
- package/dist/server/runtime.js +318 -36
- package/dist/server/services/group.js +4 -0
- package/dist/server/sessions.js +1 -0
- package/dist/server/signin.js +8 -6
- package/dist/server/sso/domain.d.ts +159 -16
- package/dist/server/sso/domain.js +1 -1
- package/dist/server/sso/http.js +144 -60
- package/dist/server/sso/oidc.js +28 -12
- package/dist/server/sso/policy.js +30 -14
- package/dist/server/sso/provision.js +1 -1
- package/dist/server/sso/saml.js +18 -9
- package/dist/server/sso/scim.js +12 -4
- package/dist/server/sso/shared.js +5 -5
- package/dist/server/telemetry.js +3 -0
- package/dist/server/tokens.js +10 -2
- package/dist/server/totp.js +127 -100
- package/dist/server/types.d.ts +224 -151
- package/dist/server/url.js +1 -1
- package/dist/server/users.js +93 -53
- package/dist/server/wellknown.d.ts +75 -0
- package/dist/server/wellknown.js +198 -0
- package/dist/shared/errors.js +0 -1
- package/package.json +36 -4
- package/dist/server/oauth/index.js +0 -12
- package/dist/server/utils/dispatch.js +0 -36
- package/dist/shared/authResults.d.ts +0 -16
- /package/dist/server/{componentContext.js → component/context.js} +0 -0
- /package/dist/server/{convexIdentity.js → identity/convex.js} +0 -0
|
@@ -7,54 +7,39 @@ import { DocumentByName, GenericDataModel, WithoutSystemFields } from "convex/se
|
|
|
7
7
|
/** Configuration for the {@link password} provider. */
|
|
8
8
|
interface PasswordConfig<DataModel extends GenericDataModel> {
|
|
9
9
|
/**
|
|
10
|
-
* Uniquely identifies the provider, allowing
|
|
11
|
-
* multiple different password providers.
|
|
10
|
+
* Uniquely identifies the provider, allowing multiple password providers.
|
|
12
11
|
*/
|
|
13
12
|
id?: string;
|
|
14
13
|
/**
|
|
15
|
-
* Perform checks on provided params and customize the user
|
|
16
|
-
*
|
|
14
|
+
* Perform checks on provided params and customize the user information
|
|
15
|
+
* stored after sign up, including email normalization.
|
|
17
16
|
*
|
|
18
|
-
* Called for every flow
|
|
19
|
-
* "reset-verification" and "email-verification").
|
|
17
|
+
* Called for every flow.
|
|
20
18
|
*/
|
|
21
|
-
profile?: (
|
|
22
|
-
/**
|
|
23
|
-
* The values passed to the `signIn` function.
|
|
24
|
-
*/
|
|
25
|
-
params: Record<string, Value | undefined>,
|
|
26
|
-
/**
|
|
27
|
-
* Convex ActionCtx in case you want to read from or write to
|
|
28
|
-
* the database.
|
|
29
|
-
*/
|
|
30
|
-
ctx: GenericActionCtxWithAuthConfig<DataModel>) => WithoutSystemFields<DocumentByName<DataModel, "User">> & {
|
|
19
|
+
profile?: (params: Record<string, Value | undefined>, ctx: GenericActionCtxWithAuthConfig<DataModel>) => WithoutSystemFields<DocumentByName<DataModel, "User">> & {
|
|
31
20
|
email: string;
|
|
32
21
|
};
|
|
33
22
|
/**
|
|
34
|
-
* Performs custom validation on password
|
|
35
|
-
*
|
|
36
|
-
* Otherwise the default validation is used (password is not empty and
|
|
37
|
-
* at least 8 characters in length).
|
|
23
|
+
* Performs custom validation on a password during `signUp`, `verify`
|
|
24
|
+
* (when `newPassword` is set), and `change`.
|
|
38
25
|
*
|
|
39
|
-
*
|
|
26
|
+
* Default: non-empty, length >= 8.
|
|
40
27
|
*
|
|
41
|
-
*
|
|
42
|
-
* "reset-verification" flows.
|
|
28
|
+
* Throw an `Error` to reject the password.
|
|
43
29
|
*/
|
|
44
30
|
validatePasswordRequirements?: (password: string) => void;
|
|
45
31
|
/**
|
|
46
|
-
*
|
|
47
|
-
* how passwords are hashed.
|
|
32
|
+
* Hashing and verification functions. Defaults to scrypt.
|
|
48
33
|
*/
|
|
49
34
|
crypto?: CredentialsConfig["crypto"];
|
|
50
35
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
36
|
+
* Email provider for the `reset` flow. Issues OTPs that the `verify` flow
|
|
37
|
+
* accepts when `newPassword` is included.
|
|
53
38
|
*/
|
|
54
39
|
reset?: EmailConfig | PasswordEmailProviderFactory;
|
|
55
40
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
41
|
+
* Email provider for post-signup email confirmation. Issues OTPs that the
|
|
42
|
+
* `verify` flow accepts when `newPassword` is omitted.
|
|
58
43
|
*/
|
|
59
44
|
verify?: EmailConfig | PasswordEmailProviderFactory;
|
|
60
45
|
}
|
|
@@ -62,24 +47,20 @@ type PasswordEmailProviderFactory = () => EmailConfig;
|
|
|
62
47
|
/**
|
|
63
48
|
* Email and password authentication provider.
|
|
64
49
|
*
|
|
65
|
-
* Passwords are by default
|
|
66
|
-
* You can customize the hashing via the `crypto` option.
|
|
50
|
+
* Passwords are hashed with scrypt by default. Customize via `crypto`.
|
|
67
51
|
*
|
|
68
|
-
* Email verification is
|
|
69
|
-
*
|
|
52
|
+
* Email verification is opt-in via the `verify` option. Password reset is
|
|
53
|
+
* opt-in via the `reset` option (typically the same email provider).
|
|
70
54
|
*
|
|
71
55
|
* @example
|
|
72
56
|
* ```ts
|
|
73
|
-
* import { password } from "@robelest/convex-auth/providers";
|
|
74
|
-
*
|
|
75
57
|
* password()
|
|
76
|
-
* password({ verify: myEmailProvider })
|
|
58
|
+
* password({ verify: myEmailProvider, reset: myEmailProvider })
|
|
77
59
|
* ```
|
|
78
60
|
*
|
|
79
61
|
* @typeParam DataModel - The Convex data model used by the auth context.
|
|
80
62
|
* @param config - Password flow hooks and optional verification providers.
|
|
81
63
|
* @returns A configured password provider for `createAuth`.
|
|
82
|
-
* @throws {Error} During sign-in flows when required password params are missing or reset is not enabled.
|
|
83
64
|
*/
|
|
84
65
|
declare function password<DataModel extends GenericDataModel = GenericDataModel>(config?: PasswordConfig<DataModel>): ConvexCredentialsConfig;
|
|
85
66
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAuthenticatedUserIdOrNull } from "../server/identity.js";
|
|
2
|
+
import { callCredentialsSignIn } from "../server/mutations/credentials/signin.js";
|
|
2
3
|
import { credentials } from "./credentials.js";
|
|
3
4
|
import { ConvexError } from "convex/values";
|
|
4
5
|
import { scryptAsync } from "@noble/hashes/scrypt.js";
|
|
@@ -8,63 +9,57 @@ import { bytesToHex } from "@noble/hashes/utils.js";
|
|
|
8
9
|
/**
|
|
9
10
|
* Configure the password provider for email/password authentication.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* by the `flow` parameter:
|
|
12
|
+
* Five flows, all single-word camelCase:
|
|
13
13
|
*
|
|
14
|
-
* - `
|
|
15
|
-
* - `
|
|
16
|
-
* - `
|
|
17
|
-
* - `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* - `signUp` — Create a new account.
|
|
15
|
+
* - `signIn` — Sign in with email + password.
|
|
16
|
+
* - `reset` — Kick off a forgot-password flow (issues an OTP via email).
|
|
17
|
+
* - `verify` — Verify any pending email OTP. With `newPassword`, completes a
|
|
18
|
+
* `reset` flow and updates the password. Without `newPassword`, completes
|
|
19
|
+
* the post-signup email confirmation. The OTP scope is enforced server-side
|
|
20
|
+
* by the issuing email provider.
|
|
21
|
+
* - `change` — Authenticated password change (requires `currentPassword`).
|
|
20
22
|
*
|
|
21
23
|
* ```ts
|
|
22
24
|
* import { password } from "@robelest/convex-auth/providers";
|
|
23
25
|
*
|
|
24
26
|
* password()
|
|
27
|
+
* password({ verify: myEmailProvider, reset: myEmailProvider })
|
|
25
28
|
* ```
|
|
26
29
|
*
|
|
27
30
|
* @module
|
|
28
31
|
*/
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
32
|
+
const PASSWORD_FLOWS = [
|
|
33
|
+
"signUp",
|
|
34
|
+
"signIn",
|
|
35
|
+
"reset",
|
|
36
|
+
"verify",
|
|
37
|
+
"change"
|
|
38
|
+
];
|
|
36
39
|
function decodePasswordFlow(flow) {
|
|
37
|
-
if (typeof flow
|
|
40
|
+
if (typeof flow === "string" && PASSWORD_FLOWS.includes(flow)) return { tag: flow };
|
|
41
|
+
return {
|
|
38
42
|
tag: "invalid",
|
|
39
43
|
flow
|
|
40
44
|
};
|
|
41
|
-
const tag = PASSWORD_FLOW_TAG[flow];
|
|
42
|
-
return tag === void 0 ? {
|
|
43
|
-
tag: "invalid",
|
|
44
|
-
flow
|
|
45
|
-
} : { tag };
|
|
46
45
|
}
|
|
47
46
|
/**
|
|
48
47
|
* Email and password authentication provider.
|
|
49
48
|
*
|
|
50
|
-
* Passwords are by default
|
|
51
|
-
* You can customize the hashing via the `crypto` option.
|
|
49
|
+
* Passwords are hashed with scrypt by default. Customize via `crypto`.
|
|
52
50
|
*
|
|
53
|
-
* Email verification is
|
|
54
|
-
*
|
|
51
|
+
* Email verification is opt-in via the `verify` option. Password reset is
|
|
52
|
+
* opt-in via the `reset` option (typically the same email provider).
|
|
55
53
|
*
|
|
56
54
|
* @example
|
|
57
55
|
* ```ts
|
|
58
|
-
* import { password } from "@robelest/convex-auth/providers";
|
|
59
|
-
*
|
|
60
56
|
* password()
|
|
61
|
-
* password({ verify: myEmailProvider })
|
|
57
|
+
* password({ verify: myEmailProvider, reset: myEmailProvider })
|
|
62
58
|
* ```
|
|
63
59
|
*
|
|
64
60
|
* @typeParam DataModel - The Convex data model used by the auth context.
|
|
65
61
|
* @param config - Password flow hooks and optional verification providers.
|
|
66
62
|
* @returns A configured password provider for `createAuth`.
|
|
67
|
-
* @throws {Error} During sign-in flows when required password params are missing or reset is not enabled.
|
|
68
63
|
*/
|
|
69
64
|
function password(config = {}) {
|
|
70
65
|
const provider = config.id ?? "password";
|
|
@@ -81,12 +76,10 @@ function password(config = {}) {
|
|
|
81
76
|
}
|
|
82
77
|
validateDefaultPasswordRequirements(password);
|
|
83
78
|
};
|
|
84
|
-
if (flowDispatch.tag === "signUp") validatePasswordRequirements(params.password);
|
|
85
|
-
else if (flowDispatch.tag === "resetVerification") validatePasswordRequirements(params.newPassword);
|
|
86
79
|
const profile = config.profile?.(params, ctx) ?? defaultProfile(params);
|
|
87
80
|
const { email } = profile;
|
|
88
|
-
const
|
|
89
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`Missing \`
|
|
81
|
+
const requireStringParam = (value, name, flow) => {
|
|
82
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`Missing \`${name}\` param for \`${flow}\` flow`);
|
|
90
83
|
return value;
|
|
91
84
|
};
|
|
92
85
|
const finalizeCredentialsResult = async (account, user) => {
|
|
@@ -100,88 +93,150 @@ function password(config = {}) {
|
|
|
100
93
|
hasTotp
|
|
101
94
|
};
|
|
102
95
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
account
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
hasTotp
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
96
|
+
switch (flowDispatch.tag) {
|
|
97
|
+
case "signUp": {
|
|
98
|
+
const secret = requireStringParam(params.password, "password", "signUp");
|
|
99
|
+
validatePasswordRequirements(secret);
|
|
100
|
+
const created = await ctx.auth.account.create(ctx, {
|
|
101
|
+
provider,
|
|
102
|
+
account: {
|
|
103
|
+
id: email,
|
|
104
|
+
secret
|
|
105
|
+
},
|
|
106
|
+
profile,
|
|
107
|
+
shouldLinkViaEmail: config.verify !== void 0,
|
|
108
|
+
shouldLinkViaPhone: false
|
|
109
|
+
});
|
|
110
|
+
return await finalizeCredentialsResult(created.account, created.user);
|
|
111
|
+
}
|
|
112
|
+
case "signIn": {
|
|
113
|
+
const result = await callCredentialsSignIn(ctx, {
|
|
114
|
+
provider,
|
|
115
|
+
account: {
|
|
116
|
+
id: email,
|
|
117
|
+
secret: requireStringParam(params.password, "password", "signIn")
|
|
118
|
+
},
|
|
119
|
+
generateTokens: true,
|
|
120
|
+
requireVerifiedEmail: verifyProvider !== void 0,
|
|
121
|
+
enforceTotp: true
|
|
122
|
+
});
|
|
123
|
+
if (result.kind === "invalidAccount" || result.kind === "invalidSecret") throw new Error("Invalid credentials");
|
|
124
|
+
if (result.kind === "tooManyAttempts") throw new ConvexError({
|
|
125
|
+
code: "RATE_LIMITED",
|
|
126
|
+
message: "Too many failed sign-in attempts. Please try again later."
|
|
127
|
+
});
|
|
128
|
+
if (result.kind === "emailVerificationRequired") return await ctx.auth.provider.signIn(ctx, verifyProvider, {
|
|
129
|
+
accountId: result.account._id,
|
|
130
|
+
params
|
|
131
|
+
});
|
|
132
|
+
const hasTotp = result.kind === "signedIn" ? result.user.hasTotp : true;
|
|
133
|
+
return {
|
|
134
|
+
userId: result.user._id,
|
|
135
|
+
hasTotp,
|
|
136
|
+
issuance: result.issuance
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
case "reset": {
|
|
140
|
+
if (!resetProvider) throw new Error(`Password reset is not enabled for ${provider}`);
|
|
141
|
+
const { account } = await ctx.auth.account.get(ctx, {
|
|
142
|
+
provider,
|
|
143
|
+
account: { id: email }
|
|
144
|
+
});
|
|
145
|
+
return await ctx.auth.provider.signIn(ctx, resetProvider, {
|
|
146
|
+
accountId: account._id,
|
|
147
|
+
params
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
case "verify": {
|
|
151
|
+
const newPassword = params.newPassword;
|
|
152
|
+
if (typeof newPassword === "string" && newPassword.length > 0) {
|
|
153
|
+
if (!resetProvider) throw new Error(`Password reset is not enabled for ${provider}`);
|
|
154
|
+
validatePasswordRequirements(newPassword);
|
|
155
|
+
const result = await ctx.auth.provider.signIn(ctx, resetProvider, { params });
|
|
156
|
+
if (result === null) throw new Error("Invalid code");
|
|
157
|
+
const { userId, sessionId } = result;
|
|
158
|
+
await ctx.auth.account.update(ctx, {
|
|
159
|
+
provider,
|
|
160
|
+
account: {
|
|
161
|
+
id: email,
|
|
162
|
+
secret: newPassword
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
await ctx.auth.session.invalidate(ctx, {
|
|
166
|
+
userId,
|
|
167
|
+
except: [sessionId]
|
|
168
|
+
});
|
|
169
|
+
await ctx.auth.config.callbacks?.after?.(ctx, {
|
|
170
|
+
kind: "passwordChanged",
|
|
171
|
+
userId,
|
|
172
|
+
flow: "reset"
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
userId,
|
|
176
|
+
sessionId
|
|
177
|
+
};
|
|
164
178
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
params
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
if (!verifyProvider) throw new Error(`Email verification is not enabled for ${provider}`);
|
|
180
|
+
const { account } = await ctx.auth.account.get(ctx, {
|
|
181
|
+
provider,
|
|
182
|
+
account: { id: email }
|
|
183
|
+
});
|
|
184
|
+
return await ctx.auth.provider.signIn(ctx, verifyProvider, {
|
|
185
|
+
accountId: account._id,
|
|
186
|
+
params
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
case "change": {
|
|
190
|
+
const authedUserId = await getAuthenticatedUserIdOrNull(ctx);
|
|
191
|
+
if (authedUserId === null) throw new ConvexError({
|
|
192
|
+
code: "NOT_SIGNED_IN",
|
|
193
|
+
message: "Sign in first to change your password."
|
|
194
|
+
});
|
|
195
|
+
const currentPassword = requireStringParam(params.currentPassword, "currentPassword", "change");
|
|
196
|
+
const newPassword = requireStringParam(params.newPassword, "newPassword", "change");
|
|
197
|
+
validatePasswordRequirements(newPassword);
|
|
198
|
+
const result = await callCredentialsSignIn(ctx, {
|
|
199
|
+
provider,
|
|
200
|
+
account: {
|
|
201
|
+
id: email,
|
|
202
|
+
secret: currentPassword
|
|
203
|
+
},
|
|
204
|
+
generateTokens: true,
|
|
205
|
+
requireVerifiedEmail: false,
|
|
206
|
+
enforceTotp: false
|
|
207
|
+
});
|
|
208
|
+
if (result.kind === "invalidAccount" || result.kind === "invalidSecret") throw new Error("Invalid current password");
|
|
209
|
+
if (result.kind === "tooManyAttempts") throw new ConvexError({
|
|
210
|
+
code: "RATE_LIMITED",
|
|
211
|
+
message: "Too many failed attempts. Please try again later."
|
|
212
|
+
});
|
|
213
|
+
if (result.kind !== "signedIn") throw new Error(`Unexpected sign-in result: ${result.kind}`);
|
|
214
|
+
const verifiedUserId = result.user._id;
|
|
215
|
+
if (verifiedUserId !== authedUserId) throw new Error("Email does not match authenticated user");
|
|
216
|
+
await ctx.auth.account.update(ctx, {
|
|
217
|
+
provider,
|
|
218
|
+
account: {
|
|
219
|
+
id: email,
|
|
220
|
+
secret: newPassword
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
await ctx.auth.session.invalidate(ctx, {
|
|
224
|
+
userId: verifiedUserId,
|
|
225
|
+
except: [result.issuance.sessionId]
|
|
226
|
+
});
|
|
227
|
+
await ctx.auth.config.callbacks?.after?.(ctx, {
|
|
228
|
+
kind: "passwordChanged",
|
|
229
|
+
userId: verifiedUserId,
|
|
230
|
+
flow: "change"
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
userId: verifiedUserId,
|
|
234
|
+
hasTotp: false,
|
|
235
|
+
issuance: result.issuance
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
default: throw new Error("Missing or invalid `flow` param. Expected one of: " + PASSWORD_FLOWS.join(", ") + ".");
|
|
239
|
+
}
|
|
185
240
|
},
|
|
186
241
|
crypto: config.crypto ?? {
|
|
187
242
|
async hashSecret(password) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { envOptionalString, readConfigSync } from "../server/env.js";
|
|
2
|
+
|
|
3
|
+
//#region src/providers/redirect.ts
|
|
4
|
+
function normalizeRoutePrefix(prefix) {
|
|
5
|
+
if (prefix === void 0 || prefix === "" || prefix === "/") return "";
|
|
6
|
+
return (prefix.startsWith("/") ? prefix : `/${prefix}`).replace(/\/$/, "");
|
|
7
|
+
}
|
|
8
|
+
/** @internal */
|
|
9
|
+
function defaultOAuthRedirectUri(providerId) {
|
|
10
|
+
const customAuthSiteUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL"));
|
|
11
|
+
if (customAuthSiteUrl) return `${customAuthSiteUrl.replace(/\/$/, "")}/callback/${providerId}`;
|
|
12
|
+
const convexSiteUrl = readConfigSync(envOptionalString("CONVEX_SITE_URL"));
|
|
13
|
+
if (!convexSiteUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
|
|
14
|
+
const prefix = normalizeRoutePrefix(readConfigSync(envOptionalString("CONVEX_AUTH_HTTP_PREFIX")) ?? "/auth");
|
|
15
|
+
return `${convexSiteUrl.replace(/\/$/, "")}${prefix}/callback/${providerId}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { defaultOAuthRedirectUri };
|
|
20
|
+
//# sourceMappingURL=redirect.js.map
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { AuthApiRefs } from "../client/core/types.js";
|
|
2
2
|
import "../client/index.js";
|
|
3
3
|
import { AuthAuthorizationConfig, AuthGrant, AuthProviderConfig, AuthRoleId, ConvexAuthConfig, HasDeviceProvider, HasPasskeyProvider, HasSSO, HasTotpProvider } from "./types.js";
|
|
4
|
-
import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory
|
|
4
|
+
import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory, AuthContextResolver, InferAuth, OptionalAuthContext, UserDoc } from "./facade.js";
|
|
5
5
|
import { Auth } from "./runtime.js";
|
|
6
6
|
|
|
7
7
|
//#region src/server/auth.d.ts
|
|
8
|
-
type AuthContextResolver = AuthContextResolver$1;
|
|
9
|
-
type AuthContextFactory = AuthContextFactory$1;
|
|
10
8
|
type MemberApiWithAuthorization<TAuthorization extends AuthAuthorizationConfig | undefined> = Omit<ReturnType<typeof Auth>["auth"]["member"], "create" | "list" | "update" | "inspect" | "require"> & {
|
|
11
9
|
create: (ctx: Parameters<ReturnType<typeof Auth>["auth"]["member"]["create"]>[0], data: {
|
|
12
10
|
groupId: string;
|
|
@@ -48,7 +46,7 @@ type MemberApiWithAuthorization<TAuthorization extends AuthAuthorizationConfig |
|
|
|
48
46
|
* The base auth API surface returned by {@link createAuth}.
|
|
49
47
|
*
|
|
50
48
|
* Provides core namespaces — `signIn`, `signOut`, `user`, `session`,
|
|
51
|
-
* `member`, `invite`, `group`, `key`, and `
|
|
49
|
+
* `member`, `invite`, `group`, `key`, and `request` — that are
|
|
52
50
|
* always available regardless of which providers are configured.
|
|
53
51
|
* Group SSO helpers under `group.sso` are added conditionally by
|
|
54
52
|
* {@link AuthApi} when an SSO provider is present.
|
|
@@ -63,6 +61,7 @@ type AuthApiBase<TAuthorization extends AuthAuthorizationConfig | undefined = un
|
|
|
63
61
|
signIn: ReturnType<typeof Auth>["signIn"];
|
|
64
62
|
signOut: ReturnType<typeof Auth>["signOut"];
|
|
65
63
|
store: ReturnType<typeof Auth>["store"];
|
|
64
|
+
http: ReturnType<typeof Auth>["http"];
|
|
66
65
|
user: ReturnType<typeof Auth>["auth"]["user"];
|
|
67
66
|
session: ReturnType<typeof Auth>["auth"]["session"];
|
|
68
67
|
provider: ReturnType<typeof Auth>["auth"]["provider"];
|
|
@@ -71,7 +70,7 @@ type AuthApiBase<TAuthorization extends AuthAuthorizationConfig | undefined = un
|
|
|
71
70
|
member: MemberApiWithAuthorization<TAuthorization>;
|
|
72
71
|
invite: ReturnType<typeof Auth>["auth"]["invite"];
|
|
73
72
|
key: ReturnType<typeof Auth>["auth"]["key"];
|
|
74
|
-
|
|
73
|
+
request: ReturnType<typeof Auth>["auth"]["request"];
|
|
75
74
|
/**
|
|
76
75
|
* Resolve the current request's auth context. Framework-agnostic — use
|
|
77
76
|
* this in fluent-convex middleware, custom wrappers, or anywhere you
|
|
@@ -84,8 +83,8 @@ type AuthApiBase<TAuthorization extends AuthAuthorizationConfig | undefined = un
|
|
|
84
83
|
* Pass `{ optional: true }` to get a null-shaped auth object instead.
|
|
85
84
|
*
|
|
86
85
|
* @param ctx - Convex query, mutation, or action context.
|
|
87
|
-
* @param config - Optional auth resolution config. Supports `optional
|
|
88
|
-
* `resolve
|
|
86
|
+
* @param config - Optional auth resolution config. Supports `optional` and
|
|
87
|
+
* `resolve`.
|
|
89
88
|
* @returns The current auth context.
|
|
90
89
|
*
|
|
91
90
|
* @example fluent-convex middleware
|
package/dist/server/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createAuthContextFacade } from "./facade.js";
|
|
2
2
|
import { Auth } from "./runtime.js";
|
|
3
3
|
import { ConvexError } from "convex/values";
|
|
4
4
|
|
|
@@ -133,6 +133,7 @@ function createAuth(component, config) {
|
|
|
133
133
|
signIn: authResult.signIn,
|
|
134
134
|
signOut: authResult.signOut,
|
|
135
135
|
store: authResult.store,
|
|
136
|
+
http: authResult.http,
|
|
136
137
|
user: authResult.auth.user,
|
|
137
138
|
session: authResult.auth.session,
|
|
138
139
|
provider: authResult.auth.provider,
|
|
@@ -144,7 +145,7 @@ function createAuth(component, config) {
|
|
|
144
145
|
member: authResult.auth.member,
|
|
145
146
|
invite: authResult.auth.invite,
|
|
146
147
|
key: authResult.auth.key,
|
|
147
|
-
|
|
148
|
+
request: authResult.auth.request,
|
|
148
149
|
...createAuthContextFacade(authResult.auth)
|
|
149
150
|
};
|
|
150
151
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/server/
|
|
1
|
+
//#region src/server/cache/context.ts
|
|
2
2
|
/**
|
|
3
3
|
* Per-execution cache for Convex auth component reads.
|
|
4
4
|
*
|
|
@@ -91,4 +91,4 @@ function invalidateCtxCache(ctx, keyPrefix) {
|
|
|
91
91
|
|
|
92
92
|
//#endregion
|
|
93
93
|
export { cached, ctxCacheHas, invalidateCtxCache };
|
|
94
|
-
//# sourceMappingURL=
|
|
94
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server";
|
|
2
2
|
|
|
3
|
-
//#region src/server/
|
|
3
|
+
//#region src/server/component/context.d.ts
|
|
4
4
|
type ComponentReadCtx = {
|
|
5
5
|
runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
|
|
6
6
|
};
|
|
@@ -9,4 +9,4 @@ type ComponentCtx = ComponentReadCtx & {
|
|
|
9
9
|
};
|
|
10
10
|
//#endregion
|
|
11
11
|
export { ComponentCtx, ComponentReadCtx };
|
|
12
|
-
//# sourceMappingURL=
|
|
12
|
+
//# sourceMappingURL=context.d.ts.map
|
package/dist/server/context.js
CHANGED
|
@@ -6,14 +6,7 @@ async function getSessionUserId(ctx) {
|
|
|
6
6
|
return await getAuthenticatedUserIdOrNull(ctx);
|
|
7
7
|
}
|
|
8
8
|
/** @internal */
|
|
9
|
-
async function getAuthContextForUser(auth, ctx, userId
|
|
10
|
-
if (opts?.group === false) return {
|
|
11
|
-
userId,
|
|
12
|
-
user: await auth.user.get(ctx, userId),
|
|
13
|
-
groupId: null,
|
|
14
|
-
role: null,
|
|
15
|
-
grants: []
|
|
16
|
-
};
|
|
9
|
+
async function getAuthContextForUser(auth, ctx, userId) {
|
|
17
10
|
const [user, groupId] = await Promise.all([auth.user.get(ctx, userId), auth.user.getActiveGroup(ctx, { userId })]);
|
|
18
11
|
let role = null;
|
|
19
12
|
let grants = [];
|
|
@@ -36,10 +29,10 @@ async function getAuthContextForUser(auth, ctx, userId, opts) {
|
|
|
36
29
|
};
|
|
37
30
|
}
|
|
38
31
|
/** @internal */
|
|
39
|
-
async function getAuthContext(auth, ctx
|
|
32
|
+
async function getAuthContext(auth, ctx) {
|
|
40
33
|
const userId = await getSessionUserId(ctx);
|
|
41
34
|
if (userId === null) return null;
|
|
42
|
-
return await getAuthContextForUser(auth, ctx, userId
|
|
35
|
+
return await getAuthContextForUser(auth, ctx, userId);
|
|
43
36
|
}
|
|
44
37
|
/** @internal */
|
|
45
38
|
function createUnauthenticatedAuthContext() {
|