@better-auth/core 1.7.0-beta.3 → 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 (188) 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 +62 -0
  4. package/dist/db/adapter/index.d.mts +35 -1
  5. package/dist/db/adapter/types.d.mts +1 -1
  6. package/dist/db/get-tables.mjs +3 -3
  7. package/dist/db/schema/account.d.mts +1 -1
  8. package/dist/db/schema/account.mjs +1 -1
  9. package/dist/db/type.d.mts +12 -0
  10. package/dist/env/env-impl.mjs +1 -1
  11. package/dist/error/codes.d.mts +6 -0
  12. package/dist/error/codes.mjs +6 -0
  13. package/dist/index.d.mts +2 -2
  14. package/dist/instrumentation/tracer.mjs +1 -1
  15. package/dist/oauth2/authorization-params.d.mts +12 -0
  16. package/dist/oauth2/authorization-params.mjs +12 -0
  17. package/dist/oauth2/basic-credentials.d.mts +30 -0
  18. package/dist/oauth2/basic-credentials.mjs +64 -0
  19. package/dist/oauth2/client-assertion.d.mts +38 -22
  20. package/dist/oauth2/client-assertion.mjs +63 -28
  21. package/dist/oauth2/client-credentials-token.d.mts +19 -40
  22. package/dist/oauth2/client-credentials-token.mjs +18 -29
  23. package/dist/oauth2/create-authorization-url.d.mts +13 -2
  24. package/dist/oauth2/create-authorization-url.mjs +28 -7
  25. package/dist/oauth2/index.d.mts +13 -8
  26. package/dist/oauth2/index.mjs +11 -7
  27. package/dist/oauth2/oauth-provider.d.mts +149 -11
  28. package/dist/oauth2/refresh-access-token.d.mts +20 -40
  29. package/dist/oauth2/refresh-access-token.mjs +20 -33
  30. package/dist/oauth2/scopes.d.mts +76 -0
  31. package/dist/oauth2/scopes.mjs +96 -0
  32. package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
  33. package/dist/oauth2/token-endpoint-auth.mjs +89 -0
  34. package/dist/oauth2/utils.d.mts +9 -1
  35. package/dist/oauth2/utils.mjs +14 -2
  36. package/dist/oauth2/validate-authorization-code.d.mts +17 -52
  37. package/dist/oauth2/validate-authorization-code.mjs +17 -30
  38. package/dist/oauth2/verify-id-token.d.mts +26 -0
  39. package/dist/oauth2/verify-id-token.mjs +62 -0
  40. package/dist/oauth2/verify.d.mts +14 -0
  41. package/dist/oauth2/verify.mjs +38 -12
  42. package/dist/social-providers/apple.d.mts +18 -20
  43. package/dist/social-providers/apple.mjs +15 -28
  44. package/dist/social-providers/atlassian.d.mts +8 -2
  45. package/dist/social-providers/atlassian.mjs +9 -6
  46. package/dist/social-providers/cognito.d.mts +29 -3
  47. package/dist/social-providers/cognito.mjs +30 -34
  48. package/dist/social-providers/discord.d.mts +8 -2
  49. package/dist/social-providers/discord.mjs +20 -6
  50. package/dist/social-providers/dropbox.d.mts +8 -2
  51. package/dist/social-providers/dropbox.mjs +10 -9
  52. package/dist/social-providers/facebook.d.mts +24 -3
  53. package/dist/social-providers/facebook.mjs +51 -24
  54. package/dist/social-providers/figma.d.mts +8 -2
  55. package/dist/social-providers/figma.mjs +8 -7
  56. package/dist/social-providers/github.d.mts +8 -2
  57. package/dist/social-providers/github.mjs +9 -8
  58. package/dist/social-providers/gitlab.d.mts +8 -2
  59. package/dist/social-providers/gitlab.mjs +8 -7
  60. package/dist/social-providers/google.d.mts +32 -4
  61. package/dist/social-providers/google.mjs +26 -29
  62. package/dist/social-providers/huggingface.d.mts +8 -2
  63. package/dist/social-providers/huggingface.mjs +11 -10
  64. package/dist/social-providers/index.d.mts +322 -75
  65. package/dist/social-providers/kakao.d.mts +8 -2
  66. package/dist/social-providers/kakao.mjs +11 -10
  67. package/dist/social-providers/kick.d.mts +8 -2
  68. package/dist/social-providers/kick.mjs +7 -6
  69. package/dist/social-providers/line.d.mts +11 -3
  70. package/dist/social-providers/line.mjs +14 -15
  71. package/dist/social-providers/linear.d.mts +8 -2
  72. package/dist/social-providers/linear.mjs +7 -6
  73. package/dist/social-providers/linkedin.d.mts +8 -2
  74. package/dist/social-providers/linkedin.mjs +12 -11
  75. package/dist/social-providers/microsoft-entra-id.d.mts +33 -7
  76. package/dist/social-providers/microsoft-entra-id.mjs +28 -38
  77. package/dist/social-providers/naver.d.mts +8 -2
  78. package/dist/social-providers/naver.mjs +7 -6
  79. package/dist/social-providers/notion.d.mts +8 -2
  80. package/dist/social-providers/notion.mjs +9 -6
  81. package/dist/social-providers/paybin.d.mts +8 -2
  82. package/dist/social-providers/paybin.mjs +12 -11
  83. package/dist/social-providers/paypal.d.mts +8 -3
  84. package/dist/social-providers/paypal.mjs +10 -14
  85. package/dist/social-providers/polar.d.mts +8 -2
  86. package/dist/social-providers/polar.mjs +11 -10
  87. package/dist/social-providers/railway.d.mts +8 -2
  88. package/dist/social-providers/railway.mjs +11 -10
  89. package/dist/social-providers/reddit.d.mts +8 -2
  90. package/dist/social-providers/reddit.mjs +11 -9
  91. package/dist/social-providers/roblox.d.mts +8 -2
  92. package/dist/social-providers/roblox.mjs +15 -5
  93. package/dist/social-providers/salesforce.d.mts +8 -2
  94. package/dist/social-providers/salesforce.mjs +11 -10
  95. package/dist/social-providers/slack.d.mts +8 -2
  96. package/dist/social-providers/slack.mjs +18 -15
  97. package/dist/social-providers/spotify.d.mts +8 -2
  98. package/dist/social-providers/spotify.mjs +7 -6
  99. package/dist/social-providers/tiktok.d.mts +8 -2
  100. package/dist/social-providers/tiktok.mjs +21 -5
  101. package/dist/social-providers/twitch.d.mts +8 -2
  102. package/dist/social-providers/twitch.mjs +7 -6
  103. package/dist/social-providers/twitter.d.mts +7 -2
  104. package/dist/social-providers/twitter.mjs +11 -10
  105. package/dist/social-providers/vercel.d.mts +8 -2
  106. package/dist/social-providers/vercel.mjs +7 -9
  107. package/dist/social-providers/vk.d.mts +8 -2
  108. package/dist/social-providers/vk.mjs +7 -6
  109. package/dist/social-providers/wechat.d.mts +8 -2
  110. package/dist/social-providers/wechat.mjs +16 -6
  111. package/dist/social-providers/zoom.d.mts +10 -3
  112. package/dist/social-providers/zoom.mjs +14 -15
  113. package/dist/types/context.d.mts +33 -11
  114. package/dist/types/index.d.mts +1 -1
  115. package/dist/types/init-options.d.mts +121 -6
  116. package/dist/utils/ip.d.mts +5 -4
  117. package/dist/utils/ip.mjs +3 -3
  118. package/dist/utils/redirect-uri.d.mts +20 -0
  119. package/dist/utils/redirect-uri.mjs +48 -0
  120. package/dist/utils/string.d.mts +5 -1
  121. package/dist/utils/string.mjs +20 -1
  122. package/dist/utils/url.d.mts +18 -1
  123. package/dist/utils/url.mjs +30 -1
  124. package/package.json +13 -12
  125. package/src/db/adapter/factory.ts +126 -0
  126. package/src/db/adapter/index.ts +32 -0
  127. package/src/db/adapter/types.ts +1 -0
  128. package/src/db/get-tables.ts +8 -3
  129. package/src/db/schema/account.ts +14 -2
  130. package/src/db/type.ts +12 -0
  131. package/src/env/env-impl.ts +1 -2
  132. package/src/error/codes.ts +6 -0
  133. package/src/oauth2/authorization-params.ts +28 -0
  134. package/src/oauth2/basic-credentials.ts +87 -0
  135. package/src/oauth2/client-assertion.ts +131 -58
  136. package/src/oauth2/client-credentials-token.ts +48 -72
  137. package/src/oauth2/create-authorization-url.ts +30 -8
  138. package/src/oauth2/index.ts +42 -10
  139. package/src/oauth2/oauth-provider.ts +161 -12
  140. package/src/oauth2/refresh-access-token.ts +52 -78
  141. package/src/oauth2/scopes.ts +118 -0
  142. package/src/oauth2/token-endpoint-auth.ts +221 -0
  143. package/src/oauth2/utils.ts +21 -5
  144. package/src/oauth2/validate-authorization-code.ts +55 -85
  145. package/src/oauth2/verify-id-token.ts +111 -0
  146. package/src/oauth2/verify.ts +82 -15
  147. package/src/social-providers/apple.ts +32 -45
  148. package/src/social-providers/atlassian.ts +20 -9
  149. package/src/social-providers/cognito.ts +51 -48
  150. package/src/social-providers/discord.ts +37 -22
  151. package/src/social-providers/dropbox.ts +20 -12
  152. package/src/social-providers/facebook.ts +108 -57
  153. package/src/social-providers/figma.ts +21 -10
  154. package/src/social-providers/github.ts +16 -10
  155. package/src/social-providers/gitlab.ts +16 -8
  156. package/src/social-providers/google.ts +67 -46
  157. package/src/social-providers/huggingface.ts +20 -9
  158. package/src/social-providers/kakao.ts +18 -9
  159. package/src/social-providers/kick.ts +20 -8
  160. package/src/social-providers/line.ts +39 -37
  161. package/src/social-providers/linear.ts +20 -7
  162. package/src/social-providers/linkedin.ts +16 -10
  163. package/src/social-providers/microsoft-entra-id.ts +66 -64
  164. package/src/social-providers/naver.ts +14 -7
  165. package/src/social-providers/notion.ts +20 -7
  166. package/src/social-providers/paybin.ts +16 -11
  167. package/src/social-providers/paypal.ts +12 -25
  168. package/src/social-providers/polar.ts +20 -9
  169. package/src/social-providers/railway.ts +20 -9
  170. package/src/social-providers/reddit.ts +22 -10
  171. package/src/social-providers/roblox.ts +31 -15
  172. package/src/social-providers/salesforce.ts +21 -10
  173. package/src/social-providers/slack.ts +31 -16
  174. package/src/social-providers/spotify.ts +20 -7
  175. package/src/social-providers/tiktok.ts +32 -13
  176. package/src/social-providers/twitch.ts +14 -9
  177. package/src/social-providers/twitter.ts +18 -8
  178. package/src/social-providers/vercel.ts +24 -11
  179. package/src/social-providers/vk.ts +20 -7
  180. package/src/social-providers/wechat.ts +28 -8
  181. package/src/social-providers/zoom.ts +28 -19
  182. package/src/types/context.ts +33 -12
  183. package/src/types/index.ts +7 -0
  184. package/src/types/init-options.ts +148 -5
  185. package/src/utils/ip.ts +12 -13
  186. package/src/utils/redirect-uri.ts +54 -0
  187. package/src/utils/string.ts +37 -0
  188. package/src/utils/url.ts +28 -0
@@ -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, 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/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,7 +25,8 @@ const cognito = (options) => {
19
25
  return {
20
26
  id: "cognito",
21
27
  name: "Cognito",
22
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
28
+ callbackPath: "/callback/cognito",
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.");
25
32
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
@@ -28,22 +35,20 @@ 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 _scopes = options.disableDefaultScope ? [] : [
32
- "openid",
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: _scopes,
43
+ scopes: requestedScopes,
43
44
  state,
44
45
  codeVerifier,
45
46
  redirectURI,
46
- prompt: options.prompt
47
+ prompt: options.prompt,
48
+ additionalParams: {
49
+ ...options.identityProvider ? { identity_provider: options.identityProvider } : {},
50
+ ...additionalParams ?? {}
51
+ }
47
52
  });
48
53
  const scopeValue = url.searchParams.get("scope");
49
54
  if (scopeValue) {
@@ -51,9 +56,15 @@ const cognito = (options) => {
51
56
  const encodedScope = encodeURIComponent(scopeValue);
52
57
  const urlString = url.toString();
53
58
  const separator = urlString.includes("?") ? "&" : "?";
54
- return new URL(`${urlString}${separator}scope=${encodedScope}`);
59
+ return {
60
+ url: new URL(`${urlString}${separator}scope=${encodedScope}`),
61
+ requestedScopes
62
+ };
55
63
  }
56
- return url;
64
+ return {
65
+ url,
66
+ requestedScopes
67
+ };
57
68
  },
58
69
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
59
70
  return validateAuthorizationCode({
@@ -75,26 +86,11 @@ const cognito = (options) => {
75
86
  tokenEndpoint
76
87
  });
77
88
  },
78
- async verifyIdToken(token, nonce) {
79
- if (options.disableIdTokenSignIn) return false;
80
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
81
- try {
82
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
83
- if (!kid || !jwtAlg) return false;
84
- const publicKey = await getCognitoPublicKey(kid, options.region, options.userPoolId);
85
- const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
86
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
87
- algorithms: [jwtAlg],
88
- issuer: expectedIssuer,
89
- audience: options.clientId,
90
- maxTokenAge: "1h"
91
- });
92
- if (nonce && jwtClaims.nonce !== nonce) return false;
93
- return true;
94
- } catch (error) {
95
- logger.error("Failed to verify ID token:", error);
96
- return false;
97
- }
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"
98
94
  },
99
95
  async getUserInfo(token) {
100
96
  if (options.getUserInfo) return options.getUserInfo(token);
@@ -77,10 +77,12 @@ 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,
83
- redirectURI
84
+ redirectURI,
85
+ additionalParams
84
86
  }: {
85
87
  state: string;
86
88
  codeVerifier: string;
@@ -88,7 +90,11 @@ declare const discord: (options: DiscordOptions) => {
88
90
  redirectURI: string;
89
91
  display?: string | undefined;
90
92
  loginHint?: string | undefined;
91
- }): URL;
93
+ additionalParams?: Record<string, string> | undefined;
94
+ }): Promise<{
95
+ url: URL;
96
+ requestedScopes: string[];
97
+ }>;
92
98
  validateAuthorizationCode: ({
93
99
  code,
94
100
  redirectURI
@@ -1,18 +1,32 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
2
+ import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
1
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
2
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
3
5
  import { betterFetch } from "@better-fetch/fetch";
4
6
  //#region src/social-providers/discord.ts
7
+ const DISCORD_DEFAULT_SCOPES = ["identify", "email"];
5
8
  const discord = (options) => {
6
9
  const tokenEndpoint = "https://discord.com/api/oauth2/token";
7
10
  return {
8
11
  id: "discord",
9
12
  name: "Discord",
10
- createAuthorizationURL({ state, scopes, redirectURI }) {
11
- const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
12
- if (scopes) _scopes.push(...scopes);
13
- if (options.scope) _scopes.push(...options.scope);
14
- const permissionsParam = _scopes.includes("bot") && options.permissions !== void 0 ? `&permissions=${options.permissions}` : "";
15
- return new URL(`https://discord.com/api/oauth2/authorize?scope=${_scopes.join("+")}&response_type=code&client_id=${options.clientId}&redirect_uri=${encodeURIComponent(options.redirectURI || redirectURI)}&state=${state}&prompt=${options.prompt || "none"}${permissionsParam}`);
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");
17
+ return createAuthorizationURL({
18
+ id: "discord",
19
+ options,
20
+ authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
21
+ scopes: requestedScopes,
22
+ state,
23
+ redirectURI,
24
+ prompt: options.prompt || "none",
25
+ additionalParams: {
26
+ ...hasBotScope && options.permissions !== void 0 ? { permissions: String(options.permissions) } : {},
27
+ ...additionalParams ?? {}
28
+ }
29
+ });
16
30
  },
17
31
  validateAuthorizationCode: async ({ code, redirectURI }) => {
18
32
  return validateAuthorizationCode({
@@ -20,11 +20,13 @@ 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,
26
27
  codeVerifier,
27
- redirectURI
28
+ redirectURI,
29
+ additionalParams
28
30
  }: {
29
31
  state: string;
30
32
  codeVerifier: string;
@@ -32,7 +34,11 @@ declare const dropbox: (options: DropboxOptions) => {
32
34
  redirectURI: string;
33
35
  display?: string | undefined;
34
36
  loginHint?: string | undefined;
35
- }) => Promise<URL>;
37
+ additionalParams?: Record<string, string> | undefined;
38
+ }) => Promise<{
39
+ url: URL;
40
+ requestedScopes: string[];
41
+ }>;
36
42
  validateAuthorizationCode: ({
37
43
  code,
38
44
  codeVerifier,
@@ -1,28 +1,29 @@
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",
11
- createAuthorizationURL: async ({ state, scopes, codeVerifier, redirectURI }) => {
12
- const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
13
- if (options.scope) _scopes.push(...options.scope);
14
- if (scopes) _scopes.push(...scopes);
15
- const additionalParams = {};
16
- if (options.accessType) additionalParams.token_access_type = options.accessType;
17
- return await createAuthorizationURL({
13
+ callbackPath: "/callback/dropbox",
14
+ createAuthorizationURL: async ({ state, scopes, codeVerifier, redirectURI, additionalParams }) => {
15
+ return createAuthorizationURL({
18
16
  id: "dropbox",
19
17
  options,
20
18
  authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
21
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, DROPBOX_DEFAULT_SCOPES, scopes),
22
20
  state,
23
21
  redirectURI,
24
22
  codeVerifier,
25
- additionalParams
23
+ additionalParams: {
24
+ ...options.accessType ? { token_access_type: options.accessType } : {},
25
+ ...additionalParams ?? {}
26
+ }
26
27
  });
27
28
  },
28
29
  validateAuthorizationCode: async ({ code, codeVerifier, 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/facebook.d.ts
3
5
  interface FacebookProfile {
4
6
  id: string;
@@ -30,11 +32,13 @@ 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,
36
39
  redirectURI,
37
- loginHint
40
+ loginHint,
41
+ additionalParams
38
42
  }: {
39
43
  state: string;
40
44
  codeVerifier: string;
@@ -42,7 +46,11 @@ declare const facebook: (options: FacebookOptions) => {
42
46
  redirectURI: string;
43
47
  display?: string | undefined;
44
48
  loginHint?: string | undefined;
45
- }): Promise<URL>;
49
+ additionalParams?: Record<string, string> | undefined;
50
+ }): Promise<{
51
+ url: URL;
52
+ requestedScopes: string[];
53
+ }>;
46
54
  validateAuthorizationCode: ({
47
55
  code,
48
56
  redirectURI
@@ -52,7 +60,20 @@ declare const facebook: (options: FacebookOptions) => {
52
60
  codeVerifier?: string | undefined;
53
61
  deviceId?: string | undefined;
54
62
  }) => Promise<OAuth2Tokens>;
55
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
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
+ };
56
77
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
57
78
  getUserInfo(token: OAuth2Tokens & {
58
79
  user?: {
@@ -1,33 +1,64 @@
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",
14
- async createAuthorizationURL({ state, scopes, redirectURI, loginHint }) {
44
+ callbackPath: "/callback/facebook",
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,
30
- additionalParams: options.configId ? { config_id: options.configId } : {}
58
+ additionalParams: {
59
+ ...options.configId ? { config_id: options.configId } : {},
60
+ ...additionalParams ?? {}
61
+ }
31
62
  });
32
63
  },
33
64
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -38,21 +69,12 @@ const facebook = (options) => {
38
69
  tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token"
39
70
  });
40
71
  },
41
- async verifyIdToken(token, nonce) {
42
- if (options.disableIdTokenSignIn) return false;
43
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
44
- if (token.split(".").length === 3) try {
45
- const { payload: jwtClaims } = await jwtVerify(token, createRemoteJWKSet(new URL("https://limited.facebook.com/.well-known/oauth/openid/jwks/")), {
46
- algorithms: ["RS256"],
47
- audience: options.clientId,
48
- issuer: "https://www.facebook.com"
49
- });
50
- if (nonce && jwtClaims.nonce !== nonce) return false;
51
- return !!jwtClaims;
52
- } catch {
53
- return false;
54
- }
55
- 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
56
78
  },
57
79
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
58
80
  return refreshAccessToken({
@@ -93,6 +115,10 @@ const facebook = (options) => {
93
115
  data: profile
94
116
  };
95
117
  }
118
+ const accessToken = token.accessToken;
119
+ if (!accessToken) return null;
120
+ const tokenUserId = await verifyFacebookAccessToken(accessToken, options);
121
+ if (!tokenUserId) return null;
96
122
  const { data: profile, error } = await betterFetch("https://graph.facebook.com/me?fields=" + [
97
123
  "id",
98
124
  "name",
@@ -101,9 +127,10 @@ const facebook = (options) => {
101
127
  ...options?.fields || []
102
128
  ].join(","), { auth: {
103
129
  type: "Bearer",
104
- token: token.accessToken
130
+ token: accessToken
105
131
  } });
106
132
  if (error) return null;
133
+ if (profile.id !== tokenUserId) return null;
107
134
  const userMap = await options.mapProfileToUser?.(profile);
108
135
  return {
109
136
  user: {
@@ -12,11 +12,13 @@ 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,
18
19
  codeVerifier,
19
- redirectURI
20
+ redirectURI,
21
+ additionalParams
20
22
  }: {
21
23
  state: string;
22
24
  codeVerifier: string;
@@ -24,7 +26,11 @@ declare const figma: (options: FigmaOptions) => {
24
26
  redirectURI: string;
25
27
  display?: string | undefined;
26
28
  loginHint?: string | undefined;
27
- }): Promise<URL>;
29
+ additionalParams?: Record<string, string> | undefined;
30
+ }): Promise<{
31
+ url: URL;
32
+ requestedScopes: string[];
33
+ }>;
28
34
  validateAuthorizationCode: ({
29
35
  code,
30
36
  codeVerifier,
@@ -1,32 +1,33 @@
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",
13
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
15
+ callbackPath: "/callback/figma",
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
- redirectURI
29
+ redirectURI,
30
+ additionalParams
30
31
  });
31
32
  },
32
33
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -52,12 +52,14 @@ 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,
58
59
  loginHint,
59
60
  codeVerifier,
60
- redirectURI
61
+ redirectURI,
62
+ additionalParams
61
63
  }: {
62
64
  state: string;
63
65
  codeVerifier: string;
@@ -65,7 +67,11 @@ declare const github: (options: GithubOptions) => {
65
67
  redirectURI: string;
66
68
  display?: string | undefined;
67
69
  loginHint?: string | undefined;
68
- }): Promise<URL>;
70
+ additionalParams?: Record<string, string> | undefined;
71
+ }): Promise<{
72
+ url: URL;
73
+ requestedScopes: string[];
74
+ }>;
69
75
  validateAuthorizationCode: ({
70
76
  code,
71
77
  codeVerifier,
@@ -1,33 +1,34 @@
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
- import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code.mjs";
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",
13
- createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI }) {
14
- const _scopes = options.disableDefaultScope ? [] : ["read:user", "user:email"];
15
- if (options.scope) _scopes.push(...options.scope);
16
- if (scopes) _scopes.push(...scopes);
15
+ callbackPath: "/callback/github",
16
+ createAuthorizationURL({ state, scopes, loginHint, codeVerifier, redirectURI, additionalParams }) {
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,
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,
@@ -52,12 +52,14 @@ 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,
58
59
  codeVerifier,
59
60
  loginHint,
60
- redirectURI
61
+ redirectURI,
62
+ additionalParams
61
63
  }: {
62
64
  state: string;
63
65
  codeVerifier: string;
@@ -65,7 +67,11 @@ declare const gitlab: (options: GitlabOptions) => {
65
67
  redirectURI: string;
66
68
  display?: string | undefined;
67
69
  loginHint?: string | undefined;
68
- }) => Promise<URL>;
70
+ additionalParams?: Record<string, string> | undefined;
71
+ }) => Promise<{
72
+ url: URL;
73
+ requestedScopes: string[];
74
+ }>;
69
75
  validateAuthorizationCode: ({
70
76
  code,
71
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,25 +15,25 @@ 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 }) => {
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,
35
- loginHint
35
+ loginHint,
36
+ additionalParams
36
37
  });
37
38
  },
38
39
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {