better-auth-evp 0.1.0 → 1.0.2
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/CHANGELOG.md +20 -0
- package/README.md +4 -2
- package/dist/client.d.ts +0 -51
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -33
- package/dist/index.d.ts +2 -52
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -54
- package/dist/types.d.ts +1 -43
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/better-auth-evp-0.1.0.tgz +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## [1.0.2](https://github.com/qamarq/better-auth-evp/compare/v1.0.1...v1.0.2) (2026-08-31)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* restrict verification surface and revoke stale sessions on promotion ([61bc289](https://github.com/qamarq/better-auth-evp/commit/61bc289f1c265be5f7301e1d13a8c526ae628dd1))
|
|
7
|
+
|
|
8
|
+
## [1.0.1](https://github.com/qamarq/better-auth-evp/compare/v1.0.0...v1.0.1) (2026-08-31)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* rely on better-auth client auto-inference instead of a custom getActions ([dc49d4e](https://github.com/qamarq/better-auth-evp/commit/dc49d4e125ace25e76d4b31cca167e5937d061b8))
|
|
14
|
+
|
|
15
|
+
# 1.0.0 (2026-08-31)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* initial Email Verification Protocol plugin for Better Auth ([753fcb6](https://github.com/qamarq/better-auth-evp/commit/753fcb66bc34cc6acb9273bacbce69bb1fecdb5e))
|
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ export const auth = betterAuth({
|
|
|
38
38
|
// Must match the origin your Chrome origin-trial token, DNS
|
|
39
39
|
// `_email-verification` record, etc. were issued for.
|
|
40
40
|
origin: "https://example.com",
|
|
41
|
+
allowedEmailDomains: ["example.com"],
|
|
41
42
|
disableSignUp: false,
|
|
42
43
|
userFields: (verified) => ({
|
|
43
44
|
// any additional fields for a newly created user
|
|
@@ -116,8 +117,8 @@ async function handleEmailSubmit(email: string, form: HTMLFormElement) {
|
|
|
116
117
|
|
|
117
118
|
### Server (`auth.api`)
|
|
118
119
|
|
|
119
|
-
- `
|
|
120
|
-
- `evpVerify({ email, token, nonce })` - `POST /evp/verify` - verifies the token and, on success, creates a session (and a user, unless `disableSignUp` is set). Returns `{ verified: false, reason }` instead of throwing on any expected failure (invalid/expired nonce, verification failure, email mismatch, sign-up disabled).
|
|
120
|
+
- `evpGetNonce()` - `GET /evp/get-nonce` - issues a single-use nonce, valid for `nonceExpiresIn` seconds (default 120).
|
|
121
|
+
- `evpVerify({ email, token, nonce })` - `POST /evp/verify` - verifies the token and, on success, creates a session (and a user, unless `disableSignUp` is set). Returns `{ verified: false, reason }` instead of throwing on any expected failure (invalid/expired nonce, disallowed email domain, verification failure, email mismatch, sign-up disabled).
|
|
121
122
|
|
|
122
123
|
### Client (`authClient.evp`)
|
|
123
124
|
|
|
@@ -130,6 +131,7 @@ async function handleEmailSubmit(email: string, form: HTMLFormElement) {
|
|
|
130
131
|
| ---------------- | ------------------------------------------------ | ---------- | --------------------------------------------------------- |
|
|
131
132
|
| `origin` | `string` | (required) | This relying party's absolute origin, used as `audience`. |
|
|
132
133
|
| `nonceExpiresIn` | `number` | `120` | Seconds a nonce stays valid. |
|
|
134
|
+
| `allowedEmailDomains` | `string[]` | optional, unrestricted if omitted | Restricts which email domains `/evp/verify` will even attempt to verify. The email field in your own form is client-side validation only and can be bypassed by calling the API directly - without this option, a caller can make the server perform a DNS lookup + issuer JWKS fetch against any domain they choose (SSRF/abuse surface). Strongly recommended whenever your app only expects a fixed set of domains. |
|
|
133
135
|
| `disableSignUp` | `boolean` | `false` | Reject verified emails with no existing account. |
|
|
134
136
|
| `userFields` | `(verified) => T` | - | Extra fields for a newly created user. |
|
|
135
137
|
| `onVerified` | `(verified & { userId }) => void \| Promise<void>` | - | Side-effect hook after a session is created. |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,59 +1,8 @@
|
|
|
1
1
|
import type { emailVerificationProtocol } from "./index";
|
|
2
|
-
import type { EvpNonceResponse, EvpVerifyResponse } from "./types";
|
|
3
|
-
/**
|
|
4
|
-
* `autocomplete` value for the visible email `<input>`. Required by the
|
|
5
|
-
* protocol so the browser can offer verified addresses.
|
|
6
|
-
*/
|
|
7
2
|
export declare const EVP_EMAIL_AUTOCOMPLETE = "email";
|
|
8
|
-
/**
|
|
9
|
-
* `autocomplete` value for the hidden token `<input>` the browser fills in
|
|
10
|
-
* with the signed Email Verification Token.
|
|
11
|
-
*/
|
|
12
3
|
export declare const EVP_TOKEN_AUTOCOMPLETE = "email-verification-token";
|
|
13
4
|
export declare function emailVerificationProtocolClient(): {
|
|
14
5
|
id: "email-verification-protocol";
|
|
15
6
|
$InferServerPlugin: ReturnType<typeof emailVerificationProtocol>;
|
|
16
|
-
getActions: ($fetch: import("better-auth/client").BetterFetch) => {
|
|
17
|
-
evp: {
|
|
18
|
-
/**
|
|
19
|
-
* Fetches a fresh, single-use nonce. Call this once per sign-in
|
|
20
|
-
* attempt and bind the result to the hidden token input's `nonce`
|
|
21
|
-
* attribute before the user submits the form.
|
|
22
|
-
*/
|
|
23
|
-
getNonce: () => Promise<{
|
|
24
|
-
data: EvpNonceResponse;
|
|
25
|
-
error: null;
|
|
26
|
-
} | {
|
|
27
|
-
data: null;
|
|
28
|
-
error: {
|
|
29
|
-
message?: string | undefined;
|
|
30
|
-
status: number;
|
|
31
|
-
statusText: string;
|
|
32
|
-
};
|
|
33
|
-
}>;
|
|
34
|
-
/**
|
|
35
|
-
* Verifies the token the browser filled into the hidden input and,
|
|
36
|
-
* on success, signs the user in. Always check `data.verified`
|
|
37
|
-
* (or the presence of `error`) and fall back to a normal sign-in
|
|
38
|
-
* method when it's falsy - this is expected for the vast majority
|
|
39
|
-
* of browsers/users today.
|
|
40
|
-
*/
|
|
41
|
-
verify: (data: {
|
|
42
|
-
email: string;
|
|
43
|
-
token: string;
|
|
44
|
-
nonce: string;
|
|
45
|
-
}) => Promise<{
|
|
46
|
-
data: null;
|
|
47
|
-
error: {
|
|
48
|
-
message?: string | undefined;
|
|
49
|
-
status: number;
|
|
50
|
-
statusText: string;
|
|
51
|
-
};
|
|
52
|
-
} | {
|
|
53
|
-
data: EvpVerifyResponse;
|
|
54
|
-
error: null;
|
|
55
|
-
}>;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
7
|
};
|
|
59
8
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAEzD,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAC9C,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AAEjE,wBAAgB,+BAA+B;;wBAGjB,UAAU,CAAC,OAAO,yBAAyB,CAAC;EAEzE"}
|
package/dist/client.js
CHANGED
|
@@ -1,41 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `autocomplete` value for the visible email `<input>`. Required by the
|
|
3
|
-
* protocol so the browser can offer verified addresses.
|
|
4
|
-
*/
|
|
5
1
|
export const EVP_EMAIL_AUTOCOMPLETE = "email";
|
|
6
|
-
/**
|
|
7
|
-
* `autocomplete` value for the hidden token `<input>` the browser fills in
|
|
8
|
-
* with the signed Email Verification Token.
|
|
9
|
-
*/
|
|
10
2
|
export const EVP_TOKEN_AUTOCOMPLETE = "email-verification-token";
|
|
11
3
|
export function emailVerificationProtocolClient() {
|
|
12
4
|
return {
|
|
13
5
|
id: "email-verification-protocol",
|
|
14
6
|
$InferServerPlugin: {},
|
|
15
|
-
getActions: ($fetch) => ({
|
|
16
|
-
evp: {
|
|
17
|
-
/**
|
|
18
|
-
* Fetches a fresh, single-use nonce. Call this once per sign-in
|
|
19
|
-
* attempt and bind the result to the hidden token input's `nonce`
|
|
20
|
-
* attribute before the user submits the form.
|
|
21
|
-
*/
|
|
22
|
-
getNonce: async () => {
|
|
23
|
-
return $fetch("/evp/nonce", { method: "GET" });
|
|
24
|
-
},
|
|
25
|
-
/**
|
|
26
|
-
* Verifies the token the browser filled into the hidden input and,
|
|
27
|
-
* on success, signs the user in. Always check `data.verified`
|
|
28
|
-
* (or the presence of `error`) and fall back to a normal sign-in
|
|
29
|
-
* method when it's falsy - this is expected for the vast majority
|
|
30
|
-
* of browsers/users today.
|
|
31
|
-
*/
|
|
32
|
-
verify: async (data) => {
|
|
33
|
-
return $fetch("/evp/verify", {
|
|
34
|
-
method: "POST",
|
|
35
|
-
body: data,
|
|
36
|
-
});
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
}),
|
|
40
7
|
};
|
|
41
8
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,66 +1,15 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import type { EvpPluginOptions } from "./types";
|
|
3
3
|
export * from "./types";
|
|
4
|
-
/**
|
|
5
|
-
* [Email Verification Protocol](https://developer.chrome.com/blog/email-verification-protocol-origin-trial)
|
|
6
|
-
* plugin for Better Auth.
|
|
7
|
-
*
|
|
8
|
-
* EVP lets a browser that supports the (Chrome-only, origin-trial-gated)
|
|
9
|
-
* protocol prove a user owns the email address they typed into a form,
|
|
10
|
-
* without sending an OTP or magic link. It is entirely progressive
|
|
11
|
-
* enhancement: unsupported browsers, mailbox providers that haven't
|
|
12
|
-
* implemented the issuer side, or a user simply not signed into their
|
|
13
|
-
* mailbox all result in an empty/unverifiable token. Callers MUST treat
|
|
14
|
-
* `verified: false` (or the nonce/verify endpoints being unreachable) as a
|
|
15
|
-
* normal, expected outcome and fall back to whatever sign-in method the
|
|
16
|
-
* app already uses (email OTP, magic link, password, ...) - this plugin
|
|
17
|
-
* does not implement a fallback itself, it only adds the EVP fast path.
|
|
18
|
-
*
|
|
19
|
-
* @see https://github.com/philnash/email-verification-api
|
|
20
|
-
*/
|
|
21
4
|
export declare function emailVerificationProtocol<T extends Record<string, any> = {}>(options: EvpPluginOptions<T>): {
|
|
22
5
|
id: "email-verification-protocol";
|
|
23
6
|
endpoints: {
|
|
24
|
-
|
|
25
|
-
* ### Endpoint
|
|
26
|
-
*
|
|
27
|
-
* GET `/evp/nonce`
|
|
28
|
-
*
|
|
29
|
-
* ### API Methods
|
|
30
|
-
*
|
|
31
|
-
* **server:** `auth.api.evpNonce`
|
|
32
|
-
*
|
|
33
|
-
* **client:** `authClient.evp.getNonce`
|
|
34
|
-
*
|
|
35
|
-
* Issues a single-use nonce to bind into the hidden
|
|
36
|
-
* `email-verification-token` input's `nonce` attribute. Call this
|
|
37
|
-
* once per sign-in attempt, right before rendering the form.
|
|
38
|
-
*/
|
|
39
|
-
evpNonce: import("better-auth").StrictEndpoint<"/evp/nonce", {
|
|
7
|
+
evpGetNonce: import("better-auth").StrictEndpoint<"/evp/get-nonce", {
|
|
40
8
|
method: "GET";
|
|
41
9
|
}, {
|
|
42
10
|
nonce: string;
|
|
43
11
|
expiresIn: number;
|
|
44
12
|
}>;
|
|
45
|
-
/**
|
|
46
|
-
* ### Endpoint
|
|
47
|
-
*
|
|
48
|
-
* POST `/evp/verify`
|
|
49
|
-
*
|
|
50
|
-
* ### API Methods
|
|
51
|
-
*
|
|
52
|
-
* **server:** `auth.api.evpVerify`
|
|
53
|
-
*
|
|
54
|
-
* **client:** `authClient.evp.verify`
|
|
55
|
-
*
|
|
56
|
-
* Verifies the browser-issued Email Verification Token. On success,
|
|
57
|
-
* signs the user in (creating an account first if none exists and
|
|
58
|
-
* sign-up isn't disabled) exactly like any other passwordless method
|
|
59
|
-
* and returns `{ verified: true }`. On any failure it returns
|
|
60
|
-
* `{ verified: false, reason }` instead of throwing, since a failure
|
|
61
|
-
* here is an expected, common outcome that the caller should recover
|
|
62
|
-
* from by falling back to a different sign-in method.
|
|
63
|
-
*/
|
|
64
13
|
evpVerify: import("better-auth").StrictEndpoint<"/evp/verify", {
|
|
65
14
|
method: "POST";
|
|
66
15
|
body: z.ZodObject<{
|
|
@@ -68,6 +17,7 @@ export declare function emailVerificationProtocol<T extends Record<string, any>
|
|
|
68
17
|
token: z.ZodString;
|
|
69
18
|
nonce: z.ZodString;
|
|
70
19
|
}, z.core.$strip>;
|
|
20
|
+
use: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>[];
|
|
71
21
|
}, {
|
|
72
22
|
verified: boolean;
|
|
73
23
|
reason: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,KAAK,EACV,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAEjB,cAAc,SAAS,CAAC;AAUxB,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,EAC1E,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAoIJ,MAAM;;;;EAQ/B"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APIError, createAuthEndpoint } from "better-auth/api";
|
|
1
|
+
import { APIError, createAuthEndpoint, formCsrfMiddleware, } from "better-auth/api";
|
|
2
2
|
import { setSessionCookie } from "better-auth/cookies";
|
|
3
3
|
import { generateRandomString } from "better-auth/crypto";
|
|
4
4
|
import { verifyEmailToken } from "email-verification-api";
|
|
@@ -10,23 +10,6 @@ const evpVerifyBodySchema = z.object({
|
|
|
10
10
|
token: z.string(),
|
|
11
11
|
nonce: z.string(),
|
|
12
12
|
});
|
|
13
|
-
/**
|
|
14
|
-
* [Email Verification Protocol](https://developer.chrome.com/blog/email-verification-protocol-origin-trial)
|
|
15
|
-
* plugin for Better Auth.
|
|
16
|
-
*
|
|
17
|
-
* EVP lets a browser that supports the (Chrome-only, origin-trial-gated)
|
|
18
|
-
* protocol prove a user owns the email address they typed into a form,
|
|
19
|
-
* without sending an OTP or magic link. It is entirely progressive
|
|
20
|
-
* enhancement: unsupported browsers, mailbox providers that haven't
|
|
21
|
-
* implemented the issuer side, or a user simply not signed into their
|
|
22
|
-
* mailbox all result in an empty/unverifiable token. Callers MUST treat
|
|
23
|
-
* `verified: false` (or the nonce/verify endpoints being unreachable) as a
|
|
24
|
-
* normal, expected outcome and fall back to whatever sign-in method the
|
|
25
|
-
* app already uses (email OTP, magic link, password, ...) - this plugin
|
|
26
|
-
* does not implement a fallback itself, it only adds the EVP fast path.
|
|
27
|
-
*
|
|
28
|
-
* @see https://github.com/philnash/email-verification-api
|
|
29
|
-
*/
|
|
30
13
|
export function emailVerificationProtocol(options) {
|
|
31
14
|
if (!options.origin) {
|
|
32
15
|
throw new Error("emailVerificationProtocol plugin requires an `origin` option (this relying party's absolute origin)");
|
|
@@ -36,22 +19,7 @@ export function emailVerificationProtocol(options) {
|
|
|
36
19
|
return {
|
|
37
20
|
id: "email-verification-protocol",
|
|
38
21
|
endpoints: {
|
|
39
|
-
|
|
40
|
-
* ### Endpoint
|
|
41
|
-
*
|
|
42
|
-
* GET `/evp/nonce`
|
|
43
|
-
*
|
|
44
|
-
* ### API Methods
|
|
45
|
-
*
|
|
46
|
-
* **server:** `auth.api.evpNonce`
|
|
47
|
-
*
|
|
48
|
-
* **client:** `authClient.evp.getNonce`
|
|
49
|
-
*
|
|
50
|
-
* Issues a single-use nonce to bind into the hidden
|
|
51
|
-
* `email-verification-token` input's `nonce` attribute. Call this
|
|
52
|
-
* once per sign-in attempt, right before rendering the form.
|
|
53
|
-
*/
|
|
54
|
-
evpNonce: createAuthEndpoint("/evp/nonce", { method: "GET" }, async (ctx) => {
|
|
22
|
+
evpGetNonce: createAuthEndpoint("/evp/get-nonce", { method: "GET" }, async (ctx) => {
|
|
55
23
|
const nonce = generateRandomString(24, "a-z", "A-Z", "0-9");
|
|
56
24
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
57
25
|
identifier: `${NONCE_IDENTIFIER_PREFIX}${nonce}`,
|
|
@@ -60,31 +28,25 @@ export function emailVerificationProtocol(options) {
|
|
|
60
28
|
});
|
|
61
29
|
return ctx.json({ nonce, expiresIn: nonceExpiresIn });
|
|
62
30
|
}),
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* ### API Methods
|
|
69
|
-
*
|
|
70
|
-
* **server:** `auth.api.evpVerify`
|
|
71
|
-
*
|
|
72
|
-
* **client:** `authClient.evp.verify`
|
|
73
|
-
*
|
|
74
|
-
* Verifies the browser-issued Email Verification Token. On success,
|
|
75
|
-
* signs the user in (creating an account first if none exists and
|
|
76
|
-
* sign-up isn't disabled) exactly like any other passwordless method
|
|
77
|
-
* and returns `{ verified: true }`. On any failure it returns
|
|
78
|
-
* `{ verified: false, reason }` instead of throwing, since a failure
|
|
79
|
-
* here is an expected, common outcome that the caller should recover
|
|
80
|
-
* from by falling back to a different sign-in method.
|
|
81
|
-
*/
|
|
82
|
-
evpVerify: createAuthEndpoint("/evp/verify", { method: "POST", body: evpVerifyBodySchema }, async (ctx) => {
|
|
31
|
+
evpVerify: createAuthEndpoint("/evp/verify", {
|
|
32
|
+
method: "POST",
|
|
33
|
+
body: evpVerifyBodySchema,
|
|
34
|
+
use: [formCsrfMiddleware],
|
|
35
|
+
}, async (ctx) => {
|
|
83
36
|
const email = ctx.body.email.trim().toLowerCase();
|
|
84
37
|
const { token, nonce } = ctx.body;
|
|
85
38
|
if (!z.email().safeParse(email).success) {
|
|
86
39
|
throw new APIError("BAD_REQUEST", { message: "Invalid email" });
|
|
87
40
|
}
|
|
41
|
+
if (options.allowedEmailDomains) {
|
|
42
|
+
const domain = email.split("@")[1];
|
|
43
|
+
if (!options.allowedEmailDomains.includes(domain)) {
|
|
44
|
+
return ctx.json({
|
|
45
|
+
verified: false,
|
|
46
|
+
reason: "domain_not_allowed",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
88
50
|
const nonceRecord = await ctx.context.internalAdapter.consumeVerificationValue(`${NONCE_IDENTIFIER_PREFIX}${nonce}`);
|
|
89
51
|
if (!nonceRecord) {
|
|
90
52
|
return ctx.json({ verified: false, reason: "nonce_invalid" });
|
|
@@ -126,6 +88,7 @@ export function emailVerificationProtocol(options) {
|
|
|
126
88
|
else {
|
|
127
89
|
user = existing.user;
|
|
128
90
|
if (!user.emailVerified) {
|
|
91
|
+
await ctx.context.internalAdapter.deleteUserSessions(user.id);
|
|
129
92
|
user = await ctx.context.internalAdapter.updateUser(user.id, {
|
|
130
93
|
emailVerified: true,
|
|
131
94
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
export interface EvpVerifiedEmail {
|
|
2
|
-
/** The mailbox address the issuer vouched for. */
|
|
3
2
|
email: string;
|
|
4
|
-
/** The issuer that signed the Email Verification Token. */
|
|
5
3
|
issuer: string;
|
|
6
|
-
/** The relying-party origin the token was bound to. */
|
|
7
4
|
audience: string;
|
|
8
|
-
/** Timestamps reported by the verification library, if any. */
|
|
9
5
|
issuedAt?: unknown;
|
|
10
|
-
/** Any additional claims the issuer included in the token. */
|
|
11
6
|
claims?: Record<string, unknown>;
|
|
12
7
|
}
|
|
13
8
|
export interface EvpVerificationError {
|
|
14
|
-
/** Which stage of verification failed (DNS lookup, signature check, ...). */
|
|
15
9
|
stage?: string;
|
|
16
|
-
/** Machine-readable error code. */
|
|
17
10
|
code: string;
|
|
18
|
-
/** Human-readable message, safe to log. */
|
|
19
11
|
message: string;
|
|
20
12
|
cause?: unknown;
|
|
21
13
|
}
|
|
@@ -27,48 +19,20 @@ export type EvpVerificationResult = {
|
|
|
27
19
|
error: EvpVerificationError;
|
|
28
20
|
};
|
|
29
21
|
export interface EvpVerifyParams {
|
|
30
|
-
/** The SD-JWT+KB presentation the browser put in the hidden form field. */
|
|
31
22
|
token: string;
|
|
32
|
-
/** The nonce this relying party issued for the current attempt. */
|
|
33
23
|
nonce: string;
|
|
34
|
-
/** The email address the user typed into the form. */
|
|
35
24
|
email: string;
|
|
36
|
-
/** This relying party's absolute HTTP(S) origin. */
|
|
37
25
|
audience: string;
|
|
38
26
|
}
|
|
39
27
|
export interface EvpPluginOptions<T extends Record<string, any> = {}> {
|
|
40
|
-
/**
|
|
41
|
-
* This relying party's absolute origin, e.g. `https://example.com`. Sent
|
|
42
|
-
* to the verifier as the `audience` and must match the origin the Chrome
|
|
43
|
-
* origin-trial token (and the DNS/issuer records) were issued for.
|
|
44
|
-
*/
|
|
45
28
|
origin: string;
|
|
46
|
-
/**
|
|
47
|
-
* How long an issued nonce stays valid for, in seconds.
|
|
48
|
-
* @default 120
|
|
49
|
-
*/
|
|
50
29
|
nonceExpiresIn?: number;
|
|
51
|
-
/**
|
|
52
|
-
* When true, a verified email that has no existing account is rejected
|
|
53
|
-
* instead of provisioning a new user.
|
|
54
|
-
* @default false
|
|
55
|
-
*/
|
|
56
30
|
disableSignUp?: boolean;
|
|
57
|
-
|
|
58
|
-
* Additional fields to set on a newly created user.
|
|
59
|
-
*/
|
|
31
|
+
allowedEmailDomains?: string[];
|
|
60
32
|
userFields?: (verified: EvpVerifiedEmail) => T;
|
|
61
|
-
/**
|
|
62
|
-
* Called after a session has been created for a successfully verified
|
|
63
|
-
* email. Useful for analytics/logging.
|
|
64
|
-
*/
|
|
65
33
|
onVerified?: (verified: EvpVerifiedEmail & {
|
|
66
34
|
userId: string;
|
|
67
35
|
}) => void | Promise<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Overrides the verification call - mainly useful for tests. Defaults to
|
|
70
|
-
* `verifyEmailToken` from the `email-verification-api` package.
|
|
71
|
-
*/
|
|
72
36
|
verify?: (params: EvpVerifyParams) => Promise<EvpVerificationResult>;
|
|
73
37
|
}
|
|
74
38
|
export interface EvpNonceResponse {
|
|
@@ -77,12 +41,6 @@ export interface EvpNonceResponse {
|
|
|
77
41
|
}
|
|
78
42
|
export interface EvpVerifyResponse {
|
|
79
43
|
verified: boolean;
|
|
80
|
-
/**
|
|
81
|
-
* Present when `verified` is false. Callers should treat any failure as
|
|
82
|
-
* a signal to fall back to a normal sign-in method (OTP, magic link,
|
|
83
|
-
* password, ...) - this is an experimental, Chrome-only capability and
|
|
84
|
-
* is expected to fail for most users.
|
|
85
|
-
*/
|
|
86
44
|
reason?: string;
|
|
87
45
|
token?: string;
|
|
88
46
|
user?: Record<string, unknown>;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,CAAC,CAAC;IAC/C,UAAU,CAAC,EAAE,CACX,QAAQ,EAAE,gBAAgB,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAC5C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth-evp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Email Verification Protocol (Chrome origin trial) plugin for Better Auth, with automatic fallback to any other sign-in method",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
Binary file
|