@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,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/apple.d.ts
|
|
3
5
|
interface AppleProfile {
|
|
4
6
|
/**
|
|
@@ -67,6 +69,7 @@ interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
|
67
69
|
declare const apple: (options: AppleOptions) => {
|
|
68
70
|
id: "apple";
|
|
69
71
|
name: string;
|
|
72
|
+
callbackPath: string;
|
|
70
73
|
createAuthorizationURL({
|
|
71
74
|
state,
|
|
72
75
|
scopes,
|
|
@@ -80,7 +83,10 @@ declare const apple: (options: AppleOptions) => {
|
|
|
80
83
|
display?: string | undefined;
|
|
81
84
|
loginHint?: string | undefined;
|
|
82
85
|
additionalParams?: Record<string, string> | undefined;
|
|
83
|
-
}): Promise<
|
|
86
|
+
}): Promise<{
|
|
87
|
+
url: URL;
|
|
88
|
+
requestedScopes: string[];
|
|
89
|
+
}>;
|
|
84
90
|
validateAuthorizationCode: ({
|
|
85
91
|
code,
|
|
86
92
|
codeVerifier,
|
|
@@ -91,7 +97,13 @@ declare const apple: (options: AppleOptions) => {
|
|
|
91
97
|
codeVerifier?: string | undefined;
|
|
92
98
|
deviceId?: string | undefined;
|
|
93
99
|
}) => Promise<OAuth2Tokens>;
|
|
94
|
-
|
|
100
|
+
idToken: {
|
|
101
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
102
|
+
issuer: string;
|
|
103
|
+
audience: string | string[];
|
|
104
|
+
maxTokenAge: string;
|
|
105
|
+
nonceComparison: "exact-or-sha256";
|
|
106
|
+
};
|
|
95
107
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
96
108
|
getUserInfo(token: OAuth2Tokens & {
|
|
97
109
|
user?: {
|
|
@@ -1,40 +1,30 @@
|
|
|
1
1
|
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
3
4
|
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
4
5
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
5
6
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
6
7
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
7
|
-
import { decodeJwt,
|
|
8
|
+
import { decodeJwt, importJWK } from "jose";
|
|
8
9
|
import { betterFetch } from "@better-fetch/fetch";
|
|
9
10
|
//#region src/social-providers/apple.ts
|
|
10
|
-
|
|
11
|
-
const data = new TextEncoder().encode(value);
|
|
12
|
-
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
13
|
-
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14
|
-
}
|
|
15
|
-
async function nonceMatches(jwtNonce, nonce) {
|
|
16
|
-
if (typeof jwtNonce !== "string") return false;
|
|
17
|
-
if (jwtNonce === nonce) return true;
|
|
18
|
-
return jwtNonce === await sha256Hex(nonce);
|
|
19
|
-
}
|
|
11
|
+
const APPLE_DEFAULT_SCOPES = ["email", "name"];
|
|
20
12
|
const apple = (options) => {
|
|
21
13
|
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
22
14
|
return {
|
|
23
15
|
id: "apple",
|
|
24
16
|
name: "Apple",
|
|
17
|
+
callbackPath: "/callback/apple",
|
|
25
18
|
async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
|
|
26
19
|
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
27
20
|
logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
|
|
28
21
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
29
22
|
}
|
|
30
|
-
|
|
31
|
-
if (options.scope) _scope.push(...options.scope);
|
|
32
|
-
if (scopes) _scope.push(...scopes);
|
|
33
|
-
return await createAuthorizationURL({
|
|
23
|
+
return createAuthorizationURL({
|
|
34
24
|
id: "apple",
|
|
35
25
|
options,
|
|
36
26
|
authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
|
|
37
|
-
scopes:
|
|
27
|
+
scopes: resolveRequestedScopes(options, APPLE_DEFAULT_SCOPES, scopes),
|
|
38
28
|
state,
|
|
39
29
|
redirectURI,
|
|
40
30
|
responseMode: "form_post",
|
|
@@ -51,26 +41,12 @@ const apple = (options) => {
|
|
|
51
41
|
tokenEndpoint
|
|
52
42
|
});
|
|
53
43
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
|
|
61
|
-
algorithms: [jwtAlg],
|
|
62
|
-
issuer: "https://appleid.apple.com",
|
|
63
|
-
audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
|
|
64
|
-
maxTokenAge: "1h"
|
|
65
|
-
});
|
|
66
|
-
["email_verified", "is_private_email"].forEach((field) => {
|
|
67
|
-
if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
|
|
68
|
-
});
|
|
69
|
-
if (nonce && !await nonceMatches(jwtClaims.nonce, nonce)) return false;
|
|
70
|
-
return !!jwtClaims;
|
|
71
|
-
} catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
44
|
+
idToken: {
|
|
45
|
+
jwks: (header) => getApplePublicKey(header.kid),
|
|
46
|
+
issuer: "https://appleid.apple.com",
|
|
47
|
+
audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
|
|
48
|
+
maxTokenAge: "1h",
|
|
49
|
+
nonceComparison: "exact-or-sha256"
|
|
74
50
|
},
|
|
75
51
|
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
76
52
|
return refreshAccessToken({
|
|
@@ -21,6 +21,7 @@ interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
|
|
|
21
21
|
declare const atlassian: (options: AtlassianOptions) => {
|
|
22
22
|
id: "atlassian";
|
|
23
23
|
name: string;
|
|
24
|
+
callbackPath: string;
|
|
24
25
|
createAuthorizationURL({
|
|
25
26
|
state,
|
|
26
27
|
scopes,
|
|
@@ -35,7 +36,10 @@ declare const atlassian: (options: AtlassianOptions) => {
|
|
|
35
36
|
display?: string | undefined;
|
|
36
37
|
loginHint?: string | undefined;
|
|
37
38
|
additionalParams?: Record<string, string> | undefined;
|
|
38
|
-
}): Promise<
|
|
39
|
+
}): Promise<{
|
|
40
|
+
url: URL;
|
|
41
|
+
requestedScopes: string[];
|
|
42
|
+
}>;
|
|
39
43
|
validateAuthorizationCode: ({
|
|
40
44
|
code,
|
|
41
45
|
codeVerifier,
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.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";
|
|
6
7
|
import { betterFetch } from "@better-fetch/fetch";
|
|
7
8
|
//#region src/social-providers/atlassian.ts
|
|
9
|
+
const ATLASSIAN_DEFAULT_SCOPES = ["read:jira-user", "offline_access"];
|
|
8
10
|
const atlassian = (options) => {
|
|
9
11
|
const tokenEndpoint = "https://auth.atlassian.com/oauth/token";
|
|
10
12
|
return {
|
|
11
13
|
id: "atlassian",
|
|
12
14
|
name: "Atlassian",
|
|
15
|
+
callbackPath: "/callback/atlassian",
|
|
13
16
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
|
|
14
17
|
if (!options.clientId || !options.clientSecret) {
|
|
15
18
|
logger.error("Client Id and Secret are required for Atlassian");
|
|
16
19
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
17
20
|
}
|
|
18
21
|
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Atlassian");
|
|
19
|
-
const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
|
|
20
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
21
|
-
if (scopes) _scopes.push(...scopes);
|
|
22
22
|
return createAuthorizationURL({
|
|
23
23
|
id: "atlassian",
|
|
24
24
|
options,
|
|
25
25
|
authorizationEndpoint: "https://auth.atlassian.com/authorize",
|
|
26
|
-
scopes:
|
|
26
|
+
scopes: resolveRequestedScopes(options, ATLASSIAN_DEFAULT_SCOPES, scopes),
|
|
27
27
|
state,
|
|
28
28
|
codeVerifier,
|
|
29
29
|
redirectURI,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/cognito.d.ts
|
|
3
5
|
interface CognitoProfile {
|
|
4
6
|
sub: string;
|
|
@@ -47,6 +49,7 @@ interface CognitoOptions extends ProviderOptions<CognitoProfile> {
|
|
|
47
49
|
declare const cognito: (options: CognitoOptions) => {
|
|
48
50
|
id: "cognito";
|
|
49
51
|
name: string;
|
|
52
|
+
callbackPath: string;
|
|
50
53
|
createAuthorizationURL({
|
|
51
54
|
state,
|
|
52
55
|
scopes,
|
|
@@ -61,7 +64,10 @@ declare const cognito: (options: CognitoOptions) => {
|
|
|
61
64
|
display?: string | undefined;
|
|
62
65
|
loginHint?: string | undefined;
|
|
63
66
|
additionalParams?: Record<string, string> | undefined;
|
|
64
|
-
}): Promise<
|
|
67
|
+
}): Promise<{
|
|
68
|
+
url: URL;
|
|
69
|
+
requestedScopes: string[];
|
|
70
|
+
}>;
|
|
65
71
|
validateAuthorizationCode: ({
|
|
66
72
|
code,
|
|
67
73
|
codeVerifier,
|
|
@@ -73,7 +79,12 @@ declare const cognito: (options: CognitoOptions) => {
|
|
|
73
79
|
deviceId?: string | undefined;
|
|
74
80
|
}) => Promise<OAuth2Tokens>;
|
|
75
81
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
76
|
-
|
|
82
|
+
idToken: {
|
|
83
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
84
|
+
issuer: string;
|
|
85
|
+
audience: string | string[];
|
|
86
|
+
maxTokenAge: string;
|
|
87
|
+
};
|
|
77
88
|
getUserInfo(token: OAuth2Tokens & {
|
|
78
89
|
user?: {
|
|
79
90
|
name?: {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
3
4
|
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
4
5
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
5
6
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
6
7
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
7
|
-
import { decodeJwt,
|
|
8
|
+
import { decodeJwt, importJWK } from "jose";
|
|
8
9
|
import { betterFetch } from "@better-fetch/fetch";
|
|
9
10
|
//#region src/social-providers/cognito.ts
|
|
11
|
+
const COGNITO_DEFAULT_SCOPES = [
|
|
12
|
+
"openid",
|
|
13
|
+
"profile",
|
|
14
|
+
"email"
|
|
15
|
+
];
|
|
10
16
|
const cognito = (options) => {
|
|
11
17
|
if (!options.domain || !options.region || !options.userPoolId) {
|
|
12
18
|
logger.error("Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.");
|
|
@@ -19,6 +25,7 @@ const cognito = (options) => {
|
|
|
19
25
|
return {
|
|
20
26
|
id: "cognito",
|
|
21
27
|
name: "Cognito",
|
|
28
|
+
callbackPath: "/callback/cognito",
|
|
22
29
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
|
|
23
30
|
if (!getPrimaryClientId(options.clientId)) {
|
|
24
31
|
logger.error("ClientId is required for Amazon Cognito. Make sure to provide them in the options.");
|
|
@@ -28,18 +35,12 @@ const cognito = (options) => {
|
|
|
28
35
|
logger.error("Client Secret is required when requireClientSecret is true. Make sure to provide it in the options.");
|
|
29
36
|
throw new BetterAuthError("CLIENT_SECRET_REQUIRED");
|
|
30
37
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
"profile",
|
|
34
|
-
"email"
|
|
35
|
-
];
|
|
36
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
37
|
-
if (scopes) _scopes.push(...scopes);
|
|
38
|
-
const url = await createAuthorizationURL({
|
|
38
|
+
const requestedScopes = resolveRequestedScopes(options, COGNITO_DEFAULT_SCOPES, scopes);
|
|
39
|
+
const { url } = await createAuthorizationURL({
|
|
39
40
|
id: "cognito",
|
|
40
41
|
options: { ...options },
|
|
41
42
|
authorizationEndpoint,
|
|
42
|
-
scopes:
|
|
43
|
+
scopes: requestedScopes,
|
|
43
44
|
state,
|
|
44
45
|
codeVerifier,
|
|
45
46
|
redirectURI,
|
|
@@ -55,9 +56,15 @@ const cognito = (options) => {
|
|
|
55
56
|
const encodedScope = encodeURIComponent(scopeValue);
|
|
56
57
|
const urlString = url.toString();
|
|
57
58
|
const separator = urlString.includes("?") ? "&" : "?";
|
|
58
|
-
return
|
|
59
|
+
return {
|
|
60
|
+
url: new URL(`${urlString}${separator}scope=${encodedScope}`),
|
|
61
|
+
requestedScopes
|
|
62
|
+
};
|
|
59
63
|
}
|
|
60
|
-
return
|
|
64
|
+
return {
|
|
65
|
+
url,
|
|
66
|
+
requestedScopes
|
|
67
|
+
};
|
|
61
68
|
},
|
|
62
69
|
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
63
70
|
return validateAuthorizationCode({
|
|
@@ -79,26 +86,11 @@ const cognito = (options) => {
|
|
|
79
86
|
tokenEndpoint
|
|
80
87
|
});
|
|
81
88
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (!kid || !jwtAlg) return false;
|
|
88
|
-
const publicKey = await getCognitoPublicKey(kid, options.region, options.userPoolId);
|
|
89
|
-
const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
|
|
90
|
-
const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
|
|
91
|
-
algorithms: [jwtAlg],
|
|
92
|
-
issuer: expectedIssuer,
|
|
93
|
-
audience: options.clientId,
|
|
94
|
-
maxTokenAge: "1h"
|
|
95
|
-
});
|
|
96
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
97
|
-
return true;
|
|
98
|
-
} catch (error) {
|
|
99
|
-
logger.error("Failed to verify ID token:", error);
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
89
|
+
idToken: {
|
|
90
|
+
jwks: (header) => getCognitoPublicKey(header.kid, options.region, options.userPoolId),
|
|
91
|
+
issuer: `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`,
|
|
92
|
+
audience: options.clientId,
|
|
93
|
+
maxTokenAge: "1h"
|
|
102
94
|
},
|
|
103
95
|
async getUserInfo(token) {
|
|
104
96
|
if (options.getUserInfo) return options.getUserInfo(token);
|
|
@@ -77,6 +77,7 @@ interface DiscordOptions extends ProviderOptions<DiscordProfile> {
|
|
|
77
77
|
declare const discord: (options: DiscordOptions) => {
|
|
78
78
|
id: "discord";
|
|
79
79
|
name: string;
|
|
80
|
+
callbackPath: string;
|
|
80
81
|
createAuthorizationURL({
|
|
81
82
|
state,
|
|
82
83
|
scopes,
|
|
@@ -90,7 +91,10 @@ declare const discord: (options: DiscordOptions) => {
|
|
|
90
91
|
display?: string | undefined;
|
|
91
92
|
loginHint?: string | undefined;
|
|
92
93
|
additionalParams?: Record<string, string> | undefined;
|
|
93
|
-
}): Promise<
|
|
94
|
+
}): Promise<{
|
|
95
|
+
url: URL;
|
|
96
|
+
requestedScopes: string[];
|
|
97
|
+
}>;
|
|
94
98
|
validateAuthorizationCode: ({
|
|
95
99
|
code,
|
|
96
100
|
redirectURI
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
1
2
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
3
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
4
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
6
|
//#region src/social-providers/discord.ts
|
|
7
|
+
const DISCORD_DEFAULT_SCOPES = ["identify", "email"];
|
|
6
8
|
const discord = (options) => {
|
|
7
9
|
const tokenEndpoint = "https://discord.com/api/oauth2/token";
|
|
8
10
|
return {
|
|
9
11
|
id: "discord",
|
|
10
12
|
name: "Discord",
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const hasBotScope = _scopes.includes("bot");
|
|
13
|
+
callbackPath: "/callback/discord",
|
|
14
|
+
async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
|
|
15
|
+
const requestedScopes = resolveRequestedScopes(options, DISCORD_DEFAULT_SCOPES, scopes);
|
|
16
|
+
const hasBotScope = requestedScopes.includes("bot");
|
|
16
17
|
return createAuthorizationURL({
|
|
17
18
|
id: "discord",
|
|
18
19
|
options,
|
|
19
20
|
authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
|
|
20
|
-
scopes:
|
|
21
|
+
scopes: requestedScopes,
|
|
21
22
|
state,
|
|
22
23
|
redirectURI,
|
|
23
24
|
prompt: options.prompt || "none",
|
|
@@ -20,6 +20,7 @@ interface DropboxOptions extends ProviderOptions<DropboxProfile> {
|
|
|
20
20
|
declare const dropbox: (options: DropboxOptions) => {
|
|
21
21
|
id: "dropbox";
|
|
22
22
|
name: string;
|
|
23
|
+
callbackPath: string;
|
|
23
24
|
createAuthorizationURL: ({
|
|
24
25
|
state,
|
|
25
26
|
scopes,
|
|
@@ -34,7 +35,10 @@ declare const dropbox: (options: DropboxOptions) => {
|
|
|
34
35
|
display?: string | undefined;
|
|
35
36
|
loginHint?: string | undefined;
|
|
36
37
|
additionalParams?: Record<string, string> | undefined;
|
|
37
|
-
}) => Promise<
|
|
38
|
+
}) => Promise<{
|
|
39
|
+
url: URL;
|
|
40
|
+
requestedScopes: string[];
|
|
41
|
+
}>;
|
|
38
42
|
validateAuthorizationCode: ({
|
|
39
43
|
code,
|
|
40
44
|
codeVerifier,
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
1
2
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
3
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
4
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
5
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
6
|
//#region src/social-providers/dropbox.ts
|
|
7
|
+
const DROPBOX_DEFAULT_SCOPES = ["account_info.read"];
|
|
6
8
|
const dropbox = (options) => {
|
|
7
9
|
const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
|
|
8
10
|
return {
|
|
9
11
|
id: "dropbox",
|
|
10
12
|
name: "Dropbox",
|
|
13
|
+
callbackPath: "/callback/dropbox",
|
|
11
14
|
createAuthorizationURL: async ({ state, scopes, codeVerifier, redirectURI, additionalParams }) => {
|
|
12
|
-
|
|
13
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
14
|
-
if (scopes) _scopes.push(...scopes);
|
|
15
|
-
return await createAuthorizationURL({
|
|
15
|
+
return createAuthorizationURL({
|
|
16
16
|
id: "dropbox",
|
|
17
17
|
options,
|
|
18
18
|
authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
|
|
19
|
-
scopes:
|
|
19
|
+
scopes: resolveRequestedScopes(options, DROPBOX_DEFAULT_SCOPES, scopes),
|
|
20
20
|
state,
|
|
21
21
|
redirectURI,
|
|
22
22
|
codeVerifier,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/facebook.d.ts
|
|
3
5
|
interface FacebookProfile {
|
|
4
6
|
id: string;
|
|
@@ -30,6 +32,7 @@ interface FacebookOptions extends ProviderOptions<FacebookProfile> {
|
|
|
30
32
|
declare const facebook: (options: FacebookOptions) => {
|
|
31
33
|
id: "facebook";
|
|
32
34
|
name: string;
|
|
35
|
+
callbackPath: string;
|
|
33
36
|
createAuthorizationURL({
|
|
34
37
|
state,
|
|
35
38
|
scopes,
|
|
@@ -44,7 +47,10 @@ declare const facebook: (options: FacebookOptions) => {
|
|
|
44
47
|
display?: string | undefined;
|
|
45
48
|
loginHint?: string | undefined;
|
|
46
49
|
additionalParams?: Record<string, string> | undefined;
|
|
47
|
-
}): Promise<
|
|
50
|
+
}): Promise<{
|
|
51
|
+
url: URL;
|
|
52
|
+
requestedScopes: string[];
|
|
53
|
+
}>;
|
|
48
54
|
validateAuthorizationCode: ({
|
|
49
55
|
code,
|
|
50
56
|
redirectURI
|
|
@@ -54,7 +60,20 @@ declare const facebook: (options: FacebookOptions) => {
|
|
|
54
60
|
codeVerifier?: string | undefined;
|
|
55
61
|
deviceId?: string | undefined;
|
|
56
62
|
}) => Promise<OAuth2Tokens>;
|
|
57
|
-
|
|
63
|
+
idToken: {
|
|
64
|
+
jwks: {
|
|
65
|
+
(protectedHeader?: jose.JWSHeaderParameters, token?: jose.FlattenedJWSInput): Promise<jose.CryptoKey>;
|
|
66
|
+
coolingDown: boolean;
|
|
67
|
+
fresh: boolean;
|
|
68
|
+
reloading: boolean;
|
|
69
|
+
reload: () => Promise<void>;
|
|
70
|
+
jwks: () => jose.JSONWebKeySet | undefined;
|
|
71
|
+
};
|
|
72
|
+
issuer: string;
|
|
73
|
+
audience: string | string[];
|
|
74
|
+
algorithms: string[];
|
|
75
|
+
allowOpaqueToken: true;
|
|
76
|
+
};
|
|
58
77
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
59
78
|
getUserInfo(token: OAuth2Tokens & {
|
|
60
79
|
user?: {
|
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
3
4
|
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
4
5
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
5
6
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
6
7
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
7
|
-
import { createRemoteJWKSet, decodeJwt
|
|
8
|
+
import { createRemoteJWKSet, decodeJwt } from "jose";
|
|
8
9
|
import { betterFetch } from "@better-fetch/fetch";
|
|
9
10
|
//#region src/social-providers/facebook.ts
|
|
11
|
+
/**
|
|
12
|
+
* Validate an opaque Facebook access token against the configured app.
|
|
13
|
+
*
|
|
14
|
+
* Facebook access tokens are not audience-bound at the Graph `/me` endpoint: a
|
|
15
|
+
* token minted for any Facebook app returns that app's profile. Without this
|
|
16
|
+
* check, a token issued to an unrelated app could be presented to this
|
|
17
|
+
* app's direct sign-in path and accepted as proof of identity. We call the
|
|
18
|
+
* `debug_token` endpoint and require the token to be valid, bound to one of the
|
|
19
|
+
* configured client ids, and tied to a user.
|
|
20
|
+
*
|
|
21
|
+
* @see https://developers.facebook.com/docs/facebook-login/guides/access-tokens/debugging
|
|
22
|
+
*
|
|
23
|
+
* @returns the inspected token's `user_id` when the token is valid and bound to
|
|
24
|
+
* the configured app, otherwise `null`.
|
|
25
|
+
*/
|
|
26
|
+
async function verifyFacebookAccessToken(accessToken, options) {
|
|
27
|
+
const primaryClientId = getPrimaryClientId(options.clientId);
|
|
28
|
+
if (!primaryClientId || !options.clientSecret) return null;
|
|
29
|
+
const clientIds = Array.isArray(options.clientId) ? options.clientId : [options.clientId];
|
|
30
|
+
const { data, error } = await betterFetch("https://graph.facebook.com/debug_token", { query: {
|
|
31
|
+
input_token: accessToken,
|
|
32
|
+
access_token: `${primaryClientId}|${options.clientSecret}`
|
|
33
|
+
} });
|
|
34
|
+
if (error || !data?.data) return null;
|
|
35
|
+
const { is_valid, app_id, user_id } = data.data;
|
|
36
|
+
if (is_valid !== true || !app_id || !clientIds.includes(app_id) || !user_id) return null;
|
|
37
|
+
return user_id;
|
|
38
|
+
}
|
|
39
|
+
const FACEBOOK_DEFAULT_SCOPES = ["email", "public_profile"];
|
|
10
40
|
const facebook = (options) => {
|
|
11
41
|
return {
|
|
12
42
|
id: "facebook",
|
|
13
43
|
name: "Facebook",
|
|
44
|
+
callbackPath: "/callback/facebook",
|
|
14
45
|
async createAuthorizationURL({ state, scopes, redirectURI, loginHint, additionalParams }) {
|
|
15
46
|
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
16
47
|
logger.error("Client ID and client secret are required for Facebook. Make sure to provide them in the options.");
|
|
17
48
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
18
49
|
}
|
|
19
|
-
|
|
20
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
21
|
-
if (scopes) _scopes.push(...scopes);
|
|
22
|
-
return await createAuthorizationURL({
|
|
50
|
+
return createAuthorizationURL({
|
|
23
51
|
id: "facebook",
|
|
24
52
|
options,
|
|
25
53
|
authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
|
|
26
|
-
scopes:
|
|
54
|
+
scopes: resolveRequestedScopes(options, FACEBOOK_DEFAULT_SCOPES, scopes),
|
|
27
55
|
state,
|
|
28
56
|
redirectURI,
|
|
29
57
|
loginHint,
|
|
@@ -41,21 +69,12 @@ const facebook = (options) => {
|
|
|
41
69
|
tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token"
|
|
42
70
|
});
|
|
43
71
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
audience: options.clientId,
|
|
51
|
-
issuer: "https://www.facebook.com"
|
|
52
|
-
});
|
|
53
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
54
|
-
return !!jwtClaims;
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
72
|
+
idToken: {
|
|
73
|
+
jwks: createRemoteJWKSet(new URL("https://limited.facebook.com/.well-known/oauth/openid/jwks/")),
|
|
74
|
+
issuer: "https://www.facebook.com",
|
|
75
|
+
audience: options.clientId,
|
|
76
|
+
algorithms: ["RS256"],
|
|
77
|
+
allowOpaqueToken: true
|
|
59
78
|
},
|
|
60
79
|
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
61
80
|
return refreshAccessToken({
|
|
@@ -96,6 +115,10 @@ const facebook = (options) => {
|
|
|
96
115
|
data: profile
|
|
97
116
|
};
|
|
98
117
|
}
|
|
118
|
+
const accessToken = token.accessToken;
|
|
119
|
+
if (!accessToken) return null;
|
|
120
|
+
const tokenUserId = await verifyFacebookAccessToken(accessToken, options);
|
|
121
|
+
if (!tokenUserId) return null;
|
|
99
122
|
const { data: profile, error } = await betterFetch("https://graph.facebook.com/me?fields=" + [
|
|
100
123
|
"id",
|
|
101
124
|
"name",
|
|
@@ -104,9 +127,10 @@ const facebook = (options) => {
|
|
|
104
127
|
...options?.fields || []
|
|
105
128
|
].join(","), { auth: {
|
|
106
129
|
type: "Bearer",
|
|
107
|
-
token:
|
|
130
|
+
token: accessToken
|
|
108
131
|
} });
|
|
109
132
|
if (error) return null;
|
|
133
|
+
if (profile.id !== tokenUserId) return null;
|
|
110
134
|
const userMap = await options.mapProfileToUser?.(profile);
|
|
111
135
|
return {
|
|
112
136
|
user: {
|
|
@@ -12,6 +12,7 @@ interface FigmaOptions extends ProviderOptions<FigmaProfile> {
|
|
|
12
12
|
declare const figma: (options: FigmaOptions) => {
|
|
13
13
|
id: "figma";
|
|
14
14
|
name: string;
|
|
15
|
+
callbackPath: string;
|
|
15
16
|
createAuthorizationURL({
|
|
16
17
|
state,
|
|
17
18
|
scopes,
|
|
@@ -26,7 +27,10 @@ declare const figma: (options: FigmaOptions) => {
|
|
|
26
27
|
display?: string | undefined;
|
|
27
28
|
loginHint?: string | undefined;
|
|
28
29
|
additionalParams?: Record<string, string> | undefined;
|
|
29
|
-
}): Promise<
|
|
30
|
+
}): Promise<{
|
|
31
|
+
url: URL;
|
|
32
|
+
requestedScopes: string[];
|
|
33
|
+
}>;
|
|
30
34
|
validateAuthorizationCode: ({
|
|
31
35
|
code,
|
|
32
36
|
codeVerifier,
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.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";
|
|
6
7
|
import { betterFetch } from "@better-fetch/fetch";
|
|
7
8
|
//#region src/social-providers/figma.ts
|
|
9
|
+
const FIGMA_DEFAULT_SCOPES = ["current_user:read"];
|
|
8
10
|
const figma = (options) => {
|
|
9
11
|
const tokenEndpoint = "https://api.figma.com/v1/oauth/token";
|
|
10
12
|
return {
|
|
11
13
|
id: "figma",
|
|
12
14
|
name: "Figma",
|
|
15
|
+
callbackPath: "/callback/figma",
|
|
13
16
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
|
|
14
17
|
if (!options.clientId || !options.clientSecret) {
|
|
15
18
|
logger.error("Client Id and Client Secret are required for Figma. Make sure to provide them in the options.");
|
|
16
19
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
17
20
|
}
|
|
18
21
|
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Figma");
|
|
19
|
-
|
|
20
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
21
|
-
if (scopes) _scopes.push(...scopes);
|
|
22
|
-
return await createAuthorizationURL({
|
|
22
|
+
return createAuthorizationURL({
|
|
23
23
|
id: "figma",
|
|
24
24
|
options,
|
|
25
25
|
authorizationEndpoint: "https://www.figma.com/oauth",
|
|
26
|
-
scopes:
|
|
26
|
+
scopes: resolveRequestedScopes(options, FIGMA_DEFAULT_SCOPES, scopes),
|
|
27
27
|
state,
|
|
28
28
|
codeVerifier,
|
|
29
29
|
redirectURI,
|
|
@@ -52,6 +52,7 @@ interface GithubOptions extends ProviderOptions<GithubProfile> {
|
|
|
52
52
|
declare const github: (options: GithubOptions) => {
|
|
53
53
|
id: "github";
|
|
54
54
|
name: string;
|
|
55
|
+
callbackPath: string;
|
|
55
56
|
createAuthorizationURL({
|
|
56
57
|
state,
|
|
57
58
|
scopes,
|
|
@@ -67,7 +68,10 @@ declare const github: (options: GithubOptions) => {
|
|
|
67
68
|
display?: string | undefined;
|
|
68
69
|
loginHint?: string | undefined;
|
|
69
70
|
additionalParams?: Record<string, string> | undefined;
|
|
70
|
-
}): Promise<
|
|
71
|
+
}): Promise<{
|
|
72
|
+
url: URL;
|
|
73
|
+
requestedScopes: string[];
|
|
74
|
+
}>;
|
|
71
75
|
validateAuthorizationCode: ({
|
|
72
76
|
code,
|
|
73
77
|
codeVerifier,
|