@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.mts +47 -4
- package/dist/api/index.mjs +40 -1
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +57 -31
- package/dist/db/adapter/index.d.mts +54 -10
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +3 -3
- package/dist/db/schema/account.d.mts +1 -1
- package/dist/db/schema/account.mjs +1 -1
- package/dist/db/type.d.mts +12 -7
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +5 -0
- package/dist/error/codes.mjs +5 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/create-authorization-url.d.mts +4 -1
- package/dist/oauth2/create-authorization-url.mjs +5 -2
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +6 -3
- package/dist/oauth2/index.mjs +5 -2
- package/dist/oauth2/oauth-provider.d.mts +128 -9
- package/dist/oauth2/refresh-access-token.mjs +1 -1
- package/dist/oauth2/scopes.d.mts +76 -0
- package/dist/oauth2/scopes.mjs +96 -0
- package/dist/oauth2/utils.mjs +2 -1
- package/dist/oauth2/verify-id-token.d.mts +26 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +187 -19
- package/dist/social-providers/apple.d.mts +14 -2
- package/dist/social-providers/apple.mjs +12 -36
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +4 -4
- package/dist/social-providers/cognito.d.mts +13 -2
- package/dist/social-providers/cognito.mjs +24 -32
- package/dist/social-providers/discord.d.mts +5 -1
- package/dist/social-providers/discord.mjs +7 -6
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -5
- package/dist/social-providers/facebook.d.mts +21 -2
- package/dist/social-providers/facebook.mjs +46 -22
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +5 -5
- package/dist/social-providers/github.d.mts +5 -1
- package/dist/social-providers/github.mjs +4 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +6 -6
- package/dist/social-providers/google.d.mts +29 -3
- package/dist/social-providers/google.mjs +24 -30
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +8 -8
- package/dist/social-providers/index.d.mts +222 -42
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +8 -8
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +4 -4
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +12 -14
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +4 -4
- package/dist/social-providers/linkedin.d.mts +5 -1
- package/dist/social-providers/linkedin.mjs +10 -10
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -6
- package/dist/social-providers/microsoft-entra-id.mjs +40 -36
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +4 -4
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +4 -4
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +10 -10
- package/dist/social-providers/paypal.d.mts +5 -2
- package/dist/social-providers/paypal.mjs +8 -13
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +8 -8
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +9 -9
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +9 -8
- package/dist/social-providers/roblox.d.mts +5 -1
- package/dist/social-providers/roblox.mjs +5 -5
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +8 -8
- package/dist/social-providers/slack.d.mts +5 -1
- package/dist/social-providers/slack.mjs +9 -9
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +5 -5
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +9 -5
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +4 -4
- package/dist/social-providers/twitter.d.mts +6 -4
- package/dist/social-providers/twitter.mjs +9 -9
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +4 -7
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +5 -5
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +10 -6
- package/dist/social-providers/zoom.d.mts +6 -1
- package/dist/social-providers/zoom.mjs +15 -9
- package/dist/types/context.d.mts +27 -8
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +137 -6
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/host.mjs +4 -0
- package/dist/utils/url.mjs +4 -3
- package/package.json +7 -7
- package/src/api/index.ts +82 -0
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +127 -64
- package/src/db/adapter/index.ts +54 -9
- package/src/db/adapter/types.ts +1 -0
- package/src/db/get-tables.ts +8 -3
- package/src/db/schema/account.ts +14 -2
- package/src/db/type.ts +12 -7
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +5 -0
- package/src/oauth2/create-authorization-url.ts +2 -2
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +61 -2
- package/src/oauth2/oauth-provider.ts +140 -10
- package/src/oauth2/refresh-access-token.ts +2 -2
- package/src/oauth2/scopes.ts +118 -0
- package/src/oauth2/utils.ts +2 -5
- package/src/oauth2/verify-id-token.ts +111 -0
- package/src/oauth2/verify.ts +372 -58
- package/src/social-providers/apple.ts +24 -61
- package/src/social-providers/atlassian.ts +12 -8
- package/src/social-providers/cognito.ts +25 -47
- package/src/social-providers/discord.ts +19 -8
- package/src/social-providers/dropbox.ts +13 -7
- package/src/social-providers/facebook.ts +97 -51
- package/src/social-providers/figma.ts +13 -9
- package/src/social-providers/github.ts +12 -8
- package/src/social-providers/gitlab.ts +14 -8
- package/src/social-providers/google.ts +66 -47
- package/src/social-providers/huggingface.ts +12 -8
- package/src/social-providers/kakao.ts +16 -8
- package/src/social-providers/kick.ts +12 -7
- package/src/social-providers/line.ts +37 -37
- package/src/social-providers/linear.ts +12 -6
- package/src/social-providers/linkedin.ts +14 -10
- package/src/social-providers/microsoft-entra-id.ts +103 -59
- package/src/social-providers/naver.ts +12 -6
- package/src/social-providers/notion.ts +12 -6
- package/src/social-providers/paybin.ts +14 -11
- package/src/social-providers/paypal.ts +6 -25
- package/src/social-providers/polar.ts +12 -8
- package/src/social-providers/railway.ts +13 -9
- package/src/social-providers/reddit.ts +25 -10
- package/src/social-providers/roblox.ts +18 -7
- package/src/social-providers/salesforce.ts +12 -8
- package/src/social-providers/slack.ts +18 -9
- package/src/social-providers/spotify.ts +13 -7
- package/src/social-providers/tiktok.ts +13 -7
- package/src/social-providers/twitch.ts +12 -8
- package/src/social-providers/twitter.ts +17 -8
- package/src/social-providers/vercel.ts +16 -10
- package/src/social-providers/vk.ts +13 -7
- package/src/social-providers/wechat.ts +28 -9
- package/src/social-providers/zoom.ts +19 -6
- package/src/types/context.ts +26 -8
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +159 -8
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/host.ts +15 -0
- package/src/utils/url.ts +10 -4
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
-
import { decodeJwt,
|
|
3
|
+
import { decodeJwt, importJWK } from "jose";
|
|
4
4
|
import { logger } from "../env";
|
|
5
5
|
import { APIError, BetterAuthError } from "../error";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ClientAssertionGetter,
|
|
8
|
+
ProviderOptions,
|
|
9
|
+
TokenEndpointAuth,
|
|
10
|
+
UpstreamProvider,
|
|
11
|
+
} from "../oauth2";
|
|
7
12
|
import {
|
|
8
13
|
createAuthorizationURL,
|
|
9
14
|
getPrimaryClientId,
|
|
10
15
|
refreshAccessToken,
|
|
16
|
+
resolveRequestedScopes,
|
|
11
17
|
validateAuthorizationCode,
|
|
12
18
|
} from "../oauth2";
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Microsoft's fixed tenant id for personal (consumer) Microsoft accounts. Every
|
|
22
|
+
* personal-account token carries it as the `tid` claim, so it distinguishes the
|
|
23
|
+
* consumer account class from work/school tenants.
|
|
24
|
+
* @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
|
|
25
|
+
*/
|
|
26
|
+
const MICROSOFT_CONSUMER_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
|
|
27
|
+
|
|
14
28
|
/**
|
|
15
29
|
* @see [Microsoft Identity Platform - Optional claims reference](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference)
|
|
16
30
|
*/
|
|
@@ -122,33 +136,67 @@ export interface MicrosoftOptions
|
|
|
122
136
|
* The tenant ID of the Microsoft account
|
|
123
137
|
* @default "common"
|
|
124
138
|
*/
|
|
125
|
-
tenantId?: string
|
|
139
|
+
tenantId?: string;
|
|
126
140
|
/**
|
|
127
141
|
* The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
|
|
128
142
|
* @default "https://login.microsoftonline.com"
|
|
129
143
|
*/
|
|
130
|
-
authority?: string
|
|
144
|
+
authority?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Function that returns a JWT client assertion for token endpoint authentication.
|
|
147
|
+
*
|
|
148
|
+
* Use this instead of `clientSecret` when your Microsoft Entra ID app is
|
|
149
|
+
* configured for client authentication with assertions (private_key_jwt or
|
|
150
|
+
* workload identity federation).
|
|
151
|
+
*/
|
|
152
|
+
clientAssertion?: ClientAssertionGetter;
|
|
131
153
|
/**
|
|
132
154
|
* The size of the profile photo
|
|
133
155
|
* @default 48
|
|
134
156
|
*/
|
|
135
|
-
profilePhotoSize?:
|
|
136
|
-
| (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648)
|
|
137
|
-
| undefined;
|
|
157
|
+
profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648;
|
|
138
158
|
/**
|
|
139
159
|
* Disable profile photo
|
|
140
160
|
*/
|
|
141
|
-
disableProfilePhoto?: boolean
|
|
161
|
+
disableProfilePhoto?: boolean;
|
|
142
162
|
}
|
|
143
163
|
|
|
164
|
+
const MICROSOFT_ENTRA_ID_DEFAULT_SCOPES = [
|
|
165
|
+
"openid",
|
|
166
|
+
"profile",
|
|
167
|
+
"email",
|
|
168
|
+
"User.Read",
|
|
169
|
+
"offline_access",
|
|
170
|
+
];
|
|
171
|
+
|
|
144
172
|
export const microsoft = (options: MicrosoftOptions) => {
|
|
145
173
|
const tenant = options.tenantId || "common";
|
|
146
|
-
|
|
174
|
+
// Trim any trailing slash so endpoint URLs and the issuer comparison below
|
|
175
|
+
// never produce a double slash (e.g. a configured `https://host/` would make
|
|
176
|
+
// the expected issuer `https://host//<tid>/v2.0` and reject every token). A
|
|
177
|
+
// loop avoids a trailing-slash regex, which is a polynomial-ReDoS shape.
|
|
178
|
+
let authority = options.authority || "https://login.microsoftonline.com";
|
|
179
|
+
while (authority.endsWith("/")) {
|
|
180
|
+
authority = authority.slice(0, -1);
|
|
181
|
+
}
|
|
147
182
|
const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
|
|
148
183
|
const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
|
|
184
|
+
if (options.clientSecret && options.clientAssertion) {
|
|
185
|
+
throw new BetterAuthError(
|
|
186
|
+
"Microsoft Entra ID clientAssertion cannot be combined with clientSecret",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const tokenEndpointAuth: TokenEndpointAuth | undefined =
|
|
190
|
+
options.clientAssertion
|
|
191
|
+
? {
|
|
192
|
+
method: "private_key_jwt",
|
|
193
|
+
getClientAssertion: options.clientAssertion,
|
|
194
|
+
}
|
|
195
|
+
: undefined;
|
|
149
196
|
return {
|
|
150
197
|
id: "microsoft",
|
|
151
198
|
name: "Microsoft EntraID",
|
|
199
|
+
callbackPath: "/callback/microsoft",
|
|
152
200
|
createAuthorizationURL(data) {
|
|
153
201
|
// Microsoft Entra supports public clients (SPA / native apps with
|
|
154
202
|
// PKCE only), so clientSecret is intentionally not required here.
|
|
@@ -159,18 +207,18 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
159
207
|
);
|
|
160
208
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
161
209
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
const requestedScopes = resolveRequestedScopes(
|
|
211
|
+
options,
|
|
212
|
+
MICROSOFT_ENTRA_ID_DEFAULT_SCOPES,
|
|
213
|
+
data.scopes,
|
|
214
|
+
);
|
|
167
215
|
return createAuthorizationURL({
|
|
168
216
|
id: "microsoft",
|
|
169
217
|
options,
|
|
170
218
|
authorizationEndpoint,
|
|
171
219
|
state: data.state,
|
|
172
220
|
codeVerifier: data.codeVerifier,
|
|
173
|
-
scopes,
|
|
221
|
+
scopes: requestedScopes,
|
|
174
222
|
redirectURI: data.redirectURI,
|
|
175
223
|
prompt: options.prompt,
|
|
176
224
|
loginHint: data.loginHint,
|
|
@@ -184,57 +232,52 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
184
232
|
redirectURI,
|
|
185
233
|
options,
|
|
186
234
|
tokenEndpoint,
|
|
235
|
+
tokenEndpointAuth,
|
|
187
236
|
});
|
|
188
237
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
*/
|
|
238
|
+
idToken: {
|
|
239
|
+
jwks: (header) => getMicrosoftPublicKey(header.kid!, tenant, authority),
|
|
240
|
+
audience: options.clientId,
|
|
241
|
+
maxTokenAge: "1h",
|
|
242
|
+
/**
|
|
243
|
+
* Issuer varies per tenant for multi-tenant endpoints, so only validate it for
|
|
244
|
+
* specific tenants.
|
|
245
|
+
* @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
|
|
246
|
+
*/
|
|
247
|
+
issuer:
|
|
248
|
+
tenant !== "common" &&
|
|
249
|
+
tenant !== "organizations" &&
|
|
250
|
+
tenant !== "consumers"
|
|
251
|
+
? `${authority}/${tenant}/v2.0`
|
|
252
|
+
: undefined,
|
|
253
|
+
/**
|
|
254
|
+
* The multi-tenant endpoints (common/organizations/consumers) skip the
|
|
255
|
+
* issuer check above because the issuer varies per tenant, and the
|
|
256
|
+
* organizations and consumers JWKS sets overlap. Enforce the tenant
|
|
257
|
+
* binding explicitly so a token from a disallowed account class cannot
|
|
258
|
+
* pass: the issuer must name the token's own tenant, and the account
|
|
259
|
+
* class must match the configured restriction.
|
|
260
|
+
* @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
|
|
261
|
+
*/
|
|
262
|
+
verifyClaims: (claims) => {
|
|
263
|
+
const tid = claims.tid;
|
|
216
264
|
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
tenant !== "consumers"
|
|
265
|
+
typeof tid !== "string" ||
|
|
266
|
+
claims.iss !== `${authority}/${tid}/v2.0`
|
|
220
267
|
) {
|
|
221
|
-
|
|
268
|
+
return false;
|
|
222
269
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
270
|
+
if (
|
|
271
|
+
tenant === "organizations" &&
|
|
272
|
+
tid === MICROSOFT_CONSUMER_TENANT_ID
|
|
273
|
+
) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (tenant === "consumers" && tid !== MICROSOFT_CONSUMER_TENANT_ID) {
|
|
230
277
|
return false;
|
|
231
278
|
}
|
|
232
|
-
|
|
233
279
|
return true;
|
|
234
|
-
}
|
|
235
|
-
logger.error("Failed to verify ID token:", error);
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
280
|
+
},
|
|
238
281
|
},
|
|
239
282
|
async getUserInfo(token) {
|
|
240
283
|
if (options.getUserInfo) {
|
|
@@ -314,10 +357,11 @@ export const microsoft = (options: MicrosoftOptions) => {
|
|
|
314
357
|
scope: scopes.join(" "), // Include the scopes in request to microsoft
|
|
315
358
|
},
|
|
316
359
|
tokenEndpoint,
|
|
360
|
+
tokenEndpointAuth,
|
|
317
361
|
});
|
|
318
362
|
},
|
|
319
363
|
options,
|
|
320
|
-
} satisfies
|
|
364
|
+
} satisfies UpstreamProvider;
|
|
321
365
|
};
|
|
322
366
|
|
|
323
367
|
export const getMicrosoftPublicKey = async (
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
3
3
|
import {
|
|
4
4
|
createAuthorizationURL,
|
|
5
5
|
refreshAccessToken,
|
|
6
|
+
resolveRequestedScopes,
|
|
6
7
|
validateAuthorizationCode,
|
|
7
8
|
} from "../oauth2";
|
|
8
9
|
|
|
@@ -39,20 +40,25 @@ export interface NaverOptions extends ProviderOptions<NaverProfile> {
|
|
|
39
40
|
clientId: string;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const NAVER_DEFAULT_SCOPES = ["profile", "email"];
|
|
44
|
+
|
|
42
45
|
export const naver = (options: NaverOptions) => {
|
|
43
46
|
const tokenEndpoint = "https://nid.naver.com/oauth2.0/token";
|
|
44
47
|
return {
|
|
45
48
|
id: "naver",
|
|
46
49
|
name: "Naver",
|
|
50
|
+
callbackPath: "/callback/naver",
|
|
47
51
|
createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
const requestedScopes = resolveRequestedScopes(
|
|
53
|
+
options,
|
|
54
|
+
NAVER_DEFAULT_SCOPES,
|
|
55
|
+
scopes,
|
|
56
|
+
);
|
|
51
57
|
return createAuthorizationURL({
|
|
52
58
|
id: "naver",
|
|
53
59
|
options,
|
|
54
60
|
authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
|
|
55
|
-
scopes:
|
|
61
|
+
scopes: requestedScopes,
|
|
56
62
|
state,
|
|
57
63
|
redirectURI,
|
|
58
64
|
additionalParams,
|
|
@@ -110,5 +116,5 @@ export const naver = (options: NaverOptions) => {
|
|
|
110
116
|
};
|
|
111
117
|
},
|
|
112
118
|
options,
|
|
113
|
-
} satisfies
|
|
119
|
+
} satisfies UpstreamProvider<NaverProfile>;
|
|
114
120
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
3
3
|
import {
|
|
4
4
|
createAuthorizationURL,
|
|
5
5
|
refreshAccessToken,
|
|
6
|
+
resolveRequestedScopes,
|
|
6
7
|
validateAuthorizationCode,
|
|
7
8
|
} from "../oauth2";
|
|
8
9
|
|
|
@@ -23,11 +24,14 @@ export interface NotionOptions extends ProviderOptions<NotionProfile> {
|
|
|
23
24
|
clientId: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const NOTION_DEFAULT_SCOPES: string[] = [];
|
|
28
|
+
|
|
26
29
|
export const notion = (options: NotionOptions) => {
|
|
27
30
|
const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
|
|
28
31
|
return {
|
|
29
32
|
id: "notion",
|
|
30
33
|
name: "Notion",
|
|
34
|
+
callbackPath: "/callback/notion",
|
|
31
35
|
createAuthorizationURL({
|
|
32
36
|
state,
|
|
33
37
|
scopes,
|
|
@@ -35,14 +39,16 @@ export const notion = (options: NotionOptions) => {
|
|
|
35
39
|
redirectURI,
|
|
36
40
|
additionalParams,
|
|
37
41
|
}) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const requestedScopes = resolveRequestedScopes(
|
|
43
|
+
options,
|
|
44
|
+
NOTION_DEFAULT_SCOPES,
|
|
45
|
+
scopes,
|
|
46
|
+
);
|
|
41
47
|
return createAuthorizationURL({
|
|
42
48
|
id: "notion",
|
|
43
49
|
options,
|
|
44
50
|
authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
|
|
45
|
-
scopes:
|
|
51
|
+
scopes: requestedScopes,
|
|
46
52
|
state,
|
|
47
53
|
redirectURI,
|
|
48
54
|
loginHint,
|
|
@@ -111,5 +117,5 @@ export const notion = (options: NotionOptions) => {
|
|
|
111
117
|
};
|
|
112
118
|
},
|
|
113
119
|
options,
|
|
114
|
-
} satisfies
|
|
120
|
+
} satisfies UpstreamProvider<NotionProfile>;
|
|
115
121
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { decodeJwt } from "jose";
|
|
2
2
|
import { logger } from "../env";
|
|
3
3
|
import { BetterAuthError } from "../error";
|
|
4
|
-
import type {
|
|
4
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
5
5
|
import {
|
|
6
6
|
createAuthorizationURL,
|
|
7
7
|
refreshAccessToken,
|
|
8
|
+
resolveRequestedScopes,
|
|
8
9
|
validateAuthorizationCode,
|
|
9
10
|
} from "../oauth2";
|
|
10
11
|
|
|
@@ -28,6 +29,8 @@ export interface PaybinOptions extends ProviderOptions<PaybinProfile> {
|
|
|
28
29
|
issuer?: string | undefined;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
const PAYBIN_DEFAULT_SCOPES = ["openid", "email", "profile"];
|
|
33
|
+
|
|
31
34
|
export const paybin = (options: PaybinOptions) => {
|
|
32
35
|
const issuer = options.issuer || "https://idp.paybin.io";
|
|
33
36
|
const authorizationEndpoint = `${issuer}/oauth2/authorize`;
|
|
@@ -36,7 +39,8 @@ export const paybin = (options: PaybinOptions) => {
|
|
|
36
39
|
return {
|
|
37
40
|
id: "paybin",
|
|
38
41
|
name: "Paybin",
|
|
39
|
-
|
|
42
|
+
callbackPath: "/callback/paybin",
|
|
43
|
+
createAuthorizationURL({
|
|
40
44
|
state,
|
|
41
45
|
scopes,
|
|
42
46
|
codeVerifier,
|
|
@@ -53,16 +57,16 @@ export const paybin = (options: PaybinOptions) => {
|
|
|
53
57
|
if (!codeVerifier) {
|
|
54
58
|
throw new BetterAuthError("codeVerifier is required for Paybin");
|
|
55
59
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const requestedScopes = resolveRequestedScopes(
|
|
61
|
+
options,
|
|
62
|
+
PAYBIN_DEFAULT_SCOPES,
|
|
63
|
+
scopes,
|
|
64
|
+
);
|
|
65
|
+
return createAuthorizationURL({
|
|
62
66
|
id: "paybin",
|
|
63
67
|
options,
|
|
64
68
|
authorizationEndpoint,
|
|
65
|
-
scopes:
|
|
69
|
+
scopes: requestedScopes,
|
|
66
70
|
state,
|
|
67
71
|
codeVerifier,
|
|
68
72
|
redirectURI,
|
|
@@ -70,7 +74,6 @@ export const paybin = (options: PaybinOptions) => {
|
|
|
70
74
|
loginHint,
|
|
71
75
|
additionalParams,
|
|
72
76
|
});
|
|
73
|
-
return url;
|
|
74
77
|
},
|
|
75
78
|
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
76
79
|
return validateAuthorizationCode({
|
|
@@ -116,5 +119,5 @@ export const paybin = (options: PaybinOptions) => {
|
|
|
116
119
|
};
|
|
117
120
|
},
|
|
118
121
|
options,
|
|
119
|
-
} satisfies
|
|
122
|
+
} satisfies UpstreamProvider<PaybinProfile>;
|
|
120
123
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
-
import { decodeJwt } from "jose";
|
|
4
3
|
import { logger } from "../env";
|
|
5
4
|
import { BetterAuthError } from "../error";
|
|
6
|
-
import type {
|
|
5
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
7
6
|
import { createAuthorizationURL } from "../oauth2";
|
|
8
7
|
|
|
9
8
|
export interface PayPalProfile {
|
|
@@ -78,7 +77,8 @@ export const paypal = (options: PayPalOptions) => {
|
|
|
78
77
|
return {
|
|
79
78
|
id: "paypal",
|
|
80
79
|
name: "PayPal",
|
|
81
|
-
|
|
80
|
+
callbackPath: "/callback/paypal",
|
|
81
|
+
createAuthorizationURL({
|
|
82
82
|
state,
|
|
83
83
|
codeVerifier,
|
|
84
84
|
redirectURI,
|
|
@@ -97,20 +97,17 @@ export const paypal = (options: PayPalOptions) => {
|
|
|
97
97
|
* We don't pass any scopes to avoid "invalid scope" errors
|
|
98
98
|
**/
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const url = await createAuthorizationURL({
|
|
100
|
+
return createAuthorizationURL({
|
|
103
101
|
id: "paypal",
|
|
104
102
|
options,
|
|
105
103
|
authorizationEndpoint,
|
|
106
|
-
scopes:
|
|
104
|
+
scopes: [],
|
|
107
105
|
state,
|
|
108
106
|
codeVerifier,
|
|
109
107
|
redirectURI,
|
|
110
108
|
prompt: options.prompt,
|
|
111
109
|
additionalParams,
|
|
112
110
|
});
|
|
113
|
-
return url;
|
|
114
111
|
},
|
|
115
112
|
|
|
116
113
|
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
|
@@ -200,22 +197,6 @@ export const paypal = (options: PayPalOptions) => {
|
|
|
200
197
|
}
|
|
201
198
|
},
|
|
202
199
|
|
|
203
|
-
async verifyIdToken(token, nonce) {
|
|
204
|
-
if (options.disableIdTokenSignIn) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
if (options.verifyIdToken) {
|
|
208
|
-
return options.verifyIdToken(token, nonce);
|
|
209
|
-
}
|
|
210
|
-
try {
|
|
211
|
-
const payload = decodeJwt(token);
|
|
212
|
-
return !!payload.sub;
|
|
213
|
-
} catch (error) {
|
|
214
|
-
logger.error("Failed to verify PayPal ID token:", error);
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
|
|
219
200
|
async getUserInfo(token) {
|
|
220
201
|
if (options.getUserInfo) {
|
|
221
202
|
return options.getUserInfo(token);
|
|
@@ -265,5 +246,5 @@ export const paypal = (options: PayPalOptions) => {
|
|
|
265
246
|
},
|
|
266
247
|
|
|
267
248
|
options,
|
|
268
|
-
} satisfies
|
|
249
|
+
} satisfies UpstreamProvider<PayPalProfile>;
|
|
269
250
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
3
3
|
import {
|
|
4
4
|
createAuthorizationURL,
|
|
5
5
|
refreshAccessToken,
|
|
6
|
+
resolveRequestedScopes,
|
|
6
7
|
validateAuthorizationCode,
|
|
7
8
|
} from "../oauth2";
|
|
8
9
|
|
|
@@ -32,11 +33,14 @@ export interface PolarProfile {
|
|
|
32
33
|
|
|
33
34
|
export interface PolarOptions extends ProviderOptions<PolarProfile> {}
|
|
34
35
|
|
|
36
|
+
const POLAR_DEFAULT_SCOPES = ["openid", "profile", "email"];
|
|
37
|
+
|
|
35
38
|
export const polar = (options: PolarOptions) => {
|
|
36
39
|
const tokenEndpoint = "https://api.polar.sh/v1/oauth2/token";
|
|
37
40
|
return {
|
|
38
41
|
id: "polar",
|
|
39
42
|
name: "Polar",
|
|
43
|
+
callbackPath: "/callback/polar",
|
|
40
44
|
createAuthorizationURL({
|
|
41
45
|
state,
|
|
42
46
|
scopes,
|
|
@@ -44,16 +48,16 @@ export const polar = (options: PolarOptions) => {
|
|
|
44
48
|
redirectURI,
|
|
45
49
|
additionalParams,
|
|
46
50
|
}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const requestedScopes = resolveRequestedScopes(
|
|
52
|
+
options,
|
|
53
|
+
POLAR_DEFAULT_SCOPES,
|
|
54
|
+
scopes,
|
|
55
|
+
);
|
|
52
56
|
return createAuthorizationURL({
|
|
53
57
|
id: "polar",
|
|
54
58
|
options,
|
|
55
59
|
authorizationEndpoint: "https://polar.sh/oauth2/authorize",
|
|
56
|
-
scopes:
|
|
60
|
+
scopes: requestedScopes,
|
|
57
61
|
state,
|
|
58
62
|
codeVerifier,
|
|
59
63
|
redirectURI,
|
|
@@ -114,5 +118,5 @@ export const polar = (options: PolarOptions) => {
|
|
|
114
118
|
};
|
|
115
119
|
},
|
|
116
120
|
options,
|
|
117
|
-
} satisfies
|
|
121
|
+
} satisfies UpstreamProvider<PolarProfile>;
|
|
118
122
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { betterFetch } from "@better-fetch/fetch";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
3
3
|
import {
|
|
4
4
|
createAuthorizationURL,
|
|
5
5
|
refreshAccessToken,
|
|
6
|
+
resolveRequestedScopes,
|
|
6
7
|
validateAuthorizationCode,
|
|
7
8
|
} from "../oauth2";
|
|
8
9
|
|
|
@@ -25,27 +26,30 @@ export interface RailwayOptions extends ProviderOptions<RailwayProfile> {
|
|
|
25
26
|
clientId: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
const RAILWAY_DEFAULT_SCOPES = ["openid", "email", "profile"];
|
|
30
|
+
|
|
28
31
|
export const railway = (options: RailwayOptions) => {
|
|
29
32
|
return {
|
|
30
33
|
id: "railway",
|
|
31
34
|
name: "Railway",
|
|
32
|
-
|
|
35
|
+
callbackPath: "/callback/railway",
|
|
36
|
+
async createAuthorizationURL({
|
|
33
37
|
state,
|
|
34
38
|
scopes,
|
|
35
39
|
codeVerifier,
|
|
36
40
|
redirectURI,
|
|
37
41
|
additionalParams,
|
|
38
42
|
}) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const requestedScopes = resolveRequestedScopes(
|
|
44
|
+
options,
|
|
45
|
+
RAILWAY_DEFAULT_SCOPES,
|
|
46
|
+
scopes,
|
|
47
|
+
);
|
|
44
48
|
return createAuthorizationURL({
|
|
45
49
|
id: "railway",
|
|
46
50
|
options,
|
|
47
51
|
authorizationEndpoint,
|
|
48
|
-
scopes:
|
|
52
|
+
scopes: requestedScopes,
|
|
49
53
|
state,
|
|
50
54
|
codeVerifier,
|
|
51
55
|
redirectURI,
|
|
@@ -103,5 +107,5 @@ export const railway = (options: RailwayOptions) => {
|
|
|
103
107
|
};
|
|
104
108
|
},
|
|
105
109
|
options,
|
|
106
|
-
} satisfies
|
|
110
|
+
} satisfies UpstreamProvider<RailwayProfile>;
|
|
107
111
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { base64 } from "@better-auth/utils/base64";
|
|
2
2
|
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ProviderOptions, UpstreamProvider } from "../oauth2";
|
|
4
4
|
import {
|
|
5
5
|
createAuthorizationURL,
|
|
6
6
|
getOAuth2Tokens,
|
|
7
7
|
refreshAccessToken,
|
|
8
|
+
resolveRequestedScopes,
|
|
8
9
|
} from "../oauth2";
|
|
9
10
|
|
|
10
11
|
export interface RedditProfile {
|
|
@@ -21,19 +22,29 @@ export interface RedditOptions extends ProviderOptions<RedditProfile> {
|
|
|
21
22
|
duration?: string | undefined;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
const REDDIT_DEFAULT_SCOPES = ["identity"];
|
|
26
|
+
|
|
24
27
|
export const reddit = (options: RedditOptions) => {
|
|
25
28
|
return {
|
|
26
29
|
id: "reddit",
|
|
27
30
|
name: "Reddit",
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
callbackPath: "/callback/reddit",
|
|
32
|
+
async createAuthorizationURL({
|
|
33
|
+
state,
|
|
34
|
+
scopes,
|
|
35
|
+
redirectURI,
|
|
36
|
+
additionalParams,
|
|
37
|
+
}) {
|
|
38
|
+
const requestedScopes = resolveRequestedScopes(
|
|
39
|
+
options,
|
|
40
|
+
REDDIT_DEFAULT_SCOPES,
|
|
41
|
+
scopes,
|
|
42
|
+
);
|
|
32
43
|
return createAuthorizationURL({
|
|
33
44
|
id: "reddit",
|
|
34
45
|
options,
|
|
35
46
|
authorizationEndpoint: "https://www.reddit.com/api/v1/authorize",
|
|
36
|
-
scopes:
|
|
47
|
+
scopes: requestedScopes,
|
|
37
48
|
state,
|
|
38
49
|
redirectURI,
|
|
39
50
|
duration: options.duration,
|
|
@@ -105,19 +116,23 @@ export const reddit = (options: RedditOptions) => {
|
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
108
|
-
|
|
119
|
+
// Reddit's identity scope does not return an email. Synthesize a stable,
|
|
120
|
+
// non-routable placeholder (RFC 2606 `.invalid`) keyed to the user's
|
|
121
|
+
// Reddit id rather than the routable `reddit.com`, which could collide
|
|
122
|
+
// with a real address. Left unverified; `mapProfileToUser` can override.
|
|
123
|
+
const email = userMap?.email || `${profile.id}@reddit.invalid`;
|
|
109
124
|
return {
|
|
110
125
|
user: {
|
|
111
126
|
id: profile.id,
|
|
112
127
|
name: profile.name,
|
|
113
|
-
email: profile.oauth_client_id,
|
|
114
|
-
emailVerified: profile.has_verified_email,
|
|
115
128
|
image: profile.icon_img?.split("?")[0]!,
|
|
116
129
|
...userMap,
|
|
130
|
+
email,
|
|
131
|
+
emailVerified: userMap?.emailVerified ?? false,
|
|
117
132
|
},
|
|
118
133
|
data: profile,
|
|
119
134
|
};
|
|
120
135
|
},
|
|
121
136
|
options,
|
|
122
|
-
} satisfies
|
|
137
|
+
} satisfies UpstreamProvider<RedditProfile>;
|
|
123
138
|
};
|