@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
@@ -2,7 +2,7 @@ import { BetterAuthDBSchema, ModelNames, SecondaryStorage } from "../db/type.mjs
2
2
  import { DBAdapter } from "../db/adapter/index.mjs";
3
3
  import { createLogger } from "../env/logger.mjs";
4
4
  import { AuthContext } from "../types/context.mjs";
5
- import { OAuthProvider } from "../oauth2/oauth-provider.mjs";
5
+ import { UpstreamProvider } from "../oauth2/oauth-provider.mjs";
6
6
  import * as better_call0 from "better-call";
7
7
  import { EndpointContext, EndpointOptions, StrictEndpoint } from "better-call";
8
8
  import * as _better_auth_core0 from "@better-auth/core";
@@ -87,7 +87,7 @@ declare const createAuthMiddleware: {
87
87
  image?: string | null | undefined;
88
88
  } & Record<string, any>;
89
89
  } | null) => void;
90
- socialProviders: OAuthProvider[];
90
+ socialProviders: UpstreamProvider[];
91
91
  authCookies: _better_auth_core0.BetterAuthCookies;
92
92
  logger: ReturnType<typeof createLogger>;
93
93
  rateLimit: {
@@ -216,7 +216,7 @@ declare const createAuthMiddleware: {
216
216
  image?: string | null | undefined;
217
217
  } & Record<string, any>;
218
218
  } | null) => void;
219
- socialProviders: OAuthProvider[];
219
+ socialProviders: UpstreamProvider[];
220
220
  authCookies: _better_auth_core0.BetterAuthCookies;
221
221
  logger: ReturnType<typeof createLogger>;
222
222
  rateLimit: {
@@ -2,7 +2,7 @@
2
2
  const symbol = Symbol.for("better-auth:global");
3
3
  let bind = null;
4
4
  const __context = {};
5
- const __betterAuthVersion = "1.7.0-beta.4";
5
+ const __betterAuthVersion = "1.7.0-beta.5";
6
6
  /**
7
7
  * We store context instance in the globalThis.
8
8
  *
@@ -1,7 +1,7 @@
1
1
  import { BetterAuthError } from "../../error/index.mjs";
2
+ import { getAuthTables } from "../get-tables.mjs";
2
3
  import { getColorDepth } from "../../env/color-depth.mjs";
3
4
  import { TTY_COLORS, createLogger } from "../../env/logger.mjs";
4
- import { getAuthTables } from "../get-tables.mjs";
5
5
  import { safeJSONParse } from "../../utils/json.mjs";
6
6
  import { initGetDefaultModelName } from "./get-default-model-name.mjs";
7
7
  import { initGetDefaultFieldName } from "./get-default-field-name.mjs";
@@ -722,7 +722,7 @@ const createAdapterFactory = ({ adapter: customAdapter, config: cfg }) => (optio
722
722
  }]
723
723
  });
724
724
  if (typeof deleted !== "number") throw new BetterAuthError(`Adapter "${config.adapterId}" returned a non-numeric value from deleteMany during the consumeOne fallback. Return the number of deleted rows, or implement a native consumeOne for atomic single-use consumption.`);
725
- return deleted > 0 ? target : null;
725
+ return Number.isFinite(deleted) && deleted > 0 ? target : null;
726
726
  }));
727
727
  resultNeedsOutputTransform = false;
728
728
  }
@@ -228,10 +228,10 @@ const getAuthTables = (options) => {
228
228
  returned: false,
229
229
  fieldName: options.account?.fields?.refreshTokenExpiresAt || "refreshTokenExpiresAt"
230
230
  },
231
- scope: {
232
- type: "string",
231
+ grantedScopes: {
232
+ type: "string[]",
233
233
  required: false,
234
- fieldName: options.account?.fields?.scope || "scope"
234
+ fieldName: options.account?.fields?.grantedScopes || "grantedScopes"
235
235
  },
236
236
  password: {
237
237
  type: "string",
@@ -16,7 +16,7 @@ declare const accountSchema: z.ZodObject<{
16
16
  idToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
17
17
  accessTokenExpiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
18
18
  refreshTokenExpiresAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
19
- scope: z.ZodOptional<z.ZodNullable<z.ZodString>>;
19
+ grantedScopes: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
20
20
  password: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
21
  }, z.core.$strip>;
22
22
  type BaseAccount = z.infer<typeof accountSchema>;
@@ -10,7 +10,7 @@ const accountSchema = coreSchema.extend({
10
10
  idToken: z.string().nullish(),
11
11
  accessTokenExpiresAt: z.date().nullish(),
12
12
  refreshTokenExpiresAt: z.date().nullish(),
13
- scope: z.string().nullish(),
13
+ grantedScopes: z.array(z.string()).nullish(),
14
14
  password: z.string().nullish()
15
15
  });
16
16
  //#endregion
@@ -27,7 +27,7 @@ const env = new Proxy(_envShim, {
27
27
  function toBoolean(val) {
28
28
  return val ? val !== "false" : false;
29
29
  }
30
- const nodeENV = typeof process !== "undefined" && process.env && process.env.NODE_ENV || "";
30
+ const nodeENV = env.NODE_ENV ?? "";
31
31
  /** Detect if `NODE_ENV` environment variable is `production` */
32
32
  const isProduction = nodeENV === "production";
33
33
  /** Detect if `NODE_ENV` environment variable is `dev` or `development` */
@@ -29,6 +29,11 @@ declare const BASE_ERROR_CODES: {
29
29
  TOKEN_EXPIRED: RawError<"TOKEN_EXPIRED">;
30
30
  ID_TOKEN_NOT_SUPPORTED: RawError<"ID_TOKEN_NOT_SUPPORTED">;
31
31
  FAILED_TO_GET_USER_INFO: RawError<"FAILED_TO_GET_USER_INFO">;
32
+ PROVIDER_NOT_SUPPORTED: RawError<"PROVIDER_NOT_SUPPORTED">;
33
+ TOKEN_REFRESH_NOT_SUPPORTED: RawError<"TOKEN_REFRESH_NOT_SUPPORTED">;
34
+ REFRESH_TOKEN_NOT_FOUND: RawError<"REFRESH_TOKEN_NOT_FOUND">;
35
+ FAILED_TO_GET_ACCESS_TOKEN: RawError<"FAILED_TO_GET_ACCESS_TOKEN">;
36
+ FAILED_TO_REFRESH_ACCESS_TOKEN: RawError<"FAILED_TO_REFRESH_ACCESS_TOKEN">;
32
37
  USER_EMAIL_NOT_FOUND: RawError<"USER_EMAIL_NOT_FOUND">;
33
38
  EMAIL_NOT_VERIFIED: RawError<"EMAIL_NOT_VERIFIED">;
34
39
  PASSWORD_TOO_SHORT: RawError<"PASSWORD_TOO_SHORT">;
@@ -16,6 +16,11 @@ const BASE_ERROR_CODES = defineErrorCodes({
16
16
  TOKEN_EXPIRED: "Token expired",
17
17
  ID_TOKEN_NOT_SUPPORTED: "id_token not supported",
18
18
  FAILED_TO_GET_USER_INFO: "Failed to get user info",
19
+ PROVIDER_NOT_SUPPORTED: "Provider not supported",
20
+ TOKEN_REFRESH_NOT_SUPPORTED: "Token refresh not supported",
21
+ REFRESH_TOKEN_NOT_FOUND: "Refresh token not found",
22
+ FAILED_TO_GET_ACCESS_TOKEN: "Failed to get a valid access token",
23
+ FAILED_TO_REFRESH_ACCESS_TOKEN: "Failed to refresh access token",
19
24
  USER_EMAIL_NOT_FOUND: "User email not found",
20
25
  EMAIL_NOT_VERIFIED: "Email not verified",
21
26
  PASSWORD_TOO_SHORT: "Password too short",
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Awaitable, AwaitableFunction, LiteralString, LiteralUnion, Prettify, Primitive, UnionToIntersection } from "./types/helper.mjs";
2
2
  import { BetterAuthPlugin, BetterAuthPluginErrorCodePart, HookEndpointContext } from "./types/plugin.mjs";
3
- import { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption } from "./types/init-options.mjs";
3
+ import { BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthDBOptions, BetterAuthOptions, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, DynamicBaseURLConfig, GenerateIdFn, StoreIdentifierOption, UserProvisioningSource, ValidateUserInfoAction, ValidateUserInfoMethod, ValidateUserInfoOAuthInfo, ValidateUserInfoResult, ValidateUserInfoSSOInfo, ValidateUserInfoSource } from "./types/init-options.mjs";
4
4
  import { BetterAuthCookie, BetterAuthCookies } from "./types/cookie.mjs";
5
5
  import { SecretConfig } from "./types/secret.mjs";
6
6
  import { AuthContext, BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, GenericEndpointContext, InfoContext, InternalAdapter, PluginContext } from "./types/context.mjs";
7
7
  import { BetterAuthClientOptions, BetterAuthClientPlugin, ClientAtomListener, ClientFetchOption, ClientStore } from "./types/plugin-client.mjs";
8
8
  import { StandardSchemaV1 } from "./types/index.mjs";
9
- export { AuthContext, Awaitable, AwaitableFunction, BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookie, BetterAuthCookies, BetterAuthDBOptions, BetterAuthOptions, BetterAuthPlugin, BetterAuthPluginErrorCodePart, BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, ClientAtomListener, ClientFetchOption, ClientStore, DynamicBaseURLConfig, GenerateIdFn, GenericEndpointContext, HookEndpointContext, InfoContext, InternalAdapter, LiteralString, LiteralUnion, PluginContext, Prettify, Primitive, SecretConfig, StandardSchemaV1, StoreIdentifierOption, UnionToIntersection };
9
+ export { AuthContext, Awaitable, AwaitableFunction, BaseURLConfig, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookie, BetterAuthCookies, BetterAuthDBOptions, BetterAuthOptions, BetterAuthPlugin, BetterAuthPluginErrorCodePart, BetterAuthPluginRegistry, BetterAuthPluginRegistryIdentifier, BetterAuthRateLimitOptions, BetterAuthRateLimitRule, BetterAuthRateLimitStorage, ClientAtomListener, ClientFetchOption, ClientStore, DynamicBaseURLConfig, GenerateIdFn, GenericEndpointContext, HookEndpointContext, InfoContext, InternalAdapter, LiteralString, LiteralUnion, PluginContext, Prettify, Primitive, SecretConfig, StandardSchemaV1, StoreIdentifierOption, UnionToIntersection, UserProvisioningSource, ValidateUserInfoAction, ValidateUserInfoMethod, ValidateUserInfoOAuthInfo, ValidateUserInfoResult, ValidateUserInfoSSOInfo, ValidateUserInfoSource };
@@ -2,7 +2,7 @@ import { ATTR_HTTP_RESPONSE_STATUS_CODE } from "./attributes.mjs";
2
2
  import { getOpenTelemetryAPI } from "./api.mjs";
3
3
  //#region src/instrumentation/tracer.ts
4
4
  const INSTRUMENTATION_SCOPE = "better-auth";
5
- const INSTRUMENTATION_VERSION = "1.7.0-beta.4";
5
+ const INSTRUMENTATION_VERSION = "1.7.0-beta.5";
6
6
  /**
7
7
  * Better-auth uses `throw ctx.redirect(url)` for flow control (e.g. OAuth
8
8
  * callbacks). These are APIErrors with 3xx status codes and should not be
@@ -47,6 +47,9 @@ declare function createAuthorizationURL({
47
47
  responseMode?: string | undefined;
48
48
  additionalParams?: Record<string, string> | undefined;
49
49
  scopeJoiner?: string | undefined;
50
- }): Promise<URL>;
50
+ }): Promise<{
51
+ url: URL;
52
+ requestedScopes: string[];
53
+ }>;
51
54
  //#endregion
52
55
  export { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL };
@@ -24,7 +24,7 @@ async function createAuthorizationURL({ id, options, authorizationEndpoint, stat
24
24
  if (!primaryClientId) throw new Error("OAuth provider requires clientId");
25
25
  url.searchParams.set("client_id", primaryClientId);
26
26
  url.searchParams.set("state", state);
27
- if (scopes) url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
27
+ if (scopes?.length) url.searchParams.set("scope", scopes.join(scopeJoiner || " "));
28
28
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
29
29
  duration && url.searchParams.set("duration", duration);
30
30
  display && url.searchParams.set("display", display);
@@ -53,7 +53,10 @@ async function createAuthorizationURL({ id, options, authorizationEndpoint, stat
53
53
  if (RESERVED_AUTHORIZATION_PARAMS_SET.has(key)) continue;
54
54
  url.searchParams.set(key, value);
55
55
  }
56
- return url;
56
+ return {
57
+ url,
58
+ requestedScopes: scopes ?? []
59
+ };
57
60
  }
58
61
  //#endregion
59
62
  export { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL };
@@ -1,12 +1,14 @@
1
1
  import { additionalAuthorizationParamsSchema } from "./authorization-params.mjs";
2
2
  import { decodeBasicCredentials, encodeBasicCredentials } from "./basic-credentials.mjs";
3
3
  import { CLIENT_ASSERTION_TYPE, ClientAssertionContext, ClientAssertionGetter, ClientAssertionGrantType, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, PrivateKeyJwtClientAssertionGetterOptions, PrivateKeyJwtSigningAlgorithm, createPrivateKeyJwtClientAssertionGetter, resolveClientAssertionParams, signPrivateKeyJwtClientAssertion } from "./client-assertion.mjs";
4
- import { OAuth2Tokens, OAuth2UserInfo, OAuthProvider, ProviderOptions } from "./oauth-provider.mjs";
4
+ import { AuthorizationURLResult, GrantAuthority, OAuth2Tokens, OAuth2UserInfo, OAuthIdTokenConfig, ProviderGrantAuthority, ProviderOptions, UpstreamProvider } from "./oauth-provider.mjs";
5
5
  import { TokenEndpointAuth, TokenEndpointAuthMethod, TokenEndpointSecretAuthentication } from "./token-endpoint-auth.mjs";
6
6
  import { clientCredentialsToken, clientCredentialsTokenRequest } from "./client-credentials-token.mjs";
7
7
  import { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL } from "./create-authorization-url.mjs";
8
8
  import { refreshAccessToken, refreshAccessTokenRequest } from "./refresh-access-token.mjs";
9
+ import { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes } from "./scopes.mjs";
9
10
  import { applyDefaultAccessTokenExpiry, generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId } from "./utils.mjs";
10
11
  import { authorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
11
12
  import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
12
- export { CLIENT_ASSERTION_TYPE, type ClientAssertionContext, type ClientAssertionGetter, type ClientAssertionGrantType, type OAuth2Tokens, type OAuth2UserInfo, type OAuthProvider, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, type PrivateKeyJwtClientAssertionGetterOptions, type PrivateKeyJwtSigningAlgorithm, type ProviderOptions, RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, type TokenEndpointAuth, type TokenEndpointAuthMethod, type TokenEndpointSecretAuthentication, additionalAuthorizationParamsSchema, applyDefaultAccessTokenExpiry, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationURL, createPrivateKeyJwtClientAssertionGetter, decodeBasicCredentials, encodeBasicCredentials, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, refreshAccessToken, refreshAccessTokenRequest, resolveClientAssertionParams, signPrivateKeyJwtClientAssertion, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
13
+ import { supportsIdTokenSignIn, verifyProviderIdToken } from "./verify-id-token.mjs";
14
+ export { type AuthorizationURLResult, CLIENT_ASSERTION_TYPE, type ClientAssertionContext, type ClientAssertionGetter, type ClientAssertionGrantType, type GrantAuthority, type OAuth2Tokens, type OAuth2UserInfo, type OAuthIdTokenConfig, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, type PrivateKeyJwtClientAssertionGetterOptions, type PrivateKeyJwtSigningAlgorithm, type ProviderGrantAuthority, type ProviderOptions, RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, type TokenEndpointAuth, type TokenEndpointAuthMethod, type TokenEndpointSecretAuthentication, type UpstreamProvider, additionalAuthorizationParamsSchema, applyDefaultAccessTokenExpiry, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationURL, createPrivateKeyJwtClientAssertionGetter, decodeBasicCredentials, encodeBasicCredentials, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, refreshAccessToken, refreshAccessTokenRequest, resolveClientAssertionParams, resolveRequestedScopes, signPrivateKeyJwtClientAssertion, supportsIdTokenSignIn, unionGrantedScopes, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken, verifyProviderIdToken };
@@ -1,3 +1,4 @@
1
+ import { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes } from "./scopes.mjs";
1
2
  import { applyDefaultAccessTokenExpiry, generateCodeChallenge, getOAuth2Tokens, getPrimaryClientId } from "./utils.mjs";
2
3
  import { RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, createAuthorizationURL } from "./create-authorization-url.mjs";
3
4
  import { additionalAuthorizationParamsSchema } from "./authorization-params.mjs";
@@ -7,4 +8,5 @@ import { clientCredentialsToken, clientCredentialsTokenRequest } from "./client-
7
8
  import { refreshAccessToken, refreshAccessTokenRequest } from "./refresh-access-token.mjs";
8
9
  import { authorizationCodeRequest, validateAuthorizationCode, validateToken } from "./validate-authorization-code.mjs";
9
10
  import { getJwks, verifyAccessToken, verifyJwsAccessToken } from "./verify.mjs";
10
- export { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, additionalAuthorizationParamsSchema, applyDefaultAccessTokenExpiry, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationURL, createPrivateKeyJwtClientAssertionGetter, decodeBasicCredentials, encodeBasicCredentials, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, refreshAccessToken, refreshAccessTokenRequest, resolveClientAssertionParams, signPrivateKeyJwtClientAssertion, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken };
11
+ import { supportsIdTokenSignIn, verifyProviderIdToken } from "./verify-id-token.mjs";
12
+ export { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS, RESERVED_AUTHORIZATION_PARAMS, RESERVED_AUTHORIZATION_PARAMS_SET, additionalAuthorizationParamsSchema, applyDefaultAccessTokenExpiry, authorizationCodeRequest, clientCredentialsToken, clientCredentialsTokenRequest, createAuthorizationURL, createPrivateKeyJwtClientAssertionGetter, decodeBasicCredentials, encodeBasicCredentials, generateCodeChallenge, getJwks, getOAuth2Tokens, getPrimaryClientId, includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, refreshAccessToken, refreshAccessTokenRequest, resolveClientAssertionParams, resolveRequestedScopes, signPrivateKeyJwtClientAssertion, supportsIdTokenSignIn, unionGrantedScopes, validateAuthorizationCode, validateToken, verifyAccessToken, verifyJwsAccessToken, verifyProviderIdToken };
@@ -1,5 +1,53 @@
1
1
  import { Awaitable, LiteralString } from "../types/helper.mjs";
2
+ import { JWTVerifyGetKey } from "jose";
3
+
2
4
  //#region src/oauth2/oauth-provider.d.ts
5
+ /**
6
+ * id_token verification config for a social provider.
7
+ *
8
+ * Declares how a client-submitted id_token is verified. The shared verifier
9
+ * (`verifyProviderIdToken`) consumes this instead of each provider implementing its own
10
+ * boolean check, so verification is centralized and fail-closed: a provider without a config
11
+ * cannot accept a forged token by omission.
12
+ */
13
+ type OAuthIdTokenConfig = {
14
+ /**
15
+ * JWKS resolver used to verify the JWS signature. Accepts a jose
16
+ * `createRemoteJWKSet` resolver or a key-resolving function
17
+ * `(protectedHeader) => key`.
18
+ */
19
+ jwks: JWTVerifyGetKey; /** Expected `iss`. Omit for providers whose issuer varies per tenant. */
20
+ issuer?: (string | string[]) | undefined; /** Expected `aud`, usually the client ID. */
21
+ audience: string | string[]; /** Permitted JWS algorithms. Defaults to the token's `alg` header. */
22
+ algorithms?: string[] | undefined; /** Maximum token age passed to jose (e.g. `"1h"`). */
23
+ maxTokenAge?: string | undefined;
24
+ /**
25
+ * How the `nonce` claim is compared to the expected nonce.
26
+ * - `"exact"` (default): strict equality.
27
+ * - `"exact-or-sha256"`: matches the raw nonce or its SHA-256 hex digest (Apple).
28
+ */
29
+ nonceComparison?: ("exact" | "exact-or-sha256") | undefined;
30
+ /**
31
+ * Accept non-JWS (opaque) tokens without signature verification. Identity is then
32
+ * resolved by getUserInfo from the access token via the provider userinfo endpoint,
33
+ * which validates it (e.g. Facebook Graph access tokens).
34
+ */
35
+ allowOpaqueToken?: boolean | undefined;
36
+ /**
37
+ * Provider-specific claim check applied after the signature, issuer,
38
+ * audience, max-age, and nonce checks pass. Return `false` to reject the
39
+ * token. Used to enforce constraints the standard checks cannot express,
40
+ * e.g. Google's hosted-domain (`hd`) restriction. Omitted by providers
41
+ * that have no extra claim requirement.
42
+ */
43
+ verifyClaims?: ((claims: Record<string, unknown>) => boolean) | undefined;
44
+ } | {
45
+ /**
46
+ * Custom verifier for providers that cannot verify against a local JWKS, such as a
47
+ * remote verification endpoint (e.g. LINE).
48
+ */
49
+ verify: (token: string, nonce?: string) => Promise<boolean>;
50
+ };
3
51
  interface OAuth2Tokens {
4
52
  tokenType?: string | undefined;
5
53
  accessToken?: string | undefined;
@@ -21,8 +69,58 @@ type OAuth2UserInfo = {
21
69
  image?: string | undefined;
22
70
  emailVerified: boolean;
23
71
  };
24
- interface OAuthProvider<T extends Record<string, any> = Record<string, any>, O extends Record<string, any> = Partial<ProviderOptions>> {
72
+ /**
73
+ * The result of building a provider authorization URL.
74
+ *
75
+ * `requestedScopes` is the effective set of scopes encoded in the URL (the
76
+ * provider's built-in defaults + configured `options.scope` + per-request
77
+ * `scopes`, composed by `resolveRequestedScopes`). Callers persist it so the
78
+ * callback can fall back to the request when the provider omits `scope` from
79
+ * its token response (RFC 6749 §5.1).
80
+ */
81
+ interface AuthorizationURLResult {
82
+ url: URL;
83
+ requestedScopes: string[];
84
+ }
85
+ /**
86
+ * How much an RP trusts a provider's echoed token-response `scope` when
87
+ * persisting `account.grantedScopes`.
88
+ *
89
+ * - `"full-grant"`: the echo is the user's complete current grant, so the seam
90
+ * replaces the stored grant with it. This is the only path that may narrow
91
+ * the grant. Declare it only for providers whose token response reports the
92
+ * full combined grant, e.g. Google with `include_granted_scopes`.
93
+ * - `"projection"`: the echo is this request's subset, so the seam unions it
94
+ * onto the stored grant. The safe default for every provider.
95
+ * - `"absent-echo"`: the provider omitted `scope`, so the grant equals what was
96
+ * requested (RFC 6749 §5.1) and the seam unions the requested set. Resolved
97
+ * at runtime by the persistence seam, never declared by a provider.
98
+ *
99
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
100
+ */
101
+ type GrantAuthority = "full-grant" | "projection" | "absent-echo";
102
+ /**
103
+ * The authority a provider may declare for its own echoed scope. `"absent-echo"`
104
+ * is excluded because it is a runtime condition (an omitted echo), not a
105
+ * provider trait.
106
+ */
107
+ type ProviderGrantAuthority = Exclude<GrantAuthority, "absent-echo">;
108
+ interface UpstreamProvider<T extends Record<string, any> = Record<string, any>, O extends Record<string, any> = Partial<ProviderOptions>> {
25
109
  id: LiteralString;
110
+ /**
111
+ * The path the provider redirects back to, relative to the app base URL,
112
+ * e.g. `/callback/google`.
113
+ */
114
+ callbackPath: string;
115
+ /**
116
+ * How the persistence seam treats this provider's echoed token-response
117
+ * `scope`. Declare `"full-grant"` only when the echo is the user's complete
118
+ * current grant (e.g. Google with `include_granted_scopes`); otherwise the
119
+ * echo is unioned onto the stored grant.
120
+ *
121
+ * @default "projection"
122
+ */
123
+ grantAuthority?: ProviderGrantAuthority | undefined;
26
124
  createAuthorizationURL: (data: {
27
125
  state: string;
28
126
  codeVerifier: string;
@@ -37,7 +135,7 @@ interface OAuthProvider<T extends Record<string, any> = Record<string, any>, O e
37
135
  * before applying them.
38
136
  */
39
137
  additionalParams?: Record<string, string> | undefined;
40
- }) => Awaitable<URL>;
138
+ }) => Awaitable<AuthorizationURLResult>;
41
139
  name: string;
42
140
  validateAuthorizationCode: (data: {
43
141
  code: string;
@@ -65,14 +163,12 @@ interface OAuthProvider<T extends Record<string, any> = Record<string, any>, O e
65
163
  * Custom function to refresh a token
66
164
  */
67
165
  refreshAccessToken?: ((refreshToken: string) => Promise<OAuth2Tokens>) | undefined;
68
- revokeToken?: ((token: string) => Promise<void>) | undefined;
69
166
  /**
70
- * Verify the id token
71
- * @param token - The id token
72
- * @param nonce - The nonce
73
- * @returns True if the id token is valid, false otherwise
167
+ * Declarative id_token verification config consumed by the shared
168
+ * `verifyProviderIdToken` verifier. Providers set this instead of implementing a boolean
169
+ * verify method, which keeps verification centralized and fail-closed.
74
170
  */
75
- verifyIdToken?: ((token: string, nonce?: string) => Promise<boolean>) | undefined;
171
+ idToken?: OAuthIdTokenConfig | undefined;
76
172
  /**
77
173
  * The expected issuer identifier for this provider (RFC 9207).
78
174
  * When set, the callback handler validates the `iss` query parameter
@@ -212,6 +308,29 @@ type ProviderOptions<Profile extends Record<string, any> = any> = {
212
308
  * @default false
213
309
  */
214
310
  overrideUserInfoOnSignIn?: boolean | undefined;
311
+ /**
312
+ * Require this provider's email to be verified before a session is created.
313
+ *
314
+ * When the provider reports the email as unverified, the user and account are
315
+ * still created/linked, but no session is issued: the OAuth callback redirects
316
+ * with `?error=email_not_verified` and id-token sign-in returns a `403`
317
+ * `EMAIL_NOT_VERIFIED`. A verification email is (re)sent per the
318
+ * `emailVerification` settings (`sendOnSignUp` / `sendOnSignIn`).
319
+ *
320
+ * The gate checks the local user's verification state, not the provider's
321
+ * claim on each request: a user already verified through another method (or a
322
+ * prior verified sign-in) keeps access even if the provider later reports the
323
+ * email as unverified.
324
+ *
325
+ * This is opt-in per provider and is independent of
326
+ * `emailAndPassword.requireEmailVerification`; enabling that does not gate
327
+ * social sign-in. Only enable it for providers that report a trustworthy
328
+ * `email_verified` signal: several providers always report the email as
329
+ * unverified, which would block every sign-in.
330
+ *
331
+ * @default false
332
+ */
333
+ requireEmailVerification?: boolean | undefined;
215
334
  };
216
335
  //#endregion
217
- export { OAuth2Tokens, OAuth2UserInfo, OAuthProvider, ProviderOptions };
336
+ export { AuthorizationURLResult, GrantAuthority, OAuth2Tokens, OAuth2UserInfo, OAuthIdTokenConfig, ProviderGrantAuthority, ProviderOptions, UpstreamProvider };
@@ -56,7 +56,7 @@ async function refreshAccessToken({ refreshToken, options, tokenEndpoint, authen
56
56
  accessToken: data.access_token,
57
57
  refreshToken: data.refresh_token,
58
58
  tokenType: data.token_type,
59
- scopes: data.scope?.split(" "),
59
+ scopes: Array.isArray(data.scope) ? data.scope : data.scope?.split(" "),
60
60
  idToken: data.id_token
61
61
  };
62
62
  if (data.expires_in) {
@@ -0,0 +1,76 @@
1
+ import { ProviderOptions } from "./oauth-provider.mjs";
2
+
3
+ //#region src/oauth2/scopes.d.ts
4
+ /**
5
+ * Parse a provider's `scope` token-response field into a string array.
6
+ *
7
+ * RFC 6749 §3.3 defines `scope` as a space-delimited string, but providers
8
+ * vary: some (e.g. Twitch) return an already-split array. Accept both, plus the
9
+ * omitted/empty case, without ever calling `.split` on a non-string. Returns
10
+ * `[]` when no scope is present.
11
+ *
12
+ * @see https://github.com/better-auth/better-auth/issues/9076
13
+ */
14
+ declare function parseScopeField(scope: unknown): string[];
15
+ /**
16
+ * Normalize a scope set into a single deduped, sorted array.
17
+ *
18
+ * Scope order is insignificant per RFC 6749 §3.3, so normalize for idempotent
19
+ * writes and trivial comparisons: trim each token, drop empties, dedupe, and
20
+ * sort ascending. Returns `[]` when the union is empty.
21
+ *
22
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
23
+ */
24
+ declare function normalizeScopes(stored: string[] | null | undefined, incoming?: string[] | undefined): string[];
25
+ /**
26
+ * Union the stored granted-scope set with the scopes observed on an
27
+ * authorization or token exchange.
28
+ *
29
+ * The provider's echoed `scope` is authoritative when present. RFC 6749 §3.3
30
+ * and §5.1 say an omitted or empty echo means the grant equals what was
31
+ * requested, so fall back to `requested` in that case. The result unions onto
32
+ * the stored grant (never narrows on a normal write) and is normalized per
33
+ * {@link normalizeScopes}.
34
+ *
35
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
36
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
37
+ */
38
+ declare function unionGrantedScopes(stored: string[] | null | undefined, echoed: string[] | undefined, requested: string[] | undefined): string[];
39
+ /**
40
+ * Coerce a stored granted-scope value into a usable array.
41
+ *
42
+ * `account.grantedScopes` is nullable (legacy rows and non-OAuth accounts read
43
+ * as unset), and on dialects that store the array as a JSON string a malformed
44
+ * operator backfill could deserialize to a non-array. Both collapse to `[]`
45
+ * here so every reader works against a real `string[]` without re-deriving the
46
+ * guard.
47
+ */
48
+ declare function readGrantedScopes(stored: string[] | null | undefined): string[];
49
+ /**
50
+ * Test whether a normalized granted-scope set contains a specific scope.
51
+ *
52
+ * Matching is exact and case-sensitive per RFC 6749 §3.3. The argument is the
53
+ * normalized `account.grantedScopes` array; a raw provider `scope` string must
54
+ * be run through {@link parseScopeField} first.
55
+ *
56
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
57
+ */
58
+ declare function includesGrantedScope(granted: string[] | null | undefined, scope: string): boolean;
59
+ /**
60
+ * Compose the effective scope set to encode in a single authorization URL.
61
+ *
62
+ * Precedence: the provider's built-in defaults (unless `disableDefaultScope`),
63
+ * then the integrator's configured `options.scope`, then the per-request
64
+ * `scopes`. The result is the value persisted into OAuth state as the RFC 6749
65
+ * §5.1 fallback, so it is preserved verbatim (not normalized) to match what is
66
+ * sent to the provider.
67
+ *
68
+ * `defaultScopes` is a parameter rather than a provider-contract field so the
69
+ * runtime-synthesized generic OAuth provider, which has no static default set,
70
+ * can pass its configured scopes here.
71
+ *
72
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
73
+ */
74
+ declare function resolveRequestedScopes(options: Pick<ProviderOptions, "scope" | "disableDefaultScope"> | undefined, defaultScopes: string[], perRequestScopes: string[] | undefined): string[];
75
+ //#endregion
76
+ export { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes };
@@ -0,0 +1,96 @@
1
+ //#region src/oauth2/scopes.ts
2
+ /**
3
+ * Parse a provider's `scope` token-response field into a string array.
4
+ *
5
+ * RFC 6749 §3.3 defines `scope` as a space-delimited string, but providers
6
+ * vary: some (e.g. Twitch) return an already-split array. Accept both, plus the
7
+ * omitted/empty case, without ever calling `.split` on a non-string. Returns
8
+ * `[]` when no scope is present.
9
+ *
10
+ * @see https://github.com/better-auth/better-auth/issues/9076
11
+ */
12
+ function parseScopeField(scope) {
13
+ if (Array.isArray(scope)) return scope.filter((s) => typeof s === "string" && s !== "");
14
+ if (typeof scope === "string") return scope.split(" ").filter(Boolean);
15
+ return [];
16
+ }
17
+ /**
18
+ * Normalize a scope set into a single deduped, sorted array.
19
+ *
20
+ * Scope order is insignificant per RFC 6749 §3.3, so normalize for idempotent
21
+ * writes and trivial comparisons: trim each token, drop empties, dedupe, and
22
+ * sort ascending. Returns `[]` when the union is empty.
23
+ *
24
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
25
+ */
26
+ function normalizeScopes(stored, incoming) {
27
+ const normalized = /* @__PURE__ */ new Set();
28
+ for (const scope of [...stored ?? [], ...incoming ?? []]) {
29
+ const trimmed = scope.trim();
30
+ if (trimmed) normalized.add(trimmed);
31
+ }
32
+ return [...normalized].sort();
33
+ }
34
+ /**
35
+ * Union the stored granted-scope set with the scopes observed on an
36
+ * authorization or token exchange.
37
+ *
38
+ * The provider's echoed `scope` is authoritative when present. RFC 6749 §3.3
39
+ * and §5.1 say an omitted or empty echo means the grant equals what was
40
+ * requested, so fall back to `requested` in that case. The result unions onto
41
+ * the stored grant (never narrows on a normal write) and is normalized per
42
+ * {@link normalizeScopes}.
43
+ *
44
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
45
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
46
+ */
47
+ function unionGrantedScopes(stored, echoed, requested) {
48
+ return normalizeScopes(stored, echoed?.length ? echoed : requested);
49
+ }
50
+ /**
51
+ * Coerce a stored granted-scope value into a usable array.
52
+ *
53
+ * `account.grantedScopes` is nullable (legacy rows and non-OAuth accounts read
54
+ * as unset), and on dialects that store the array as a JSON string a malformed
55
+ * operator backfill could deserialize to a non-array. Both collapse to `[]`
56
+ * here so every reader works against a real `string[]` without re-deriving the
57
+ * guard.
58
+ */
59
+ function readGrantedScopes(stored) {
60
+ return Array.isArray(stored) ? stored : [];
61
+ }
62
+ /**
63
+ * Test whether a normalized granted-scope set contains a specific scope.
64
+ *
65
+ * Matching is exact and case-sensitive per RFC 6749 §3.3. The argument is the
66
+ * normalized `account.grantedScopes` array; a raw provider `scope` string must
67
+ * be run through {@link parseScopeField} first.
68
+ *
69
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-3.3
70
+ */
71
+ function includesGrantedScope(granted, scope) {
72
+ return granted?.includes(scope) ?? false;
73
+ }
74
+ /**
75
+ * Compose the effective scope set to encode in a single authorization URL.
76
+ *
77
+ * Precedence: the provider's built-in defaults (unless `disableDefaultScope`),
78
+ * then the integrator's configured `options.scope`, then the per-request
79
+ * `scopes`. The result is the value persisted into OAuth state as the RFC 6749
80
+ * §5.1 fallback, so it is preserved verbatim (not normalized) to match what is
81
+ * sent to the provider.
82
+ *
83
+ * `defaultScopes` is a parameter rather than a provider-contract field so the
84
+ * runtime-synthesized generic OAuth provider, which has no static default set,
85
+ * can pass its configured scopes here.
86
+ *
87
+ * @see https://www.rfc-editor.org/rfc/rfc6749#section-5.1
88
+ */
89
+ function resolveRequestedScopes(options, defaultScopes, perRequestScopes) {
90
+ const scopes = options?.disableDefaultScope ? [] : [...defaultScopes];
91
+ if (options?.scope) scopes.push(...options.scope);
92
+ if (perRequestScopes) scopes.push(...perRequestScopes);
93
+ return scopes;
94
+ }
95
+ //#endregion
96
+ export { includesGrantedScope, normalizeScopes, parseScopeField, readGrantedScopes, resolveRequestedScopes, unionGrantedScopes };
@@ -1,3 +1,4 @@
1
+ import { parseScopeField } from "./scopes.mjs";
1
2
  import { base64Url } from "@better-auth/utils/base64";
2
3
  //#region src/oauth2/utils.ts
3
4
  function getOAuth2Tokens(data) {
@@ -11,7 +12,7 @@ function getOAuth2Tokens(data) {
11
12
  refreshToken: data.refresh_token,
12
13
  accessTokenExpiresAt: data.expires_in ? getDate(data.expires_in) : void 0,
13
14
  refreshTokenExpiresAt: data.refresh_token_expires_in ? getDate(data.refresh_token_expires_in) : void 0,
14
- scopes: data?.scope ? typeof data.scope === "string" ? data.scope.split(" ") : data.scope : [],
15
+ scopes: parseScopeField(data.scope),
15
16
  idToken: data.id_token,
16
17
  raw: data
17
18
  };
@@ -0,0 +1,26 @@
1
+ import { UpstreamProvider } from "./oauth-provider.mjs";
2
+
3
+ //#region src/oauth2/verify-id-token.d.ts
4
+ /**
5
+ * Whether a provider can verify a client-submitted id_token.
6
+ *
7
+ * A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
8
+ * verification config, or when the integrator supplies a `verifyIdToken` override on the
9
+ * provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
10
+ * neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
11
+ */
12
+ declare function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>): boolean;
13
+ /**
14
+ * Verify a client-submitted id_token against a provider's verification config.
15
+ *
16
+ * This is the single id_token verifier for every social provider. Providers no longer
17
+ * implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
18
+ * config and this function performs the cryptographic check. The contract is fail-closed: a
19
+ * provider without a config (and without an integrator `verifyIdToken` override) returns
20
+ * `false`, so a forged token can never be accepted by omission.
21
+ *
22
+ * @returns `true` only when the token is authentic for the provider.
23
+ */
24
+ declare function verifyProviderIdToken(provider: UpstreamProvider<any, any>, token: string, nonce?: string): Promise<boolean>;
25
+ //#endregion
26
+ export { supportsIdTokenSignIn, verifyProviderIdToken };