@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.
Files changed (150) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/adapter/factory.mjs +2 -2
  4. package/dist/db/get-tables.mjs +3 -3
  5. package/dist/db/schema/account.d.mts +1 -1
  6. package/dist/db/schema/account.mjs +1 -1
  7. package/dist/env/env-impl.mjs +1 -1
  8. package/dist/error/codes.d.mts +5 -0
  9. package/dist/error/codes.mjs +5 -0
  10. package/dist/index.d.mts +2 -2
  11. package/dist/instrumentation/tracer.mjs +1 -1
  12. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  13. package/dist/oauth2/create-authorization-url.mjs +5 -2
  14. package/dist/oauth2/index.d.mts +4 -2
  15. package/dist/oauth2/index.mjs +3 -1
  16. package/dist/oauth2/oauth-provider.d.mts +128 -9
  17. package/dist/oauth2/refresh-access-token.mjs +1 -1
  18. package/dist/oauth2/scopes.d.mts +76 -0
  19. package/dist/oauth2/scopes.mjs +96 -0
  20. package/dist/oauth2/utils.mjs +2 -1
  21. package/dist/oauth2/verify-id-token.d.mts +26 -0
  22. package/dist/oauth2/verify-id-token.mjs +62 -0
  23. package/dist/oauth2/verify.d.mts +14 -0
  24. package/dist/oauth2/verify.mjs +23 -7
  25. package/dist/social-providers/apple.d.mts +14 -2
  26. package/dist/social-providers/apple.mjs +12 -36
  27. package/dist/social-providers/atlassian.d.mts +5 -1
  28. package/dist/social-providers/atlassian.mjs +4 -4
  29. package/dist/social-providers/cognito.d.mts +13 -2
  30. package/dist/social-providers/cognito.mjs +24 -32
  31. package/dist/social-providers/discord.d.mts +5 -1
  32. package/dist/social-providers/discord.mjs +7 -6
  33. package/dist/social-providers/dropbox.d.mts +5 -1
  34. package/dist/social-providers/dropbox.mjs +5 -5
  35. package/dist/social-providers/facebook.d.mts +21 -2
  36. package/dist/social-providers/facebook.mjs +46 -22
  37. package/dist/social-providers/figma.d.mts +5 -1
  38. package/dist/social-providers/figma.mjs +5 -5
  39. package/dist/social-providers/github.d.mts +5 -1
  40. package/dist/social-providers/github.mjs +4 -4
  41. package/dist/social-providers/gitlab.d.mts +5 -1
  42. package/dist/social-providers/gitlab.mjs +6 -6
  43. package/dist/social-providers/google.d.mts +29 -3
  44. package/dist/social-providers/google.mjs +24 -30
  45. package/dist/social-providers/huggingface.d.mts +5 -1
  46. package/dist/social-providers/huggingface.mjs +8 -8
  47. package/dist/social-providers/index.d.mts +221 -42
  48. package/dist/social-providers/kakao.d.mts +5 -1
  49. package/dist/social-providers/kakao.mjs +8 -8
  50. package/dist/social-providers/kick.d.mts +5 -1
  51. package/dist/social-providers/kick.mjs +4 -4
  52. package/dist/social-providers/line.d.mts +8 -2
  53. package/dist/social-providers/line.mjs +12 -14
  54. package/dist/social-providers/linear.d.mts +5 -1
  55. package/dist/social-providers/linear.mjs +4 -4
  56. package/dist/social-providers/linkedin.d.mts +5 -1
  57. package/dist/social-providers/linkedin.mjs +10 -10
  58. package/dist/social-providers/microsoft-entra-id.d.mts +31 -6
  59. package/dist/social-providers/microsoft-entra-id.mjs +26 -37
  60. package/dist/social-providers/naver.d.mts +5 -1
  61. package/dist/social-providers/naver.mjs +4 -4
  62. package/dist/social-providers/notion.d.mts +5 -1
  63. package/dist/social-providers/notion.mjs +4 -4
  64. package/dist/social-providers/paybin.d.mts +5 -1
  65. package/dist/social-providers/paybin.mjs +10 -10
  66. package/dist/social-providers/paypal.d.mts +5 -2
  67. package/dist/social-providers/paypal.mjs +8 -13
  68. package/dist/social-providers/polar.d.mts +5 -1
  69. package/dist/social-providers/polar.mjs +8 -8
  70. package/dist/social-providers/railway.d.mts +5 -1
  71. package/dist/social-providers/railway.mjs +9 -9
  72. package/dist/social-providers/reddit.d.mts +5 -1
  73. package/dist/social-providers/reddit.mjs +9 -8
  74. package/dist/social-providers/roblox.d.mts +5 -1
  75. package/dist/social-providers/roblox.mjs +5 -5
  76. package/dist/social-providers/salesforce.d.mts +5 -1
  77. package/dist/social-providers/salesforce.mjs +8 -8
  78. package/dist/social-providers/slack.d.mts +5 -1
  79. package/dist/social-providers/slack.mjs +9 -9
  80. package/dist/social-providers/spotify.d.mts +5 -1
  81. package/dist/social-providers/spotify.mjs +5 -5
  82. package/dist/social-providers/tiktok.d.mts +5 -1
  83. package/dist/social-providers/tiktok.mjs +9 -5
  84. package/dist/social-providers/twitch.d.mts +5 -1
  85. package/dist/social-providers/twitch.mjs +4 -4
  86. package/dist/social-providers/twitter.d.mts +6 -4
  87. package/dist/social-providers/twitter.mjs +9 -9
  88. package/dist/social-providers/vercel.d.mts +5 -1
  89. package/dist/social-providers/vercel.mjs +4 -7
  90. package/dist/social-providers/vk.d.mts +5 -1
  91. package/dist/social-providers/vk.mjs +5 -5
  92. package/dist/social-providers/wechat.d.mts +5 -1
  93. package/dist/social-providers/wechat.mjs +9 -5
  94. package/dist/social-providers/zoom.d.mts +6 -1
  95. package/dist/social-providers/zoom.mjs +15 -9
  96. package/dist/types/context.d.mts +10 -8
  97. package/dist/types/index.d.mts +1 -1
  98. package/dist/types/init-options.d.mts +92 -1
  99. package/package.json +5 -5
  100. package/src/db/adapter/factory.ts +10 -2
  101. package/src/db/get-tables.ts +8 -3
  102. package/src/db/schema/account.ts +14 -2
  103. package/src/env/env-impl.ts +1 -2
  104. package/src/error/codes.ts +5 -0
  105. package/src/oauth2/create-authorization-url.ts +2 -2
  106. package/src/oauth2/index.ts +17 -1
  107. package/src/oauth2/oauth-provider.ts +140 -10
  108. package/src/oauth2/refresh-access-token.ts +2 -2
  109. package/src/oauth2/scopes.ts +118 -0
  110. package/src/oauth2/utils.ts +2 -5
  111. package/src/oauth2/verify-id-token.ts +111 -0
  112. package/src/oauth2/verify.ts +62 -11
  113. package/src/social-providers/apple.ts +24 -61
  114. package/src/social-providers/atlassian.ts +12 -8
  115. package/src/social-providers/cognito.ts +25 -47
  116. package/src/social-providers/discord.ts +19 -8
  117. package/src/social-providers/dropbox.ts +13 -7
  118. package/src/social-providers/facebook.ts +97 -51
  119. package/src/social-providers/figma.ts +13 -9
  120. package/src/social-providers/github.ts +12 -8
  121. package/src/social-providers/gitlab.ts +14 -8
  122. package/src/social-providers/google.ts +66 -47
  123. package/src/social-providers/huggingface.ts +12 -8
  124. package/src/social-providers/kakao.ts +16 -8
  125. package/src/social-providers/kick.ts +12 -7
  126. package/src/social-providers/line.ts +37 -37
  127. package/src/social-providers/linear.ts +12 -6
  128. package/src/social-providers/linkedin.ts +14 -10
  129. package/src/social-providers/microsoft-entra-id.ts +65 -64
  130. package/src/social-providers/naver.ts +12 -6
  131. package/src/social-providers/notion.ts +12 -6
  132. package/src/social-providers/paybin.ts +14 -11
  133. package/src/social-providers/paypal.ts +6 -25
  134. package/src/social-providers/polar.ts +12 -8
  135. package/src/social-providers/railway.ts +13 -9
  136. package/src/social-providers/reddit.ts +21 -10
  137. package/src/social-providers/roblox.ts +18 -7
  138. package/src/social-providers/salesforce.ts +12 -8
  139. package/src/social-providers/slack.ts +18 -9
  140. package/src/social-providers/spotify.ts +13 -7
  141. package/src/social-providers/tiktok.ts +13 -7
  142. package/src/social-providers/twitch.ts +12 -8
  143. package/src/social-providers/twitter.ts +17 -8
  144. package/src/social-providers/vercel.ts +16 -10
  145. package/src/social-providers/vk.ts +13 -7
  146. package/src/social-providers/wechat.ts +20 -8
  147. package/src/social-providers/zoom.ts +19 -6
  148. package/src/types/context.ts +8 -8
  149. package/src/types/index.ts +7 -0
  150. 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, jwtVerify } from "jose";
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
- const _scopes = options.disableDefaultScope ? [] : ["email", "public_profile"];
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: _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
- async verifyIdToken(token, nonce) {
45
- if (options.disableIdTokenSignIn) return false;
46
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
47
- if (token.split(".").length === 3) try {
48
- const { payload: jwtClaims } = await jwtVerify(token, createRemoteJWKSet(new URL("https://limited.facebook.com/.well-known/oauth/openid/jwks/")), {
49
- algorithms: ["RS256"],
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: token.accessToken
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<URL>;
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
- const _scopes = options.disableDefaultScope ? [] : ["current_user:read"];
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: _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<URL>;
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: _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<URL>;
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
- createAuthorizationURL: async ({ state, scopes, codeVerifier, loginHint, redirectURI, additionalParams }) => {
24
- const _scopes = options.disableDefaultScope ? [] : ["read_user"];
25
- if (options.scope) _scopes.push(...options.scope);
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: _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 of the user
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<URL>;
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
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
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, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
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
- const _scopes = options.disableDefaultScope ? [] : [
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: _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
- include_granted_scopes: "true",
42
- ...additionalParams ?? {}
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
- async verifyIdToken(token, nonce) {
67
- if (options.disableIdTokenSignIn) return false;
68
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
69
- try {
70
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
71
- if (!kid || !jwtAlg) return false;
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<URL>;
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: _scopes,
23
+ scopes: resolveRequestedScopes(options, HUGGINGFACE_DEFAULT_SCOPES, scopes),
24
24
  state,
25
25
  codeVerifier,
26
26
  redirectURI,