@better-auth/core 1.6.6 → 1.6.8
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.mjs +29 -3
- package/dist/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +1 -1
- package/dist/instrumentation/api.mjs +1 -31
- 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 +1 -1
- package/dist/oauth2/index.d.mts +2 -2
- package/dist/oauth2/index.mjs +2 -2
- package/dist/oauth2/utils.d.mts +10 -1
- package/dist/oauth2/utils.mjs +13 -1
- package/dist/social-providers/apple.d.mts +19 -3
- package/dist/social-providers/apple.mjs +7 -1
- package/dist/social-providers/atlassian.mjs +1 -1
- package/dist/social-providers/cognito.d.mts +1 -1
- package/dist/social-providers/cognito.mjs +3 -2
- package/dist/social-providers/discord.d.mts +1 -1
- package/dist/social-providers/facebook.d.mts +3 -3
- package/dist/social-providers/facebook.mjs +8 -1
- package/dist/social-providers/figma.mjs +1 -1
- package/dist/social-providers/github.d.mts +1 -1
- package/dist/social-providers/google.d.mts +1 -1
- package/dist/social-providers/google.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +2 -2
- package/dist/social-providers/linkedin.mjs +1 -1
- package/dist/social-providers/microsoft-entra-id.d.mts +3 -3
- package/dist/social-providers/microsoft-entra-id.mjs +6 -1
- package/dist/social-providers/paybin.mjs +1 -1
- package/dist/social-providers/paypal.mjs +1 -1
- package/dist/social-providers/salesforce.mjs +1 -1
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/package.json +7 -1
- package/src/api/index.ts +39 -5
- package/src/db/get-tables.ts +2 -0
- package/src/db/schema/user.ts +3 -0
- package/src/instrumentation/api.ts +2 -49
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/oauth2/index.ts +5 -1
- package/src/oauth2/utils.ts +13 -0
- package/src/social-providers/apple.ts +11 -3
- package/src/social-providers/cognito.ts +3 -2
- package/src/social-providers/discord.ts +1 -1
- package/src/social-providers/facebook.ts +13 -4
- package/src/social-providers/github.ts +1 -1
- package/src/social-providers/google.ts +3 -2
- package/src/social-providers/linkedin.ts +3 -3
- package/src/social-providers/microsoft-entra-id.ts +14 -4
- package/src/utils/is-api-error.ts +10 -0
package/dist/api/index.mjs
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { runWithEndpointContext } from "../context/endpoint-context.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
3
|
+
import { createEndpoint, createMiddleware, kAPIErrorHeaderSymbol } from "better-call";
|
|
3
4
|
//#region src/api/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Better-call's createEndpoint re-throws APIError without exposing the headers
|
|
7
|
+
* accumulated on ctx.responseHeaders (e.g. Set-Cookie from deleteSessionCookie
|
|
8
|
+
* before throw). Attach them to the error via kAPIErrorHeaderSymbol — matching
|
|
9
|
+
* better-call's createMiddleware contract so the outer pipeline can merge them
|
|
10
|
+
* into the response.
|
|
11
|
+
*/
|
|
12
|
+
function attachResponseHeadersToAPIError(responseHeaders, e) {
|
|
13
|
+
if (!isAPIError(e) || !responseHeaders) return;
|
|
14
|
+
Object.defineProperty(e, kAPIErrorHeaderSymbol, {
|
|
15
|
+
enumerable: false,
|
|
16
|
+
configurable: true,
|
|
17
|
+
value: responseHeaders,
|
|
18
|
+
writable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
4
21
|
const optionsMiddleware = createMiddleware(async () => {
|
|
5
22
|
/**
|
|
6
23
|
* This will be passed on the instance of
|
|
@@ -17,14 +34,23 @@ function createAuthEndpoint(pathOrOptions, handlerOrOptions, handlerOrNever) {
|
|
|
17
34
|
const path = typeof pathOrOptions === "string" ? pathOrOptions : void 0;
|
|
18
35
|
const options = typeof handlerOrOptions === "object" ? handlerOrOptions : pathOrOptions;
|
|
19
36
|
const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
|
|
37
|
+
const wrapped = async (ctx) => {
|
|
38
|
+
const runtimeCtx = ctx;
|
|
39
|
+
try {
|
|
40
|
+
return await runWithEndpointContext(ctx, () => handler(ctx));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
attachResponseHeadersToAPIError(runtimeCtx.responseHeaders, e);
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
20
46
|
if (path) return createEndpoint(path, {
|
|
21
47
|
...options,
|
|
22
48
|
use: [...options?.use || [], ...use]
|
|
23
|
-
},
|
|
49
|
+
}, wrapped);
|
|
24
50
|
return createEndpoint({
|
|
25
51
|
...options,
|
|
26
52
|
use: [...options?.use || [], ...use]
|
|
27
|
-
},
|
|
53
|
+
}, wrapped);
|
|
28
54
|
}
|
|
29
55
|
//#endregion
|
|
30
56
|
export { createAuthEndpoint, createAuthMiddleware, optionsMiddleware };
|
package/dist/context/global.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { BetterAuthError } from "../../error/index.mjs";
|
|
1
2
|
import { getAuthTables } from "../get-tables.mjs";
|
|
2
3
|
import { getColorDepth } from "../../env/color-depth.mjs";
|
|
3
4
|
import { TTY_COLORS, createLogger } from "../../env/logger.mjs";
|
|
4
|
-
import { BetterAuthError } from "../../error/index.mjs";
|
|
5
5
|
import { ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME } from "../../instrumentation/attributes.mjs";
|
|
6
6
|
import { withSpan } from "../../instrumentation/tracer.mjs";
|
|
7
7
|
import { safeJSONParse } from "../../utils/json.mjs";
|
|
@@ -1,35 +1,5 @@
|
|
|
1
|
+
import { noopOpenTelemetryAPI } from "./noop.mjs";
|
|
1
2
|
//#region src/instrumentation/api.ts
|
|
2
|
-
function createNoopSpan() {
|
|
3
|
-
return {
|
|
4
|
-
end() {},
|
|
5
|
-
setAttribute(_key, _value) {},
|
|
6
|
-
setStatus(_status) {},
|
|
7
|
-
recordException(_exception) {}
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function createNoopTracer(noopSpanInstance) {
|
|
11
|
-
function startActiveSpan(_name, _options, fn) {
|
|
12
|
-
return fn(noopSpanInstance);
|
|
13
|
-
}
|
|
14
|
-
return { startActiveSpan };
|
|
15
|
-
}
|
|
16
|
-
function createNoopTraceAPI() {
|
|
17
|
-
const noopTracer = createNoopTracer(createNoopSpan());
|
|
18
|
-
return { getTracer(_name, _version) {
|
|
19
|
-
return noopTracer;
|
|
20
|
-
} };
|
|
21
|
-
}
|
|
22
|
-
function createNoopOpenTelemetryAPI() {
|
|
23
|
-
return {
|
|
24
|
-
SpanStatusCode: {
|
|
25
|
-
UNSET: 0,
|
|
26
|
-
OK: 1,
|
|
27
|
-
ERROR: 2
|
|
28
|
-
},
|
|
29
|
-
trace: createNoopTraceAPI()
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
const noopOpenTelemetryAPI = createNoopOpenTelemetryAPI();
|
|
33
3
|
let openTelemetryAPIPromise;
|
|
34
4
|
let openTelemetryAPI;
|
|
35
5
|
function getOpenTelemetryAPI() {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/instrumentation/noop.ts
|
|
2
|
+
function createNoopSpan() {
|
|
3
|
+
const span = {
|
|
4
|
+
end() {},
|
|
5
|
+
setAttribute(_key, _value) {},
|
|
6
|
+
setStatus(_status) {},
|
|
7
|
+
recordException(_exception) {},
|
|
8
|
+
updateName(_name) {
|
|
9
|
+
return span;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
return span;
|
|
13
|
+
}
|
|
14
|
+
function createNoopTracer(noopSpan) {
|
|
15
|
+
function startActiveSpan(_name, ...rest) {
|
|
16
|
+
const fn = rest[rest.length - 1];
|
|
17
|
+
return fn(noopSpan);
|
|
18
|
+
}
|
|
19
|
+
return { startActiveSpan };
|
|
20
|
+
}
|
|
21
|
+
function createNoopTraceAPI() {
|
|
22
|
+
const noopTracer = createNoopTracer(createNoopSpan());
|
|
23
|
+
return {
|
|
24
|
+
getTracer(_name, _version) {
|
|
25
|
+
return noopTracer;
|
|
26
|
+
},
|
|
27
|
+
getActiveSpan() {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createNoopOpenTelemetryAPI() {
|
|
31
|
+
return {
|
|
32
|
+
SpanStatusCode: {
|
|
33
|
+
UNSET: 0,
|
|
34
|
+
OK: 1,
|
|
35
|
+
ERROR: 2
|
|
36
|
+
},
|
|
37
|
+
trace: createNoopTraceAPI()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const noopOpenTelemetryAPI = createNoopOpenTelemetryAPI();
|
|
41
|
+
//#endregion
|
|
42
|
+
export { noopOpenTelemetryAPI };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID } from "./attributes.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/instrumentation/pure.index.d.ts
|
|
4
|
+
declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: () => T): T;
|
|
5
|
+
declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: () => Promise<T>): Promise<T>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID } from "./attributes.mjs";
|
|
2
|
+
//#region src/instrumentation/pure.index.ts
|
|
3
|
+
function withSpan(_name, _attributes, fn) {
|
|
4
|
+
return fn();
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_DB_OPERATION_NAME, ATTR_HOOK_TYPE, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan };
|
|
@@ -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.6.
|
|
5
|
+
const INSTRUMENTATION_VERSION = "1.6.8";
|
|
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
|
package/dist/oauth2/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { OAuth2Tokens, OAuth2UserInfo, OAuthProvider, ProviderOptions } from "./
|
|
|
2
2
|
import { clientCredentialsToken, clientCredentialsTokenRequest, createClientCredentialsTokenRequest } from "./client-credentials-token.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "./create-authorization-url.mjs";
|
|
4
4
|
import { createRefreshAccessTokenRequest, refreshAccessToken, refreshAccessTokenRequest } from "./refresh-access-token.mjs";
|
|
5
|
-
import { generateCodeChallenge, getOAuth2Tokens } from "./utils.mjs";
|
|
5
|
+
import { generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId } from "./utils.mjs";
|
|
6
6
|
import { authorizationCodeRequest, createAuthorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
|
|
7
7
|
import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
|
|
8
|
-
export { type OAuth2Tokens, type OAuth2UserInfo, type OAuthProvider, type ProviderOptions, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
|
8
|
+
export { type OAuth2Tokens, type OAuth2UserInfo, type OAuthProvider, type ProviderOptions, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
package/dist/oauth2/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { clientCredentialsToken, clientCredentialsTokenRequest, createClientCredentialsTokenRequest } from "./client-credentials-token.mjs";
|
|
2
|
-
import { generateCodeChallenge, getOAuth2Tokens } from "./utils.mjs";
|
|
2
|
+
import { generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId } from "./utils.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "./create-authorization-url.mjs";
|
|
4
4
|
import { createRefreshAccessTokenRequest, refreshAccessToken, refreshAccessTokenRequest } from "./refresh-access-token.mjs";
|
|
5
5
|
import { authorizationCodeRequest, createAuthorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
|
|
6
6
|
import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
|
|
7
|
-
export { authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
|
7
|
+
export { authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationCodeRequest, createAuthorizationURL, createClientCredentialsTokenRequest, createRefreshAccessTokenRequest, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, refreshAccessToken, refreshAccessTokenRequest, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
|
package/dist/oauth2/utils.d.mts
CHANGED
|
@@ -2,6 +2,15 @@ import { OAuth2Tokens } from "./oauth-provider.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/oauth2/utils.d.ts
|
|
4
4
|
declare function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens;
|
|
5
|
+
/**
|
|
6
|
+
* Return the provider's primary Client ID: the single string, or the entry at
|
|
7
|
+
* array index 0 for the cross-platform form used by ID token audience
|
|
8
|
+
* verification. Index 0 is the designated primary and pairs with
|
|
9
|
+
* `clientSecret` for the authorization code flow; later array entries are
|
|
10
|
+
* only used as additional accepted audiences. Returns `undefined` when the
|
|
11
|
+
* primary value is missing or an empty string.
|
|
12
|
+
*/
|
|
13
|
+
declare function getPrimaryClientId(clientId: unknown): string | undefined;
|
|
5
14
|
declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
|
|
6
15
|
//#endregion
|
|
7
|
-
export { generateCodeChallenge, getOAuth2Tokens };
|
|
16
|
+
export { generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId };
|
package/dist/oauth2/utils.mjs
CHANGED
|
@@ -16,10 +16,22 @@ function getOAuth2Tokens(data) {
|
|
|
16
16
|
raw: data
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Return the provider's primary Client ID: the single string, or the entry at
|
|
21
|
+
* array index 0 for the cross-platform form used by ID token audience
|
|
22
|
+
* verification. Index 0 is the designated primary and pairs with
|
|
23
|
+
* `clientSecret` for the authorization code flow; later array entries are
|
|
24
|
+
* only used as additional accepted audiences. Returns `undefined` when the
|
|
25
|
+
* primary value is missing or an empty string.
|
|
26
|
+
*/
|
|
27
|
+
function getPrimaryClientId(clientId) {
|
|
28
|
+
const value = Array.isArray(clientId) ? clientId[0] : clientId;
|
|
29
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
30
|
+
}
|
|
19
31
|
async function generateCodeChallenge(codeVerifier) {
|
|
20
32
|
const data = new TextEncoder().encode(codeVerifier);
|
|
21
33
|
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
22
34
|
return base64Url.encode(new Uint8Array(hash), { padding: false });
|
|
23
35
|
}
|
|
24
36
|
//#endregion
|
|
25
|
-
export { generateCodeChallenge, getOAuth2Tokens };
|
|
37
|
+
export { generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId };
|
|
@@ -12,7 +12,7 @@ interface AppleProfile {
|
|
|
12
12
|
* The email address is either the user's real email address or the proxy
|
|
13
13
|
* address, depending on their status private email relay service.
|
|
14
14
|
*/
|
|
15
|
-
email
|
|
15
|
+
email?: string;
|
|
16
16
|
/**
|
|
17
17
|
* A string or Boolean value that indicates whether the service verifies
|
|
18
18
|
* the email. The value can either be a string ("true" or "false") or a
|
|
@@ -60,7 +60,7 @@ interface AppleNonConformUser {
|
|
|
60
60
|
email: string;
|
|
61
61
|
}
|
|
62
62
|
interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
63
|
-
clientId: string;
|
|
63
|
+
clientId: string | string[];
|
|
64
64
|
appBundleIdentifier?: string | undefined;
|
|
65
65
|
audience?: (string | string[]) | undefined;
|
|
66
66
|
}
|
|
@@ -93,7 +93,23 @@ declare const apple: (options: AppleOptions) => {
|
|
|
93
93
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
94
94
|
getUserInfo(token: OAuth2Tokens & {
|
|
95
95
|
user?: {
|
|
96
|
-
name
|
|
96
|
+
name /**
|
|
97
|
+
* An Integer value that indicates whether the user appears to be a real
|
|
98
|
+
* person. Use the value of this claim to mitigate fraud. The possible
|
|
99
|
+
* values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
|
|
100
|
+
* more information, see ASUserDetectionStatus. This claim is present only
|
|
101
|
+
* in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
|
|
102
|
+
* and later. The claim isn’t present or supported for web-based apps.
|
|
103
|
+
*/?
|
|
104
|
+
/**
|
|
105
|
+
* An Integer value that indicates whether the user appears to be a real
|
|
106
|
+
* person. Use the value of this claim to mitigate fraud. The possible
|
|
107
|
+
* values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
|
|
108
|
+
* more information, see ASUserDetectionStatus. This claim is present only
|
|
109
|
+
* in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
|
|
110
|
+
* and later. The claim isn’t present or supported for web-based apps.
|
|
111
|
+
*/
|
|
112
|
+
: {
|
|
97
113
|
firstName?: string;
|
|
98
114
|
lastName?: string;
|
|
99
115
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { APIError } from "../error/index.mjs";
|
|
1
|
+
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
2
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
3
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
4
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -11,6 +13,10 @@ const apple = (options) => {
|
|
|
11
13
|
id: "apple",
|
|
12
14
|
name: "Apple",
|
|
13
15
|
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
16
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
17
|
+
logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
|
|
18
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
19
|
+
}
|
|
14
20
|
const _scope = options.disableDefaultScope ? [] : ["email", "name"];
|
|
15
21
|
if (options.scope) _scope.push(...options.scope);
|
|
16
22
|
if (scopes) _scope.push(...scopes);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -19,7 +19,7 @@ interface CognitoProfile {
|
|
|
19
19
|
[key: string]: any;
|
|
20
20
|
}
|
|
21
21
|
interface CognitoOptions extends ProviderOptions<CognitoProfile> {
|
|
22
|
-
clientId: string;
|
|
22
|
+
clientId: string | string[];
|
|
23
23
|
/**
|
|
24
24
|
* The Cognito domain (e.g., "your-app.auth.us-east-1.amazoncognito.com")
|
|
25
25
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -19,7 +20,7 @@ const cognito = (options) => {
|
|
|
19
20
|
id: "cognito",
|
|
20
21
|
name: "Cognito",
|
|
21
22
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
22
|
-
if (!options.clientId) {
|
|
23
|
+
if (!getPrimaryClientId(options.clientId)) {
|
|
23
24
|
logger.error("ClientId is required for Amazon Cognito. Make sure to provide them in the options.");
|
|
24
25
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
25
26
|
}
|
|
@@ -38,7 +38,7 @@ interface DiscordProfile extends Record<string, any> {
|
|
|
38
38
|
/** whether the email on this account has been verified */
|
|
39
39
|
verified: boolean;
|
|
40
40
|
/** the user's email */
|
|
41
|
-
email
|
|
41
|
+
email?: string | null;
|
|
42
42
|
/**
|
|
43
43
|
* the flags on a user's account:
|
|
44
44
|
* https://discord.com/developers/docs/resources/user#user-object-user-flags
|
|
@@ -3,8 +3,8 @@ import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
|
3
3
|
interface FacebookProfile {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
6
|
-
email
|
|
7
|
-
email_verified
|
|
6
|
+
email?: string;
|
|
7
|
+
email_verified?: boolean;
|
|
8
8
|
picture: {
|
|
9
9
|
data: {
|
|
10
10
|
height: number;
|
|
@@ -15,7 +15,7 @@ interface FacebookProfile {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
interface FacebookOptions extends ProviderOptions<FacebookProfile> {
|
|
18
|
-
clientId: string;
|
|
18
|
+
clientId: string | string[];
|
|
19
19
|
/**
|
|
20
20
|
* Extend list of fields to retrieve from the Facebook user profile.
|
|
21
21
|
*
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
1
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -9,6 +12,10 @@ const facebook = (options) => {
|
|
|
9
12
|
id: "facebook",
|
|
10
13
|
name: "Facebook",
|
|
11
14
|
async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
|
|
15
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
16
|
+
logger.error("Client ID and client secret are required for Facebook. Make sure to provide them in the options.");
|
|
17
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
18
|
+
}
|
|
12
19
|
const _scopes = options.disableDefaultScope ? [] : ["email", "public_profile"];
|
|
13
20
|
if (options.scope) _scopes.push(...options.scope);
|
|
14
21
|
if (scopes) _scopes.push(...scopes);
|
|
@@ -104,7 +111,7 @@ const facebook = (options) => {
|
|
|
104
111
|
name: profile.name,
|
|
105
112
|
email: profile.email,
|
|
106
113
|
image: profile.picture.data.url,
|
|
107
|
-
emailVerified: profile.email_verified,
|
|
114
|
+
emailVerified: profile.email_verified ?? false,
|
|
108
115
|
...userMap
|
|
109
116
|
},
|
|
110
117
|
data: profile
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -11,7 +12,7 @@ const google = (options) => {
|
|
|
11
12
|
id: "google",
|
|
12
13
|
name: "Google",
|
|
13
14
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, display }) {
|
|
14
|
-
if (!options.clientId || !options.clientSecret) {
|
|
15
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
15
16
|
logger.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options.");
|
|
16
17
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
17
18
|
}
|
|
@@ -10,8 +10,8 @@ interface LinkedInProfile {
|
|
|
10
10
|
country: string;
|
|
11
11
|
language: string;
|
|
12
12
|
};
|
|
13
|
-
email
|
|
14
|
-
email_verified
|
|
13
|
+
email?: string;
|
|
14
|
+
email_verified?: boolean;
|
|
15
15
|
}
|
|
16
16
|
interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
|
|
17
17
|
clientId: string;
|
|
@@ -25,7 +25,7 @@ interface MicrosoftEntraIDProfile extends Record<string, any> {
|
|
|
25
25
|
/** The primary username that represents the user */
|
|
26
26
|
preferred_username: string;
|
|
27
27
|
/** User's email address */
|
|
28
|
-
email
|
|
28
|
+
email?: string;
|
|
29
29
|
/** Human-readable value that identifies the subject of the token */
|
|
30
30
|
name: string;
|
|
31
31
|
/** Matches the parameter included in the original authorize request */
|
|
@@ -104,7 +104,7 @@ interface MicrosoftEntraIDProfile extends Record<string, any> {
|
|
|
104
104
|
given_name: string;
|
|
105
105
|
}
|
|
106
106
|
interface MicrosoftOptions extends ProviderOptions<MicrosoftEntraIDProfile> {
|
|
107
|
-
clientId: string;
|
|
107
|
+
clientId: string | string[];
|
|
108
108
|
/**
|
|
109
109
|
* The tenant ID of the Microsoft account
|
|
110
110
|
* @default "common"
|
|
@@ -151,7 +151,7 @@ declare const microsoft: (options: MicrosoftOptions) => {
|
|
|
151
151
|
user?: {
|
|
152
152
|
name?: {
|
|
153
153
|
firstName?: string;
|
|
154
|
-
lastName
|
|
154
|
+
lastName? /** The primary username that represents the user */: string;
|
|
155
155
|
};
|
|
156
156
|
email?: string;
|
|
157
157
|
} | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
1
2
|
import { logger } from "../env/logger.mjs";
|
|
2
|
-
import {
|
|
3
|
+
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -16,6 +17,10 @@ const microsoft = (options) => {
|
|
|
16
17
|
id: "microsoft",
|
|
17
18
|
name: "Microsoft EntraID",
|
|
18
19
|
createAuthorizationURL(data) {
|
|
20
|
+
if (!getPrimaryClientId(options.clientId)) {
|
|
21
|
+
logger.error("Client Id is required for Microsoft Entra ID. Make sure to provide it in the options.");
|
|
22
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
23
|
+
}
|
|
19
24
|
const scopes = options.disableDefaultScope ? [] : [
|
|
20
25
|
"openid",
|
|
21
26
|
"profile",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { base64 } from "@better-auth/utils/base64";
|
|
5
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { logger } from "../env/logger.mjs";
|
|
2
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
|
+
import { logger } from "../env/logger.mjs";
|
|
3
3
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
4
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
5
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { APIError as APIError$1 } from "../error/index.mjs";
|
|
2
|
+
import { APIError } from "better-call";
|
|
3
|
+
//#region src/utils/is-api-error.ts
|
|
4
|
+
function isAPIError(error) {
|
|
5
|
+
return error instanceof APIError || error instanceof APIError$1 || error?.name === "APIError";
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { isAPIError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -93,6 +93,12 @@
|
|
|
93
93
|
"./instrumentation": {
|
|
94
94
|
"dev-source": "./src/instrumentation/index.ts",
|
|
95
95
|
"types": "./dist/instrumentation/index.d.mts",
|
|
96
|
+
"node": "./dist/instrumentation/index.mjs",
|
|
97
|
+
"deno": "./dist/instrumentation/index.mjs",
|
|
98
|
+
"bun": "./dist/instrumentation/index.mjs",
|
|
99
|
+
"edge": "./dist/instrumentation/pure.index.mjs",
|
|
100
|
+
"workerd": "./dist/instrumentation/index.mjs",
|
|
101
|
+
"browser": "./dist/instrumentation/pure.index.mjs",
|
|
96
102
|
"default": "./dist/instrumentation/index.mjs"
|
|
97
103
|
}
|
|
98
104
|
},
|
package/src/api/index.ts
CHANGED
|
@@ -3,9 +3,34 @@ 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
|
+
* Better-call's createEndpoint re-throws APIError without exposing the headers
|
|
17
|
+
* accumulated on ctx.responseHeaders (e.g. Set-Cookie from deleteSessionCookie
|
|
18
|
+
* before throw). Attach them to the error via kAPIErrorHeaderSymbol — matching
|
|
19
|
+
* better-call's createMiddleware contract so the outer pipeline can merge them
|
|
20
|
+
* into the response.
|
|
21
|
+
*/
|
|
22
|
+
function attachResponseHeadersToAPIError(
|
|
23
|
+
responseHeaders: Headers | undefined,
|
|
24
|
+
e: unknown,
|
|
25
|
+
): void {
|
|
26
|
+
if (!isAPIError(e) || !responseHeaders) return;
|
|
27
|
+
Object.defineProperty(e, kAPIErrorHeaderSymbol, {
|
|
28
|
+
enumerable: false,
|
|
29
|
+
configurable: true,
|
|
30
|
+
value: responseHeaders,
|
|
31
|
+
writable: false,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
9
34
|
|
|
10
35
|
export const optionsMiddleware = createMiddleware(async () => {
|
|
11
36
|
/**
|
|
@@ -76,6 +101,17 @@ export function createAuthEndpoint<
|
|
|
76
101
|
const handler: EndpointHandler<Path, Opts, R> =
|
|
77
102
|
typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
|
|
78
103
|
|
|
104
|
+
// todo: prettify the code, we want to call `runWithEndpointContext` to top level
|
|
105
|
+
const wrapped: EndpointHandler<Path, Opts, R> = async (ctx) => {
|
|
106
|
+
const runtimeCtx = ctx as unknown as { responseHeaders?: Headers };
|
|
107
|
+
try {
|
|
108
|
+
return await runWithEndpointContext(ctx as any, () => handler(ctx));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
attachResponseHeadersToAPIError(runtimeCtx.responseHeaders, e);
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
79
115
|
if (path) {
|
|
80
116
|
return createEndpoint(
|
|
81
117
|
path,
|
|
@@ -83,8 +119,7 @@ export function createAuthEndpoint<
|
|
|
83
119
|
...options,
|
|
84
120
|
use: [...(options?.use || []), ...use],
|
|
85
121
|
},
|
|
86
|
-
|
|
87
|
-
async (ctx) => runWithEndpointContext(ctx as any, () => handler(ctx)),
|
|
122
|
+
wrapped,
|
|
88
123
|
);
|
|
89
124
|
}
|
|
90
125
|
|
|
@@ -93,8 +128,7 @@ export function createAuthEndpoint<
|
|
|
93
128
|
...options,
|
|
94
129
|
use: [...(options?.use || []), ...use],
|
|
95
130
|
},
|
|
96
|
-
|
|
97
|
-
async (ctx) => runWithEndpointContext(ctx as any, () => handler(ctx)),
|
|
131
|
+
wrapped,
|
|
98
132
|
);
|
|
99
133
|
}
|
|
100
134
|
|
package/src/db/get-tables.ts
CHANGED
|
@@ -162,6 +162,8 @@ export const getAuthTables = (
|
|
|
162
162
|
},
|
|
163
163
|
email: {
|
|
164
164
|
type: "string",
|
|
165
|
+
// TODO(#9124): drop required+unique in v2; use a partial unique
|
|
166
|
+
// index where email is not null (see schema/user.ts).
|
|
165
167
|
unique: true,
|
|
166
168
|
required: true,
|
|
167
169
|
fieldName: options.user?.fields?.email || "email",
|
package/src/db/schema/user.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
import { coreSchema } from "./shared";
|
|
8
8
|
|
|
9
9
|
export const userSchema = coreSchema.extend({
|
|
10
|
+
// TODO(#9124): widen to nullish in v2. OAuth providers (Discord phone-only,
|
|
11
|
+
// Apple subsequent sign-ins, etc.) can legitimately omit email; identity
|
|
12
|
+
// must key on (providerId, accountId) per OpenID Connect Core §5.7.
|
|
10
13
|
email: z.string().transform((val) => val.toLowerCase()),
|
|
11
14
|
emailVerified: z.boolean().default(false),
|
|
12
15
|
name: z.string(),
|
|
@@ -1,52 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
type OpenTelemetryAPI = Pick<
|
|
4
|
-
typeof import("@opentelemetry/api"),
|
|
5
|
-
"trace" | "SpanStatusCode"
|
|
6
|
-
>;
|
|
7
|
-
|
|
8
|
-
function createNoopSpan(): Span {
|
|
9
|
-
return {
|
|
10
|
-
end(): void {},
|
|
11
|
-
setAttribute(_key: string, _value: unknown): void {},
|
|
12
|
-
setStatus(_status: unknown): void {},
|
|
13
|
-
recordException(_exception: unknown): void {},
|
|
14
|
-
} as Span;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function createNoopTracer(noopSpanInstance: Span): Tracer {
|
|
18
|
-
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
19
|
-
_name: string,
|
|
20
|
-
_options: { attributes?: Record<string, string | number | boolean> },
|
|
21
|
-
fn: F,
|
|
22
|
-
): ReturnType<F> {
|
|
23
|
-
return fn(noopSpanInstance) as ReturnType<F>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return { startActiveSpan } as Tracer;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function createNoopTraceAPI() {
|
|
30
|
-
const noopTracer = createNoopTracer(createNoopSpan());
|
|
31
|
-
return {
|
|
32
|
-
getTracer(_name?: string, _version?: string) {
|
|
33
|
-
return noopTracer;
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function createNoopOpenTelemetryAPI(): OpenTelemetryAPI {
|
|
39
|
-
return {
|
|
40
|
-
SpanStatusCode: {
|
|
41
|
-
UNSET: 0,
|
|
42
|
-
OK: 1,
|
|
43
|
-
ERROR: 2,
|
|
44
|
-
},
|
|
45
|
-
trace: createNoopTraceAPI(),
|
|
46
|
-
} as OpenTelemetryAPI;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const noopOpenTelemetryAPI = createNoopOpenTelemetryAPI();
|
|
1
|
+
import type { OpenTelemetryAPI } from "./noop";
|
|
2
|
+
import { noopOpenTelemetryAPI } from "./noop";
|
|
50
3
|
|
|
51
4
|
let openTelemetryAPIPromise: Promise<void> | undefined;
|
|
52
5
|
let openTelemetryAPI: OpenTelemetryAPI | undefined;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Span, Tracer } from "@opentelemetry/api";
|
|
2
|
+
|
|
3
|
+
export type OpenTelemetryAPI = Pick<
|
|
4
|
+
typeof import("@opentelemetry/api"),
|
|
5
|
+
"trace" | "SpanStatusCode"
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
function createNoopSpan(): Span {
|
|
9
|
+
const span = {
|
|
10
|
+
end(): void {},
|
|
11
|
+
setAttribute(_key: string, _value: unknown): void {},
|
|
12
|
+
setStatus(_status: unknown): void {},
|
|
13
|
+
recordException(_exception: unknown): void {},
|
|
14
|
+
updateName(_name: string) {
|
|
15
|
+
return span;
|
|
16
|
+
},
|
|
17
|
+
} as unknown as Span;
|
|
18
|
+
return span;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createNoopTracer(noopSpan: Span): Tracer {
|
|
22
|
+
// OpenTelemetry `Tracer.startActiveSpan` has three overloads:
|
|
23
|
+
// (name, fn)
|
|
24
|
+
// (name, options, fn)
|
|
25
|
+
// (name, options, context, fn)
|
|
26
|
+
// The callback is always the last argument; fish it out by arity so a
|
|
27
|
+
// 2-arg call (options omitted) doesn't try to invoke `undefined`.
|
|
28
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
29
|
+
_name: string,
|
|
30
|
+
fn: F,
|
|
31
|
+
): ReturnType<F>;
|
|
32
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
33
|
+
_name: string,
|
|
34
|
+
_options: { attributes?: Record<string, string | number | boolean> },
|
|
35
|
+
fn: F,
|
|
36
|
+
): ReturnType<F>;
|
|
37
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
38
|
+
_name: string,
|
|
39
|
+
_options: { attributes?: Record<string, string | number | boolean> },
|
|
40
|
+
_context: unknown,
|
|
41
|
+
fn: F,
|
|
42
|
+
): ReturnType<F>;
|
|
43
|
+
function startActiveSpan(_name: string, ...rest: Array<unknown>): unknown {
|
|
44
|
+
const fn = rest[rest.length - 1] as (span: Span) => unknown;
|
|
45
|
+
return fn(noopSpan);
|
|
46
|
+
}
|
|
47
|
+
return { startActiveSpan } as Tracer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createNoopTraceAPI() {
|
|
51
|
+
const noopTracer = createNoopTracer(createNoopSpan());
|
|
52
|
+
return {
|
|
53
|
+
getTracer(_name?: string, _version?: string) {
|
|
54
|
+
return noopTracer;
|
|
55
|
+
},
|
|
56
|
+
getActiveSpan(): Span | undefined {
|
|
57
|
+
return undefined;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createNoopOpenTelemetryAPI(): OpenTelemetryAPI {
|
|
63
|
+
return {
|
|
64
|
+
SpanStatusCode: {
|
|
65
|
+
UNSET: 0,
|
|
66
|
+
OK: 1,
|
|
67
|
+
ERROR: 2,
|
|
68
|
+
},
|
|
69
|
+
trace: createNoopTraceAPI(),
|
|
70
|
+
} as OpenTelemetryAPI;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const noopOpenTelemetryAPI: OpenTelemetryAPI =
|
|
74
|
+
createNoopOpenTelemetryAPI();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noop variant of `./instrumentation` for runtimes where the dynamic
|
|
3
|
+
* `import("@opentelemetry/api")` in `./api` throws synchronously instead of
|
|
4
|
+
* rejecting its returned promise. Convex's V8 isolate is the reproducer: bare
|
|
5
|
+
* specifiers are rejected at resolve time in `get-convex/convex-backend`
|
|
6
|
+
* `crates/isolate/src/request_scope.rs`, so the `.catch()` in
|
|
7
|
+
* `getOpenTelemetryAPI` never runs and every `withSpan` call surfaces an
|
|
8
|
+
* uncaught error.
|
|
9
|
+
*
|
|
10
|
+
* Public surface must stay identical to `./index` (enforced by `pure.test.ts`).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export * from "./attributes";
|
|
14
|
+
|
|
15
|
+
export function withSpan<T>(
|
|
16
|
+
name: string,
|
|
17
|
+
attributes: Record<string, string | number | boolean>,
|
|
18
|
+
fn: () => T,
|
|
19
|
+
): T;
|
|
20
|
+
export function withSpan<T>(
|
|
21
|
+
name: string,
|
|
22
|
+
attributes: Record<string, string | number | boolean>,
|
|
23
|
+
fn: () => Promise<T>,
|
|
24
|
+
): Promise<T>;
|
|
25
|
+
export function withSpan<T>(
|
|
26
|
+
_name: string,
|
|
27
|
+
_attributes: Record<string, string | number | boolean>,
|
|
28
|
+
fn: () => T | Promise<T>,
|
|
29
|
+
): T | Promise<T> {
|
|
30
|
+
return fn();
|
|
31
|
+
}
|
package/src/oauth2/index.ts
CHANGED
|
@@ -15,7 +15,11 @@ export {
|
|
|
15
15
|
refreshAccessToken,
|
|
16
16
|
refreshAccessTokenRequest,
|
|
17
17
|
} from "./refresh-access-token";
|
|
18
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
generateCodeChallenge,
|
|
20
|
+
getOAuth2Tokens,
|
|
21
|
+
getPrimaryClientId,
|
|
22
|
+
} from "./utils";
|
|
19
23
|
export {
|
|
20
24
|
authorizationCodeRequest,
|
|
21
25
|
createAuthorizationCodeRequest,
|
package/src/oauth2/utils.ts
CHANGED
|
@@ -28,6 +28,19 @@ export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Return the provider's primary Client ID: the single string, or the entry at
|
|
33
|
+
* array index 0 for the cross-platform form used by ID token audience
|
|
34
|
+
* verification. Index 0 is the designated primary and pairs with
|
|
35
|
+
* `clientSecret` for the authorization code flow; later array entries are
|
|
36
|
+
* only used as additional accepted audiences. Returns `undefined` when the
|
|
37
|
+
* primary value is missing or an empty string.
|
|
38
|
+
*/
|
|
39
|
+
export function getPrimaryClientId(clientId: unknown): string | undefined {
|
|
40
|
+
const value = Array.isArray(clientId) ? clientId[0] : clientId;
|
|
41
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
export async function generateCodeChallenge(codeVerifier: string) {
|
|
32
45
|
const encoder = new TextEncoder();
|
|
33
46
|
const data = encoder.encode(codeVerifier);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
2
|
|
|
3
3
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
4
|
-
import {
|
|
4
|
+
import { logger } from "../env";
|
|
5
|
+
import { APIError, BetterAuthError } from "../error";
|
|
5
6
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
6
7
|
import {
|
|
7
8
|
createAuthorizationURL,
|
|
9
|
+
getPrimaryClientId,
|
|
8
10
|
refreshAccessToken,
|
|
9
11
|
validateAuthorizationCode,
|
|
10
12
|
} from "../oauth2";
|
|
@@ -20,7 +22,7 @@ export interface AppleProfile {
|
|
|
20
22
|
* The email address is either the user's real email address or the proxy
|
|
21
23
|
* address, depending on their status private email relay service.
|
|
22
24
|
*/
|
|
23
|
-
email
|
|
25
|
+
email?: string;
|
|
24
26
|
/**
|
|
25
27
|
* A string or Boolean value that indicates whether the service verifies
|
|
26
28
|
* the email. The value can either be a string ("true" or "false") or a
|
|
@@ -70,7 +72,7 @@ export interface AppleNonConformUser {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
73
|
-
clientId: string;
|
|
75
|
+
clientId: string | string[];
|
|
74
76
|
appBundleIdentifier?: string | undefined;
|
|
75
77
|
audience?: (string | string[]) | undefined;
|
|
76
78
|
}
|
|
@@ -81,6 +83,12 @@ export const apple = (options: AppleOptions) => {
|
|
|
81
83
|
id: "apple",
|
|
82
84
|
name: "Apple",
|
|
83
85
|
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
86
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
87
|
+
logger.error(
|
|
88
|
+
"Client ID and client secret are required for Apple. Make sure to provide them in the options.",
|
|
89
|
+
);
|
|
90
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
91
|
+
}
|
|
84
92
|
const _scope = options.disableDefaultScope ? [] : ["email", "name"];
|
|
85
93
|
if (options.scope) _scope.push(...options.scope);
|
|
86
94
|
if (scopes) _scope.push(...scopes);
|
|
@@ -5,6 +5,7 @@ import { APIError, BetterAuthError } from "../error";
|
|
|
5
5
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
6
6
|
import {
|
|
7
7
|
createAuthorizationURL,
|
|
8
|
+
getPrimaryClientId,
|
|
8
9
|
refreshAccessToken,
|
|
9
10
|
validateAuthorizationCode,
|
|
10
11
|
} from "../oauth2";
|
|
@@ -30,7 +31,7 @@ export interface CognitoProfile {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export interface CognitoOptions extends ProviderOptions<CognitoProfile> {
|
|
33
|
-
clientId: string;
|
|
34
|
+
clientId: string | string[];
|
|
34
35
|
/**
|
|
35
36
|
* The Cognito domain (e.g., "your-app.auth.us-east-1.amazoncognito.com")
|
|
36
37
|
*/
|
|
@@ -60,7 +61,7 @@ export const cognito = (options: CognitoOptions) => {
|
|
|
60
61
|
id: "cognito",
|
|
61
62
|
name: "Cognito",
|
|
62
63
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
63
|
-
if (!options.clientId) {
|
|
64
|
+
if (!getPrimaryClientId(options.clientId)) {
|
|
64
65
|
logger.error(
|
|
65
66
|
"ClientId is required for Amazon Cognito. Make sure to provide them in the options.",
|
|
66
67
|
);
|
|
@@ -41,7 +41,7 @@ export interface DiscordProfile extends Record<string, any> {
|
|
|
41
41
|
/** whether the email on this account has been verified */
|
|
42
42
|
verified: boolean;
|
|
43
43
|
/** the user's email */
|
|
44
|
-
email
|
|
44
|
+
email?: string | null;
|
|
45
45
|
/**
|
|
46
46
|
* the flags on a user's account:
|
|
47
47
|
* https://discord.com/developers/docs/resources/user#user-object-user-flags
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
2
|
import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
|
|
3
|
+
import { logger } from "../env";
|
|
4
|
+
import { BetterAuthError } from "../error";
|
|
3
5
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
4
6
|
import {
|
|
5
7
|
createAuthorizationURL,
|
|
8
|
+
getPrimaryClientId,
|
|
6
9
|
refreshAccessToken,
|
|
7
10
|
validateAuthorizationCode,
|
|
8
11
|
} from "../oauth2";
|
|
9
12
|
export interface FacebookProfile {
|
|
10
13
|
id: string;
|
|
11
14
|
name: string;
|
|
12
|
-
email
|
|
13
|
-
email_verified
|
|
15
|
+
email?: string;
|
|
16
|
+
email_verified?: boolean;
|
|
14
17
|
picture: {
|
|
15
18
|
data: {
|
|
16
19
|
height: number;
|
|
@@ -22,7 +25,7 @@ export interface FacebookProfile {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export interface FacebookOptions extends ProviderOptions<FacebookProfile> {
|
|
25
|
-
clientId: string;
|
|
28
|
+
clientId: string | string[];
|
|
26
29
|
/**
|
|
27
30
|
* Extend list of fields to retrieve from the Facebook user profile.
|
|
28
31
|
*
|
|
@@ -41,6 +44,12 @@ export const facebook = (options: FacebookOptions) => {
|
|
|
41
44
|
id: "facebook",
|
|
42
45
|
name: "Facebook",
|
|
43
46
|
async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
|
|
47
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
48
|
+
logger.error(
|
|
49
|
+
"Client ID and client secret are required for Facebook. Make sure to provide them in the options.",
|
|
50
|
+
);
|
|
51
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
52
|
+
}
|
|
44
53
|
const _scopes = options.disableDefaultScope
|
|
45
54
|
? []
|
|
46
55
|
: ["email", "public_profile"];
|
|
@@ -195,7 +204,7 @@ export const facebook = (options: FacebookOptions) => {
|
|
|
195
204
|
name: profile.name,
|
|
196
205
|
email: profile.email,
|
|
197
206
|
image: profile.picture.data.url,
|
|
198
|
-
emailVerified: profile.email_verified,
|
|
207
|
+
emailVerified: profile.email_verified ?? false,
|
|
199
208
|
...userMap,
|
|
200
209
|
},
|
|
201
210
|
data: profile,
|
|
@@ -5,6 +5,7 @@ import { APIError, BetterAuthError } from "../error";
|
|
|
5
5
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
6
6
|
import {
|
|
7
7
|
createAuthorizationURL,
|
|
8
|
+
getPrimaryClientId,
|
|
8
9
|
refreshAccessToken,
|
|
9
10
|
validateAuthorizationCode,
|
|
10
11
|
} from "../oauth2";
|
|
@@ -37,7 +38,7 @@ export interface GoogleProfile {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export interface GoogleOptions extends ProviderOptions<GoogleProfile> {
|
|
40
|
-
clientId: string;
|
|
41
|
+
clientId: string | string[];
|
|
41
42
|
/**
|
|
42
43
|
* The access type to use for the authorization code request
|
|
43
44
|
*/
|
|
@@ -64,7 +65,7 @@ export const google = (options: GoogleOptions) => {
|
|
|
64
65
|
loginHint,
|
|
65
66
|
display,
|
|
66
67
|
}) {
|
|
67
|
-
if (!options.clientId || !options.clientSecret) {
|
|
68
|
+
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
68
69
|
logger.error(
|
|
69
70
|
"Client Id and Client Secret is required for Google. Make sure to provide them in the options.",
|
|
70
71
|
);
|
|
@@ -16,8 +16,8 @@ export interface LinkedInProfile {
|
|
|
16
16
|
country: string;
|
|
17
17
|
language: string;
|
|
18
18
|
};
|
|
19
|
-
email
|
|
20
|
-
email_verified
|
|
19
|
+
email?: string;
|
|
20
|
+
email_verified?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
|
|
@@ -98,7 +98,7 @@ export const linkedin = (options: LinkedInOptions) => {
|
|
|
98
98
|
id: profile.sub,
|
|
99
99
|
name: profile.name,
|
|
100
100
|
email: profile.email,
|
|
101
|
-
emailVerified: profile.email_verified
|
|
101
|
+
emailVerified: profile.email_verified ?? false,
|
|
102
102
|
image: profile.picture,
|
|
103
103
|
...userMap,
|
|
104
104
|
},
|
|
@@ -2,10 +2,11 @@ import { base64 } from "@better-auth/utils/base64";
|
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
3
|
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
4
4
|
import { logger } from "../env";
|
|
5
|
-
import { APIError } from "../error";
|
|
5
|
+
import { APIError, BetterAuthError } from "../error";
|
|
6
6
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
7
7
|
import {
|
|
8
8
|
createAuthorizationURL,
|
|
9
|
+
getPrimaryClientId,
|
|
9
10
|
refreshAccessToken,
|
|
10
11
|
validateAuthorizationCode,
|
|
11
12
|
} from "../oauth2";
|
|
@@ -35,7 +36,7 @@ export interface MicrosoftEntraIDProfile extends Record<string, any> {
|
|
|
35
36
|
/** The primary username that represents the user */
|
|
36
37
|
preferred_username: string;
|
|
37
38
|
/** User's email address */
|
|
38
|
-
email
|
|
39
|
+
email?: string;
|
|
39
40
|
/** Human-readable value that identifies the subject of the token */
|
|
40
41
|
name: string;
|
|
41
42
|
/** Matches the parameter included in the original authorize request */
|
|
@@ -116,7 +117,7 @@ export interface MicrosoftEntraIDProfile extends Record<string, any> {
|
|
|
116
117
|
|
|
117
118
|
export interface MicrosoftOptions
|
|
118
119
|
extends ProviderOptions<MicrosoftEntraIDProfile> {
|
|
119
|
-
clientId: string;
|
|
120
|
+
clientId: string | string[];
|
|
120
121
|
/**
|
|
121
122
|
* The tenant ID of the Microsoft account
|
|
122
123
|
* @default "common"
|
|
@@ -149,6 +150,15 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
149
150
|
id: "microsoft",
|
|
150
151
|
name: "Microsoft EntraID",
|
|
151
152
|
createAuthorizationURL(data) {
|
|
153
|
+
// Microsoft Entra supports public clients (SPA / native apps with
|
|
154
|
+
// PKCE only), so clientSecret is intentionally not required here.
|
|
155
|
+
// See https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
156
|
+
if (!getPrimaryClientId(options.clientId)) {
|
|
157
|
+
logger.error(
|
|
158
|
+
"Client Id is required for Microsoft Entra ID. Make sure to provide it in the options.",
|
|
159
|
+
);
|
|
160
|
+
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
161
|
+
}
|
|
152
162
|
const scopes = options.disableDefaultScope
|
|
153
163
|
? []
|
|
154
164
|
: ["openid", "profile", "email", "User.Read", "offline_access"];
|
|
@@ -190,7 +200,7 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
190
200
|
const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
|
|
191
201
|
const verifyOptions: {
|
|
192
202
|
algorithms: [string];
|
|
193
|
-
audience: string;
|
|
203
|
+
audience: string | string[];
|
|
194
204
|
maxTokenAge: string;
|
|
195
205
|
issuer?: string;
|
|
196
206
|
} = {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { APIError as BaseAPIError } from "better-call";
|
|
2
|
+
import { APIError } from "../error";
|
|
3
|
+
|
|
4
|
+
export function isAPIError(error: unknown): error is APIError {
|
|
5
|
+
return (
|
|
6
|
+
error instanceof BaseAPIError ||
|
|
7
|
+
error instanceof APIError ||
|
|
8
|
+
(error as { name?: string })?.name === "APIError"
|
|
9
|
+
);
|
|
10
|
+
}
|