@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.5
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 +3 -3
- package/dist/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +2 -2
- 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/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/index.d.mts +4 -2
- package/dist/oauth2/index.mjs +3 -1
- 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 +14 -0
- package/dist/oauth2/verify.mjs +23 -7
- 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 +221 -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 +31 -6
- package/dist/social-providers/microsoft-entra-id.mjs +26 -37
- 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 +9 -5
- package/dist/social-providers/zoom.d.mts +6 -1
- package/dist/social-providers/zoom.mjs +15 -9
- package/dist/types/context.d.mts +10 -8
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +92 -1
- package/package.json +5 -5
- package/src/db/adapter/factory.ts +10 -2
- package/src/db/get-tables.ts +8 -3
- package/src/db/schema/account.ts +14 -2
- 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/index.ts +17 -1
- 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 +62 -11
- 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 +65 -64
- 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 +21 -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 +20 -8
- package/src/social-providers/zoom.ts +19 -6
- package/src/types/context.ts +8 -8
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +119 -0
|
@@ -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,
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { logger } from "../env/logger.mjs";
|
|
2
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
2
3
|
import { getOAuth2Tokens } from "../oauth2/utils.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
6
|
import { authorizationCodeRequest } from "../oauth2/validate-authorization-code.mjs";
|
|
6
7
|
import { betterFetch } from "@better-fetch/fetch";
|
|
7
8
|
//#region src/social-providers/github.ts
|
|
9
|
+
const GITHUB_DEFAULT_SCOPES = ["read:user", "user:email"];
|
|
8
10
|
const github = (options) => {
|
|
9
11
|
const tokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
10
12
|
return {
|
|
11
13
|
id: "github",
|
|
12
14
|
name: "GitHub",
|
|
15
|
+
callbackPath: "/callback/github",
|
|
13
16
|
createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI, additionalParams }) {
|
|
14
|
-
const _scopes = options.disableDefaultScope ? [] : ["read:user", "user:email"];
|
|
15
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
16
|
-
if (scopes) _scopes.push(...scopes);
|
|
17
17
|
return createAuthorizationURL({
|
|
18
18
|
id: "github",
|
|
19
19
|
options,
|
|
20
20
|
authorizationEndpoint: "https://github.com/login/oauth/authorize",
|
|
21
|
-
scopes:
|
|
21
|
+
scopes: resolveRequestedScopes(options, GITHUB_DEFAULT_SCOPES, scopes),
|
|
22
22
|
state,
|
|
23
23
|
codeVerifier,
|
|
24
24
|
redirectURI,
|
|
@@ -52,6 +52,7 @@ interface GitlabOptions extends ProviderOptions<GitlabProfile> {
|
|
|
52
52
|
declare const gitlab: (options: GitlabOptions) => {
|
|
53
53
|
id: "gitlab";
|
|
54
54
|
name: string;
|
|
55
|
+
callbackPath: string;
|
|
55
56
|
createAuthorizationURL: ({
|
|
56
57
|
state,
|
|
57
58
|
scopes,
|
|
@@ -67,7 +68,10 @@ declare const gitlab: (options: GitlabOptions) => {
|
|
|
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
|
redirectURI,
|
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -14,21 +15,20 @@ const issuerToEndpoints = (issuer) => {
|
|
|
14
15
|
userinfoEndpoint: cleanDoubleSlashes(`${baseUrl}/api/v4/user`)
|
|
15
16
|
};
|
|
16
17
|
};
|
|
18
|
+
const GITLAB_DEFAULT_SCOPES = ["read_user"];
|
|
17
19
|
const gitlab = (options) => {
|
|
18
20
|
const { authorizationEndpoint, tokenEndpoint, userinfoEndpoint } = issuerToEndpoints(options.issuer);
|
|
19
21
|
const issuerId = "gitlab";
|
|
20
22
|
return {
|
|
21
23
|
id: issuerId,
|
|
22
24
|
name: "Gitlab",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (scopes) _scopes.push(...scopes);
|
|
27
|
-
return await createAuthorizationURL({
|
|
25
|
+
callbackPath: "/callback/gitlab",
|
|
26
|
+
createAuthorizationURL: ({ state, scopes, codeVerifier, loginHint, redirectURI, additionalParams }) => {
|
|
27
|
+
return createAuthorizationURL({
|
|
28
28
|
id: issuerId,
|
|
29
29
|
options,
|
|
30
30
|
authorizationEndpoint,
|
|
31
|
-
scopes:
|
|
31
|
+
scopes: resolveRequestedScopes(options, GITLAB_DEFAULT_SCOPES, scopes),
|
|
32
32
|
state,
|
|
33
33
|
redirectURI,
|
|
34
34
|
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/google.d.ts
|
|
3
5
|
interface GoogleProfile {
|
|
4
6
|
aud: string;
|
|
@@ -37,13 +39,28 @@ interface GoogleOptions extends ProviderOptions<GoogleProfile> {
|
|
|
37
39
|
*/
|
|
38
40
|
display?: ("page" | "popup" | "touch" | "wap") | undefined;
|
|
39
41
|
/**
|
|
40
|
-
* The hosted domain
|
|
42
|
+
* The hosted domain (Google Workspace) the user must belong to.
|
|
43
|
+
*
|
|
44
|
+
* This is sent to Google as the `hd` authorization hint and, when set, is
|
|
45
|
+
* also enforced against the `hd` claim of the returned id token/profile.
|
|
46
|
+
* Sign-in is rejected when the claim is missing or does not match, so this
|
|
47
|
+
* can be used to restrict sign-in to a Workspace domain.
|
|
41
48
|
*/
|
|
42
49
|
hd?: string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Enable incremental authorization via Google's `include_granted_scopes`
|
|
52
|
+
* parameter. When enabled, Google reports the user's full granted scope set
|
|
53
|
+
* in the token response.
|
|
54
|
+
*
|
|
55
|
+
* @default true
|
|
56
|
+
*/
|
|
57
|
+
includeGrantedScopes?: boolean | undefined;
|
|
43
58
|
}
|
|
44
59
|
declare const google: (options: GoogleOptions) => {
|
|
45
60
|
id: "google";
|
|
46
61
|
name: string;
|
|
62
|
+
callbackPath: string;
|
|
63
|
+
grantAuthority: "full-grant" | "projection";
|
|
47
64
|
createAuthorizationURL({
|
|
48
65
|
state,
|
|
49
66
|
scopes,
|
|
@@ -60,7 +77,10 @@ declare const google: (options: GoogleOptions) => {
|
|
|
60
77
|
display?: string | undefined;
|
|
61
78
|
loginHint?: string | undefined;
|
|
62
79
|
additionalParams?: Record<string, string> | undefined;
|
|
63
|
-
}): Promise<
|
|
80
|
+
}): Promise<{
|
|
81
|
+
url: URL;
|
|
82
|
+
requestedScopes: string[];
|
|
83
|
+
}>;
|
|
64
84
|
validateAuthorizationCode: ({
|
|
65
85
|
code,
|
|
66
86
|
codeVerifier,
|
|
@@ -72,7 +92,13 @@ declare const google: (options: GoogleOptions) => {
|
|
|
72
92
|
deviceId?: string | undefined;
|
|
73
93
|
}) => Promise<OAuth2Tokens>;
|
|
74
94
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
75
|
-
|
|
95
|
+
idToken: {
|
|
96
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
97
|
+
issuer: string[];
|
|
98
|
+
audience: string | string[];
|
|
99
|
+
maxTokenAge: string;
|
|
100
|
+
verifyClaims: ((claims: Record<string, unknown>) => boolean) | undefined;
|
|
101
|
+
};
|
|
76
102
|
getUserInfo(token: OAuth2Tokens & {
|
|
77
103
|
user?: {
|
|
78
104
|
name?: {
|
|
@@ -1,34 +1,35 @@
|
|
|
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/google.ts
|
|
11
|
+
const GOOGLE_DEFAULT_SCOPES = [
|
|
12
|
+
"email",
|
|
13
|
+
"profile",
|
|
14
|
+
"openid"
|
|
15
|
+
];
|
|
10
16
|
const google = (options) => {
|
|
11
17
|
return {
|
|
12
18
|
id: "google",
|
|
13
19
|
name: "Google",
|
|
20
|
+
callbackPath: "/callback/google",
|
|
21
|
+
grantAuthority: options.includeGrantedScopes !== false ? "full-grant" : "projection",
|
|
14
22
|
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, display, additionalParams }) {
|
|
15
23
|
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
16
24
|
logger.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options.");
|
|
17
25
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
18
26
|
}
|
|
19
27
|
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Google");
|
|
20
|
-
|
|
21
|
-
"email",
|
|
22
|
-
"profile",
|
|
23
|
-
"openid"
|
|
24
|
-
];
|
|
25
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
26
|
-
if (scopes) _scopes.push(...scopes);
|
|
27
|
-
return await createAuthorizationURL({
|
|
28
|
+
return createAuthorizationURL({
|
|
28
29
|
id: "google",
|
|
29
30
|
options,
|
|
30
31
|
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
31
|
-
scopes:
|
|
32
|
+
scopes: resolveRequestedScopes(options, GOOGLE_DEFAULT_SCOPES, scopes),
|
|
32
33
|
state,
|
|
33
34
|
codeVerifier,
|
|
34
35
|
redirectURI,
|
|
@@ -37,9 +38,9 @@ const google = (options) => {
|
|
|
37
38
|
display: display || options.display,
|
|
38
39
|
loginHint,
|
|
39
40
|
hd: options.hd,
|
|
40
|
-
additionalParams: {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
additionalParams: options.includeGrantedScopes === false ? { ...additionalParams ?? {} } : {
|
|
42
|
+
...additionalParams ?? {},
|
|
43
|
+
include_granted_scopes: "true"
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
46
|
},
|
|
@@ -63,28 +64,21 @@ const google = (options) => {
|
|
|
63
64
|
tokenEndpoint: "https://oauth2.googleapis.com/token"
|
|
64
65
|
});
|
|
65
66
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const { payload: jwtClaims } = await jwtVerify(token, await getGooglePublicKey(kid), {
|
|
73
|
-
algorithms: [jwtAlg],
|
|
74
|
-
issuer: ["https://accounts.google.com", "accounts.google.com"],
|
|
75
|
-
audience: options.clientId,
|
|
76
|
-
maxTokenAge: "1h"
|
|
77
|
-
});
|
|
78
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
79
|
-
return true;
|
|
80
|
-
} catch {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
67
|
+
idToken: {
|
|
68
|
+
jwks: (header) => getGooglePublicKey(header.kid),
|
|
69
|
+
issuer: ["https://accounts.google.com", "accounts.google.com"],
|
|
70
|
+
audience: options.clientId,
|
|
71
|
+
maxTokenAge: "1h",
|
|
72
|
+
verifyClaims: options.hd ? (claims) => claims.hd === options.hd : void 0
|
|
83
73
|
},
|
|
84
74
|
async getUserInfo(token) {
|
|
85
75
|
if (options.getUserInfo) return options.getUserInfo(token);
|
|
86
76
|
if (!token.idToken) return null;
|
|
87
77
|
const user = decodeJwt(token.idToken);
|
|
78
|
+
if (options.hd && user.hd !== options.hd) {
|
|
79
|
+
logger.error(`Google sign-in rejected: id token hosted domain (hd) "${user.hd ?? "<missing>"}" does not match the configured "hd" option "${options.hd}".`);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
88
82
|
const userMap = await options.mapProfileToUser?.(user);
|
|
89
83
|
return {
|
|
90
84
|
user: {
|
|
@@ -34,6 +34,7 @@ interface HuggingFaceOptions extends ProviderOptions<HuggingFaceProfile> {
|
|
|
34
34
|
declare const huggingface: (options: HuggingFaceOptions) => {
|
|
35
35
|
id: "huggingface";
|
|
36
36
|
name: string;
|
|
37
|
+
callbackPath: string;
|
|
37
38
|
createAuthorizationURL({
|
|
38
39
|
state,
|
|
39
40
|
scopes,
|
|
@@ -48,7 +49,10 @@ declare const huggingface: (options: HuggingFaceOptions) => {
|
|
|
48
49
|
display?: string | undefined;
|
|
49
50
|
loginHint?: string | undefined;
|
|
50
51
|
additionalParams?: Record<string, string> | undefined;
|
|
51
|
-
}): Promise<
|
|
52
|
+
}): Promise<{
|
|
53
|
+
url: URL;
|
|
54
|
+
requestedScopes: string[];
|
|
55
|
+
}>;
|
|
52
56
|
validateAuthorizationCode: ({
|
|
53
57
|
code,
|
|
54
58
|
codeVerifier,
|
|
@@ -1,26 +1,26 @@
|
|
|
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/huggingface.ts
|
|
7
|
+
const HUGGINGFACE_DEFAULT_SCOPES = [
|
|
8
|
+
"openid",
|
|
9
|
+
"profile",
|
|
10
|
+
"email"
|
|
11
|
+
];
|
|
6
12
|
const huggingface = (options) => {
|
|
7
13
|
const tokenEndpoint = "https://huggingface.co/oauth/token";
|
|
8
14
|
return {
|
|
9
15
|
id: "huggingface",
|
|
10
16
|
name: "Hugging Face",
|
|
17
|
+
callbackPath: "/callback/huggingface",
|
|
11
18
|
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
|
|
12
|
-
const _scopes = options.disableDefaultScope ? [] : [
|
|
13
|
-
"openid",
|
|
14
|
-
"profile",
|
|
15
|
-
"email"
|
|
16
|
-
];
|
|
17
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
18
|
-
if (scopes) _scopes.push(...scopes);
|
|
19
19
|
return createAuthorizationURL({
|
|
20
20
|
id: "huggingface",
|
|
21
21
|
options,
|
|
22
22
|
authorizationEndpoint: "https://huggingface.co/oauth/authorize",
|
|
23
|
-
scopes:
|
|
23
|
+
scopes: resolveRequestedScopes(options, HUGGINGFACE_DEFAULT_SCOPES, scopes),
|
|
24
24
|
state,
|
|
25
25
|
codeVerifier,
|
|
26
26
|
redirectURI,
|