@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.10
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/api/index.d.mts +44 -1
- package/dist/api/index.mjs +69 -4
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +91 -3
- package/dist/db/adapter/get-id-field.mjs +1 -1
- package/dist/db/adapter/index.d.mts +87 -3
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +2 -1
- package/dist/db/type.d.mts +17 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/error/index.d.mts +7 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.mjs +6 -3
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +20 -31
- package/dist/oauth2/create-authorization-url.d.mts +11 -1
- package/dist/oauth2/create-authorization-url.mjs +27 -7
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +14 -9
- package/dist/oauth2/index.mjs +12 -8
- package/dist/oauth2/oauth-provider.d.mts +150 -10
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +38 -36
- package/dist/oauth2/reject-redirects.mjs +65 -0
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +23 -1
- package/dist/oauth2/utils.mjs +48 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +28 -34
- package/dist/oauth2/verify-id-token.d.mts +27 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +203 -25
- package/dist/social-providers/apple.d.mts +16 -4
- package/dist/social-providers/apple.mjs +17 -24
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +6 -3
- package/dist/social-providers/cognito.d.mts +27 -3
- package/dist/social-providers/cognito.mjs +15 -25
- package/dist/social-providers/discord.d.mts +7 -3
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +24 -5
- package/dist/social-providers/facebook.mjs +54 -20
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +4 -3
- package/dist/social-providers/github.d.mts +6 -2
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +53 -5
- package/dist/social-providers/google.mjs +69 -25
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +221 -45
- package/dist/social-providers/index.mjs +2 -2
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +5 -6
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +7 -3
- package/dist/social-providers/linkedin.mjs +4 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
- package/dist/social-providers/microsoft-entra-id.mjs +37 -28
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +4 -3
- package/dist/social-providers/paypal.d.mts +6 -2
- package/dist/social-providers/paypal.mjs +19 -14
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +7 -5
- package/dist/social-providers/roblox.d.mts +6 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +4 -3
- package/dist/social-providers/slack.d.mts +6 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +4 -1
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +8 -2
- package/dist/social-providers/zoom.d.mts +5 -1
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +55 -6
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +182 -12
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +298 -0
- package/dist/utils/ip.d.mts +28 -5
- package/dist/utils/ip.mjs +118 -4
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +34 -4
- package/package.json +26 -16
- package/src/api/index.ts +121 -5
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +193 -7
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/db/adapter/index.ts +85 -2
- package/src/db/adapter/types.ts +2 -0
- package/src/db/get-tables.ts +9 -1
- package/src/db/schema/account.ts +4 -1
- package/src/db/schema/user.ts +3 -0
- package/src/db/type.ts +17 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +1 -0
- package/src/error/index.ts +9 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +8 -3
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +50 -74
- package/src/oauth2/create-authorization-url.ts +33 -7
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +81 -11
- package/src/oauth2/oauth-provider.ts +159 -11
- package/src/oauth2/refresh-access-token.ts +82 -83
- package/src/oauth2/reject-redirects.ts +75 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +72 -5
- package/src/oauth2/validate-authorization-code.ts +69 -89
- package/src/oauth2/verify-id-token.ts +115 -0
- package/src/oauth2/verify.ts +409 -78
- package/src/social-providers/apple.ts +30 -40
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +36 -39
- package/src/social-providers/discord.ts +22 -18
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +108 -52
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +5 -3
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +141 -39
- package/src/social-providers/huggingface.ts +8 -1
- package/src/social-providers/kakao.ts +2 -1
- package/src/social-providers/kick.ts +8 -1
- package/src/social-providers/line.ts +25 -27
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +5 -3
- package/src/social-providers/microsoft-entra-id.ts +100 -55
- package/src/social-providers/naver.ts +2 -1
- package/src/social-providers/notion.ts +8 -1
- package/src/social-providers/paybin.ts +2 -0
- package/src/social-providers/paypal.ts +28 -17
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +9 -4
- package/src/social-providers/roblox.ts +16 -11
- package/src/social-providers/salesforce.ts +8 -1
- package/src/social-providers/slack.ts +15 -9
- package/src/social-providers/spotify.ts +8 -1
- package/src/social-providers/tiktok.ts +22 -9
- package/src/social-providers/twitch.ts +2 -1
- package/src/social-providers/twitter.ts +1 -0
- package/src/social-providers/vercel.ts +8 -1
- package/src/social-providers/vk.ts +8 -1
- package/src/social-providers/wechat.ts +17 -2
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +57 -5
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +204 -14
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/async.ts +53 -0
- package/src/utils/host.ts +425 -0
- package/src/utils/ip.ts +197 -13
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +38 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/redirect-uri.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for OAuth redirect URIs and other developer-supplied URLs that the
|
|
6
|
+
* server stores and later hands back to a browser.
|
|
7
|
+
*
|
|
8
|
+
* - Rejects dangerous schemes (`javascript:`, `data:`, `vbscript:`).
|
|
9
|
+
* - Rejects URIs with a fragment component (`#...`) per RFC 6749 §3.1.2.
|
|
10
|
+
* - Requires HTTPS, except for loopback hosts (`127.0.0.0/8`, `[::1]`,
|
|
11
|
+
* `*.localhost` per RFC 6761), where HTTP is allowed for local development.
|
|
12
|
+
* - Allows custom schemes for mobile apps (e.g. `myapp://callback`).
|
|
13
|
+
*
|
|
14
|
+
* This is the single source of truth for redirect-URI validation across the
|
|
15
|
+
* OAuth provider plugins. Consume it from `@better-auth/core/utils/redirect-uri`
|
|
16
|
+
* rather than re-implementing the scheme policy per plugin.
|
|
17
|
+
*/
|
|
18
|
+
declare const SafeUrlSchema: z.ZodURL;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { SafeUrlSchema };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isLoopbackHost } from "./host.mjs";
|
|
2
|
+
import { DANGEROUS_URL_SCHEMES } from "./url.mjs";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
//#region src/utils/redirect-uri.ts
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for OAuth redirect URIs and other developer-supplied URLs that the
|
|
7
|
+
* server stores and later hands back to a browser.
|
|
8
|
+
*
|
|
9
|
+
* - Rejects dangerous schemes (`javascript:`, `data:`, `vbscript:`).
|
|
10
|
+
* - Rejects URIs with a fragment component (`#...`) per RFC 6749 §3.1.2.
|
|
11
|
+
* - Requires HTTPS, except for loopback hosts (`127.0.0.0/8`, `[::1]`,
|
|
12
|
+
* `*.localhost` per RFC 6761), where HTTP is allowed for local development.
|
|
13
|
+
* - Allows custom schemes for mobile apps (e.g. `myapp://callback`).
|
|
14
|
+
*
|
|
15
|
+
* This is the single source of truth for redirect-URI validation across the
|
|
16
|
+
* OAuth provider plugins. Consume it from `@better-auth/core/utils/redirect-uri`
|
|
17
|
+
* rather than re-implementing the scheme policy per plugin.
|
|
18
|
+
*/
|
|
19
|
+
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
20
|
+
let u;
|
|
21
|
+
try {
|
|
22
|
+
u = new URL(val);
|
|
23
|
+
} catch {
|
|
24
|
+
ctx.addIssue({
|
|
25
|
+
code: "custom",
|
|
26
|
+
message: "URL must be parseable",
|
|
27
|
+
fatal: true
|
|
28
|
+
});
|
|
29
|
+
return z.NEVER;
|
|
30
|
+
}
|
|
31
|
+
if (DANGEROUS_URL_SCHEMES.includes(u.protocol)) {
|
|
32
|
+
ctx.addIssue({
|
|
33
|
+
code: "custom",
|
|
34
|
+
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (val.includes("#")) ctx.addIssue({
|
|
39
|
+
code: "custom",
|
|
40
|
+
message: "Redirect URI must not contain a fragment component"
|
|
41
|
+
});
|
|
42
|
+
if (u.protocol === "http:" && !isLoopbackHost(u.host)) ctx.addIssue({
|
|
43
|
+
code: "custom",
|
|
44
|
+
message: "Redirect URI must use HTTPS (HTTP allowed only for loopback hosts)"
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//#endregion
|
|
48
|
+
export { SafeUrlSchema };
|
package/dist/utils/string.d.mts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
//#region src/utils/string.d.ts
|
|
2
2
|
declare function capitalizeFirstLetter(str: string): string;
|
|
3
|
+
declare function toSnakeCase(input: string): string;
|
|
4
|
+
declare function toKebabCase(input: string): string;
|
|
5
|
+
declare function toCamelCase(input: string): string;
|
|
6
|
+
declare function toPascalCase(input: string): string;
|
|
3
7
|
//#endregion
|
|
4
|
-
export { capitalizeFirstLetter };
|
|
8
|
+
export { capitalizeFirstLetter, toCamelCase, toKebabCase, toPascalCase, toSnakeCase };
|
package/dist/utils/string.mjs
CHANGED
|
@@ -2,5 +2,24 @@
|
|
|
2
2
|
function capitalizeFirstLetter(str) {
|
|
3
3
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
4
4
|
}
|
|
5
|
+
const WORD_PATTERN = /[\p{Ll}\d]+|\p{Lu}+(?!\p{Ll})|\p{Lu}[\p{Ll}\d]+|\p{Lo}+/gu;
|
|
6
|
+
const APOSTROPHE_PATTERN = /['\u2019]/g;
|
|
7
|
+
function splitWords(input) {
|
|
8
|
+
return input.replace(APOSTROPHE_PATTERN, "").match(WORD_PATTERN) ?? [];
|
|
9
|
+
}
|
|
10
|
+
function toSnakeCase(input) {
|
|
11
|
+
return splitWords(input).map((word) => word.toLowerCase()).join("_");
|
|
12
|
+
}
|
|
13
|
+
function toKebabCase(input) {
|
|
14
|
+
return splitWords(input).map((word) => word.toLowerCase()).join("-");
|
|
15
|
+
}
|
|
16
|
+
function toCamelCase(input) {
|
|
17
|
+
return splitWords(input).reduce((acc, word, i) => {
|
|
18
|
+
return acc + (i === 0 ? word.toLowerCase() : `${word[0].toUpperCase()}${word.slice(1)}`);
|
|
19
|
+
}, "");
|
|
20
|
+
}
|
|
21
|
+
function toPascalCase(input) {
|
|
22
|
+
return splitWords(input).map((word) => `${word[0].toUpperCase()}${word.slice(1).toLowerCase()}`).join("");
|
|
23
|
+
}
|
|
5
24
|
//#endregion
|
|
6
|
-
export { capitalizeFirstLetter };
|
|
25
|
+
export { capitalizeFirstLetter, toCamelCase, toKebabCase, toPascalCase, toSnakeCase };
|
package/dist/utils/url.d.mts
CHANGED
|
@@ -16,5 +16,22 @@
|
|
|
16
16
|
* // Returns: "/sso/saml2/callback/provider1"
|
|
17
17
|
*/
|
|
18
18
|
declare function normalizePathname(requestUrl: string, basePath: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Schemes that execute or embed code when navigated to or accepted as a
|
|
21
|
+
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|
|
22
|
+
* client-side navigation target (`window.location.href`, `location.assign`, ...).
|
|
23
|
+
*/
|
|
24
|
+
declare const DANGEROUS_URL_SCHEMES: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Returns `false` only when `value` is an absolute URL using a dangerous scheme
|
|
27
|
+
* (`javascript:`, `data:`, `vbscript:`). Relative URLs (e.g. `/dashboard`) and
|
|
28
|
+
* safe absolute schemes (`http`, `https`, custom app schemes such as
|
|
29
|
+
* `myapp://`) return `true`.
|
|
30
|
+
*
|
|
31
|
+
* Use this to guard browser navigation sinks and any redirect target that may
|
|
32
|
+
* originate from untrusted input. It is intentionally narrow: it blocks code
|
|
33
|
+
* execution schemes without rejecting relative paths or mobile deep links.
|
|
34
|
+
*/
|
|
35
|
+
declare function isSafeUrlScheme(value: string): boolean;
|
|
19
36
|
//#endregion
|
|
20
|
-
export { normalizePathname };
|
|
37
|
+
export { DANGEROUS_URL_SCHEMES, isSafeUrlScheme, normalizePathname };
|
package/dist/utils/url.mjs
CHANGED
|
@@ -22,10 +22,40 @@ function normalizePathname(requestUrl, basePath) {
|
|
|
22
22
|
} catch {
|
|
23
23
|
return "/";
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
if (pathname
|
|
25
|
+
const normalizedBasePath = basePath.replace(/\/+$/, "");
|
|
26
|
+
if (normalizedBasePath === "") return pathname;
|
|
27
|
+
if (pathname === normalizedBasePath) return "/";
|
|
28
|
+
if (pathname.startsWith(normalizedBasePath + "/")) return pathname.slice(normalizedBasePath.length).replace(/\/+$/, "") || "/";
|
|
28
29
|
return pathname;
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Schemes that execute or embed code when navigated to or accepted as a
|
|
33
|
+
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|
|
34
|
+
* client-side navigation target (`window.location.href`, `location.assign`, ...).
|
|
35
|
+
*/
|
|
36
|
+
const DANGEROUS_URL_SCHEMES = [
|
|
37
|
+
"javascript:",
|
|
38
|
+
"data:",
|
|
39
|
+
"vbscript:"
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Returns `false` only when `value` is an absolute URL using a dangerous scheme
|
|
43
|
+
* (`javascript:`, `data:`, `vbscript:`). Relative URLs (e.g. `/dashboard`) and
|
|
44
|
+
* safe absolute schemes (`http`, `https`, custom app schemes such as
|
|
45
|
+
* `myapp://`) return `true`.
|
|
46
|
+
*
|
|
47
|
+
* Use this to guard browser navigation sinks and any redirect target that may
|
|
48
|
+
* originate from untrusted input. It is intentionally narrow: it blocks code
|
|
49
|
+
* execution schemes without rejecting relative paths or mobile deep links.
|
|
50
|
+
*/
|
|
51
|
+
function isSafeUrlScheme(value) {
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = new URL(value);
|
|
55
|
+
} catch {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return !DANGEROUS_URL_SCHEMES.includes(parsed.protocol);
|
|
59
|
+
}
|
|
30
60
|
//#endregion
|
|
31
|
-
export { normalizePathname };
|
|
61
|
+
export { DANGEROUS_URL_SCHEMES, isSafeUrlScheme, normalizePathname };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.10",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -93,6 +93,13 @@
|
|
|
93
93
|
"./instrumentation": {
|
|
94
94
|
"dev-source": "./src/instrumentation/index.ts",
|
|
95
95
|
"types": "./dist/instrumentation/index.d.mts",
|
|
96
|
+
"workerd": "./dist/instrumentation/pure.index.mjs",
|
|
97
|
+
"edge": "./dist/instrumentation/pure.index.mjs",
|
|
98
|
+
"browser": "./dist/instrumentation/pure.index.mjs",
|
|
99
|
+
"node": "./dist/instrumentation/index.mjs",
|
|
100
|
+
"deno": "./dist/instrumentation/index.mjs",
|
|
101
|
+
"bun": "./dist/instrumentation/index.mjs",
|
|
102
|
+
"import": "./dist/instrumentation/index.mjs",
|
|
96
103
|
"default": "./dist/instrumentation/index.mjs"
|
|
97
104
|
}
|
|
98
105
|
},
|
|
@@ -140,36 +147,39 @@
|
|
|
140
147
|
}
|
|
141
148
|
},
|
|
142
149
|
"dependencies": {
|
|
143
|
-
"@opentelemetry/semantic-conventions": "^1.
|
|
150
|
+
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
144
151
|
"@standard-schema/spec": "^1.1.0",
|
|
145
152
|
"zod": "^4.3.6"
|
|
146
153
|
},
|
|
147
154
|
"devDependencies": {
|
|
148
|
-
"@better-auth/utils": "0.4.
|
|
149
|
-
"@better-fetch/fetch": "1.1
|
|
150
|
-
"@opentelemetry/api": "^1.9.0",
|
|
151
|
-
"@opentelemetry/sdk-trace-base": "^1.30.0",
|
|
152
|
-
"@opentelemetry/sdk-trace-node": "^1.30.0",
|
|
153
|
-
"better-call": "1.3.5",
|
|
155
|
+
"@better-auth/utils": "0.4.2",
|
|
156
|
+
"@better-fetch/fetch": "1.3.1",
|
|
154
157
|
"@cloudflare/workers-types": "^4.20250121.0",
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
+
"@opentelemetry/api": "^1.9.1",
|
|
159
|
+
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
160
|
+
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
161
|
+
"better-call": "1.3.7",
|
|
162
|
+
"jose": "^6.2.3",
|
|
163
|
+
"kysely": "^0.28.17 || ^0.29.0",
|
|
164
|
+
"nanostores": "^1.3.0",
|
|
158
165
|
"tsdown": "0.21.1"
|
|
159
166
|
},
|
|
160
167
|
"peerDependencies": {
|
|
161
|
-
"@better-auth/utils": "0.4.
|
|
162
|
-
"@better-fetch/fetch": "1.1
|
|
163
|
-
"@opentelemetry/api": "^1.9.0",
|
|
164
|
-
"better-call": "1.3.5",
|
|
168
|
+
"@better-auth/utils": "0.4.2",
|
|
169
|
+
"@better-fetch/fetch": "1.3.1",
|
|
165
170
|
"@cloudflare/workers-types": ">=4",
|
|
171
|
+
"@opentelemetry/api": "^1.9.0",
|
|
172
|
+
"better-call": "1.3.7",
|
|
166
173
|
"jose": "^6.1.0",
|
|
167
|
-
"kysely": "^0.28.5",
|
|
174
|
+
"kysely": "^0.28.5 || ^0.29.0",
|
|
168
175
|
"nanostores": "^1.0.1"
|
|
169
176
|
},
|
|
170
177
|
"peerDependenciesMeta": {
|
|
171
178
|
"@cloudflare/workers-types": {
|
|
172
179
|
"optional": true
|
|
180
|
+
},
|
|
181
|
+
"@opentelemetry/api": {
|
|
182
|
+
"optional": true
|
|
173
183
|
}
|
|
174
184
|
},
|
|
175
185
|
"scripts": {
|
package/src/api/index.ts
CHANGED
|
@@ -3,9 +3,53 @@ import type {
|
|
|
3
3
|
EndpointOptions,
|
|
4
4
|
StrictEndpoint,
|
|
5
5
|
} from "better-call";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createEndpoint,
|
|
8
|
+
createMiddleware,
|
|
9
|
+
kAPIErrorHeaderSymbol,
|
|
10
|
+
} from "better-call";
|
|
7
11
|
import { runWithEndpointContext } from "../context";
|
|
8
12
|
import type { AuthContext } from "../types";
|
|
13
|
+
import { isAPIError } from "../utils/is-api-error";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Response headers that forbid any intermediary (proxy, CDN, browser) from
|
|
17
|
+
* caching a response body. Credential-bearing responses (access/refresh tokens,
|
|
18
|
+
* ID tokens, client secrets, device codes) must carry them.
|
|
19
|
+
*
|
|
20
|
+
* Set `metadata: { noStore: true }` on an endpoint and {@link createAuthEndpoint}
|
|
21
|
+
* applies these to the responses its handler produces: the success body and any
|
|
22
|
+
* error the handler throws. A request rejected by schema or media-type
|
|
23
|
+
* validation before the handler runs is not covered, and carries no credentials
|
|
24
|
+
* to protect. Spread them into a hand-built `Response` or `APIError`'s headers
|
|
25
|
+
* for the rare endpoint that constructs its own response.
|
|
26
|
+
*
|
|
27
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
|
|
28
|
+
*/
|
|
29
|
+
export const NO_STORE_HEADERS = {
|
|
30
|
+
"Cache-Control": "no-store",
|
|
31
|
+
Pragma: "no-cache",
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Better-call's createEndpoint re-throws APIError without exposing the headers
|
|
36
|
+
* accumulated on ctx.responseHeaders (e.g. Set-Cookie from deleteSessionCookie
|
|
37
|
+
* before throw). Attach them to the error via kAPIErrorHeaderSymbol — matching
|
|
38
|
+
* better-call's createMiddleware contract so the outer pipeline can merge them
|
|
39
|
+
* into the response.
|
|
40
|
+
*/
|
|
41
|
+
function attachResponseHeadersToAPIError(
|
|
42
|
+
responseHeaders: Headers | undefined,
|
|
43
|
+
e: unknown,
|
|
44
|
+
): void {
|
|
45
|
+
if (!isAPIError(e) || !responseHeaders) return;
|
|
46
|
+
Object.defineProperty(e, kAPIErrorHeaderSymbol, {
|
|
47
|
+
enumerable: false,
|
|
48
|
+
configurable: true,
|
|
49
|
+
value: responseHeaders,
|
|
50
|
+
writable: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
9
53
|
|
|
10
54
|
export const optionsMiddleware = createMiddleware(async () => {
|
|
11
55
|
/**
|
|
@@ -76,6 +120,32 @@ export function createAuthEndpoint<
|
|
|
76
120
|
const handler: EndpointHandler<Path, Opts, R> =
|
|
77
121
|
typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
|
|
78
122
|
|
|
123
|
+
// Endpoints that return credentials declare `metadata: { noStore: true }`.
|
|
124
|
+
// Emit the no-store headers at the boundary, before the handler runs, so they
|
|
125
|
+
// land on every response the handler produces: a success harvests
|
|
126
|
+
// `responseHeaders`, and a thrown error carries the same headers through
|
|
127
|
+
// `attachResponseHeadersToAPIError`. Validation that rejects the request
|
|
128
|
+
// before the handler runs is not covered (and returns no credentials).
|
|
129
|
+
const noStore =
|
|
130
|
+
(options as { metadata?: { noStore?: boolean } }).metadata?.noStore ===
|
|
131
|
+
true;
|
|
132
|
+
|
|
133
|
+
// todo: prettify the code, we want to call `runWithEndpointContext` to top level
|
|
134
|
+
const wrapped: EndpointHandler<Path, Opts, R> = async (ctx) => {
|
|
135
|
+
if (noStore) {
|
|
136
|
+
for (const [name, value] of Object.entries(NO_STORE_HEADERS)) {
|
|
137
|
+
ctx.setHeader(name, value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const runtimeCtx = ctx as unknown as { responseHeaders?: Headers };
|
|
141
|
+
try {
|
|
142
|
+
return await runWithEndpointContext(ctx as any, () => handler(ctx));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
attachResponseHeadersToAPIError(runtimeCtx.responseHeaders, e);
|
|
145
|
+
throw e;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
79
149
|
if (path) {
|
|
80
150
|
return createEndpoint(
|
|
81
151
|
path,
|
|
@@ -83,8 +153,7 @@ export function createAuthEndpoint<
|
|
|
83
153
|
...options,
|
|
84
154
|
use: [...(options?.use || []), ...use],
|
|
85
155
|
},
|
|
86
|
-
|
|
87
|
-
async (ctx) => runWithEndpointContext(ctx as any, () => handler(ctx)),
|
|
156
|
+
wrapped,
|
|
88
157
|
);
|
|
89
158
|
}
|
|
90
159
|
|
|
@@ -93,11 +162,58 @@ export function createAuthEndpoint<
|
|
|
93
162
|
...options,
|
|
94
163
|
use: [...(options?.use || []), ...use],
|
|
95
164
|
},
|
|
96
|
-
|
|
97
|
-
async (ctx) => runWithEndpointContext(ctx as any, () => handler(ctx)),
|
|
165
|
+
wrapped,
|
|
98
166
|
);
|
|
99
167
|
}
|
|
100
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Set `metadata.SERVER_ONLY` while preserving any existing metadata
|
|
171
|
+
* (`$Infer`, `openapi`, ...).
|
|
172
|
+
*/
|
|
173
|
+
function withServerOnly<Options extends EndpointOptions>(
|
|
174
|
+
options: Options,
|
|
175
|
+
): Options {
|
|
176
|
+
return {
|
|
177
|
+
...options,
|
|
178
|
+
metadata: { ...options.metadata, SERVER_ONLY: true },
|
|
179
|
+
} as Options;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export namespace createAuthEndpoint {
|
|
183
|
+
/**
|
|
184
|
+
* Declare a **server-only** endpoint.
|
|
185
|
+
*
|
|
186
|
+
* The endpoint is callable through `auth.api.*` from trusted server code but is
|
|
187
|
+
* never registered on the HTTP router and never emitted into the OpenAPI
|
|
188
|
+
* schema. It takes no path because it has no URL to be reached at.
|
|
189
|
+
*
|
|
190
|
+
* Prefer this over the path-less `createAuthEndpoint({ ... }, handler)` form.
|
|
191
|
+
* Setting `metadata.SERVER_ONLY` makes the intent explicit at the call site and
|
|
192
|
+
* keeps the endpoint off the HTTP surface even if a path is later added by
|
|
193
|
+
* mistake: better-call's router skips an endpoint when its path is missing *or*
|
|
194
|
+
* when `SERVER_ONLY` is set, so the two together are defense in depth. Relying
|
|
195
|
+
* on path omission alone is invisible and one keystroke away from exposure.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* viewBackupCodes: createAuthEndpoint.serverOnly(
|
|
200
|
+
* { method: "POST", body: schema },
|
|
201
|
+
* async (ctx) => { ... },
|
|
202
|
+
* )
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function serverOnly<
|
|
206
|
+
Path extends string,
|
|
207
|
+
Options extends EndpointOptions,
|
|
208
|
+
R,
|
|
209
|
+
>(
|
|
210
|
+
options: Options,
|
|
211
|
+
handler: EndpointHandler<Path, Options, R>,
|
|
212
|
+
): StrictEndpoint<Path, Options, R> {
|
|
213
|
+
return createAuthEndpoint(withServerOnly(options), handler);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
101
217
|
export type AuthEndpoint<
|
|
102
218
|
Path extends string,
|
|
103
219
|
Opts extends EndpointOptions,
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
3
3
|
import type { DBAdapter, DBTransactionAdapter } from "../db/adapter";
|
|
4
|
+
import type { BetterAuthOptions } from "../types";
|
|
4
5
|
import { __getBetterAuthGlobal } from "./global";
|
|
5
6
|
|
|
7
|
+
type StoredAdapter = DBTransactionAdapter<BetterAuthOptions>;
|
|
8
|
+
|
|
6
9
|
type HookContext = {
|
|
7
|
-
adapter:
|
|
10
|
+
adapter: StoredAdapter;
|
|
8
11
|
pendingHooks: Array<() => Promise<void>>;
|
|
12
|
+
isTransactionActive: boolean;
|
|
9
13
|
};
|
|
10
14
|
|
|
11
15
|
const ensureAsyncStorage = async () => {
|
|
@@ -27,21 +31,29 @@ export const getCurrentDBAdapterAsyncLocalStorage = async () => {
|
|
|
27
31
|
return ensureAsyncStorage();
|
|
28
32
|
};
|
|
29
33
|
|
|
30
|
-
export const getCurrentAdapter = async
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
export const getCurrentAdapter = async <
|
|
35
|
+
Options extends BetterAuthOptions = BetterAuthOptions,
|
|
36
|
+
>(
|
|
37
|
+
fallback: DBTransactionAdapter<Options>,
|
|
38
|
+
): Promise<DBTransactionAdapter<Options>> => {
|
|
33
39
|
return ensureAsyncStorage()
|
|
34
40
|
.then((als) => {
|
|
35
41
|
const store = als.getStore();
|
|
36
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
(store?.adapter as DBTransactionAdapter<Options> | undefined) ||
|
|
44
|
+
fallback
|
|
45
|
+
);
|
|
37
46
|
})
|
|
38
47
|
.catch(() => {
|
|
39
48
|
return fallback;
|
|
40
49
|
});
|
|
41
50
|
};
|
|
42
51
|
|
|
43
|
-
export const runWithAdapter = async <
|
|
44
|
-
|
|
52
|
+
export const runWithAdapter = async <
|
|
53
|
+
R,
|
|
54
|
+
Options extends BetterAuthOptions = BetterAuthOptions,
|
|
55
|
+
>(
|
|
56
|
+
adapter: DBAdapter<Options>,
|
|
45
57
|
fn: () => R,
|
|
46
58
|
): Promise<R> => {
|
|
47
59
|
let called = false;
|
|
@@ -53,7 +65,14 @@ export const runWithAdapter = async <R>(
|
|
|
53
65
|
let error: unknown;
|
|
54
66
|
let hasError = false;
|
|
55
67
|
try {
|
|
56
|
-
result = await als.run(
|
|
68
|
+
result = await als.run(
|
|
69
|
+
{
|
|
70
|
+
adapter: adapter as unknown as StoredAdapter,
|
|
71
|
+
pendingHooks,
|
|
72
|
+
isTransactionActive: false,
|
|
73
|
+
},
|
|
74
|
+
fn,
|
|
75
|
+
);
|
|
57
76
|
} catch (err) {
|
|
58
77
|
error = err;
|
|
59
78
|
hasError = true;
|
|
@@ -75,21 +94,35 @@ export const runWithAdapter = async <R>(
|
|
|
75
94
|
});
|
|
76
95
|
};
|
|
77
96
|
|
|
78
|
-
export const runWithTransaction = async <
|
|
79
|
-
|
|
97
|
+
export const runWithTransaction = async <
|
|
98
|
+
R,
|
|
99
|
+
Options extends BetterAuthOptions = BetterAuthOptions,
|
|
100
|
+
>(
|
|
101
|
+
adapter: DBAdapter<Options>,
|
|
80
102
|
fn: () => R,
|
|
81
103
|
): Promise<R> => {
|
|
82
|
-
let called =
|
|
104
|
+
let called = false;
|
|
83
105
|
return ensureAsyncStorage()
|
|
84
106
|
.then(async (als) => {
|
|
85
107
|
called = true;
|
|
108
|
+
const store = als.getStore();
|
|
109
|
+
if (store?.isTransactionActive) {
|
|
110
|
+
return fn();
|
|
111
|
+
}
|
|
86
112
|
const pendingHooks: Array<() => Promise<void>> = [];
|
|
87
113
|
let result: Awaited<R>;
|
|
88
114
|
let error: unknown;
|
|
89
115
|
let hasError = false;
|
|
90
116
|
try {
|
|
91
117
|
result = await adapter.transaction(async (trx) => {
|
|
92
|
-
return als.run(
|
|
118
|
+
return als.run(
|
|
119
|
+
{
|
|
120
|
+
adapter: trx as unknown as StoredAdapter,
|
|
121
|
+
pendingHooks,
|
|
122
|
+
isTransactionActive: true,
|
|
123
|
+
},
|
|
124
|
+
fn,
|
|
125
|
+
);
|
|
93
126
|
});
|
|
94
127
|
} catch (e) {
|
|
95
128
|
hasError = true;
|