@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.10

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 (218) hide show
  1. package/dist/api/index.d.mts +44 -1
  2. package/dist/api/index.mjs +69 -4
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +91 -3
  7. package/dist/db/adapter/get-id-field.mjs +1 -1
  8. package/dist/db/adapter/index.d.mts +87 -3
  9. package/dist/db/adapter/types.d.mts +1 -1
  10. package/dist/db/get-tables.mjs +2 -1
  11. package/dist/db/type.d.mts +17 -0
  12. package/dist/env/env-impl.mjs +1 -1
  13. package/dist/error/codes.d.mts +1 -0
  14. package/dist/error/codes.mjs +1 -0
  15. package/dist/error/index.d.mts +7 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/api.mjs +12 -0
  18. package/dist/instrumentation/noop.mjs +42 -0
  19. package/dist/instrumentation/pure.index.d.mts +7 -0
  20. package/dist/instrumentation/pure.index.mjs +7 -0
  21. package/dist/instrumentation/tracer.mjs +6 -3
  22. package/dist/oauth2/authorization-params.d.mts +12 -0
  23. package/dist/oauth2/authorization-params.mjs +12 -0
  24. package/dist/oauth2/basic-credentials.d.mts +30 -0
  25. package/dist/oauth2/basic-credentials.mjs +64 -0
  26. package/dist/oauth2/client-assertion.d.mts +38 -22
  27. package/dist/oauth2/client-assertion.mjs +63 -28
  28. package/dist/oauth2/client-credentials-token.d.mts +19 -40
  29. package/dist/oauth2/client-credentials-token.mjs +20 -31
  30. package/dist/oauth2/create-authorization-url.d.mts +11 -1
  31. package/dist/oauth2/create-authorization-url.mjs +27 -7
  32. package/dist/oauth2/dpop.d.mts +142 -0
  33. package/dist/oauth2/dpop.mjs +246 -0
  34. package/dist/oauth2/index.d.mts +14 -9
  35. package/dist/oauth2/index.mjs +12 -8
  36. package/dist/oauth2/oauth-provider.d.mts +150 -10
  37. package/dist/oauth2/refresh-access-token.d.mts +20 -40
  38. package/dist/oauth2/refresh-access-token.mjs +38 -36
  39. package/dist/oauth2/reject-redirects.mjs +65 -0
  40. package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
  41. package/dist/oauth2/token-endpoint-auth.mjs +89 -0
  42. package/dist/oauth2/utils.d.mts +23 -1
  43. package/dist/oauth2/utils.mjs +48 -2
  44. package/dist/oauth2/validate-authorization-code.d.mts +17 -52
  45. package/dist/oauth2/validate-authorization-code.mjs +28 -34
  46. package/dist/oauth2/verify-id-token.d.mts +27 -0
  47. package/dist/oauth2/verify-id-token.mjs +62 -0
  48. package/dist/oauth2/verify.d.mts +88 -15
  49. package/dist/oauth2/verify.mjs +203 -25
  50. package/dist/social-providers/apple.d.mts +16 -4
  51. package/dist/social-providers/apple.mjs +17 -24
  52. package/dist/social-providers/atlassian.d.mts +5 -1
  53. package/dist/social-providers/atlassian.mjs +6 -3
  54. package/dist/social-providers/cognito.d.mts +27 -3
  55. package/dist/social-providers/cognito.mjs +15 -25
  56. package/dist/social-providers/discord.d.mts +7 -3
  57. package/dist/social-providers/discord.mjs +16 -3
  58. package/dist/social-providers/dropbox.d.mts +5 -1
  59. package/dist/social-providers/dropbox.mjs +5 -4
  60. package/dist/social-providers/facebook.d.mts +24 -5
  61. package/dist/social-providers/facebook.mjs +54 -20
  62. package/dist/social-providers/figma.d.mts +5 -1
  63. package/dist/social-providers/figma.mjs +4 -3
  64. package/dist/social-providers/github.d.mts +6 -2
  65. package/dist/social-providers/github.mjs +5 -4
  66. package/dist/social-providers/gitlab.d.mts +5 -1
  67. package/dist/social-providers/gitlab.mjs +3 -2
  68. package/dist/social-providers/google.d.mts +53 -5
  69. package/dist/social-providers/google.mjs +69 -25
  70. package/dist/social-providers/huggingface.d.mts +5 -1
  71. package/dist/social-providers/huggingface.mjs +3 -2
  72. package/dist/social-providers/index.d.mts +221 -45
  73. package/dist/social-providers/index.mjs +2 -2
  74. package/dist/social-providers/kakao.d.mts +5 -1
  75. package/dist/social-providers/kakao.mjs +3 -2
  76. package/dist/social-providers/kick.d.mts +5 -1
  77. package/dist/social-providers/kick.mjs +3 -2
  78. package/dist/social-providers/line.d.mts +8 -2
  79. package/dist/social-providers/line.mjs +5 -6
  80. package/dist/social-providers/linear.d.mts +5 -1
  81. package/dist/social-providers/linear.mjs +3 -2
  82. package/dist/social-providers/linkedin.d.mts +7 -3
  83. package/dist/social-providers/linkedin.mjs +4 -3
  84. package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
  85. package/dist/social-providers/microsoft-entra-id.mjs +37 -28
  86. package/dist/social-providers/naver.d.mts +5 -1
  87. package/dist/social-providers/naver.mjs +3 -2
  88. package/dist/social-providers/notion.d.mts +5 -1
  89. package/dist/social-providers/notion.mjs +5 -2
  90. package/dist/social-providers/paybin.d.mts +5 -1
  91. package/dist/social-providers/paybin.mjs +4 -3
  92. package/dist/social-providers/paypal.d.mts +6 -2
  93. package/dist/social-providers/paypal.mjs +19 -14
  94. package/dist/social-providers/polar.d.mts +5 -1
  95. package/dist/social-providers/polar.mjs +3 -2
  96. package/dist/social-providers/railway.d.mts +5 -1
  97. package/dist/social-providers/railway.mjs +3 -2
  98. package/dist/social-providers/reddit.d.mts +5 -1
  99. package/dist/social-providers/reddit.mjs +7 -5
  100. package/dist/social-providers/roblox.d.mts +6 -2
  101. package/dist/social-providers/roblox.mjs +12 -2
  102. package/dist/social-providers/salesforce.d.mts +5 -1
  103. package/dist/social-providers/salesforce.mjs +4 -3
  104. package/dist/social-providers/slack.d.mts +6 -2
  105. package/dist/social-providers/slack.mjs +11 -8
  106. package/dist/social-providers/spotify.d.mts +5 -1
  107. package/dist/social-providers/spotify.mjs +3 -2
  108. package/dist/social-providers/tiktok.d.mts +5 -1
  109. package/dist/social-providers/tiktok.mjs +14 -2
  110. package/dist/social-providers/twitch.d.mts +5 -1
  111. package/dist/social-providers/twitch.mjs +3 -2
  112. package/dist/social-providers/twitter.d.mts +4 -1
  113. package/dist/social-providers/twitter.mjs +2 -1
  114. package/dist/social-providers/vercel.d.mts +5 -1
  115. package/dist/social-providers/vercel.mjs +3 -2
  116. package/dist/social-providers/vk.d.mts +5 -1
  117. package/dist/social-providers/vk.mjs +3 -2
  118. package/dist/social-providers/wechat.d.mts +5 -1
  119. package/dist/social-providers/wechat.mjs +8 -2
  120. package/dist/social-providers/zoom.d.mts +5 -1
  121. package/dist/social-providers/zoom.mjs +10 -17
  122. package/dist/types/context.d.mts +55 -6
  123. package/dist/types/index.d.mts +1 -1
  124. package/dist/types/init-options.d.mts +182 -12
  125. package/dist/types/plugin-client.d.mts +12 -2
  126. package/dist/utils/async.d.mts +22 -0
  127. package/dist/utils/async.mjs +32 -0
  128. package/dist/utils/host.d.mts +147 -0
  129. package/dist/utils/host.mjs +298 -0
  130. package/dist/utils/ip.d.mts +28 -5
  131. package/dist/utils/ip.mjs +118 -4
  132. package/dist/utils/is-api-error.d.mts +6 -0
  133. package/dist/utils/is-api-error.mjs +8 -0
  134. package/dist/utils/redirect-uri.d.mts +20 -0
  135. package/dist/utils/redirect-uri.mjs +48 -0
  136. package/dist/utils/string.d.mts +5 -1
  137. package/dist/utils/string.mjs +20 -1
  138. package/dist/utils/url.d.mts +18 -1
  139. package/dist/utils/url.mjs +34 -4
  140. package/package.json +26 -16
  141. package/src/api/index.ts +121 -5
  142. package/src/context/transaction.ts +45 -12
  143. package/src/db/adapter/factory.ts +193 -7
  144. package/src/db/adapter/get-id-field.ts +2 -2
  145. package/src/db/adapter/index.ts +85 -2
  146. package/src/db/adapter/types.ts +2 -0
  147. package/src/db/get-tables.ts +9 -1
  148. package/src/db/schema/account.ts +4 -1
  149. package/src/db/schema/user.ts +3 -0
  150. package/src/db/type.ts +17 -0
  151. package/src/env/env-impl.ts +1 -2
  152. package/src/error/codes.ts +1 -0
  153. package/src/error/index.ts +9 -0
  154. package/src/instrumentation/api.ts +17 -0
  155. package/src/instrumentation/noop.ts +74 -0
  156. package/src/instrumentation/pure.index.ts +31 -0
  157. package/src/instrumentation/tracer.ts +8 -3
  158. package/src/oauth2/authorization-params.ts +28 -0
  159. package/src/oauth2/basic-credentials.ts +87 -0
  160. package/src/oauth2/client-assertion.ts +131 -58
  161. package/src/oauth2/client-credentials-token.ts +50 -74
  162. package/src/oauth2/create-authorization-url.ts +33 -7
  163. package/src/oauth2/dpop.ts +568 -0
  164. package/src/oauth2/index.ts +81 -11
  165. package/src/oauth2/oauth-provider.ts +159 -11
  166. package/src/oauth2/refresh-access-token.ts +82 -83
  167. package/src/oauth2/reject-redirects.ts +75 -0
  168. package/src/oauth2/token-endpoint-auth.ts +221 -0
  169. package/src/oauth2/utils.ts +72 -5
  170. package/src/oauth2/validate-authorization-code.ts +69 -89
  171. package/src/oauth2/verify-id-token.ts +115 -0
  172. package/src/oauth2/verify.ts +409 -78
  173. package/src/social-providers/apple.ts +30 -40
  174. package/src/social-providers/atlassian.ts +8 -1
  175. package/src/social-providers/cognito.ts +36 -39
  176. package/src/social-providers/discord.ts +22 -18
  177. package/src/social-providers/dropbox.ts +7 -5
  178. package/src/social-providers/facebook.ts +108 -52
  179. package/src/social-providers/figma.ts +8 -1
  180. package/src/social-providers/github.ts +5 -3
  181. package/src/social-providers/gitlab.ts +2 -0
  182. package/src/social-providers/google.ts +141 -39
  183. package/src/social-providers/huggingface.ts +8 -1
  184. package/src/social-providers/kakao.ts +2 -1
  185. package/src/social-providers/kick.ts +8 -1
  186. package/src/social-providers/line.ts +25 -27
  187. package/src/social-providers/linear.ts +8 -1
  188. package/src/social-providers/linkedin.ts +5 -3
  189. package/src/social-providers/microsoft-entra-id.ts +100 -55
  190. package/src/social-providers/naver.ts +2 -1
  191. package/src/social-providers/notion.ts +8 -1
  192. package/src/social-providers/paybin.ts +2 -0
  193. package/src/social-providers/paypal.ts +28 -17
  194. package/src/social-providers/polar.ts +8 -1
  195. package/src/social-providers/railway.ts +8 -1
  196. package/src/social-providers/reddit.ts +9 -4
  197. package/src/social-providers/roblox.ts +16 -11
  198. package/src/social-providers/salesforce.ts +8 -1
  199. package/src/social-providers/slack.ts +15 -9
  200. package/src/social-providers/spotify.ts +8 -1
  201. package/src/social-providers/tiktok.ts +22 -9
  202. package/src/social-providers/twitch.ts +2 -1
  203. package/src/social-providers/twitter.ts +1 -0
  204. package/src/social-providers/vercel.ts +8 -1
  205. package/src/social-providers/vk.ts +8 -1
  206. package/src/social-providers/wechat.ts +17 -2
  207. package/src/social-providers/zoom.ts +15 -19
  208. package/src/types/context.ts +57 -5
  209. package/src/types/index.ts +7 -0
  210. package/src/types/init-options.ts +204 -14
  211. package/src/types/plugin-client.ts +16 -2
  212. package/src/utils/async.ts +53 -0
  213. package/src/utils/host.ts +425 -0
  214. package/src/utils/ip.ts +197 -13
  215. package/src/utils/is-api-error.ts +10 -0
  216. package/src/utils/redirect-uri.ts +54 -0
  217. package/src/utils/string.ts +37 -0
  218. package/src/utils/url.ts +38 -4
@@ -2,7 +2,7 @@ import { logger } from "../env/logger.mjs";
2
2
  import { getOAuth2Tokens } from "../oauth2/utils.mjs";
3
3
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
4
4
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
5
- import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code.mjs";
5
+ import { authorizationCodeRequest } from "../oauth2/validate-authorization-code.mjs";
6
6
  import { betterFetch } from "@better-fetch/fetch";
7
7
  //#region src/social-providers/github.ts
8
8
  const github = (options) => {
@@ -10,7 +10,7 @@ const github = (options) => {
10
10
  return {
11
11
  id: "github",
12
12
  name: "GitHub",
13
- createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI }) {
13
+ createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI, additionalParams }) {
14
14
  const _scopes = options.disableDefaultScope ? [] : ["read:user", "user:email"];
15
15
  if (options.scope) _scopes.push(...options.scope);
16
16
  if (scopes) _scopes.push(...scopes);
@@ -23,11 +23,12 @@ const github = (options) => {
23
23
  codeVerifier,
24
24
  redirectURI,
25
25
  loginHint,
26
- prompt: options.prompt
26
+ prompt: options.prompt,
27
+ additionalParams
27
28
  });
28
29
  },
29
30
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
30
- const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
31
+ const { body, headers: requestHeaders } = await authorizationCodeRequest({
31
32
  code,
32
33
  codeVerifier,
33
34
  redirectURI,
@@ -57,7 +57,8 @@ declare const gitlab: (options: GitlabOptions) => {
57
57
  scopes,
58
58
  codeVerifier,
59
59
  loginHint,
60
- redirectURI
60
+ redirectURI,
61
+ additionalParams
61
62
  }: {
62
63
  state: string;
63
64
  codeVerifier: string;
@@ -65,6 +66,8 @@ declare const gitlab: (options: GitlabOptions) => {
65
66
  redirectURI: string;
66
67
  display?: string | undefined;
67
68
  loginHint?: string | undefined;
69
+ idTokenNonce?: string | undefined;
70
+ additionalParams?: Record<string, string> | undefined;
68
71
  }) => Promise<URL>;
69
72
  validateAuthorizationCode: ({
70
73
  code,
@@ -78,6 +81,7 @@ declare const gitlab: (options: GitlabOptions) => {
78
81
  }) => Promise<OAuth2Tokens>;
79
82
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
80
83
  getUserInfo(token: OAuth2Tokens & {
84
+ expectedIdTokenNonce?: string | undefined;
81
85
  user?: {
82
86
  name?: {
83
87
  firstName?: string;
@@ -20,7 +20,7 @@ const gitlab = (options) => {
20
20
  return {
21
21
  id: issuerId,
22
22
  name: "Gitlab",
23
- createAuthorizationURL: async ({ state, scopes, codeVerifier, loginHint, redirectURI }) => {
23
+ createAuthorizationURL: async ({ state, scopes, codeVerifier, loginHint, redirectURI, additionalParams }) => {
24
24
  const _scopes = options.disableDefaultScope ? [] : ["read_user"];
25
25
  if (options.scope) _scopes.push(...options.scope);
26
26
  if (scopes) _scopes.push(...scopes);
@@ -32,7 +32,8 @@ const gitlab = (options) => {
32
32
  state,
33
33
  redirectURI,
34
34
  codeVerifier,
35
- loginHint
35
+ loginHint,
36
+ additionalParams
36
37
  });
37
38
  },
38
39
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
@@ -1,4 +1,7 @@
1
1
  import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import * as jose from "jose";
3
+ import { JWTPayload } from "jose";
4
+
2
5
  //#region src/social-providers/google.d.ts
3
6
  interface GoogleProfile {
4
7
  aud: string;
@@ -27,7 +30,7 @@ interface GoogleProfile {
27
30
  sub: string;
28
31
  }
29
32
  interface GoogleOptions extends ProviderOptions<GoogleProfile> {
30
- clientId: string;
33
+ clientId: string | string[];
31
34
  /**
32
35
  * The access type to use for the authorization code request
33
36
  */
@@ -37,10 +40,45 @@ interface GoogleOptions extends ProviderOptions<GoogleProfile> {
37
40
  */
38
41
  display?: ("page" | "popup" | "touch" | "wap") | undefined;
39
42
  /**
40
- * The hosted domain of the user
43
+ * The hosted domain (Google Workspace) the user must belong to.
44
+ *
45
+ * This is sent to Google as the `hd` authorization hint and, when set, is
46
+ * also enforced against the `hd` claim of the returned id token/profile.
47
+ * Set `hd: "*"` to require any Workspace hosted-domain claim. Sign-in is
48
+ * rejected when the claim is missing or does not satisfy this restriction.
41
49
  */
42
50
  hd?: string | undefined;
51
+ /**
52
+ * Whether to send `include_granted_scopes=true` to Google's authorization
53
+ * endpoint, which lets new access tokens cover scopes from prior grants
54
+ * in addition to the ones requested for this flow. Set to `false` when
55
+ * each OAuth flow should request only its own scopes.
56
+ *
57
+ * Defaults to `true`.
58
+ *
59
+ * @see https://developers.google.com/identity/protocols/oauth2/web-server#incrementalAuth
60
+ */
61
+ includeGrantedScopes?: boolean | undefined;
62
+ }
63
+ interface VerifyGoogleIdTokenOptions {
64
+ token: string;
65
+ audience: string | string[];
66
+ nonce?: string | undefined;
43
67
  }
68
+ /**
69
+ * Verifies a Google ID token against Google's issuer, audience, signature,
70
+ * expiry, and maximum token age.
71
+ */
72
+ declare const verifyGoogleIdToken: ({
73
+ token,
74
+ audience,
75
+ nonce
76
+ }: VerifyGoogleIdTokenOptions) => Promise<JWTPayload | null>;
77
+ /**
78
+ * Checks whether Google's verified `hd` claim satisfies the configured hosted
79
+ * domain restriction. `hd: "*"` accepts any Google Workspace hosted domain.
80
+ */
81
+ declare const isGoogleHostedDomainAllowed: (configuredHostedDomain: string | undefined, tokenHostedDomain: unknown) => boolean;
44
82
  declare const google: (options: GoogleOptions) => {
45
83
  id: "google";
46
84
  name: string;
@@ -50,7 +88,8 @@ declare const google: (options: GoogleOptions) => {
50
88
  codeVerifier,
51
89
  redirectURI,
52
90
  loginHint,
53
- display
91
+ display,
92
+ additionalParams
54
93
  }: {
55
94
  state: string;
56
95
  codeVerifier: string;
@@ -58,6 +97,8 @@ declare const google: (options: GoogleOptions) => {
58
97
  redirectURI: string;
59
98
  display?: string | undefined;
60
99
  loginHint?: string | undefined;
100
+ idTokenNonce?: string | undefined;
101
+ additionalParams?: Record<string, string> | undefined;
61
102
  }): Promise<URL>;
62
103
  validateAuthorizationCode: ({
63
104
  code,
@@ -70,8 +111,15 @@ declare const google: (options: GoogleOptions) => {
70
111
  deviceId?: string | undefined;
71
112
  }) => Promise<OAuth2Tokens>;
72
113
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
73
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
114
+ idToken: {
115
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
116
+ issuer: string[];
117
+ audience: string | string[];
118
+ maxTokenAge: string;
119
+ verifyClaims: ((claims: Record<string, unknown>) => boolean) | undefined;
120
+ };
74
121
  getUserInfo(token: OAuth2Tokens & {
122
+ expectedIdTokenNonce?: string | undefined;
75
123
  user?: {
76
124
  name?: {
77
125
  firstName?: string;
@@ -94,4 +142,4 @@ declare const google: (options: GoogleOptions) => {
94
142
  };
95
143
  declare const getGooglePublicKey: (kid: string) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
96
144
  //#endregion
97
- export { GoogleOptions, GoogleProfile, getGooglePublicKey, google };
145
+ export { GoogleOptions, GoogleProfile, VerifyGoogleIdTokenOptions, getGooglePublicKey, google, isGoogleHostedDomainAllowed, verifyGoogleIdToken };
@@ -1,17 +1,60 @@
1
- import { logger } from "../env/logger.mjs";
2
1
  import { APIError, BetterAuthError } from "../error/index.mjs";
2
+ import { logger } from "../env/logger.mjs";
3
+ import { getPrimaryClientId } 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 { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
6
7
  import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
7
8
  import { betterFetch } from "@better-fetch/fetch";
8
9
  //#region src/social-providers/google.ts
10
+ const GOOGLE_ID_TOKEN_MAX_AGE = "1h";
11
+ const GOOGLE_ID_TOKEN_ALGORITHM = "RS256";
12
+ const GOOGLE_ID_TOKEN_ALGORITHMS = [GOOGLE_ID_TOKEN_ALGORITHM];
13
+ function isGoogleIdTokenAlgorithm(algorithm) {
14
+ return GOOGLE_ID_TOKEN_ALGORITHMS.includes(algorithm);
15
+ }
16
+ /**
17
+ * Verifies a Google ID token against Google's issuer, audience, signature,
18
+ * expiry, and maximum token age.
19
+ */
20
+ const verifyGoogleIdToken = async ({ token, audience, nonce }) => {
21
+ try {
22
+ const { kid, alg } = decodeProtectedHeader(token);
23
+ if (!isGoogleIdTokenAlgorithm(alg)) return null;
24
+ const publicKeys = await getGooglePublicKeys(kid);
25
+ for (const publicKey of publicKeys) try {
26
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
27
+ algorithms: GOOGLE_ID_TOKEN_ALGORITHMS,
28
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
29
+ audience,
30
+ maxTokenAge: GOOGLE_ID_TOKEN_MAX_AGE
31
+ });
32
+ if (nonce && jwtClaims.nonce !== nonce) return null;
33
+ return jwtClaims;
34
+ } catch {
35
+ continue;
36
+ }
37
+ return null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ };
42
+ /**
43
+ * Checks whether Google's verified `hd` claim satisfies the configured hosted
44
+ * domain restriction. `hd: "*"` accepts any Google Workspace hosted domain.
45
+ */
46
+ const isGoogleHostedDomainAllowed = (configuredHostedDomain, tokenHostedDomain) => {
47
+ if (!configuredHostedDomain) return true;
48
+ if (typeof tokenHostedDomain !== "string" || !tokenHostedDomain) return false;
49
+ if (configuredHostedDomain === "*") return true;
50
+ return tokenHostedDomain === configuredHostedDomain;
51
+ };
9
52
  const google = (options) => {
10
53
  return {
11
54
  id: "google",
12
55
  name: "Google",
13
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, display }) {
14
- if (!options.clientId || !options.clientSecret) {
56
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, display, additionalParams }) {
57
+ if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
15
58
  logger.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options.");
16
59
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
17
60
  }
@@ -36,7 +79,10 @@ const google = (options) => {
36
79
  display: display || options.display,
37
80
  loginHint,
38
81
  hd: options.hd,
39
- additionalParams: { include_granted_scopes: "true" }
82
+ additionalParams: {
83
+ ...options.includeGrantedScopes === false ? {} : { include_granted_scopes: "true" },
84
+ ...additionalParams ?? {}
85
+ }
40
86
  });
41
87
  },
42
88
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -59,28 +105,21 @@ const google = (options) => {
59
105
  tokenEndpoint: "https://oauth2.googleapis.com/token"
60
106
  });
61
107
  },
62
- async verifyIdToken(token, nonce) {
63
- if (options.disableIdTokenSignIn) return false;
64
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
65
- try {
66
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
67
- if (!kid || !jwtAlg) return false;
68
- const { payload: jwtClaims } = await jwtVerify(token, await getGooglePublicKey(kid), {
69
- algorithms: [jwtAlg],
70
- issuer: ["https://accounts.google.com", "accounts.google.com"],
71
- audience: options.clientId,
72
- maxTokenAge: "1h"
73
- });
74
- if (nonce && jwtClaims.nonce !== nonce) return false;
75
- return true;
76
- } catch {
77
- return false;
78
- }
108
+ idToken: {
109
+ jwks: (header) => getGooglePublicKey(header.kid),
110
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
111
+ audience: options.clientId,
112
+ maxTokenAge: GOOGLE_ID_TOKEN_MAX_AGE,
113
+ verifyClaims: options.hd ? (claims) => isGoogleHostedDomainAllowed(options.hd, claims.hd) : void 0
79
114
  },
80
115
  async getUserInfo(token) {
81
116
  if (options.getUserInfo) return options.getUserInfo(token);
82
117
  if (!token.idToken) return null;
83
118
  const user = decodeJwt(token.idToken);
119
+ if (!isGoogleHostedDomainAllowed(options.hd, user.hd)) {
120
+ logger.error(`Google sign-in rejected: id token hosted domain (hd) "${user.hd ?? "<missing>"}" does not satisfy the configured "hd" option "${options.hd}".`);
121
+ return null;
122
+ }
84
123
  const userMap = await options.mapProfileToUser?.(user);
85
124
  return {
86
125
  user: {
@@ -98,11 +137,16 @@ const google = (options) => {
98
137
  };
99
138
  };
100
139
  const getGooglePublicKey = async (kid) => {
140
+ const [publicKey] = await getGooglePublicKeys(kid);
141
+ if (!publicKey) throw new Error(`JWK with kid ${kid} not found`);
142
+ return publicKey;
143
+ };
144
+ const getGooglePublicKeys = async (kid) => {
101
145
  const { data } = await betterFetch("https://www.googleapis.com/oauth2/v3/certs");
102
146
  if (!data?.keys) throw new APIError("BAD_REQUEST", { message: "Keys not found" });
103
- const jwk = data.keys.find((key) => key.kid === kid);
104
- if (!jwk) throw new Error(`JWK with kid ${kid} not found`);
105
- return await importJWK(jwk, jwk.alg);
147
+ const jwks = kid ? data.keys.filter((key) => key.kid === kid) : data.keys;
148
+ if (!jwks.length) throw new Error(`JWK with kid ${kid} not found`);
149
+ return Promise.all(jwks.map((jwk) => importJWK(jwk, GOOGLE_ID_TOKEN_ALGORITHM)));
106
150
  };
107
151
  //#endregion
108
- export { getGooglePublicKey, google };
152
+ export { getGooglePublicKey, google, isGoogleHostedDomainAllowed, verifyGoogleIdToken };
@@ -38,7 +38,8 @@ declare const huggingface: (options: HuggingFaceOptions) => {
38
38
  state,
39
39
  scopes,
40
40
  codeVerifier,
41
- redirectURI
41
+ redirectURI,
42
+ additionalParams
42
43
  }: {
43
44
  state: string;
44
45
  codeVerifier: string;
@@ -46,6 +47,8 @@ declare const huggingface: (options: HuggingFaceOptions) => {
46
47
  redirectURI: string;
47
48
  display?: string | undefined;
48
49
  loginHint?: string | undefined;
50
+ idTokenNonce?: string | undefined;
51
+ additionalParams?: Record<string, string> | undefined;
49
52
  }): Promise<URL>;
50
53
  validateAuthorizationCode: ({
51
54
  code,
@@ -59,6 +62,7 @@ declare const huggingface: (options: HuggingFaceOptions) => {
59
62
  }) => Promise<OAuth2Tokens>;
60
63
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
61
64
  getUserInfo(token: OAuth2Tokens & {
65
+ expectedIdTokenNonce?: string | undefined;
62
66
  user?: {
63
67
  name?: {
64
68
  firstName?: string;
@@ -8,7 +8,7 @@ const huggingface = (options) => {
8
8
  return {
9
9
  id: "huggingface",
10
10
  name: "Hugging Face",
11
- createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
11
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
12
12
  const _scopes = options.disableDefaultScope ? [] : [
13
13
  "openid",
14
14
  "profile",
@@ -23,7 +23,8 @@ const huggingface = (options) => {
23
23
  scopes: _scopes,
24
24
  state,
25
25
  codeVerifier,
26
- redirectURI
26
+ redirectURI,
27
+ additionalParams
27
28
  });
28
29
  },
29
30
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {