@better-auth/core 1.7.0 → 1.7.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/dist/context/endpoint-context.d.mts +19 -5
- package/dist/context/endpoint-context.mjs +35 -16
- package/dist/context/global.mjs +5 -2
- package/dist/context/index.d.mts +2 -2
- package/dist/context/index.mjs +2 -2
- package/dist/env/logger.mjs +16 -1
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/social-providers/reddit.mjs +5 -1
- package/dist/social-providers/roblox.mjs +5 -1
- package/dist/social-providers/tiktok.mjs +5 -1
- package/dist/social-providers/twitter.mjs +5 -1
- package/dist/social-providers/wechat.mjs +6 -1
- package/dist/utils/url.d.mts +10 -1
- package/dist/utils/url.mjs +21 -1
- package/package.json +2 -2
- package/src/context/endpoint-context.ts +46 -21
- package/src/context/global.ts +7 -0
- package/src/context/index.ts +2 -0
- package/src/env/logger.ts +22 -1
- package/src/social-providers/reddit.ts +7 -1
- package/src/social-providers/roblox.ts +5 -3
- package/src/social-providers/tiktok.ts +7 -1
- package/src/social-providers/twitter.ts +8 -2
- package/src/social-providers/wechat.ts +6 -6
- package/src/utils/url.ts +43 -0
|
@@ -6,13 +6,27 @@ import { AsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
|
6
6
|
type AuthEndpointContext = Partial<InputContext<string, any> & EndpointContext<string, any>> & {
|
|
7
7
|
context: AuthContext;
|
|
8
8
|
};
|
|
9
|
+
type AuthEndpointContextStorage = AsyncLocalStorage<AuthEndpointContext>;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* @deprecated Use `getCurrentAuthEndpointContext`,
|
|
12
|
+
* `tryGetCurrentAuthEndpointContext`, or `runWithEndpointContext` instead.
|
|
13
|
+
*/
|
|
14
|
+
declare function getCurrentAuthContextAsyncLocalStorage(): Promise<AuthEndpointContextStorage>;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current auth endpoint context, or `undefined` when called outside
|
|
17
|
+
* of `runWithEndpointContext`.
|
|
18
|
+
*/
|
|
19
|
+
declare function tryGetCurrentAuthEndpointContext(): AuthEndpointContext | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the current auth endpoint context.
|
|
11
22
|
*
|
|
12
|
-
*
|
|
23
|
+
* @throws When called outside of `runWithEndpointContext`.
|
|
24
|
+
*/
|
|
25
|
+
declare function getCurrentAuthEndpointContext(): AuthEndpointContext;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `getCurrentAuthEndpointContext` instead.
|
|
13
28
|
*/
|
|
14
|
-
declare function getCurrentAuthContextAsyncLocalStorage(): Promise<AsyncLocalStorage<AuthEndpointContext>>;
|
|
15
29
|
declare function getCurrentAuthContext(): Promise<AuthEndpointContext>;
|
|
16
|
-
declare function runWithEndpointContext<T>(
|
|
30
|
+
declare function runWithEndpointContext<T>(authEndpointContext: AuthEndpointContext, fn: () => T): Promise<T>;
|
|
17
31
|
//#endregion
|
|
18
|
-
export { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext };
|
|
32
|
+
export { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext };
|
|
@@ -1,29 +1,48 @@
|
|
|
1
|
-
import { __getBetterAuthGlobal } from "./global.mjs";
|
|
1
|
+
import { __getBetterAuthGlobal, __getCurrentEndpointContext } from "./global.mjs";
|
|
2
2
|
import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
3
3
|
//#region src/context/endpoint-context.ts
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const getExistingEndpointContextStorage = () => {
|
|
5
|
+
return __getBetterAuthGlobal().context.endpointContextAsyncStorage;
|
|
6
|
+
};
|
|
7
|
+
const getOrCreateEndpointContextStorage = async () => {
|
|
8
|
+
const existing = getExistingEndpointContextStorage();
|
|
7
9
|
if (existing) return existing;
|
|
8
10
|
const AsyncLocalStorage = await getAsyncLocalStorage();
|
|
9
|
-
|
|
10
|
-
return
|
|
11
|
+
const globalContext = __getBetterAuthGlobal().context;
|
|
12
|
+
return globalContext.endpointContextAsyncStorage ??= new AsyncLocalStorage();
|
|
11
13
|
};
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* It is exposed for advanced use cases where you need direct access to the AsyncLocalStorage instance.
|
|
15
|
+
* @deprecated Use `getCurrentAuthEndpointContext`,
|
|
16
|
+
* `tryGetCurrentAuthEndpointContext`, or `runWithEndpointContext` instead.
|
|
16
17
|
*/
|
|
17
18
|
async function getCurrentAuthContextAsyncLocalStorage() {
|
|
18
|
-
return
|
|
19
|
+
return getOrCreateEndpointContextStorage();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current auth endpoint context, or `undefined` when called outside
|
|
23
|
+
* of `runWithEndpointContext`.
|
|
24
|
+
*/
|
|
25
|
+
function tryGetCurrentAuthEndpointContext() {
|
|
26
|
+
return __getCurrentEndpointContext();
|
|
19
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the current auth endpoint context.
|
|
30
|
+
*
|
|
31
|
+
* @throws When called outside of `runWithEndpointContext`.
|
|
32
|
+
*/
|
|
33
|
+
function getCurrentAuthEndpointContext() {
|
|
34
|
+
const authEndpointContext = tryGetCurrentAuthEndpointContext();
|
|
35
|
+
if (!authEndpointContext) throw new Error("No auth context found. Please make sure you are calling this function within a `runWithEndpointContext` callback.");
|
|
36
|
+
return authEndpointContext;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use `getCurrentAuthEndpointContext` instead.
|
|
40
|
+
*/
|
|
20
41
|
async function getCurrentAuthContext() {
|
|
21
|
-
|
|
22
|
-
if (!context) throw new Error("No auth context found. Please make sure you are calling this function within a `runWithEndpointContext` callback.");
|
|
23
|
-
return context;
|
|
42
|
+
return getCurrentAuthEndpointContext();
|
|
24
43
|
}
|
|
25
|
-
async function runWithEndpointContext(
|
|
26
|
-
return (await
|
|
44
|
+
async function runWithEndpointContext(authEndpointContext, fn) {
|
|
45
|
+
return (await getOrCreateEndpointContextStorage()).run(authEndpointContext, fn);
|
|
27
46
|
}
|
|
28
47
|
//#endregion
|
|
29
|
-
export { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext };
|
|
48
|
+
export { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext };
|
package/dist/context/global.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const symbol = Symbol.for("better-auth:global");
|
|
3
3
|
let bind = null;
|
|
4
4
|
const __context = {};
|
|
5
|
-
const __betterAuthVersion = "1.7.
|
|
5
|
+
const __betterAuthVersion = "1.7.2";
|
|
6
6
|
/**
|
|
7
7
|
* We store context instance in the globalThis.
|
|
8
8
|
*
|
|
@@ -29,8 +29,11 @@ function __getBetterAuthGlobal() {
|
|
|
29
29
|
}
|
|
30
30
|
return globalThis[symbol];
|
|
31
31
|
}
|
|
32
|
+
function __getCurrentEndpointContext() {
|
|
33
|
+
return __getBetterAuthGlobal().context.endpointContextAsyncStorage?.getStore();
|
|
34
|
+
}
|
|
32
35
|
function getBetterAuthVersion() {
|
|
33
36
|
return __getBetterAuthGlobal().version;
|
|
34
37
|
}
|
|
35
38
|
//#endregion
|
|
36
|
-
export { __getBetterAuthGlobal, getBetterAuthVersion };
|
|
39
|
+
export { __getBetterAuthGlobal, __getCurrentEndpointContext, getBetterAuthVersion };
|
package/dist/context/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
1
|
+
import { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext } from "./endpoint-context.mjs";
|
|
2
2
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
3
3
|
import { RequestState, RequestStateWeakMap, defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
-
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
5
|
+
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction, tryGetCurrentAuthEndpointContext };
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
2
|
-
import { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
2
|
+
import { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext } from "./endpoint-context.mjs";
|
|
3
3
|
import { defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
-
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
5
|
+
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction, tryGetCurrentAuthEndpointContext };
|
package/dist/env/logger.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { __getCurrentEndpointContext } from "../context/global.mjs";
|
|
1
2
|
import { getColorDepth } from "./color-depth.mjs";
|
|
2
3
|
//#region src/env/logger.ts
|
|
3
4
|
const TTY_COLORS = {
|
|
@@ -74,6 +75,20 @@ const createLogger = (options) => {
|
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
};
|
|
77
|
-
const
|
|
78
|
+
const defaultLogger = createLogger();
|
|
79
|
+
const getCurrentLogger = () => {
|
|
80
|
+
const currentLogger = __getCurrentEndpointContext()?.context.logger;
|
|
81
|
+
return currentLogger && currentLogger !== logger ? currentLogger : defaultLogger;
|
|
82
|
+
};
|
|
83
|
+
const logger = {
|
|
84
|
+
debug: (...params) => getCurrentLogger().debug(...params),
|
|
85
|
+
info: (...params) => getCurrentLogger().info(...params),
|
|
86
|
+
success: (...params) => getCurrentLogger().success(...params),
|
|
87
|
+
warn: (...params) => getCurrentLogger().warn(...params),
|
|
88
|
+
error: (...params) => getCurrentLogger().error(...params),
|
|
89
|
+
get level() {
|
|
90
|
+
return getCurrentLogger().level;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
78
93
|
//#endregion
|
|
79
94
|
export { TTY_COLORS, createLogger, levels, logger, shouldPublishLog };
|
|
@@ -2,7 +2,7 @@ import { ATTR_HTTP_RESPONSE_STATUS_CODE } from "./attributes.mjs";
|
|
|
2
2
|
import { getOpenTelemetryAPI } from "./api.mjs";
|
|
3
3
|
//#region src/instrumentation/tracer.ts
|
|
4
4
|
const INSTRUMENTATION_SCOPE = "better-auth";
|
|
5
|
-
const INSTRUMENTATION_VERSION = "1.7.
|
|
5
|
+
const INSTRUMENTATION_VERSION = "1.7.2";
|
|
6
6
|
/**
|
|
7
7
|
* Better-auth uses `throw ctx.redirect(url)` for flow control (e.g. OAuth
|
|
8
8
|
* callbacks). These are APIErrors with 3xx status codes and should not be
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOAuth2Tokens } from "../oauth2/utils.mjs";
|
|
2
2
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
3
3
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email.mjs";
|
|
4
5
|
import { base64 } from "@better-auth/utils/base64";
|
|
5
6
|
import { betterFetch } from "@better-fetch/fetch";
|
|
6
7
|
//#region src/social-providers/reddit.ts
|
|
@@ -63,7 +64,10 @@ const reddit = (options) => {
|
|
|
63
64
|
} });
|
|
64
65
|
if (error) return null;
|
|
65
66
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
66
|
-
const email = userMap?.email ||
|
|
67
|
+
const email = userMap?.email || createPlaceholderEmail({
|
|
68
|
+
identifier: profile.id,
|
|
69
|
+
namespace: "reddit"
|
|
70
|
+
});
|
|
67
71
|
return {
|
|
68
72
|
user: {
|
|
69
73
|
name: profile.name,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
2
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
3
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email.mjs";
|
|
4
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
6
|
//#region src/social-providers/roblox.ts
|
|
6
7
|
const roblox = (options) => {
|
|
@@ -53,7 +54,10 @@ const roblox = (options) => {
|
|
|
53
54
|
user: {
|
|
54
55
|
name: profile.nickname || profile.preferred_username || "",
|
|
55
56
|
image: profile.picture,
|
|
56
|
-
email:
|
|
57
|
+
email: createPlaceholderEmail({
|
|
58
|
+
identifier: profile.sub,
|
|
59
|
+
namespace: "roblox"
|
|
60
|
+
}),
|
|
57
61
|
emailVerified: false,
|
|
58
62
|
...userMap
|
|
59
63
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2/create-authorization-url.mjs";
|
|
2
2
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
3
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email.mjs";
|
|
4
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
6
|
//#region src/social-providers/tiktok.ts
|
|
6
7
|
const tiktok = (options) => {
|
|
@@ -57,7 +58,10 @@ const tiktok = (options) => {
|
|
|
57
58
|
if (error) return null;
|
|
58
59
|
return {
|
|
59
60
|
user: {
|
|
60
|
-
email: profile.data.user.email ||
|
|
61
|
+
email: profile.data.user.email || createPlaceholderEmail({
|
|
62
|
+
identifier: profile.data.user.open_id,
|
|
63
|
+
namespace: "tiktok"
|
|
64
|
+
}),
|
|
61
65
|
name: profile.data.user.display_name || profile.data.user.username || "",
|
|
62
66
|
image: profile.data.user.avatar_large_url,
|
|
63
67
|
emailVerified: false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
2
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
3
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email.mjs";
|
|
4
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
6
|
//#region src/social-providers/twitter.ts
|
|
6
7
|
const twitter = (options) => {
|
|
@@ -71,7 +72,10 @@ const twitter = (options) => {
|
|
|
71
72
|
return {
|
|
72
73
|
user: {
|
|
73
74
|
name: profile.data.name,
|
|
74
|
-
email: profile.data.email ||
|
|
75
|
+
email: profile.data.email || createPlaceholderEmail({
|
|
76
|
+
identifier: profile.data.id,
|
|
77
|
+
namespace: "twitter"
|
|
78
|
+
}),
|
|
75
79
|
image: profile.data.profile_image_url,
|
|
76
80
|
emailVerified,
|
|
77
81
|
...userMap
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2/create-authorization-url.mjs";
|
|
2
|
+
import { createPlaceholderEmail } from "../utils/email.mjs";
|
|
2
3
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
4
|
//#region src/social-providers/wechat.ts
|
|
4
5
|
const wechat = (options) => {
|
|
@@ -69,10 +70,14 @@ const wechat = (options) => {
|
|
|
69
70
|
}).toString(), { method: "GET" });
|
|
70
71
|
if (error || !profile || profile.errcode) return null;
|
|
71
72
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
73
|
+
const userId = profile.unionid || profile.openid || openid;
|
|
72
74
|
return {
|
|
73
75
|
user: {
|
|
74
76
|
name: profile.nickname,
|
|
75
|
-
email: profile.email ||
|
|
77
|
+
email: profile.email || createPlaceholderEmail({
|
|
78
|
+
identifier: userId,
|
|
79
|
+
namespace: "wechat"
|
|
80
|
+
}),
|
|
76
81
|
image: profile.headimgurl,
|
|
77
82
|
emailVerified: false,
|
|
78
83
|
...userMap
|
package/dist/utils/url.d.mts
CHANGED
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
* // Returns: "/sso/saml2/callback/provider1"
|
|
17
17
|
*/
|
|
18
18
|
declare function normalizePathname(requestUrl: string, basePath: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Appends query parameters before the fragment of an absolute or root-relative URL.
|
|
21
|
+
* Existing query text is retained without parsing it into name-value pairs.
|
|
22
|
+
*
|
|
23
|
+
* This function only composes URLs. Callers must validate untrusted input.
|
|
24
|
+
*
|
|
25
|
+
* @throws TypeError if parsing fails or a relative input changes authority.
|
|
26
|
+
*/
|
|
27
|
+
declare function appendQueryParams(input: string, params: URLSearchParams): string;
|
|
19
28
|
/**
|
|
20
29
|
* Schemes that execute or embed code when navigated to or accepted as a
|
|
21
30
|
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|
|
@@ -34,4 +43,4 @@ declare const DANGEROUS_URL_SCHEMES: string[];
|
|
|
34
43
|
*/
|
|
35
44
|
declare function isSafeUrlScheme(value: string): boolean;
|
|
36
45
|
//#endregion
|
|
37
|
-
export { DANGEROUS_URL_SCHEMES, isSafeUrlScheme, normalizePathname };
|
|
46
|
+
export { DANGEROUS_URL_SCHEMES, appendQueryParams, isSafeUrlScheme, normalizePathname };
|
package/dist/utils/url.mjs
CHANGED
|
@@ -28,6 +28,26 @@ function normalizePathname(requestUrl, basePath) {
|
|
|
28
28
|
if (pathname.startsWith(normalizedBasePath + "/")) return pathname.slice(normalizedBasePath.length).replace(/\/+$/, "") || "/";
|
|
29
29
|
return pathname;
|
|
30
30
|
}
|
|
31
|
+
const URL_REFERENCE_ORIGIN = "https://better-auth.invalid";
|
|
32
|
+
/**
|
|
33
|
+
* Appends query parameters before the fragment of an absolute or root-relative URL.
|
|
34
|
+
* Existing query text is retained without parsing it into name-value pairs.
|
|
35
|
+
*
|
|
36
|
+
* This function only composes URLs. Callers must validate untrusted input.
|
|
37
|
+
*
|
|
38
|
+
* @throws TypeError if parsing fails or a relative input changes authority.
|
|
39
|
+
*/
|
|
40
|
+
function appendQueryParams(input, params) {
|
|
41
|
+
const relative = input.startsWith("/");
|
|
42
|
+
if (input.startsWith("//") || input.startsWith("/\\")) throw new TypeError("Expected an absolute or root-relative URL");
|
|
43
|
+
const parsedURL = relative ? new URL(input, URL_REFERENCE_ORIGIN) : new URL(input);
|
|
44
|
+
if (relative && parsedURL.origin !== URL_REFERENCE_ORIGIN) throw new TypeError("Expected an absolute or root-relative URL");
|
|
45
|
+
const query = params.toString();
|
|
46
|
+
if (!query) return input;
|
|
47
|
+
const separator = parsedURL.search.endsWith("&") ? "" : "&";
|
|
48
|
+
parsedURL.search = parsedURL.search ? `${parsedURL.search}${separator}${query}` : query;
|
|
49
|
+
return relative ? parsedURL.href.slice(parsedURL.origin.length) : parsedURL.href;
|
|
50
|
+
}
|
|
31
51
|
/**
|
|
32
52
|
* Schemes that execute or embed code when navigated to or accepted as a
|
|
33
53
|
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|
|
@@ -58,4 +78,4 @@ function isSafeUrlScheme(value) {
|
|
|
58
78
|
return !DANGEROUS_URL_SCHEMES.includes(parsed.protocol);
|
|
59
79
|
}
|
|
60
80
|
//#endregion
|
|
61
|
-
export { DANGEROUS_URL_SCHEMES, isSafeUrlScheme, normalizePathname };
|
|
81
|
+
export { DANGEROUS_URL_SCHEMES, appendQueryParams, isSafeUrlScheme, normalizePathname };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"node": "./dist/async_hooks/index.mjs",
|
|
46
46
|
"deno": "./dist/async_hooks/index.mjs",
|
|
47
47
|
"bun": "./dist/async_hooks/index.mjs",
|
|
48
|
-
"edge": "./dist/async_hooks/pure.index.mjs",
|
|
49
48
|
"workerd": "./dist/async_hooks/index.mjs",
|
|
49
|
+
"edge": "./dist/async_hooks/pure.index.mjs",
|
|
50
50
|
"browser": "./dist/async_hooks/pure.index.mjs",
|
|
51
51
|
"default": "./dist/async_hooks/index.mjs"
|
|
52
52
|
},
|
|
@@ -2,7 +2,7 @@ import type { AsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
|
2
2
|
import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
3
3
|
import type { EndpointContext, InputContext } from "better-call";
|
|
4
4
|
import type { AuthContext } from "../types";
|
|
5
|
-
import { __getBetterAuthGlobal } from "./global";
|
|
5
|
+
import { __getBetterAuthGlobal, __getCurrentEndpointContext } from "./global";
|
|
6
6
|
|
|
7
7
|
export type AuthEndpointContext = Partial<
|
|
8
8
|
InputContext<string, any> & EndpointContext<string, any>
|
|
@@ -10,43 +10,68 @@ export type AuthEndpointContext = Partial<
|
|
|
10
10
|
context: AuthContext;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
type AuthEndpointContextStorage = AsyncLocalStorage<AuthEndpointContext>;
|
|
14
|
+
|
|
15
|
+
const getExistingEndpointContextStorage = () => {
|
|
16
|
+
return __getBetterAuthGlobal().context.endpointContextAsyncStorage as
|
|
17
|
+
| AuthEndpointContextStorage
|
|
18
|
+
| undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getOrCreateEndpointContextStorage = async () => {
|
|
22
|
+
const existing = getExistingEndpointContextStorage();
|
|
16
23
|
if (existing) {
|
|
17
|
-
return existing
|
|
24
|
+
return existing;
|
|
18
25
|
}
|
|
19
26
|
const AsyncLocalStorage = await getAsyncLocalStorage();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const globalContext = __getBetterAuthGlobal().context;
|
|
28
|
+
const storage = (globalContext.endpointContextAsyncStorage ??=
|
|
29
|
+
new AsyncLocalStorage<AuthEndpointContext>()) as AuthEndpointContextStorage;
|
|
30
|
+
return storage;
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* It is exposed for advanced use cases where you need direct access to the AsyncLocalStorage instance.
|
|
34
|
+
* @deprecated Use `getCurrentAuthEndpointContext`,
|
|
35
|
+
* `tryGetCurrentAuthEndpointContext`, or `runWithEndpointContext` instead.
|
|
30
36
|
*/
|
|
31
37
|
export async function getCurrentAuthContextAsyncLocalStorage() {
|
|
32
|
-
return
|
|
38
|
+
return getOrCreateEndpointContextStorage();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the current auth endpoint context, or `undefined` when called outside
|
|
43
|
+
* of `runWithEndpointContext`.
|
|
44
|
+
*/
|
|
45
|
+
export function tryGetCurrentAuthEndpointContext() {
|
|
46
|
+
return __getCurrentEndpointContext<AuthEndpointContext>();
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Returns the current auth endpoint context.
|
|
51
|
+
*
|
|
52
|
+
* @throws When called outside of `runWithEndpointContext`.
|
|
53
|
+
*/
|
|
54
|
+
export function getCurrentAuthEndpointContext() {
|
|
55
|
+
const authEndpointContext = tryGetCurrentAuthEndpointContext();
|
|
56
|
+
if (!authEndpointContext) {
|
|
39
57
|
throw new Error(
|
|
40
58
|
"No auth context found. Please make sure you are calling this function within a `runWithEndpointContext` callback.",
|
|
41
59
|
);
|
|
42
60
|
}
|
|
43
|
-
return
|
|
61
|
+
return authEndpointContext;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use `getCurrentAuthEndpointContext` instead.
|
|
66
|
+
*/
|
|
67
|
+
export async function getCurrentAuthContext() {
|
|
68
|
+
return getCurrentAuthEndpointContext();
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
export async function runWithEndpointContext<T>(
|
|
47
|
-
|
|
72
|
+
authEndpointContext: AuthEndpointContext,
|
|
48
73
|
fn: () => T,
|
|
49
74
|
): Promise<T> {
|
|
50
|
-
const
|
|
51
|
-
return
|
|
75
|
+
const storage = await getOrCreateEndpointContextStorage();
|
|
76
|
+
return storage.run(authEndpointContext, fn);
|
|
52
77
|
}
|
package/src/context/global.ts
CHANGED
|
@@ -52,6 +52,13 @@ export function __getBetterAuthGlobal(): BetterAuthGlobal {
|
|
|
52
52
|
return (globalThis as any)[symbol] as BetterAuthGlobal;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export function __getCurrentEndpointContext<T>(): T | undefined {
|
|
56
|
+
const storage = __getBetterAuthGlobal().context.endpointContextAsyncStorage as
|
|
57
|
+
| AsyncLocalStorage<T>
|
|
58
|
+
| undefined;
|
|
59
|
+
return storage?.getStore();
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
export function getBetterAuthVersion(): string {
|
|
56
63
|
return __getBetterAuthGlobal().version;
|
|
57
64
|
}
|
package/src/context/index.ts
CHANGED
|
@@ -2,7 +2,9 @@ export {
|
|
|
2
2
|
type AuthEndpointContext,
|
|
3
3
|
getCurrentAuthContext,
|
|
4
4
|
getCurrentAuthContextAsyncLocalStorage,
|
|
5
|
+
getCurrentAuthEndpointContext,
|
|
5
6
|
runWithEndpointContext,
|
|
7
|
+
tryGetCurrentAuthEndpointContext,
|
|
6
8
|
} from "./endpoint-context";
|
|
7
9
|
export { getBetterAuthVersion } from "./global";
|
|
8
10
|
export {
|
package/src/env/logger.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AuthEndpointContext } from "../context/endpoint-context";
|
|
2
|
+
import { __getCurrentEndpointContext } from "../context/global";
|
|
1
3
|
import { getColorDepth } from "./color-depth";
|
|
2
4
|
|
|
3
5
|
export const TTY_COLORS = {
|
|
@@ -142,4 +144,23 @@ export const createLogger = (options?: Logger | undefined): InternalLogger => {
|
|
|
142
144
|
};
|
|
143
145
|
};
|
|
144
146
|
|
|
145
|
-
|
|
147
|
+
const defaultLogger = createLogger();
|
|
148
|
+
|
|
149
|
+
const getCurrentLogger = (): InternalLogger => {
|
|
150
|
+
const currentLogger =
|
|
151
|
+
__getCurrentEndpointContext<AuthEndpointContext>()?.context.logger;
|
|
152
|
+
return currentLogger && currentLogger !== logger
|
|
153
|
+
? currentLogger
|
|
154
|
+
: defaultLogger;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const logger: InternalLogger = {
|
|
158
|
+
debug: (...params) => getCurrentLogger().debug(...params),
|
|
159
|
+
info: (...params) => getCurrentLogger().info(...params),
|
|
160
|
+
success: (...params) => getCurrentLogger().success(...params),
|
|
161
|
+
warn: (...params) => getCurrentLogger().warn(...params),
|
|
162
|
+
error: (...params) => getCurrentLogger().error(...params),
|
|
163
|
+
get level() {
|
|
164
|
+
return getCurrentLogger().level;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getOAuth2Tokens,
|
|
7
7
|
refreshAccessToken,
|
|
8
8
|
} from "../oauth2";
|
|
9
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
9
10
|
|
|
10
11
|
export interface RedditProfile {
|
|
11
12
|
id: string;
|
|
@@ -110,7 +111,12 @@ export const reddit = (options: RedditOptions) => {
|
|
|
110
111
|
// non-routable placeholder (RFC 2606 `.invalid`) keyed to the user's
|
|
111
112
|
// Reddit id rather than the routable `reddit.com`, which could collide
|
|
112
113
|
// with a real address. Left unverified; `mapProfileToUser` can override.
|
|
113
|
-
const email =
|
|
114
|
+
const email =
|
|
115
|
+
userMap?.email ||
|
|
116
|
+
createPlaceholderEmail({
|
|
117
|
+
identifier: profile.id,
|
|
118
|
+
namespace: "reddit",
|
|
119
|
+
});
|
|
114
120
|
return {
|
|
115
121
|
user: {
|
|
116
122
|
name: profile.name,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
refreshAccessToken,
|
|
6
6
|
validateAuthorizationCode,
|
|
7
7
|
} from "../oauth2";
|
|
8
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
8
9
|
|
|
9
10
|
export interface RobloxProfile extends Record<string, any> {
|
|
10
11
|
/** the user's id */
|
|
@@ -97,13 +98,14 @@ export const roblox = (options: RobloxOptions) => {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
100
|
-
// Roblox does not provide email or email_verified claim.
|
|
101
|
-
// We default to false for security consistency.
|
|
102
101
|
return {
|
|
103
102
|
user: {
|
|
104
103
|
name: profile.nickname || profile.preferred_username || "",
|
|
105
104
|
image: profile.picture,
|
|
106
|
-
email:
|
|
105
|
+
email: createPlaceholderEmail({
|
|
106
|
+
identifier: profile.sub,
|
|
107
|
+
namespace: "roblox",
|
|
108
|
+
}),
|
|
107
109
|
emailVerified: false,
|
|
108
110
|
...userMap,
|
|
109
111
|
},
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
refreshAccessToken,
|
|
6
6
|
validateAuthorizationCode,
|
|
7
7
|
} from "../oauth2";
|
|
8
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
|
|
@@ -210,7 +211,12 @@ export const tiktok = (options: TiktokOptions) => {
|
|
|
210
211
|
|
|
211
212
|
return {
|
|
212
213
|
user: {
|
|
213
|
-
email:
|
|
214
|
+
email:
|
|
215
|
+
profile.data.user.email ||
|
|
216
|
+
createPlaceholderEmail({
|
|
217
|
+
identifier: profile.data.user.open_id,
|
|
218
|
+
namespace: "tiktok",
|
|
219
|
+
}),
|
|
214
220
|
name:
|
|
215
221
|
profile.data.user.display_name || profile.data.user.username || "",
|
|
216
222
|
image: profile.data.user.avatar_large_url,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
refreshAccessToken,
|
|
6
6
|
validateAuthorizationCode,
|
|
7
7
|
} from "../oauth2";
|
|
8
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
8
9
|
|
|
9
10
|
export interface TwitterProfile {
|
|
10
11
|
data: {
|
|
@@ -187,9 +188,14 @@ export const twitter = (options: TwitterOption) => {
|
|
|
187
188
|
return {
|
|
188
189
|
user: {
|
|
189
190
|
name: profile.data.name,
|
|
190
|
-
email:
|
|
191
|
+
email:
|
|
192
|
+
profile.data.email ||
|
|
193
|
+
createPlaceholderEmail({
|
|
194
|
+
identifier: profile.data.id,
|
|
195
|
+
namespace: "twitter",
|
|
196
|
+
}),
|
|
191
197
|
image: profile.data.profile_image_url,
|
|
192
|
-
emailVerified
|
|
198
|
+
emailVerified,
|
|
193
199
|
...userMap,
|
|
194
200
|
},
|
|
195
201
|
data: profile,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
2
|
import type { OAuth2Tokens, OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
3
|
import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2";
|
|
4
|
+
import { createPlaceholderEmail } from "../utils/email";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* WeChat user profile information
|
|
@@ -205,17 +206,16 @@ export const wechat = (options: WeChatOptions) => {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
209
|
+
const userId = profile.unionid || profile.openid || openid;
|
|
208
210
|
return {
|
|
209
211
|
user: {
|
|
210
212
|
name: profile.nickname,
|
|
211
|
-
// WeChat does not return an email, and the OAuth callback rejects a
|
|
212
|
-
// missing one, so the default sign-in would always fail. Synthesize a
|
|
213
|
-
// stable, non-routable placeholder (RFC 2606 `.invalid`) keyed to the
|
|
214
|
-
// user's WeChat id, left unverified. Applications that collect a real
|
|
215
|
-
// email override it via `mapProfileToUser`.
|
|
216
213
|
email:
|
|
217
214
|
profile.email ||
|
|
218
|
-
|
|
215
|
+
createPlaceholderEmail({
|
|
216
|
+
identifier: userId,
|
|
217
|
+
namespace: "wechat",
|
|
218
|
+
}),
|
|
219
219
|
image: profile.headimgurl,
|
|
220
220
|
emailVerified: false,
|
|
221
221
|
...userMap,
|
package/src/utils/url.ts
CHANGED
|
@@ -48,6 +48,49 @@ export function normalizePathname(
|
|
|
48
48
|
return pathname;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const URL_REFERENCE_ORIGIN = "https://better-auth.invalid";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Appends query parameters before the fragment of an absolute or root-relative URL.
|
|
55
|
+
* Existing query text is retained without parsing it into name-value pairs.
|
|
56
|
+
*
|
|
57
|
+
* This function only composes URLs. Callers must validate untrusted input.
|
|
58
|
+
*
|
|
59
|
+
* @throws TypeError if parsing fails or a relative input changes authority.
|
|
60
|
+
*/
|
|
61
|
+
export function appendQueryParams(
|
|
62
|
+
input: string,
|
|
63
|
+
params: URLSearchParams,
|
|
64
|
+
): string {
|
|
65
|
+
const relative = input.startsWith("/");
|
|
66
|
+
const hasAuthorityPrefix = input.startsWith("//") || input.startsWith("/\\");
|
|
67
|
+
if (hasAuthorityPrefix) {
|
|
68
|
+
throw new TypeError("Expected an absolute or root-relative URL");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const parsedURL = relative
|
|
72
|
+
? new URL(input, URL_REFERENCE_ORIGIN)
|
|
73
|
+
: new URL(input);
|
|
74
|
+
|
|
75
|
+
if (relative && parsedURL.origin !== URL_REFERENCE_ORIGIN) {
|
|
76
|
+
throw new TypeError("Expected an absolute or root-relative URL");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const query = params.toString();
|
|
80
|
+
if (!query) {
|
|
81
|
+
return input;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const separator = parsedURL.search.endsWith("&") ? "" : "&";
|
|
85
|
+
parsedURL.search = parsedURL.search
|
|
86
|
+
? `${parsedURL.search}${separator}${query}`
|
|
87
|
+
: query;
|
|
88
|
+
|
|
89
|
+
return relative
|
|
90
|
+
? parsedURL.href.slice(parsedURL.origin.length)
|
|
91
|
+
: parsedURL.href;
|
|
92
|
+
}
|
|
93
|
+
|
|
51
94
|
/**
|
|
52
95
|
* Schemes that execute or embed code when navigated to or accepted as a
|
|
53
96
|
* redirect target. These are never safe as an OAuth `redirect_uri` or as a
|