@better-auth/core 1.4.17 → 1.4.19
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/.turbo/turbo-build.log +253 -176
- package/dist/api/index.d.mts +11 -5
- package/dist/api/index.mjs +2 -1
- package/dist/api/index.mjs.map +1 -0
- package/dist/async_hooks/index.d.mts +2 -1
- package/dist/async_hooks/index.mjs +2 -1
- package/dist/async_hooks/index.mjs.map +1 -0
- package/dist/async_hooks/pure.index.d.mts +2 -1
- package/dist/async_hooks/pure.index.mjs +2 -1
- package/dist/async_hooks/pure.index.mjs.map +1 -0
- package/dist/context/endpoint-context.d.mts +2 -1
- package/dist/context/endpoint-context.mjs +2 -1
- package/dist/context/endpoint-context.mjs.map +1 -0
- package/dist/context/global.d.mts +2 -1
- package/dist/context/global.mjs +3 -2
- package/dist/context/global.mjs.map +1 -0
- package/dist/context/request-state.d.mts +2 -1
- package/dist/context/request-state.mjs +2 -1
- package/dist/context/request-state.mjs.map +1 -0
- package/dist/context/transaction.d.mts +2 -1
- package/dist/context/transaction.mjs +2 -1
- package/dist/context/transaction.mjs.map +1 -0
- package/dist/db/adapter/factory.d.mts +2 -1
- package/dist/db/adapter/factory.mjs +9 -27
- package/dist/db/adapter/factory.mjs.map +1 -0
- package/dist/db/adapter/get-default-field-name.d.mts +2 -1
- package/dist/db/adapter/get-default-field-name.mjs +2 -1
- package/dist/db/adapter/get-default-field-name.mjs.map +1 -0
- package/dist/db/adapter/get-default-model-name.d.mts +2 -1
- package/dist/db/adapter/get-default-model-name.mjs +2 -1
- package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
- package/dist/db/adapter/get-field-attributes.d.mts +2 -1
- package/dist/db/adapter/get-field-attributes.mjs +2 -1
- package/dist/db/adapter/get-field-attributes.mjs.map +1 -0
- package/dist/db/adapter/get-field-name.d.mts +2 -1
- package/dist/db/adapter/get-field-name.mjs +2 -1
- package/dist/db/adapter/get-field-name.mjs.map +1 -0
- package/dist/db/adapter/get-id-field.d.mts +2 -1
- package/dist/db/adapter/get-id-field.mjs +2 -1
- package/dist/db/adapter/get-id-field.mjs.map +1 -0
- package/dist/db/adapter/get-model-name.d.mts +2 -1
- package/dist/db/adapter/get-model-name.mjs +2 -1
- package/dist/db/adapter/get-model-name.mjs.map +1 -0
- package/dist/db/adapter/index.d.mts +5 -1
- package/dist/db/adapter/types.d.mts +2 -1
- package/dist/db/adapter/utils.d.mts +2 -1
- package/dist/db/adapter/utils.mjs +2 -1
- package/dist/db/adapter/utils.mjs.map +1 -0
- package/dist/db/get-tables.d.mts +2 -1
- package/dist/db/get-tables.mjs +8 -2
- package/dist/db/get-tables.mjs.map +1 -0
- package/dist/db/plugin.d.mts +2 -1
- package/dist/db/schema/account.d.mts +2 -1
- package/dist/db/schema/account.mjs +2 -1
- package/dist/db/schema/account.mjs.map +1 -0
- package/dist/db/schema/rate-limit.d.mts +2 -1
- package/dist/db/schema/rate-limit.mjs +2 -1
- package/dist/db/schema/rate-limit.mjs.map +1 -0
- package/dist/db/schema/session.d.mts +2 -1
- package/dist/db/schema/session.mjs +2 -1
- package/dist/db/schema/session.mjs.map +1 -0
- package/dist/db/schema/shared.d.mts +2 -1
- package/dist/db/schema/shared.mjs +2 -1
- package/dist/db/schema/shared.mjs.map +1 -0
- package/dist/db/schema/user.d.mts +2 -1
- package/dist/db/schema/user.mjs +2 -1
- package/dist/db/schema/user.mjs.map +1 -0
- package/dist/db/schema/verification.d.mts +2 -1
- package/dist/db/schema/verification.mjs +2 -1
- package/dist/db/schema/verification.mjs.map +1 -0
- package/dist/db/type.d.mts +2 -1
- package/dist/env/color-depth.d.mts +2 -1
- package/dist/env/color-depth.mjs +2 -1
- package/dist/env/color-depth.mjs.map +1 -0
- package/dist/env/env-impl.d.mts +3 -2
- package/dist/env/env-impl.mjs +3 -2
- package/dist/env/env-impl.mjs.map +1 -0
- package/dist/env/logger.d.mts +2 -1
- package/dist/env/logger.mjs +3 -2
- package/dist/env/logger.mjs.map +1 -0
- package/dist/error/codes.d.mts +2 -1
- package/dist/error/codes.mjs +2 -1
- package/dist/error/codes.mjs.map +1 -0
- package/dist/error/index.d.mts +2 -1
- package/dist/error/index.mjs +2 -1
- package/dist/error/index.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/oauth2/client-credentials-token.d.mts +2 -1
- package/dist/oauth2/client-credentials-token.mjs +2 -1
- package/dist/oauth2/client-credentials-token.mjs.map +1 -0
- package/dist/oauth2/create-authorization-url.d.mts +2 -1
- package/dist/oauth2/create-authorization-url.mjs +2 -1
- package/dist/oauth2/create-authorization-url.mjs.map +1 -0
- package/dist/oauth2/oauth-provider.d.mts +3 -2
- package/dist/oauth2/refresh-access-token.d.mts +2 -1
- package/dist/oauth2/refresh-access-token.mjs +2 -1
- package/dist/oauth2/refresh-access-token.mjs.map +1 -0
- package/dist/oauth2/utils.d.mts +2 -1
- package/dist/oauth2/utils.mjs +2 -1
- package/dist/oauth2/utils.mjs.map +1 -0
- package/dist/oauth2/validate-authorization-code.d.mts +6 -2
- package/dist/oauth2/validate-authorization-code.mjs +7 -12
- package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
- package/dist/oauth2/verify.d.mts +2 -1
- package/dist/oauth2/verify.mjs +2 -1
- package/dist/oauth2/verify.mjs.map +1 -0
- package/dist/social-providers/apple.d.mts +2 -1
- package/dist/social-providers/apple.mjs +22 -15
- package/dist/social-providers/apple.mjs.map +1 -0
- package/dist/social-providers/atlassian.d.mts +2 -1
- package/dist/social-providers/atlassian.mjs +2 -1
- package/dist/social-providers/atlassian.mjs.map +1 -0
- package/dist/social-providers/cognito.d.mts +2 -1
- package/dist/social-providers/cognito.mjs +2 -1
- package/dist/social-providers/cognito.mjs.map +1 -0
- package/dist/social-providers/discord.d.mts +2 -1
- package/dist/social-providers/discord.mjs +2 -1
- package/dist/social-providers/discord.mjs.map +1 -0
- package/dist/social-providers/dropbox.d.mts +2 -1
- package/dist/social-providers/dropbox.mjs +2 -1
- package/dist/social-providers/dropbox.mjs.map +1 -0
- package/dist/social-providers/facebook.d.mts +2 -1
- package/dist/social-providers/facebook.mjs +5 -4
- package/dist/social-providers/facebook.mjs.map +1 -0
- package/dist/social-providers/figma.d.mts +2 -1
- package/dist/social-providers/figma.mjs +2 -1
- package/dist/social-providers/figma.mjs.map +1 -0
- package/dist/social-providers/github.d.mts +3 -2
- package/dist/social-providers/github.mjs +22 -5
- package/dist/social-providers/github.mjs.map +1 -0
- package/dist/social-providers/gitlab.d.mts +2 -1
- package/dist/social-providers/gitlab.mjs +2 -1
- package/dist/social-providers/gitlab.mjs.map +1 -0
- package/dist/social-providers/google.d.mts +2 -1
- package/dist/social-providers/google.mjs +18 -13
- package/dist/social-providers/google.mjs.map +1 -0
- package/dist/social-providers/huggingface.d.mts +2 -1
- package/dist/social-providers/huggingface.mjs +2 -1
- package/dist/social-providers/huggingface.mjs.map +1 -0
- package/dist/social-providers/index.d.mts +5 -3
- package/dist/social-providers/index.mjs +3 -2
- package/dist/social-providers/index.mjs.map +1 -0
- package/dist/social-providers/kakao.d.mts +2 -1
- package/dist/social-providers/kakao.mjs +2 -1
- package/dist/social-providers/kakao.mjs.map +1 -0
- package/dist/social-providers/kick.d.mts +2 -1
- package/dist/social-providers/kick.mjs +2 -1
- package/dist/social-providers/kick.mjs.map +1 -0
- package/dist/social-providers/line.d.mts +2 -1
- package/dist/social-providers/line.mjs +2 -1
- package/dist/social-providers/line.mjs.map +1 -0
- package/dist/social-providers/linear.d.mts +2 -1
- package/dist/social-providers/linear.mjs +2 -1
- package/dist/social-providers/linear.mjs.map +1 -0
- package/dist/social-providers/linkedin.d.mts +2 -1
- package/dist/social-providers/linkedin.mjs +2 -1
- package/dist/social-providers/linkedin.mjs.map +1 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +4 -1
- package/dist/social-providers/microsoft-entra-id.mjs +36 -2
- package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
- package/dist/social-providers/naver.d.mts +2 -1
- package/dist/social-providers/naver.mjs +2 -1
- package/dist/social-providers/naver.mjs.map +1 -0
- package/dist/social-providers/notion.d.mts +2 -1
- package/dist/social-providers/notion.mjs +2 -1
- package/dist/social-providers/notion.mjs.map +1 -0
- package/dist/social-providers/paybin.d.mts +2 -1
- package/dist/social-providers/paybin.mjs +2 -1
- package/dist/social-providers/paybin.mjs.map +1 -0
- package/dist/social-providers/paypal.d.mts +2 -1
- package/dist/social-providers/paypal.mjs +2 -1
- package/dist/social-providers/paypal.mjs.map +1 -0
- package/dist/social-providers/polar.d.mts +2 -1
- package/dist/social-providers/polar.mjs +2 -1
- package/dist/social-providers/polar.mjs.map +1 -0
- package/dist/social-providers/reddit.d.mts +2 -1
- package/dist/social-providers/reddit.mjs +2 -1
- package/dist/social-providers/reddit.mjs.map +1 -0
- package/dist/social-providers/roblox.d.mts +2 -1
- package/dist/social-providers/roblox.mjs +2 -1
- package/dist/social-providers/roblox.mjs.map +1 -0
- package/dist/social-providers/salesforce.d.mts +2 -1
- package/dist/social-providers/salesforce.mjs +2 -1
- package/dist/social-providers/salesforce.mjs.map +1 -0
- package/dist/social-providers/slack.d.mts +2 -1
- package/dist/social-providers/slack.mjs +2 -1
- package/dist/social-providers/slack.mjs.map +1 -0
- package/dist/social-providers/spotify.d.mts +2 -1
- package/dist/social-providers/spotify.mjs +2 -1
- package/dist/social-providers/spotify.mjs.map +1 -0
- package/dist/social-providers/tiktok.d.mts +2 -1
- package/dist/social-providers/tiktok.mjs +2 -1
- package/dist/social-providers/tiktok.mjs.map +1 -0
- package/dist/social-providers/twitch.d.mts +2 -1
- package/dist/social-providers/twitch.mjs +2 -1
- package/dist/social-providers/twitch.mjs.map +1 -0
- package/dist/social-providers/twitter.d.mts +3 -2
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/twitter.mjs.map +1 -0
- package/dist/social-providers/vercel.d.mts +2 -1
- package/dist/social-providers/vercel.mjs +2 -1
- package/dist/social-providers/vercel.mjs.map +1 -0
- package/dist/social-providers/vk.d.mts +2 -1
- package/dist/social-providers/vk.mjs +2 -1
- package/dist/social-providers/vk.mjs.map +1 -0
- package/dist/social-providers/zoom.d.mts +3 -3
- package/dist/social-providers/zoom.mjs +2 -1
- package/dist/social-providers/zoom.mjs.map +1 -0
- package/dist/types/context.d.mts +7 -3
- package/dist/types/cookie.d.mts +2 -1
- package/dist/types/helper.d.mts +2 -1
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +36 -33
- package/dist/types/plugin-client.d.mts +2 -1
- package/dist/types/plugin.d.mts +2 -1
- package/dist/utils/db.d.mts +13 -0
- package/dist/utils/db.mjs +17 -0
- package/dist/utils/db.mjs.map +1 -0
- package/dist/utils/deprecate.d.mts +2 -1
- package/dist/utils/deprecate.mjs +2 -1
- package/dist/utils/deprecate.mjs.map +1 -0
- package/dist/utils/error-codes.d.mts +2 -1
- package/dist/utils/error-codes.mjs +2 -1
- package/dist/utils/error-codes.mjs.map +1 -0
- package/dist/utils/id.d.mts +2 -1
- package/dist/utils/id.mjs +2 -1
- package/dist/utils/id.mjs.map +1 -0
- package/dist/utils/index.d.mts +2 -1
- package/dist/utils/index.mjs +2 -1
- package/dist/utils/ip.d.mts +2 -1
- package/dist/utils/ip.mjs +2 -1
- package/dist/utils/ip.mjs.map +1 -0
- package/dist/utils/json.d.mts +2 -1
- package/dist/utils/json.mjs +2 -1
- package/dist/utils/json.mjs.map +1 -0
- package/dist/utils/string.d.mts +2 -1
- package/dist/utils/string.mjs +2 -1
- package/dist/utils/string.mjs.map +1 -0
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +2 -1
- package/dist/utils/url.mjs.map +1 -0
- package/package.json +1 -1
- package/src/db/adapter/factory.ts +5 -41
- package/src/db/adapter/index.ts +3 -0
- package/src/db/get-tables.ts +5 -0
- package/src/env/env-impl.ts +2 -2
- package/src/env/logger.ts +1 -1
- package/src/oauth2/oauth-provider.ts +1 -1
- package/src/oauth2/validate-authorization-code.ts +13 -26
- package/src/oauth2/validate-token.test.ts +241 -0
- package/src/social-providers/apple.ts +37 -24
- package/src/social-providers/facebook.ts +3 -3
- package/src/social-providers/github.ts +25 -3
- package/src/social-providers/google.ts +18 -14
- package/src/social-providers/microsoft-entra-id.ts +84 -1
- package/src/types/context.ts +6 -3
- package/src/types/index.ts +6 -1
- package/src/types/init-options.ts +44 -36
- package/src/utils/db.ts +20 -0
- package/src/utils/index.ts +1 -0
- package/tsdown.config.ts +3 -0
|
@@ -111,30 +111,34 @@ export const apple = (options: AppleOptions) => {
|
|
|
111
111
|
if (options.verifyIdToken) {
|
|
112
112
|
return options.verifyIdToken(token, nonce);
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
jwtClaims[field]
|
|
114
|
+
try {
|
|
115
|
+
const decodedHeader = decodeProtectedHeader(token);
|
|
116
|
+
const { kid, alg: jwtAlg } = decodedHeader;
|
|
117
|
+
if (!kid || !jwtAlg) return false;
|
|
118
|
+
const publicKey = await getApplePublicKey(kid);
|
|
119
|
+
const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
|
|
120
|
+
algorithms: [jwtAlg],
|
|
121
|
+
issuer: "https://appleid.apple.com",
|
|
122
|
+
audience:
|
|
123
|
+
options.audience && options.audience.length
|
|
124
|
+
? options.audience
|
|
125
|
+
: options.appBundleIdentifier
|
|
126
|
+
? options.appBundleIdentifier
|
|
127
|
+
: options.clientId,
|
|
128
|
+
maxTokenAge: "1h",
|
|
129
|
+
});
|
|
130
|
+
["email_verified", "is_private_email"].forEach((field) => {
|
|
131
|
+
if (jwtClaims[field] !== undefined) {
|
|
132
|
+
jwtClaims[field] = Boolean(jwtClaims[field]);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (nonce && jwtClaims.nonce !== nonce) {
|
|
136
|
+
return false;
|
|
132
137
|
}
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
return !!jwtClaims;
|
|
139
|
+
} catch {
|
|
135
140
|
return false;
|
|
136
141
|
}
|
|
137
|
-
return !!jwtClaims;
|
|
138
142
|
},
|
|
139
143
|
refreshAccessToken: options.refreshAccessToken
|
|
140
144
|
? options.refreshAccessToken
|
|
@@ -160,9 +164,18 @@ export const apple = (options: AppleOptions) => {
|
|
|
160
164
|
if (!profile) {
|
|
161
165
|
return null;
|
|
162
166
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
|
|
168
|
+
// TODO: " " masking will be removed when the name field is made optional
|
|
169
|
+
let name: string;
|
|
170
|
+
if (token.user?.name) {
|
|
171
|
+
const firstName = token.user.name.firstName || "";
|
|
172
|
+
const lastName = token.user.name.lastName || "";
|
|
173
|
+
const fullName = `${firstName} ${lastName}`.trim();
|
|
174
|
+
name = fullName || " ";
|
|
175
|
+
} else {
|
|
176
|
+
name = profile.name || " ";
|
|
177
|
+
}
|
|
178
|
+
|
|
166
179
|
const emailVerified =
|
|
167
180
|
typeof profile.email_verified === "boolean"
|
|
168
181
|
? profile.email_verified
|
|
@@ -49,7 +49,7 @@ export const facebook = (options: FacebookOptions) => {
|
|
|
49
49
|
return await createAuthorizationURL({
|
|
50
50
|
id: "facebook",
|
|
51
51
|
options,
|
|
52
|
-
authorizationEndpoint: "https://www.facebook.com/
|
|
52
|
+
authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
|
|
53
53
|
scopes: _scopes,
|
|
54
54
|
state,
|
|
55
55
|
redirectURI,
|
|
@@ -66,7 +66,7 @@ export const facebook = (options: FacebookOptions) => {
|
|
|
66
66
|
code,
|
|
67
67
|
redirectURI,
|
|
68
68
|
options,
|
|
69
|
-
tokenEndpoint: "https://graph.facebook.com/oauth/access_token",
|
|
69
|
+
tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token",
|
|
70
70
|
});
|
|
71
71
|
},
|
|
72
72
|
async verifyIdToken(token, nonce) {
|
|
@@ -121,7 +121,7 @@ export const facebook = (options: FacebookOptions) => {
|
|
|
121
121
|
clientSecret: options.clientSecret,
|
|
122
122
|
},
|
|
123
123
|
tokenEndpoint:
|
|
124
|
-
"https://graph.facebook.com/
|
|
124
|
+
"https://graph.facebook.com/v24.0/oauth/access_token",
|
|
125
125
|
});
|
|
126
126
|
},
|
|
127
127
|
async getUserInfo(token) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
+
import { logger } from "../env";
|
|
2
3
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
3
4
|
import {
|
|
4
5
|
createAuthorizationURL,
|
|
6
|
+
getOAuth2Tokens,
|
|
5
7
|
refreshAccessToken,
|
|
6
|
-
validateAuthorizationCode,
|
|
7
8
|
} from "../oauth2";
|
|
9
|
+
import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code";
|
|
8
10
|
|
|
9
11
|
export interface GithubProfile {
|
|
10
12
|
login: string;
|
|
@@ -86,13 +88,33 @@ export const github = (options: GithubOptions) => {
|
|
|
86
88
|
});
|
|
87
89
|
},
|
|
88
90
|
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
89
|
-
|
|
91
|
+
const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
|
|
90
92
|
code,
|
|
91
93
|
codeVerifier,
|
|
92
94
|
redirectURI,
|
|
93
95
|
options,
|
|
94
|
-
tokenEndpoint,
|
|
95
96
|
});
|
|
97
|
+
|
|
98
|
+
const { data, error } = await betterFetch<
|
|
99
|
+
| { access_token: string; token_type: string; scope: string }
|
|
100
|
+
| { error: string; error_description?: string; error_uri?: string }
|
|
101
|
+
>(tokenEndpoint, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: body,
|
|
104
|
+
headers: requestHeaders,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (error) {
|
|
108
|
+
logger.error("GitHub OAuth token exchange failed:", error);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ("error" in data) {
|
|
113
|
+
logger.error("GitHub OAuth token exchange failed:", data);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return getOAuth2Tokens(data);
|
|
96
118
|
},
|
|
97
119
|
refreshAccessToken: options.refreshAccessToken
|
|
98
120
|
? options.refreshAccessToken
|
|
@@ -82,7 +82,7 @@ export const google = (options: GoogleOptions) => {
|
|
|
82
82
|
const url = await createAuthorizationURL({
|
|
83
83
|
id: "google",
|
|
84
84
|
options,
|
|
85
|
-
authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
|
|
85
|
+
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
86
86
|
scopes: _scopes,
|
|
87
87
|
state,
|
|
88
88
|
codeVerifier,
|
|
@@ -117,7 +117,7 @@ export const google = (options: GoogleOptions) => {
|
|
|
117
117
|
clientKey: options.clientKey,
|
|
118
118
|
clientSecret: options.clientSecret,
|
|
119
119
|
},
|
|
120
|
-
tokenEndpoint: "https://
|
|
120
|
+
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
|
121
121
|
});
|
|
122
122
|
},
|
|
123
123
|
async verifyIdToken(token, nonce) {
|
|
@@ -131,22 +131,26 @@ export const google = (options: GoogleOptions) => {
|
|
|
131
131
|
// Verify JWT integrity
|
|
132
132
|
// See https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
try {
|
|
135
|
+
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
136
|
+
if (!kid || !jwtAlg) return false;
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
const publicKey = await getGooglePublicKey(kid);
|
|
139
|
+
const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
|
|
140
|
+
algorithms: [jwtAlg],
|
|
141
|
+
issuer: ["https://accounts.google.com", "accounts.google.com"],
|
|
142
|
+
audience: options.clientId,
|
|
143
|
+
maxTokenAge: "1h",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (nonce && jwtClaims.nonce !== nonce) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
146
152
|
return false;
|
|
147
153
|
}
|
|
148
|
-
|
|
149
|
-
return true;
|
|
150
154
|
},
|
|
151
155
|
async getUserInfo(token) {
|
|
152
156
|
if (options.getUserInfo) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
-
import {
|
|
3
|
+
import { APIError } from "better-call";
|
|
4
|
+
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
4
5
|
import { logger } from "../env";
|
|
5
6
|
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
|
6
7
|
import {
|
|
@@ -174,6 +175,56 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
174
175
|
tokenEndpoint,
|
|
175
176
|
});
|
|
176
177
|
},
|
|
178
|
+
async verifyIdToken(token, nonce) {
|
|
179
|
+
if (options.disableIdTokenSignIn) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (options.verifyIdToken) {
|
|
183
|
+
return options.verifyIdToken(token, nonce);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
188
|
+
if (!kid || !jwtAlg) return false;
|
|
189
|
+
|
|
190
|
+
const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
|
|
191
|
+
const verifyOptions: {
|
|
192
|
+
algorithms: [string];
|
|
193
|
+
audience: string;
|
|
194
|
+
maxTokenAge: string;
|
|
195
|
+
issuer?: string;
|
|
196
|
+
} = {
|
|
197
|
+
algorithms: [jwtAlg],
|
|
198
|
+
audience: options.clientId,
|
|
199
|
+
maxTokenAge: "1h",
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
|
|
203
|
+
* @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
|
|
204
|
+
*/
|
|
205
|
+
if (
|
|
206
|
+
tenant !== "common" &&
|
|
207
|
+
tenant !== "organizations" &&
|
|
208
|
+
tenant !== "consumers"
|
|
209
|
+
) {
|
|
210
|
+
verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
|
|
211
|
+
}
|
|
212
|
+
const { payload: jwtClaims } = await jwtVerify(
|
|
213
|
+
token,
|
|
214
|
+
publicKey,
|
|
215
|
+
verifyOptions,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (nonce && jwtClaims.nonce !== nonce) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return true;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
logger.error("Failed to verify ID token:", error);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
},
|
|
177
228
|
async getUserInfo(token) {
|
|
178
229
|
if (options.getUserInfo) {
|
|
179
230
|
return options.getUserInfo(token);
|
|
@@ -257,3 +308,35 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
257
308
|
options,
|
|
258
309
|
} satisfies OAuthProvider;
|
|
259
310
|
};
|
|
311
|
+
|
|
312
|
+
export const getMicrosoftPublicKey = async (
|
|
313
|
+
kid: string,
|
|
314
|
+
tenant: string,
|
|
315
|
+
authority: string,
|
|
316
|
+
) => {
|
|
317
|
+
const { data } = await betterFetch<{
|
|
318
|
+
keys: Array<{
|
|
319
|
+
kid: string;
|
|
320
|
+
alg: string;
|
|
321
|
+
kty: string;
|
|
322
|
+
use: string;
|
|
323
|
+
n: string;
|
|
324
|
+
e: string;
|
|
325
|
+
x5c?: string[];
|
|
326
|
+
x5t?: string;
|
|
327
|
+
}>;
|
|
328
|
+
}>(`${authority}/${tenant}/discovery/v2.0/keys`);
|
|
329
|
+
|
|
330
|
+
if (!data?.keys) {
|
|
331
|
+
throw new APIError("BAD_REQUEST", {
|
|
332
|
+
message: "Keys not found",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const jwk = data.keys.find((key) => key.kid === kid);
|
|
337
|
+
if (!jwk) {
|
|
338
|
+
throw new Error(`JWK with kid ${kid} not found`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return await importJWK(jwk, jwk.alg);
|
|
342
|
+
};
|
package/src/types/context.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { DBAdapter, Where } from "../db/adapter";
|
|
|
12
12
|
import type { createLogger } from "../env";
|
|
13
13
|
import type { OAuthProvider } from "../oauth2";
|
|
14
14
|
import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
|
|
15
|
+
import type { Awaitable } from "./helper";
|
|
15
16
|
import type {
|
|
16
17
|
BetterAuthOptions,
|
|
17
18
|
BetterAuthRateLimitOptions,
|
|
@@ -177,6 +178,8 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
|
|
|
177
178
|
PluginContext &
|
|
178
179
|
InfoContext & {
|
|
179
180
|
options: Options;
|
|
181
|
+
appName: string;
|
|
182
|
+
baseURL: string;
|
|
180
183
|
trustedOrigins: string[];
|
|
181
184
|
/**
|
|
182
185
|
* Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
|
|
@@ -299,7 +302,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
|
|
|
299
302
|
* This is inferred from the `options.advanced?.backgroundTasks?.handler` option.
|
|
300
303
|
* Defaults to a no-op that just runs the promise.
|
|
301
304
|
*/
|
|
302
|
-
runInBackground: (promise: Promise<
|
|
305
|
+
runInBackground: (promise: Promise<unknown>) => void;
|
|
303
306
|
/**
|
|
304
307
|
* Runs a task in the background if `runInBackground` is configured,
|
|
305
308
|
* otherwise awaits the task directly.
|
|
@@ -309,6 +312,6 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
|
|
|
309
312
|
* mitigation), but still ensure the operation completes.
|
|
310
313
|
*/
|
|
311
314
|
runInBackgroundOrAwait: (
|
|
312
|
-
promise: Promise<unknown> |
|
|
313
|
-
) =>
|
|
315
|
+
promise: Promise<unknown> | void,
|
|
316
|
+
) => Awaitable<unknown>;
|
|
314
317
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -6,12 +6,17 @@ export type {
|
|
|
6
6
|
InternalAdapter,
|
|
7
7
|
PluginContext,
|
|
8
8
|
} from "./context";
|
|
9
|
-
export type {
|
|
9
|
+
export type {
|
|
10
|
+
BetterAuthCookie,
|
|
11
|
+
BetterAuthCookies,
|
|
12
|
+
} from "./cookie";
|
|
10
13
|
export type * from "./helper";
|
|
11
14
|
export type {
|
|
12
15
|
BetterAuthAdvancedOptions,
|
|
13
16
|
BetterAuthOptions,
|
|
14
17
|
BetterAuthRateLimitOptions,
|
|
18
|
+
BetterAuthRateLimitRule,
|
|
19
|
+
BetterAuthRateLimitStorage,
|
|
15
20
|
GenerateIdFn,
|
|
16
21
|
} from "./init-options";
|
|
17
22
|
export type { BetterAuthPlugin, HookEndpointContext } from "./plugin";
|
|
@@ -37,25 +37,37 @@ export type GenerateIdFn = (options: {
|
|
|
37
37
|
size?: number | undefined;
|
|
38
38
|
}) => string | false;
|
|
39
39
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
export interface BetterAuthRateLimitStorage {
|
|
41
|
+
get: (key: string) => Promise<RateLimit | null | undefined>;
|
|
42
|
+
set: (
|
|
43
|
+
key: string,
|
|
44
|
+
value: RateLimit,
|
|
45
|
+
update?: boolean | undefined,
|
|
46
|
+
) => Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type BetterAuthRateLimitRule = {
|
|
46
50
|
/**
|
|
47
51
|
* Default window to use for rate limiting. The value
|
|
48
52
|
* should be in seconds.
|
|
49
53
|
*
|
|
50
54
|
* @default 10 seconds
|
|
51
55
|
*/
|
|
52
|
-
window
|
|
56
|
+
window: number;
|
|
53
57
|
/**
|
|
54
58
|
* The default maximum number of requests allowed within the window.
|
|
55
59
|
*
|
|
56
60
|
* @default 100 requests
|
|
57
61
|
*/
|
|
58
|
-
max
|
|
62
|
+
max: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type BetterAuthRateLimitOptions = Optional<BetterAuthRateLimitRule> & {
|
|
66
|
+
/**
|
|
67
|
+
* By default, rate limiting is only
|
|
68
|
+
* enabled on production.
|
|
69
|
+
*/
|
|
70
|
+
enabled?: boolean | undefined;
|
|
59
71
|
/**
|
|
60
72
|
* Custom rate limit rules to apply to
|
|
61
73
|
* specific paths.
|
|
@@ -63,27 +75,12 @@ export type BetterAuthRateLimitOptions = {
|
|
|
63
75
|
customRules?:
|
|
64
76
|
| {
|
|
65
77
|
[key: string]:
|
|
66
|
-
|
|
|
67
|
-
/**
|
|
68
|
-
* The window to use for the custom rule.
|
|
69
|
-
*/
|
|
70
|
-
window: number;
|
|
71
|
-
/**
|
|
72
|
-
* The maximum number of requests allowed within the window.
|
|
73
|
-
*/
|
|
74
|
-
max: number;
|
|
75
|
-
}
|
|
78
|
+
| BetterAuthRateLimitRule
|
|
76
79
|
| false
|
|
77
|
-
| ((
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
| {
|
|
82
|
-
window: number;
|
|
83
|
-
max: number;
|
|
84
|
-
}
|
|
85
|
-
| false
|
|
86
|
-
>);
|
|
80
|
+
| ((
|
|
81
|
+
request: Request,
|
|
82
|
+
currentRule: BetterAuthRateLimitRule,
|
|
83
|
+
) => Awaitable<false | BetterAuthRateLimitRule>);
|
|
87
84
|
}
|
|
88
85
|
| undefined;
|
|
89
86
|
/**
|
|
@@ -113,10 +110,7 @@ export type BetterAuthRateLimitOptions = {
|
|
|
113
110
|
* NOTE: If custom storage is used storage
|
|
114
111
|
* is ignored
|
|
115
112
|
*/
|
|
116
|
-
customStorage?:
|
|
117
|
-
get: (key: string) => Promise<RateLimit | undefined>;
|
|
118
|
-
set: (key: string, value: RateLimit) => Promise<void>;
|
|
119
|
-
};
|
|
113
|
+
customStorage?: BetterAuthRateLimitStorage;
|
|
120
114
|
};
|
|
121
115
|
|
|
122
116
|
export type BetterAuthAdvancedOptions = {
|
|
@@ -328,7 +322,7 @@ export type BetterAuthAdvancedOptions = {
|
|
|
328
322
|
* }
|
|
329
323
|
*/
|
|
330
324
|
backgroundTasks?: {
|
|
331
|
-
handler: (promise: Promise<
|
|
325
|
+
handler: (promise: Promise<unknown>) => void;
|
|
332
326
|
};
|
|
333
327
|
/**
|
|
334
328
|
* Skip trailing slash validation in route matching
|
|
@@ -491,10 +485,13 @@ export type BetterAuthOptions = {
|
|
|
491
485
|
request?: Request,
|
|
492
486
|
) => Promise<void>;
|
|
493
487
|
/**
|
|
494
|
-
* Send a verification email automatically
|
|
495
|
-
* after sign up
|
|
488
|
+
* Send a verification email automatically after sign up.
|
|
496
489
|
*
|
|
497
|
-
*
|
|
490
|
+
* - `true`: Always send verification email on sign up
|
|
491
|
+
* - `false`: Never send verification email on sign up
|
|
492
|
+
* - `undefined`: Follows `requireEmailVerification` behavior
|
|
493
|
+
*
|
|
494
|
+
* @default undefined
|
|
498
495
|
*/
|
|
499
496
|
sendOnSignUp?: boolean;
|
|
500
497
|
/**
|
|
@@ -947,6 +944,17 @@ export type BetterAuthOptions = {
|
|
|
947
944
|
* @default true
|
|
948
945
|
*/
|
|
949
946
|
enabled?: boolean;
|
|
947
|
+
/**
|
|
948
|
+
* Disable implicit account linking on sign-in.
|
|
949
|
+
*
|
|
950
|
+
* When enabled, accounts will not be automatically linked
|
|
951
|
+
* during OAuth sign-in, even if the email is verified or
|
|
952
|
+
* the provider is trusted. Users must explicitly link
|
|
953
|
+
* accounts using `linkSocial()` while authenticated.
|
|
954
|
+
*
|
|
955
|
+
* @default false
|
|
956
|
+
*/
|
|
957
|
+
disableImplicitLinking?: boolean;
|
|
950
958
|
/**
|
|
951
959
|
* List of trusted providers
|
|
952
960
|
*/
|
package/src/utils/db.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DBFieldAttribute } from "../db";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters output data by removing fields with the `returned: false` attribute.
|
|
5
|
+
* This ensures sensitive fields are not exposed in API responses.
|
|
6
|
+
*/
|
|
7
|
+
export function filterOutputFields<T extends Record<string, unknown> | null>(
|
|
8
|
+
data: T,
|
|
9
|
+
additionalFields: Record<string, DBFieldAttribute> | undefined,
|
|
10
|
+
): T {
|
|
11
|
+
if (!data || !additionalFields) {
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
const returnFiltered = Object.entries(additionalFields)
|
|
15
|
+
.filter(([, { returned }]) => returned === false)
|
|
16
|
+
.map(([key]) => key);
|
|
17
|
+
return Object.entries(structuredClone(data))
|
|
18
|
+
.filter(([key]) => !returnFiltered.includes(key))
|
|
19
|
+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {} as T);
|
|
20
|
+
}
|
package/src/utils/index.ts
CHANGED
package/tsdown.config.ts
CHANGED
|
@@ -25,7 +25,10 @@ export default defineConfig({
|
|
|
25
25
|
external: ["@better-auth/core/async_hooks"],
|
|
26
26
|
env: {
|
|
27
27
|
BETTER_AUTH_VERSION: packageJson.version,
|
|
28
|
+
BETTER_AUTH_TELEMETRY_ENDPOINT:
|
|
29
|
+
process.env.BETTER_AUTH_TELEMETRY_ENDPOINT ?? "",
|
|
28
30
|
},
|
|
31
|
+
sourcemap: true,
|
|
29
32
|
unbundle: true,
|
|
30
33
|
clean: true,
|
|
31
34
|
});
|