@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
@@ -0,0 +1,62 @@
1
+ import { decodeProtectedHeader, jwtVerify } from "jose";
2
+ //#region src/oauth2/verify-id-token.ts
3
+ async function sha256Hex(value) {
4
+ const data = new TextEncoder().encode(value);
5
+ const digest = await crypto.subtle.digest("SHA-256", data);
6
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
7
+ }
8
+ async function nonceMatches(claimNonce, nonce, comparison = "exact") {
9
+ if (typeof claimNonce !== "string") return false;
10
+ if (claimNonce === nonce) return true;
11
+ if (comparison === "exact-or-sha256") return claimNonce === await sha256Hex(nonce);
12
+ return false;
13
+ }
14
+ /**
15
+ * Whether a provider can verify a client-submitted id_token.
16
+ *
17
+ * A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
18
+ * verification config, or when the integrator supplies a `verifyIdToken` override on the
19
+ * provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
20
+ * neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
21
+ */
22
+ function supportsIdTokenSignIn(provider) {
23
+ const options = provider.options ?? {};
24
+ if (options.disableIdTokenSignIn) return false;
25
+ return Boolean(provider.idToken || options.verifyIdToken);
26
+ }
27
+ /**
28
+ * Verify a client-submitted id_token against a provider's verification config.
29
+ *
30
+ * This is the single id_token verifier for every social provider. Providers no longer
31
+ * implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
32
+ * config and this function performs the cryptographic check. The contract is fail-closed: a
33
+ * provider without a config (and without an integrator `verifyIdToken` override) returns
34
+ * `false`, so a forged token can never be accepted by omission.
35
+ *
36
+ * @returns `true` only when the token is authentic for the provider.
37
+ */
38
+ async function verifyProviderIdToken(provider, token, nonce) {
39
+ const options = provider.options ?? {};
40
+ if (options.disableIdTokenSignIn) return false;
41
+ try {
42
+ if (options.verifyIdToken) return await options.verifyIdToken(token, nonce);
43
+ const config = provider.idToken;
44
+ if (!config) return false;
45
+ if ("verify" in config) return await config.verify(token, nonce);
46
+ if (token.split(".").length !== 3) return config.allowOpaqueToken === true;
47
+ const { alg } = decodeProtectedHeader(token);
48
+ const { payload } = await jwtVerify(token, config.jwks, {
49
+ issuer: config.issuer,
50
+ audience: config.audience,
51
+ algorithms: config.algorithms ?? (alg ? [alg] : void 0),
52
+ maxTokenAge: config.maxTokenAge
53
+ });
54
+ if (nonce && !await nonceMatches(payload.nonce, nonce, config.nonceComparison)) return false;
55
+ if (config.verifyClaims && !config.verifyClaims(payload)) return false;
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ //#endregion
62
+ export { supportsIdTokenSignIn, verifyProviderIdToken };
@@ -14,6 +14,20 @@ interface VerifyAccessTokenRemote {
14
14
  * is also still active.
15
15
  */
16
16
  force?: boolean;
17
+ /**
18
+ * Accept introspection responses that omit the `aud` claim even when a
19
+ * required `audience` is configured in `verifyOptions`.
20
+ *
21
+ * By default verification fails closed: if you configure an `audience` and
22
+ * the introspection response has no `aud` (or a mismatching one), the token
23
+ * is rejected. Some authorization servers legitimately omit `aud` from
24
+ * introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
25
+ * this if you trust the issuer to bind the token to this resource through
26
+ * another mechanism, as it skips the audience check in that case.
27
+ *
28
+ * @default false
29
+ */
30
+ allowMissingAudience?: boolean;
17
31
  }
18
32
  /**
19
33
  * Performs local verification of an access token for your APIs.
@@ -11,8 +11,13 @@ const joseInfrastructureErrorCodes = new Set([
11
11
  function isJoseInfrastructureError(error) {
12
12
  return joseInfrastructureErrorCodes.has(error.code);
13
13
  }
14
- /** Last fetched jwks used locally in getJwks @internal */
15
- let jwks;
14
+ const jwksCache = /* @__PURE__ */ new Map();
15
+ /**
16
+ * How long a cached JWKS is trusted before it is refetched
17
+ *
18
+ * @internal
19
+ */
20
+ const JWKS_CACHE_TTL_MS = 300 * 1e3;
16
21
  /**
17
22
  * Performs local verification of an access token for your APIs.
18
23
  *
@@ -37,14 +42,24 @@ async function getJwks(token, opts) {
37
42
  throw new Error(error);
38
43
  }
39
44
  if (!jwtHeaders.kid) throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
40
- if (!jwks || !jwks.keys.find((jwk) => jwk.kid === jwtHeaders.kid)) {
41
- jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
45
+ const kid = jwtHeaders.kid;
46
+ const cacheKey = opts.jwksFetch;
47
+ const cached = jwksCache.get(cacheKey);
48
+ const isFresh = cached ? Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS : false;
49
+ const hasKid = cached?.jwks.keys.some((jwk) => jwk.kid === kid) ?? false;
50
+ if (!cached || !isFresh || !hasKid) {
51
+ const jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
42
52
  if (res.error) throw new Error(`Jwks failed: ${res.error.message ?? res.error.statusText}`);
43
53
  return res.data;
44
54
  }) : await opts.jwksFetch();
45
55
  if (!jwks) throw new Error("No jwks found");
56
+ jwksCache.set(cacheKey, {
57
+ jwks,
58
+ fetchedAt: Date.now()
59
+ });
60
+ return jwks;
46
61
  }
47
- return jwks;
62
+ return cached.jwks;
48
63
  }
49
64
  /**
50
65
  * Performs local verification of an access token for your API.
@@ -85,8 +100,9 @@ async function verifyAccessToken(token, opts) {
85
100
  if (!introspect.active) throw new APIError("UNAUTHORIZED", { message: "token inactive" });
86
101
  try {
87
102
  const unsecuredJwt = new UnsecuredJWT(introspect).encode();
88
- const { audience: _audience, ...verifyOptions } = opts.verifyOptions;
89
- payload = (introspect.aud ? UnsecuredJWT.decode(unsecuredJwt, opts.verifyOptions) : UnsecuredJWT.decode(unsecuredJwt, verifyOptions)).payload;
103
+ const { audience: _audience, ...verifyOptionsNoAudience } = opts.verifyOptions;
104
+ const skipAudience = !introspect.aud && opts.remoteVerify.allowMissingAudience === true;
105
+ payload = UnsecuredJWT.decode(unsecuredJwt, skipAudience ? verifyOptionsNoAudience : opts.verifyOptions).payload;
90
106
  } catch (error) {
91
107
  throw new Error(error);
92
108
  }
@@ -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/apple.d.ts
3
5
  interface AppleProfile {
4
6
  /**
@@ -67,6 +69,7 @@ interface AppleOptions extends ProviderOptions<AppleProfile> {
67
69
  declare const apple: (options: AppleOptions) => {
68
70
  id: "apple";
69
71
  name: string;
72
+ callbackPath: string;
70
73
  createAuthorizationURL({
71
74
  state,
72
75
  scopes,
@@ -80,7 +83,10 @@ declare const apple: (options: AppleOptions) => {
80
83
  display?: string | undefined;
81
84
  loginHint?: string | undefined;
82
85
  additionalParams?: Record<string, string> | undefined;
83
- }): Promise<URL>;
86
+ }): Promise<{
87
+ url: URL;
88
+ requestedScopes: string[];
89
+ }>;
84
90
  validateAuthorizationCode: ({
85
91
  code,
86
92
  codeVerifier,
@@ -91,7 +97,13 @@ declare const apple: (options: AppleOptions) => {
91
97
  codeVerifier?: string | undefined;
92
98
  deviceId?: string | undefined;
93
99
  }) => Promise<OAuth2Tokens>;
94
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
100
+ idToken: {
101
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
102
+ issuer: string;
103
+ audience: string | string[];
104
+ maxTokenAge: string;
105
+ nonceComparison: "exact-or-sha256";
106
+ };
95
107
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
96
108
  getUserInfo(token: OAuth2Tokens & {
97
109
  user?: {
@@ -1,40 +1,30 @@
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/apple.ts
10
- async function sha256Hex(value) {
11
- const data = new TextEncoder().encode(value);
12
- const digest = await crypto.subtle.digest("SHA-256", data);
13
- return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
14
- }
15
- async function nonceMatches(jwtNonce, nonce) {
16
- if (typeof jwtNonce !== "string") return false;
17
- if (jwtNonce === nonce) return true;
18
- return jwtNonce === await sha256Hex(nonce);
19
- }
11
+ const APPLE_DEFAULT_SCOPES = ["email", "name"];
20
12
  const apple = (options) => {
21
13
  const tokenEndpoint = "https://appleid.apple.com/auth/token";
22
14
  return {
23
15
  id: "apple",
24
16
  name: "Apple",
17
+ callbackPath: "/callback/apple",
25
18
  async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
26
19
  if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
27
20
  logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
28
21
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
29
22
  }
30
- const _scope = options.disableDefaultScope ? [] : ["email", "name"];
31
- if (options.scope) _scope.push(...options.scope);
32
- if (scopes) _scope.push(...scopes);
33
- return await createAuthorizationURL({
23
+ return createAuthorizationURL({
34
24
  id: "apple",
35
25
  options,
36
26
  authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
37
- scopes: _scope,
27
+ scopes: resolveRequestedScopes(options, APPLE_DEFAULT_SCOPES, scopes),
38
28
  state,
39
29
  redirectURI,
40
30
  responseMode: "form_post",
@@ -51,26 +41,12 @@ const apple = (options) => {
51
41
  tokenEndpoint
52
42
  });
53
43
  },
54
- async verifyIdToken(token, nonce) {
55
- if (options.disableIdTokenSignIn) return false;
56
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
57
- try {
58
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
59
- if (!kid || !jwtAlg) return false;
60
- const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
61
- algorithms: [jwtAlg],
62
- issuer: "https://appleid.apple.com",
63
- audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
64
- maxTokenAge: "1h"
65
- });
66
- ["email_verified", "is_private_email"].forEach((field) => {
67
- if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
68
- });
69
- if (nonce && !await nonceMatches(jwtClaims.nonce, nonce)) return false;
70
- return !!jwtClaims;
71
- } catch {
72
- return false;
73
- }
44
+ idToken: {
45
+ jwks: (header) => getApplePublicKey(header.kid),
46
+ issuer: "https://appleid.apple.com",
47
+ audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
48
+ maxTokenAge: "1h",
49
+ nonceComparison: "exact-or-sha256"
74
50
  },
75
51
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
76
52
  return refreshAccessToken({
@@ -21,6 +21,7 @@ interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
21
21
  declare const atlassian: (options: AtlassianOptions) => {
22
22
  id: "atlassian";
23
23
  name: string;
24
+ callbackPath: string;
24
25
  createAuthorizationURL({
25
26
  state,
26
27
  scopes,
@@ -35,7 +36,10 @@ declare const atlassian: (options: AtlassianOptions) => {
35
36
  display?: string | undefined;
36
37
  loginHint?: string | undefined;
37
38
  additionalParams?: Record<string, string> | undefined;
38
- }): Promise<URL>;
39
+ }): Promise<{
40
+ url: URL;
41
+ requestedScopes: string[];
42
+ }>;
39
43
  validateAuthorizationCode: ({
40
44
  code,
41
45
  codeVerifier,
@@ -1,29 +1,29 @@
1
1
  import { BetterAuthError } from "../error/index.mjs";
2
2
  import { logger } from "../env/logger.mjs";
3
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
3
4
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
4
5
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
5
6
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
6
7
  import { betterFetch } from "@better-fetch/fetch";
7
8
  //#region src/social-providers/atlassian.ts
9
+ const ATLASSIAN_DEFAULT_SCOPES = ["read:jira-user", "offline_access"];
8
10
  const atlassian = (options) => {
9
11
  const tokenEndpoint = "https://auth.atlassian.com/oauth/token";
10
12
  return {
11
13
  id: "atlassian",
12
14
  name: "Atlassian",
15
+ callbackPath: "/callback/atlassian",
13
16
  async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
14
17
  if (!options.clientId || !options.clientSecret) {
15
18
  logger.error("Client Id and Secret are required for Atlassian");
16
19
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
17
20
  }
18
21
  if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Atlassian");
19
- const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
20
- if (options.scope) _scopes.push(...options.scope);
21
- if (scopes) _scopes.push(...scopes);
22
22
  return createAuthorizationURL({
23
23
  id: "atlassian",
24
24
  options,
25
25
  authorizationEndpoint: "https://auth.atlassian.com/authorize",
26
- scopes: _scopes,
26
+ scopes: resolveRequestedScopes(options, ATLASSIAN_DEFAULT_SCOPES, scopes),
27
27
  state,
28
28
  codeVerifier,
29
29
  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/cognito.d.ts
3
5
  interface CognitoProfile {
4
6
  sub: string;
@@ -47,6 +49,7 @@ interface CognitoOptions extends ProviderOptions<CognitoProfile> {
47
49
  declare const cognito: (options: CognitoOptions) => {
48
50
  id: "cognito";
49
51
  name: string;
52
+ callbackPath: string;
50
53
  createAuthorizationURL({
51
54
  state,
52
55
  scopes,
@@ -61,7 +64,10 @@ declare const cognito: (options: CognitoOptions) => {
61
64
  display?: string | undefined;
62
65
  loginHint?: string | undefined;
63
66
  additionalParams?: Record<string, string> | undefined;
64
- }): Promise<URL>;
67
+ }): Promise<{
68
+ url: URL;
69
+ requestedScopes: string[];
70
+ }>;
65
71
  validateAuthorizationCode: ({
66
72
  code,
67
73
  codeVerifier,
@@ -73,7 +79,12 @@ declare const cognito: (options: CognitoOptions) => {
73
79
  deviceId?: string | undefined;
74
80
  }) => Promise<OAuth2Tokens>;
75
81
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
76
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
82
+ idToken: {
83
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
84
+ issuer: string;
85
+ audience: string | string[];
86
+ maxTokenAge: string;
87
+ };
77
88
  getUserInfo(token: OAuth2Tokens & {
78
89
  user?: {
79
90
  name?: {
@@ -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,6 +25,7 @@ const cognito = (options) => {
19
25
  return {
20
26
  id: "cognito",
21
27
  name: "Cognito",
28
+ callbackPath: "/callback/cognito",
22
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.");
@@ -28,18 +35,12 @@ 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,
@@ -55,9 +56,15 @@ const cognito = (options) => {
55
56
  const encodedScope = encodeURIComponent(scopeValue);
56
57
  const urlString = url.toString();
57
58
  const separator = urlString.includes("?") ? "&" : "?";
58
- return new URL(`${urlString}${separator}scope=${encodedScope}`);
59
+ return {
60
+ url: new URL(`${urlString}${separator}scope=${encodedScope}`),
61
+ requestedScopes
62
+ };
59
63
  }
60
- return url;
64
+ return {
65
+ url,
66
+ requestedScopes
67
+ };
61
68
  },
62
69
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
63
70
  return validateAuthorizationCode({
@@ -79,26 +86,11 @@ const cognito = (options) => {
79
86
  tokenEndpoint
80
87
  });
81
88
  },
82
- async verifyIdToken(token, nonce) {
83
- if (options.disableIdTokenSignIn) return false;
84
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
85
- try {
86
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
87
- if (!kid || !jwtAlg) return false;
88
- const publicKey = await getCognitoPublicKey(kid, options.region, options.userPoolId);
89
- const expectedIssuer = `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`;
90
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
91
- algorithms: [jwtAlg],
92
- issuer: expectedIssuer,
93
- audience: options.clientId,
94
- maxTokenAge: "1h"
95
- });
96
- if (nonce && jwtClaims.nonce !== nonce) return false;
97
- return true;
98
- } catch (error) {
99
- logger.error("Failed to verify ID token:", error);
100
- return false;
101
- }
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"
102
94
  },
103
95
  async getUserInfo(token) {
104
96
  if (options.getUserInfo) return options.getUserInfo(token);
@@ -77,6 +77,7 @@ 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,
@@ -90,7 +91,10 @@ declare const discord: (options: DiscordOptions) => {
90
91
  display?: string | undefined;
91
92
  loginHint?: string | undefined;
92
93
  additionalParams?: Record<string, string> | undefined;
93
- }): Promise<URL>;
94
+ }): Promise<{
95
+ url: URL;
96
+ requestedScopes: string[];
97
+ }>;
94
98
  validateAuthorizationCode: ({
95
99
  code,
96
100
  redirectURI
@@ -1,23 +1,24 @@
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/discord.ts
7
+ const DISCORD_DEFAULT_SCOPES = ["identify", "email"];
6
8
  const discord = (options) => {
7
9
  const tokenEndpoint = "https://discord.com/api/oauth2/token";
8
10
  return {
9
11
  id: "discord",
10
12
  name: "Discord",
11
- createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
12
- const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
13
- if (scopes) _scopes.push(...scopes);
14
- if (options.scope) _scopes.push(...options.scope);
15
- const hasBotScope = _scopes.includes("bot");
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");
16
17
  return createAuthorizationURL({
17
18
  id: "discord",
18
19
  options,
19
20
  authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
20
- scopes: _scopes,
21
+ scopes: requestedScopes,
21
22
  state,
22
23
  redirectURI,
23
24
  prompt: options.prompt || "none",
@@ -20,6 +20,7 @@ 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,
@@ -34,7 +35,10 @@ declare const dropbox: (options: DropboxOptions) => {
34
35
  display?: string | undefined;
35
36
  loginHint?: string | undefined;
36
37
  additionalParams?: Record<string, string> | undefined;
37
- }) => Promise<URL>;
38
+ }) => Promise<{
39
+ url: URL;
40
+ requestedScopes: string[];
41
+ }>;
38
42
  validateAuthorizationCode: ({
39
43
  code,
40
44
  codeVerifier,
@@ -1,22 +1,22 @@
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",
13
+ callbackPath: "/callback/dropbox",
11
14
  createAuthorizationURL: async ({ state, scopes, codeVerifier, redirectURI, additionalParams }) => {
12
- const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
13
- if (options.scope) _scopes.push(...options.scope);
14
- if (scopes) _scopes.push(...scopes);
15
- return await createAuthorizationURL({
15
+ return createAuthorizationURL({
16
16
  id: "dropbox",
17
17
  options,
18
18
  authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
19
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, DROPBOX_DEFAULT_SCOPES, scopes),
20
20
  state,
21
21
  redirectURI,
22
22
  codeVerifier,
@@ -1,4 +1,6 @@
1
1
  import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import * as jose from "jose";
3
+
2
4
  //#region src/social-providers/facebook.d.ts
3
5
  interface FacebookProfile {
4
6
  id: string;
@@ -30,6 +32,7 @@ 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,
@@ -44,7 +47,10 @@ declare const facebook: (options: FacebookOptions) => {
44
47
  display?: string | undefined;
45
48
  loginHint?: string | undefined;
46
49
  additionalParams?: Record<string, string> | undefined;
47
- }): Promise<URL>;
50
+ }): Promise<{
51
+ url: URL;
52
+ requestedScopes: string[];
53
+ }>;
48
54
  validateAuthorizationCode: ({
49
55
  code,
50
56
  redirectURI
@@ -54,7 +60,20 @@ declare const facebook: (options: FacebookOptions) => {
54
60
  codeVerifier?: string | undefined;
55
61
  deviceId?: string | undefined;
56
62
  }) => Promise<OAuth2Tokens>;
57
- 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
+ };
58
77
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
59
78
  getUserInfo(token: OAuth2Tokens & {
60
79
  user?: {