@supa-media/convex 1.3.0 → 1.4.0
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/package.json +1 -1
- package/src/auth/setup.ts +14 -50
- package/src/auth/testEmail.ts +87 -0
package/package.json
CHANGED
package/src/auth/setup.ts
CHANGED
|
@@ -27,6 +27,15 @@ import { Email } from "@convex-dev/auth/providers/Email";
|
|
|
27
27
|
import { Phone } from "@convex-dev/auth/providers/Phone";
|
|
28
28
|
import type { GenericId } from "convex/values";
|
|
29
29
|
|
|
30
|
+
import { createTestEmailOtps, type SupaAuthTestEmailConfig } from "./testEmail";
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
createTestEmailOtp,
|
|
34
|
+
createTestEmailOtps,
|
|
35
|
+
TEST_EMAIL_PROVIDER_ID,
|
|
36
|
+
type SupaAuthTestEmailConfig,
|
|
37
|
+
} from "./testEmail";
|
|
38
|
+
|
|
30
39
|
export interface SupaAuthResendConfig {
|
|
31
40
|
/** The "from" address for OTP emails. */
|
|
32
41
|
fromAddress: string;
|
|
@@ -57,16 +66,6 @@ export interface SupaAuthTwilioConfig {
|
|
|
57
66
|
*/
|
|
58
67
|
export const MAGIC_LINK_PROVIDER_ID = "magic-link";
|
|
59
68
|
|
|
60
|
-
/** Provider id for an explicitly scoped production test account. */
|
|
61
|
-
export const TEST_EMAIL_PROVIDER_ID = "test-email";
|
|
62
|
-
|
|
63
|
-
export interface SupaAuthTestEmailConfig {
|
|
64
|
-
/** The only email address allowed to use the fixed test code. */
|
|
65
|
-
email: string;
|
|
66
|
-
/** Fixed verification code. Defaults to `000000`. */
|
|
67
|
-
code?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
69
|
export interface SupaAuthMagicLinkConfig {
|
|
71
70
|
/**
|
|
72
71
|
* How long a link stays live, in seconds. Defaults to one hour.
|
|
@@ -111,11 +110,13 @@ export interface SupaAuthConfig {
|
|
|
111
110
|
*/
|
|
112
111
|
magicLink?: SupaAuthMagicLinkConfig;
|
|
113
112
|
/**
|
|
114
|
-
* Register a fixed-code provider for one exact test account
|
|
113
|
+
* Register a fixed-code provider for one exact test account, or one
|
|
114
|
+
* provider per account when given a list (each with its own `id` and code;
|
|
115
|
+
* an app store or connector-directory reviewer is the usual second one).
|
|
115
116
|
* Unlike `DEV_OTP_BYPASS`, this may be used in production because every
|
|
116
117
|
* other email address is refused by a distinct provider.
|
|
117
118
|
*/
|
|
118
|
-
testEmail?: SupaAuthTestEmailConfig;
|
|
119
|
+
testEmail?: SupaAuthTestEmailConfig | SupaAuthTestEmailConfig[];
|
|
119
120
|
/** Resend email OTP configuration. */
|
|
120
121
|
resend?: SupaAuthResendConfig;
|
|
121
122
|
/** Twilio phone OTP configuration. */
|
|
@@ -209,41 +210,6 @@ function createEmailOtp(config: SupaAuthConfig) {
|
|
|
209
210
|
});
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
/** Build the fixed-code provider separately so its security boundary is testable. */
|
|
213
|
-
export function createTestEmailOtp(config: SupaAuthTestEmailConfig) {
|
|
214
|
-
const email = config.email.trim().toLowerCase();
|
|
215
|
-
const code = config.code ?? DEV_BYPASS_CODE;
|
|
216
|
-
if (!/^\d{6}$/.test(code)) {
|
|
217
|
-
throw new Error("testEmail.code must be exactly six digits");
|
|
218
|
-
}
|
|
219
|
-
if (email.length === 0) {
|
|
220
|
-
throw new Error("testEmail.email must not be empty");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const provider = Email({
|
|
224
|
-
maxAge: 10 * 60,
|
|
225
|
-
generateVerificationToken: () => code,
|
|
226
|
-
sendVerificationRequest: async ({ identifier }) => {
|
|
227
|
-
if (identifier.trim().toLowerCase() !== email) {
|
|
228
|
-
throw new Error("This test sign-in provider is not available for that email.");
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
return {
|
|
233
|
-
...provider,
|
|
234
|
-
id: TEST_EMAIL_PROVIDER_ID,
|
|
235
|
-
authorize: async (params: Record<string, unknown>, account: { providerAccountId: string }) => {
|
|
236
|
-
if (
|
|
237
|
-
typeof params.email !== "string" ||
|
|
238
|
-
params.email.trim().toLowerCase() !== email ||
|
|
239
|
-
account.providerAccountId.trim().toLowerCase() !== email
|
|
240
|
-
) {
|
|
241
|
-
throw new Error("This test sign-in provider is not available for that email.");
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
213
|
/**
|
|
248
214
|
* The link-only email provider.
|
|
249
215
|
*
|
|
@@ -458,9 +424,7 @@ export function createSupaAuth(config: SupaAuthConfig = {}) {
|
|
|
458
424
|
...(methods.includes("email") && config.magicLink !== undefined
|
|
459
425
|
? [createMagicLink(config)]
|
|
460
426
|
: []),
|
|
461
|
-
...(methods.includes("email")
|
|
462
|
-
? [createTestEmailOtp(config.testEmail)]
|
|
463
|
-
: []),
|
|
427
|
+
...(methods.includes("email") ? createTestEmailOtps(config.testEmail) : []),
|
|
464
428
|
...(methods.includes("phone") ? [createPhoneOtp(config)] : []),
|
|
465
429
|
];
|
|
466
430
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-code sign-in for named test accounts (the CUJ account, a reviewer).
|
|
3
|
+
*
|
|
4
|
+
* Each account gets a provider of its own that refuses every other address,
|
|
5
|
+
* which is what makes a fixed code safe to register in production.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Email } from "@convex-dev/auth/providers/Email";
|
|
9
|
+
|
|
10
|
+
/** The code a test account uses when its config names none. */
|
|
11
|
+
const DEFAULT_TEST_CODE = "000000";
|
|
12
|
+
|
|
13
|
+
/** Provider id for an explicitly scoped production test account. */
|
|
14
|
+
export const TEST_EMAIL_PROVIDER_ID = "test-email";
|
|
15
|
+
|
|
16
|
+
export interface SupaAuthTestEmailConfig {
|
|
17
|
+
/** The only email address allowed to use the fixed test code. */
|
|
18
|
+
email: string;
|
|
19
|
+
/** Fixed verification code. Defaults to `000000`. */
|
|
20
|
+
code?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Provider id the client signs in with. Defaults to `TEST_EMAIL_PROVIDER_ID`.
|
|
23
|
+
*
|
|
24
|
+
* Needed only when an app has more than one fixed-code account, because
|
|
25
|
+
* `@convex-dev/auth` mints a provider's code without knowing the address,
|
|
26
|
+
* so each account needs a provider, and so an id, of its own. Must start
|
|
27
|
+
* with `TEST_EMAIL_PROVIDER_ID`, which keeps it clear of the customer
|
|
28
|
+
* `email` provider whose `authorize` it must never share.
|
|
29
|
+
*/
|
|
30
|
+
id?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build the fixed-code provider separately so its security boundary is testable. */
|
|
34
|
+
export function createTestEmailOtp(config: SupaAuthTestEmailConfig) {
|
|
35
|
+
const email = config.email.trim().toLowerCase();
|
|
36
|
+
const code = config.code ?? DEFAULT_TEST_CODE;
|
|
37
|
+
const id = config.id ?? TEST_EMAIL_PROVIDER_ID;
|
|
38
|
+
if (!/^test-email(-[a-z0-9]+)*$/.test(id)) {
|
|
39
|
+
throw new Error(`testEmail.id must be "${TEST_EMAIL_PROVIDER_ID}" or start with "${TEST_EMAIL_PROVIDER_ID}-"`);
|
|
40
|
+
}
|
|
41
|
+
if (!/^\d{6}$/.test(code)) {
|
|
42
|
+
throw new Error("testEmail.code must be exactly six digits");
|
|
43
|
+
}
|
|
44
|
+
if (email.length === 0) {
|
|
45
|
+
throw new Error("testEmail.email must not be empty");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const provider = Email({
|
|
49
|
+
maxAge: 10 * 60,
|
|
50
|
+
generateVerificationToken: () => code,
|
|
51
|
+
sendVerificationRequest: async ({ identifier }) => {
|
|
52
|
+
if (identifier.trim().toLowerCase() !== email) {
|
|
53
|
+
throw new Error("This test sign-in provider is not available for that email.");
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
...provider,
|
|
59
|
+
id,
|
|
60
|
+
authorize: async (params: Record<string, unknown>, account: { providerAccountId: string }) => {
|
|
61
|
+
if (
|
|
62
|
+
typeof params.email !== "string" ||
|
|
63
|
+
params.email.trim().toLowerCase() !== email ||
|
|
64
|
+
account.providerAccountId.trim().toLowerCase() !== email
|
|
65
|
+
) {
|
|
66
|
+
throw new Error("This test sign-in provider is not available for that email.");
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One fixed-code provider per configured account, refusing a repeated id or address. */
|
|
73
|
+
export function createTestEmailOtps(
|
|
74
|
+
config: SupaAuthTestEmailConfig | SupaAuthTestEmailConfig[] | undefined,
|
|
75
|
+
) {
|
|
76
|
+
const configs = config === undefined ? [] : Array.isArray(config) ? config : [config];
|
|
77
|
+
const providers = configs.map(createTestEmailOtp);
|
|
78
|
+
const ids = new Set(providers.map((provider) => provider.id));
|
|
79
|
+
const emails = new Set(configs.map((entry) => entry.email.trim().toLowerCase()));
|
|
80
|
+
if (ids.size !== providers.length) {
|
|
81
|
+
throw new Error("testEmail entries must each have their own id");
|
|
82
|
+
}
|
|
83
|
+
if (emails.size !== configs.length) {
|
|
84
|
+
throw new Error("testEmail entries must each have their own email");
|
|
85
|
+
}
|
|
86
|
+
return providers;
|
|
87
|
+
}
|