@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.6

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 (170) hide show
  1. package/dist/api/index.d.mts +47 -4
  2. package/dist/api/index.mjs +40 -1
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +57 -31
  7. package/dist/db/adapter/index.d.mts +54 -10
  8. package/dist/db/adapter/types.d.mts +1 -1
  9. package/dist/db/get-tables.mjs +3 -3
  10. package/dist/db/schema/account.d.mts +1 -1
  11. package/dist/db/schema/account.mjs +1 -1
  12. package/dist/db/type.d.mts +12 -7
  13. package/dist/env/env-impl.mjs +1 -1
  14. package/dist/error/codes.d.mts +5 -0
  15. package/dist/error/codes.mjs +5 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/tracer.mjs +1 -1
  18. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  19. package/dist/oauth2/create-authorization-url.mjs +5 -2
  20. package/dist/oauth2/dpop.d.mts +142 -0
  21. package/dist/oauth2/dpop.mjs +246 -0
  22. package/dist/oauth2/index.d.mts +6 -3
  23. package/dist/oauth2/index.mjs +5 -2
  24. package/dist/oauth2/oauth-provider.d.mts +128 -9
  25. package/dist/oauth2/refresh-access-token.mjs +1 -1
  26. package/dist/oauth2/scopes.d.mts +76 -0
  27. package/dist/oauth2/scopes.mjs +96 -0
  28. package/dist/oauth2/utils.mjs +2 -1
  29. package/dist/oauth2/verify-id-token.d.mts +26 -0
  30. package/dist/oauth2/verify-id-token.mjs +62 -0
  31. package/dist/oauth2/verify.d.mts +88 -15
  32. package/dist/oauth2/verify.mjs +187 -19
  33. package/dist/social-providers/apple.d.mts +14 -2
  34. package/dist/social-providers/apple.mjs +12 -36
  35. package/dist/social-providers/atlassian.d.mts +5 -1
  36. package/dist/social-providers/atlassian.mjs +4 -4
  37. package/dist/social-providers/cognito.d.mts +13 -2
  38. package/dist/social-providers/cognito.mjs +24 -32
  39. package/dist/social-providers/discord.d.mts +5 -1
  40. package/dist/social-providers/discord.mjs +7 -6
  41. package/dist/social-providers/dropbox.d.mts +5 -1
  42. package/dist/social-providers/dropbox.mjs +5 -5
  43. package/dist/social-providers/facebook.d.mts +21 -2
  44. package/dist/social-providers/facebook.mjs +46 -22
  45. package/dist/social-providers/figma.d.mts +5 -1
  46. package/dist/social-providers/figma.mjs +5 -5
  47. package/dist/social-providers/github.d.mts +5 -1
  48. package/dist/social-providers/github.mjs +4 -4
  49. package/dist/social-providers/gitlab.d.mts +5 -1
  50. package/dist/social-providers/gitlab.mjs +6 -6
  51. package/dist/social-providers/google.d.mts +29 -3
  52. package/dist/social-providers/google.mjs +24 -30
  53. package/dist/social-providers/huggingface.d.mts +5 -1
  54. package/dist/social-providers/huggingface.mjs +8 -8
  55. package/dist/social-providers/index.d.mts +222 -42
  56. package/dist/social-providers/kakao.d.mts +5 -1
  57. package/dist/social-providers/kakao.mjs +8 -8
  58. package/dist/social-providers/kick.d.mts +5 -1
  59. package/dist/social-providers/kick.mjs +4 -4
  60. package/dist/social-providers/line.d.mts +8 -2
  61. package/dist/social-providers/line.mjs +12 -14
  62. package/dist/social-providers/linear.d.mts +5 -1
  63. package/dist/social-providers/linear.mjs +4 -4
  64. package/dist/social-providers/linkedin.d.mts +5 -1
  65. package/dist/social-providers/linkedin.mjs +10 -10
  66. package/dist/social-providers/microsoft-entra-id.d.mts +41 -6
  67. package/dist/social-providers/microsoft-entra-id.mjs +40 -36
  68. package/dist/social-providers/naver.d.mts +5 -1
  69. package/dist/social-providers/naver.mjs +4 -4
  70. package/dist/social-providers/notion.d.mts +5 -1
  71. package/dist/social-providers/notion.mjs +4 -4
  72. package/dist/social-providers/paybin.d.mts +5 -1
  73. package/dist/social-providers/paybin.mjs +10 -10
  74. package/dist/social-providers/paypal.d.mts +5 -2
  75. package/dist/social-providers/paypal.mjs +8 -13
  76. package/dist/social-providers/polar.d.mts +5 -1
  77. package/dist/social-providers/polar.mjs +8 -8
  78. package/dist/social-providers/railway.d.mts +5 -1
  79. package/dist/social-providers/railway.mjs +9 -9
  80. package/dist/social-providers/reddit.d.mts +5 -1
  81. package/dist/social-providers/reddit.mjs +9 -8
  82. package/dist/social-providers/roblox.d.mts +5 -1
  83. package/dist/social-providers/roblox.mjs +5 -5
  84. package/dist/social-providers/salesforce.d.mts +5 -1
  85. package/dist/social-providers/salesforce.mjs +8 -8
  86. package/dist/social-providers/slack.d.mts +5 -1
  87. package/dist/social-providers/slack.mjs +9 -9
  88. package/dist/social-providers/spotify.d.mts +5 -1
  89. package/dist/social-providers/spotify.mjs +5 -5
  90. package/dist/social-providers/tiktok.d.mts +5 -1
  91. package/dist/social-providers/tiktok.mjs +9 -5
  92. package/dist/social-providers/twitch.d.mts +5 -1
  93. package/dist/social-providers/twitch.mjs +4 -4
  94. package/dist/social-providers/twitter.d.mts +6 -4
  95. package/dist/social-providers/twitter.mjs +9 -9
  96. package/dist/social-providers/vercel.d.mts +5 -1
  97. package/dist/social-providers/vercel.mjs +4 -7
  98. package/dist/social-providers/vk.d.mts +5 -1
  99. package/dist/social-providers/vk.mjs +5 -5
  100. package/dist/social-providers/wechat.d.mts +5 -1
  101. package/dist/social-providers/wechat.mjs +10 -6
  102. package/dist/social-providers/zoom.d.mts +6 -1
  103. package/dist/social-providers/zoom.mjs +15 -9
  104. package/dist/types/context.d.mts +27 -8
  105. package/dist/types/index.d.mts +1 -1
  106. package/dist/types/init-options.d.mts +137 -6
  107. package/dist/types/plugin-client.d.mts +12 -2
  108. package/dist/utils/host.mjs +4 -0
  109. package/dist/utils/url.mjs +4 -3
  110. package/package.json +7 -7
  111. package/src/api/index.ts +82 -0
  112. package/src/context/transaction.ts +45 -12
  113. package/src/db/adapter/factory.ts +127 -64
  114. package/src/db/adapter/index.ts +54 -9
  115. package/src/db/adapter/types.ts +1 -0
  116. package/src/db/get-tables.ts +8 -3
  117. package/src/db/schema/account.ts +14 -2
  118. package/src/db/type.ts +12 -7
  119. package/src/env/env-impl.ts +1 -2
  120. package/src/error/codes.ts +5 -0
  121. package/src/oauth2/create-authorization-url.ts +2 -2
  122. package/src/oauth2/dpop.ts +568 -0
  123. package/src/oauth2/index.ts +61 -2
  124. package/src/oauth2/oauth-provider.ts +140 -10
  125. package/src/oauth2/refresh-access-token.ts +2 -2
  126. package/src/oauth2/scopes.ts +118 -0
  127. package/src/oauth2/utils.ts +2 -5
  128. package/src/oauth2/verify-id-token.ts +111 -0
  129. package/src/oauth2/verify.ts +372 -58
  130. package/src/social-providers/apple.ts +24 -61
  131. package/src/social-providers/atlassian.ts +12 -8
  132. package/src/social-providers/cognito.ts +25 -47
  133. package/src/social-providers/discord.ts +19 -8
  134. package/src/social-providers/dropbox.ts +13 -7
  135. package/src/social-providers/facebook.ts +97 -51
  136. package/src/social-providers/figma.ts +13 -9
  137. package/src/social-providers/github.ts +12 -8
  138. package/src/social-providers/gitlab.ts +14 -8
  139. package/src/social-providers/google.ts +66 -47
  140. package/src/social-providers/huggingface.ts +12 -8
  141. package/src/social-providers/kakao.ts +16 -8
  142. package/src/social-providers/kick.ts +12 -7
  143. package/src/social-providers/line.ts +37 -37
  144. package/src/social-providers/linear.ts +12 -6
  145. package/src/social-providers/linkedin.ts +14 -10
  146. package/src/social-providers/microsoft-entra-id.ts +103 -59
  147. package/src/social-providers/naver.ts +12 -6
  148. package/src/social-providers/notion.ts +12 -6
  149. package/src/social-providers/paybin.ts +14 -11
  150. package/src/social-providers/paypal.ts +6 -25
  151. package/src/social-providers/polar.ts +12 -8
  152. package/src/social-providers/railway.ts +13 -9
  153. package/src/social-providers/reddit.ts +25 -10
  154. package/src/social-providers/roblox.ts +18 -7
  155. package/src/social-providers/salesforce.ts +12 -8
  156. package/src/social-providers/slack.ts +18 -9
  157. package/src/social-providers/spotify.ts +13 -7
  158. package/src/social-providers/tiktok.ts +13 -7
  159. package/src/social-providers/twitch.ts +12 -8
  160. package/src/social-providers/twitter.ts +17 -8
  161. package/src/social-providers/vercel.ts +16 -10
  162. package/src/social-providers/vk.ts +13 -7
  163. package/src/social-providers/wechat.ts +28 -9
  164. package/src/social-providers/zoom.ts +19 -6
  165. package/src/types/context.ts +26 -8
  166. package/src/types/index.ts +7 -0
  167. package/src/types/init-options.ts +159 -8
  168. package/src/types/plugin-client.ts +16 -2
  169. package/src/utils/host.ts +15 -0
  170. package/src/utils/url.ts +10 -4
@@ -1,12 +1,13 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
2
+ import { decodeJwt, importJWK } from "jose";
3
3
  import { logger } from "../env";
4
4
  import { APIError, BetterAuthError } from "../error";
5
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
6
6
  import {
7
7
  createAuthorizationURL,
8
8
  getPrimaryClientId,
9
9
  refreshAccessToken,
10
+ resolveRequestedScopes,
10
11
  validateAuthorizationCode,
11
12
  } from "../oauth2";
12
13
 
@@ -48,15 +49,33 @@ export interface GoogleOptions extends ProviderOptions<GoogleProfile> {
48
49
  */
49
50
  display?: ("page" | "popup" | "touch" | "wap") | undefined;
50
51
  /**
51
- * The hosted domain of the user
52
+ * The hosted domain (Google Workspace) the user must belong to.
53
+ *
54
+ * This is sent to Google as the `hd` authorization hint and, when set, is
55
+ * also enforced against the `hd` claim of the returned id token/profile.
56
+ * Sign-in is rejected when the claim is missing or does not match, so this
57
+ * can be used to restrict sign-in to a Workspace domain.
52
58
  */
53
59
  hd?: string | undefined;
60
+ /**
61
+ * Enable incremental authorization via Google's `include_granted_scopes`
62
+ * parameter. When enabled, Google reports the user's full granted scope set
63
+ * in the token response.
64
+ *
65
+ * @default true
66
+ */
67
+ includeGrantedScopes?: boolean | undefined;
54
68
  }
55
69
 
70
+ const GOOGLE_DEFAULT_SCOPES = ["email", "profile", "openid"];
71
+
56
72
  export const google = (options: GoogleOptions) => {
57
73
  return {
58
74
  id: "google",
59
75
  name: "Google",
76
+ callbackPath: "/callback/google",
77
+ grantAuthority:
78
+ options.includeGrantedScopes !== false ? "full-grant" : "projection",
60
79
  async createAuthorizationURL({
61
80
  state,
62
81
  scopes,
@@ -75,16 +94,16 @@ export const google = (options: GoogleOptions) => {
75
94
  if (!codeVerifier) {
76
95
  throw new BetterAuthError("codeVerifier is required for Google");
77
96
  }
78
- const _scopes = options.disableDefaultScope
79
- ? []
80
- : ["email", "profile", "openid"];
81
- if (options.scope) _scopes.push(...options.scope);
82
- if (scopes) _scopes.push(...scopes);
83
- const url = await createAuthorizationURL({
97
+ const requestedScopes = resolveRequestedScopes(
98
+ options,
99
+ GOOGLE_DEFAULT_SCOPES,
100
+ scopes,
101
+ );
102
+ return createAuthorizationURL({
84
103
  id: "google",
85
104
  options,
86
105
  authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
87
- scopes: _scopes,
106
+ scopes: requestedScopes,
88
107
  state,
89
108
  codeVerifier,
90
109
  redirectURI,
@@ -93,12 +112,17 @@ export const google = (options: GoogleOptions) => {
93
112
  display: display || options.display,
94
113
  loginHint,
95
114
  hd: options.hd,
96
- additionalParams: {
97
- include_granted_scopes: "true",
98
- ...(additionalParams ?? {}),
99
- },
115
+ additionalParams:
116
+ options.includeGrantedScopes === false
117
+ ? { ...(additionalParams ?? {}) }
118
+ : {
119
+ ...(additionalParams ?? {}),
120
+ // Not caller-overridable: the emitted param must stay in
121
+ // lockstep with `grantAuthority` (driven by the option), or
122
+ // the callback would treat a non-authoritative grant as full.
123
+ include_granted_scopes: "true",
124
+ },
100
125
  });
101
- return url;
102
126
  },
103
127
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
104
128
  return validateAuthorizationCode({
@@ -122,37 +146,20 @@ export const google = (options: GoogleOptions) => {
122
146
  tokenEndpoint: "https://oauth2.googleapis.com/token",
123
147
  });
124
148
  },
125
- async verifyIdToken(token, nonce) {
126
- if (options.disableIdTokenSignIn) {
127
- return false;
128
- }
129
- if (options.verifyIdToken) {
130
- return options.verifyIdToken(token, nonce);
131
- }
132
-
133
- // Verify JWT integrity
134
- // See https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
135
-
136
- try {
137
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
138
- if (!kid || !jwtAlg) return false;
139
-
140
- const publicKey = await getGooglePublicKey(kid);
141
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
142
- algorithms: [jwtAlg],
143
- issuer: ["https://accounts.google.com", "accounts.google.com"],
144
- audience: options.clientId,
145
- maxTokenAge: "1h",
146
- });
147
-
148
- if (nonce && jwtClaims.nonce !== nonce) {
149
- return false;
150
- }
151
-
152
- return true;
153
- } catch {
154
- return false;
155
- }
149
+ idToken: {
150
+ // https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
151
+ jwks: (header) => getGooglePublicKey(header.kid!),
152
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
153
+ audience: options.clientId,
154
+ maxTokenAge: "1h",
155
+ // Google's `hd` authorization parameter is only a UI hint and can be
156
+ // removed or changed by the user. When a hosted domain is configured,
157
+ // the `hd` claim in the verified id token is the authoritative value
158
+ // and must match, otherwise accounts outside the workspace domain would
159
+ // be accepted on the id_token sign-in path.
160
+ verifyClaims: options.hd
161
+ ? (claims) => claims.hd === options.hd
162
+ : undefined,
156
163
  },
157
164
  async getUserInfo(token) {
158
165
  if (options.getUserInfo) {
@@ -162,6 +169,18 @@ export const google = (options: GoogleOptions) => {
162
169
  return null;
163
170
  }
164
171
  const user = decodeJwt(token.idToken) as GoogleProfile;
172
+ // Enforce the configured hosted domain on the callback profile path
173
+ // as well. The `hd` claim must be present and match, since the
174
+ // authorization-time `hd` hint does not restrict which account signs
175
+ // in.
176
+ if (options.hd && user.hd !== options.hd) {
177
+ logger.error(
178
+ `Google sign-in rejected: id token hosted domain (hd) "${
179
+ user.hd ?? "<missing>"
180
+ }" does not match the configured "hd" option "${options.hd}".`,
181
+ );
182
+ return null;
183
+ }
165
184
  const userMap = await options.mapProfileToUser?.(user);
166
185
  return {
167
186
  user: {
@@ -176,7 +195,7 @@ export const google = (options: GoogleOptions) => {
176
195
  };
177
196
  },
178
197
  options,
179
- } satisfies OAuthProvider<GoogleProfile>;
198
+ } satisfies UpstreamProvider<GoogleProfile>;
180
199
  };
181
200
 
182
201
  export const getGooglePublicKey = async (kid: string) => {
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -42,11 +43,14 @@ export interface HuggingFaceOptions
42
43
  clientId: string;
43
44
  }
44
45
 
46
+ const HUGGINGFACE_DEFAULT_SCOPES = ["openid", "profile", "email"];
47
+
45
48
  export const huggingface = (options: HuggingFaceOptions) => {
46
49
  const tokenEndpoint = "https://huggingface.co/oauth/token";
47
50
  return {
48
51
  id: "huggingface",
49
52
  name: "Hugging Face",
53
+ callbackPath: "/callback/huggingface",
50
54
  createAuthorizationURL({
51
55
  state,
52
56
  scopes,
@@ -54,16 +58,16 @@ export const huggingface = (options: HuggingFaceOptions) => {
54
58
  redirectURI,
55
59
  additionalParams,
56
60
  }) {
57
- const _scopes = options.disableDefaultScope
58
- ? []
59
- : ["openid", "profile", "email"];
60
- if (options.scope) _scopes.push(...options.scope);
61
- if (scopes) _scopes.push(...scopes);
61
+ const requestedScopes = resolveRequestedScopes(
62
+ options,
63
+ HUGGINGFACE_DEFAULT_SCOPES,
64
+ scopes,
65
+ );
62
66
  return createAuthorizationURL({
63
67
  id: "huggingface",
64
68
  options,
65
69
  authorizationEndpoint: "https://huggingface.co/oauth/authorize",
66
- scopes: _scopes,
70
+ scopes: requestedScopes,
67
71
  state,
68
72
  codeVerifier,
69
73
  redirectURI,
@@ -122,5 +126,5 @@ export const huggingface = (options: HuggingFaceOptions) => {
122
126
  };
123
127
  },
124
128
  options,
125
- } satisfies OAuthProvider<HuggingFaceProfile>;
129
+ } satisfies UpstreamProvider<HuggingFaceProfile>;
126
130
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -101,22 +102,29 @@ export interface KakaoOptions extends ProviderOptions<KakaoProfile> {
101
102
  clientId: string;
102
103
  }
103
104
 
105
+ const KAKAO_DEFAULT_SCOPES = [
106
+ "account_email",
107
+ "profile_image",
108
+ "profile_nickname",
109
+ ];
110
+
104
111
  export const kakao = (options: KakaoOptions) => {
105
112
  const tokenEndpoint = "https://kauth.kakao.com/oauth/token";
106
113
  return {
107
114
  id: "kakao",
108
115
  name: "Kakao",
116
+ callbackPath: "/callback/kakao",
109
117
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
110
- const _scopes = options.disableDefaultScope
111
- ? []
112
- : ["account_email", "profile_image", "profile_nickname"];
113
- if (options.scope) _scopes.push(...options.scope);
114
- if (scopes) _scopes.push(...scopes);
118
+ const requestedScopes = resolveRequestedScopes(
119
+ options,
120
+ KAKAO_DEFAULT_SCOPES,
121
+ scopes,
122
+ );
115
123
  return createAuthorizationURL({
116
124
  id: "kakao",
117
125
  options,
118
126
  authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
119
- scopes: _scopes,
127
+ scopes: requestedScopes,
120
128
  state,
121
129
  redirectURI,
122
130
  additionalParams,
@@ -176,5 +184,5 @@ export const kakao = (options: KakaoOptions) => {
176
184
  };
177
185
  },
178
186
  options,
179
- } satisfies OAuthProvider<KakaoProfile>;
187
+ } satisfies UpstreamProvider<KakaoProfile>;
180
188
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -29,10 +30,13 @@ export interface KickOptions extends ProviderOptions<KickProfile> {
29
30
  clientId: string;
30
31
  }
31
32
 
33
+ const KICK_DEFAULT_SCOPES = ["user:read"];
34
+
32
35
  export const kick = (options: KickOptions) => {
33
36
  return {
34
37
  id: "kick",
35
38
  name: "Kick",
39
+ callbackPath: "/callback/kick",
36
40
  createAuthorizationURL({
37
41
  state,
38
42
  scopes,
@@ -40,16 +44,17 @@ export const kick = (options: KickOptions) => {
40
44
  codeVerifier,
41
45
  additionalParams,
42
46
  }) {
43
- const _scopes = options.disableDefaultScope ? [] : ["user:read"];
44
- if (options.scope) _scopes.push(...options.scope);
45
- if (scopes) _scopes.push(...scopes);
46
-
47
+ const requestedScopes = resolveRequestedScopes(
48
+ options,
49
+ KICK_DEFAULT_SCOPES,
50
+ scopes,
51
+ );
47
52
  return createAuthorizationURL({
48
53
  id: "kick",
49
54
  redirectURI,
50
55
  options,
51
56
  authorizationEndpoint: "https://id.kick.com/oauth/authorize",
52
- scopes: _scopes,
57
+ scopes: requestedScopes,
53
58
  codeVerifier,
54
59
  state,
55
60
  additionalParams,
@@ -112,5 +117,5 @@ export const kick = (options: KickOptions) => {
112
117
  };
113
118
  },
114
119
  options,
115
- } satisfies OAuthProvider<KickProfile>;
120
+ } satisfies UpstreamProvider<KickProfile>;
116
121
  };
@@ -1,9 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { decodeJwt } from "jose";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  refreshAccessToken,
7
+ resolveRequestedScopes,
7
8
  validateAuthorizationCode,
8
9
  } from "../oauth2";
9
10
 
@@ -32,6 +33,8 @@ export interface LineOptions
32
33
  clientId: string;
33
34
  }
34
35
 
36
+ const LINE_DEFAULT_SCOPES = ["openid", "profile", "email"];
37
+
35
38
  /**
36
39
  * LINE Login v2.1
37
40
  * - Authorization endpoint: https://access.line.me/oauth2/v2.1/authorize
@@ -50,7 +53,8 @@ export const line = (options: LineOptions) => {
50
53
  return {
51
54
  id: "line",
52
55
  name: "LINE",
53
- async createAuthorizationURL({
56
+ callbackPath: "/callback/line",
57
+ createAuthorizationURL({
54
58
  state,
55
59
  scopes,
56
60
  codeVerifier,
@@ -58,16 +62,16 @@ export const line = (options: LineOptions) => {
58
62
  loginHint,
59
63
  additionalParams,
60
64
  }) {
61
- const _scopes = options.disableDefaultScope
62
- ? []
63
- : ["openid", "profile", "email"];
64
- if (options.scope) _scopes.push(...options.scope);
65
- if (scopes) _scopes.push(...scopes);
66
- return await createAuthorizationURL({
65
+ const requestedScopes = resolveRequestedScopes(
66
+ options,
67
+ LINE_DEFAULT_SCOPES,
68
+ scopes,
69
+ );
70
+ return createAuthorizationURL({
67
71
  id: "line",
68
72
  options,
69
73
  authorizationEndpoint,
70
- scopes: _scopes,
74
+ scopes: requestedScopes,
71
75
  state,
72
76
  codeVerifier,
73
77
  redirectURI,
@@ -96,34 +100,30 @@ export const line = (options: LineOptions) => {
96
100
  tokenEndpoint,
97
101
  });
98
102
  },
99
- async verifyIdToken(token, nonce) {
100
- if (options.disableIdTokenSignIn) {
101
- return false;
102
- }
103
- if (options.verifyIdToken) {
104
- return options.verifyIdToken(token, nonce);
105
- }
106
- const body = new URLSearchParams();
107
- body.set("id_token", token);
108
- body.set("client_id", options.clientId);
109
- if (nonce) body.set("nonce", nonce);
110
- const { data, error } = await betterFetch<LineIdTokenPayload>(
111
- verifyIdTokenEndpoint,
112
- {
113
- method: "POST",
114
- headers: {
115
- "content-type": "application/x-www-form-urlencoded",
103
+ idToken: {
104
+ verify: async (token, nonce) => {
105
+ const body = new URLSearchParams();
106
+ body.set("id_token", token);
107
+ body.set("client_id", options.clientId);
108
+ if (nonce) body.set("nonce", nonce);
109
+ const { data, error } = await betterFetch<LineIdTokenPayload>(
110
+ verifyIdTokenEndpoint,
111
+ {
112
+ method: "POST",
113
+ headers: {
114
+ "content-type": "application/x-www-form-urlencoded",
115
+ },
116
+ body,
116
117
  },
117
- body,
118
- },
119
- );
120
- if (error || !data) {
121
- return false;
122
- }
123
- // aud must match clientId; nonce (if provided) must also match nonce
124
- if (data.aud !== options.clientId) return false;
125
- if (data.nonce && data.nonce !== nonce) return false;
126
- return true;
118
+ );
119
+ if (error || !data) {
120
+ return false;
121
+ }
122
+ // aud must match clientId; nonce (if provided) must also match nonce
123
+ if (data.aud !== options.clientId) return false;
124
+ if (data.nonce && data.nonce !== nonce) return false;
125
+ return true;
126
+ },
127
127
  },
128
128
  async getUserInfo(token) {
129
129
  if (options.getUserInfo) {
@@ -167,5 +167,5 @@ export const line = (options: LineOptions) => {
167
167
  };
168
168
  },
169
169
  options,
170
- } satisfies OAuthProvider<LineUserInfo | LineIdTokenPayload, LineOptions>;
170
+ } satisfies UpstreamProvider<LineUserInfo | LineIdTokenPayload, LineOptions>;
171
171
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -26,11 +27,14 @@ export interface LinearOptions extends ProviderOptions<LinearUser> {
26
27
  clientId: string;
27
28
  }
28
29
 
30
+ const LINEAR_DEFAULT_SCOPES = ["read"];
31
+
29
32
  export const linear = (options: LinearOptions) => {
30
33
  const tokenEndpoint = "https://api.linear.app/oauth/token";
31
34
  return {
32
35
  id: "linear",
33
36
  name: "Linear",
37
+ callbackPath: "/callback/linear",
34
38
  createAuthorizationURL({
35
39
  state,
36
40
  scopes,
@@ -38,14 +42,16 @@ export const linear = (options: LinearOptions) => {
38
42
  redirectURI,
39
43
  additionalParams,
40
44
  }) {
41
- const _scopes = options.disableDefaultScope ? [] : ["read"];
42
- if (options.scope) _scopes.push(...options.scope);
43
- if (scopes) _scopes.push(...scopes);
45
+ const requestedScopes = resolveRequestedScopes(
46
+ options,
47
+ LINEAR_DEFAULT_SCOPES,
48
+ scopes,
49
+ );
44
50
  return createAuthorizationURL({
45
51
  id: "linear",
46
52
  options,
47
53
  authorizationEndpoint: "https://linear.app/oauth/authorize",
48
- scopes: _scopes,
54
+ scopes: requestedScopes,
49
55
  state,
50
56
  redirectURI,
51
57
  loginHint,
@@ -124,5 +130,5 @@ export const linear = (options: LinearOptions) => {
124
130
  };
125
131
  },
126
132
  options,
127
- } satisfies OAuthProvider<LinearUser>;
133
+ } satisfies UpstreamProvider<LinearUser>;
128
134
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -24,6 +25,8 @@ export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
24
25
  clientId: string;
25
26
  }
26
27
 
28
+ const LINKEDIN_DEFAULT_SCOPES = ["profile", "email", "openid"];
29
+
27
30
  export const linkedin = (options: LinkedInOptions) => {
28
31
  const authorizationEndpoint =
29
32
  "https://www.linkedin.com/oauth/v2/authorization";
@@ -32,23 +35,24 @@ export const linkedin = (options: LinkedInOptions) => {
32
35
  return {
33
36
  id: "linkedin",
34
37
  name: "Linkedin",
35
- createAuthorizationURL: async ({
38
+ callbackPath: "/callback/linkedin",
39
+ createAuthorizationURL: ({
36
40
  state,
37
41
  scopes,
38
42
  redirectURI,
39
43
  loginHint,
40
44
  additionalParams,
41
45
  }) => {
42
- const _scopes = options.disableDefaultScope
43
- ? []
44
- : ["profile", "email", "openid"];
45
- if (options.scope) _scopes.push(...options.scope);
46
- if (scopes) _scopes.push(...scopes);
47
- return await createAuthorizationURL({
46
+ const requestedScopes = resolveRequestedScopes(
47
+ options,
48
+ LINKEDIN_DEFAULT_SCOPES,
49
+ scopes,
50
+ );
51
+ return createAuthorizationURL({
48
52
  id: "linkedin",
49
53
  options,
50
54
  authorizationEndpoint,
51
- scopes: _scopes,
55
+ scopes: requestedScopes,
52
56
  state,
53
57
  loginHint,
54
58
  redirectURI,
@@ -108,5 +112,5 @@ export const linkedin = (options: LinkedInOptions) => {
108
112
  };
109
113
  },
110
114
  options,
111
- } satisfies OAuthProvider<LinkedInProfile>;
115
+ } satisfies UpstreamProvider<LinkedInProfile>;
112
116
  };