@better-auth/core 1.7.0-rc.1 → 1.7.0-rc.2

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 (208) hide show
  1. package/dist/api/index.d.mts +28 -28
  2. package/dist/async_hooks/index.d.mts +0 -1
  3. package/dist/async_hooks/pure.index.d.mts +0 -1
  4. package/dist/context/endpoint-context.d.mts +1 -1
  5. package/dist/context/global.d.mts +0 -1
  6. package/dist/context/global.mjs +1 -1
  7. package/dist/context/request-state.d.mts +0 -1
  8. package/dist/context/request-state.mjs +8 -5
  9. package/dist/context/transaction.d.mts +8 -3
  10. package/dist/context/transaction.mjs +27 -10
  11. package/dist/db/adapter/factory.d.mts +3 -5
  12. package/dist/db/adapter/factory.mjs +7 -5
  13. package/dist/db/adapter/get-default-field-name.d.mts +2 -9
  14. package/dist/db/adapter/get-default-model-name.d.mts +1 -5
  15. package/dist/db/adapter/get-default-model-name.mjs +6 -5
  16. package/dist/db/adapter/get-field-attributes.d.mts +4 -11
  17. package/dist/db/adapter/get-field-name.d.mts +2 -9
  18. package/dist/db/adapter/get-id-field.d.mts +4 -12
  19. package/dist/db/adapter/get-model-name.d.mts +1 -5
  20. package/dist/db/adapter/index.d.mts +7 -32
  21. package/dist/db/adapter/types.d.mts +5 -17
  22. package/dist/db/adapter/utils.d.mts +0 -1
  23. package/dist/db/database-index.d.mts +44 -0
  24. package/dist/db/database-index.mjs +168 -0
  25. package/dist/db/get-tables.d.mts +7 -1
  26. package/dist/db/get-tables.mjs +47 -6
  27. package/dist/db/index.d.mts +3 -3
  28. package/dist/db/index.mjs +2 -2
  29. package/dist/db/internal.d.mts +3 -0
  30. package/dist/db/internal.mjs +3 -0
  31. package/dist/db/plugin.d.mts +4 -3
  32. package/dist/db/schema/account.d.mts +16 -3
  33. package/dist/db/schema/account.mjs +35 -2
  34. package/dist/db/schema/rate-limit.d.mts +1 -1
  35. package/dist/db/schema/rate-limit.mjs +9 -0
  36. package/dist/db/schema/session.d.mts +1 -1
  37. package/dist/db/schema/shared.d.mts +0 -1
  38. package/dist/db/schema/user.d.mts +1 -1
  39. package/dist/db/schema/verification.d.mts +1 -1
  40. package/dist/db/type.d.mts +17 -9
  41. package/dist/env/logger.d.mts +25 -25
  42. package/dist/error/codes.d.mts +0 -1
  43. package/dist/error/index.d.mts +0 -1
  44. package/dist/index.d.mts +1 -1
  45. package/dist/instrumentation/attributes.d.mts +0 -1
  46. package/dist/instrumentation/pure.index.d.mts +0 -1
  47. package/dist/instrumentation/tracer.mjs +1 -1
  48. package/dist/oauth2/authorization-params.d.mts +0 -1
  49. package/dist/oauth2/client-assertion.d.mts +3 -13
  50. package/dist/oauth2/client-credentials-token.d.mts +3 -17
  51. package/dist/oauth2/client-credentials-token.mjs +1 -4
  52. package/dist/oauth2/create-authorization-url.d.mts +3 -21
  53. package/dist/oauth2/dpop.d.mts +2 -23
  54. package/dist/oauth2/dpop.mjs +1 -1
  55. package/dist/oauth2/index.d.mts +2 -2
  56. package/dist/oauth2/oauth-provider.d.mts +85 -36
  57. package/dist/oauth2/refresh-access-token.d.mts +3 -19
  58. package/dist/oauth2/refresh-access-token.mjs +3 -9
  59. package/dist/oauth2/reject-redirects.mjs +1 -1
  60. package/dist/oauth2/token-endpoint-auth.d.mts +0 -1
  61. package/dist/oauth2/utils.d.mts +0 -1
  62. package/dist/oauth2/utils.mjs +2 -3
  63. package/dist/oauth2/validate-authorization-code.d.mts +5 -29
  64. package/dist/oauth2/verify-id-token.d.mts +3 -2
  65. package/dist/oauth2/verify-id-token.mjs +3 -3
  66. package/dist/oauth2/verify.d.mts +5 -7
  67. package/dist/oauth2/verify.mjs +2 -2
  68. package/dist/social-providers/apple.d.mts +9 -24
  69. package/dist/social-providers/apple.mjs +2 -1
  70. package/dist/social-providers/atlassian.d.mts +18 -19
  71. package/dist/social-providers/atlassian.mjs +1 -1
  72. package/dist/social-providers/cognito.d.mts +33 -22
  73. package/dist/social-providers/cognito.mjs +2 -2
  74. package/dist/social-providers/discord.d.mts +7 -20
  75. package/dist/social-providers/discord.mjs +1 -1
  76. package/dist/social-providers/dropbox.d.mts +7 -22
  77. package/dist/social-providers/dropbox.mjs +1 -1
  78. package/dist/social-providers/facebook.d.mts +19 -27
  79. package/dist/social-providers/facebook.mjs +6 -17
  80. package/dist/social-providers/figma.d.mts +7 -22
  81. package/dist/social-providers/figma.mjs +1 -1
  82. package/dist/social-providers/github.d.mts +7 -23
  83. package/dist/social-providers/github.mjs +1 -1
  84. package/dist/social-providers/gitlab.d.mts +6 -43
  85. package/dist/social-providers/gitlab.mjs +1 -1
  86. package/dist/social-providers/google.d.mts +10 -32
  87. package/dist/social-providers/google.mjs +2 -1
  88. package/dist/social-providers/huggingface.d.mts +7 -22
  89. package/dist/social-providers/huggingface.mjs +1 -1
  90. package/dist/social-providers/index.d.mts +229 -787
  91. package/dist/social-providers/kakao.d.mts +8 -33
  92. package/dist/social-providers/kakao.mjs +1 -1
  93. package/dist/social-providers/kick.d.mts +7 -22
  94. package/dist/social-providers/kick.mjs +1 -1
  95. package/dist/social-providers/line.d.mts +8 -44
  96. package/dist/social-providers/line.mjs +4 -6
  97. package/dist/social-providers/linear.d.mts +7 -21
  98. package/dist/social-providers/linear.mjs +1 -1
  99. package/dist/social-providers/linkedin.d.mts +7 -21
  100. package/dist/social-providers/linkedin.mjs +1 -1
  101. package/dist/social-providers/microsoft-entra-id.d.mts +8 -18
  102. package/dist/social-providers/microsoft-entra-id.mjs +18 -2
  103. package/dist/social-providers/naver.d.mts +27 -42
  104. package/dist/social-providers/naver.mjs +1 -1
  105. package/dist/social-providers/notion.d.mts +7 -21
  106. package/dist/social-providers/notion.mjs +1 -1
  107. package/dist/social-providers/paybin.d.mts +8 -23
  108. package/dist/social-providers/paybin.mjs +2 -1
  109. package/dist/social-providers/paypal.d.mts +8 -33
  110. package/dist/social-providers/paypal.mjs +1 -1
  111. package/dist/social-providers/polar.d.mts +7 -22
  112. package/dist/social-providers/polar.mjs +1 -1
  113. package/dist/social-providers/railway.d.mts +7 -22
  114. package/dist/social-providers/railway.mjs +1 -1
  115. package/dist/social-providers/reddit.d.mts +7 -20
  116. package/dist/social-providers/reddit.mjs +1 -1
  117. package/dist/social-providers/roblox.d.mts +7 -20
  118. package/dist/social-providers/roblox.mjs +1 -1
  119. package/dist/social-providers/salesforce.d.mts +7 -22
  120. package/dist/social-providers/salesforce.mjs +1 -1
  121. package/dist/social-providers/slack.d.mts +7 -20
  122. package/dist/social-providers/slack.mjs +1 -1
  123. package/dist/social-providers/spotify.d.mts +7 -22
  124. package/dist/social-providers/spotify.mjs +1 -1
  125. package/dist/social-providers/tiktok.d.mts +10 -22
  126. package/dist/social-providers/tiktok.mjs +1 -1
  127. package/dist/social-providers/twitch.d.mts +7 -20
  128. package/dist/social-providers/twitch.mjs +1 -1
  129. package/dist/social-providers/twitter.d.mts +29 -26
  130. package/dist/social-providers/twitter.mjs +1 -1
  131. package/dist/social-providers/vercel.d.mts +7 -22
  132. package/dist/social-providers/vercel.mjs +1 -1
  133. package/dist/social-providers/vk.d.mts +8 -24
  134. package/dist/social-providers/vk.mjs +5 -6
  135. package/dist/social-providers/wechat.d.mts +7 -19
  136. package/dist/social-providers/wechat.mjs +1 -1
  137. package/dist/social-providers/zoom.d.mts +9 -25
  138. package/dist/social-providers/zoom.mjs +1 -1
  139. package/dist/types/context.d.mts +16 -9
  140. package/dist/types/cookie.d.mts +0 -1
  141. package/dist/types/helper.d.mts +1 -1
  142. package/dist/types/index.d.mts +1 -1
  143. package/dist/types/init-options.d.mts +37 -26
  144. package/dist/types/plugin-client.d.mts +1 -3
  145. package/dist/types/plugin.d.mts +2 -2
  146. package/dist/utils/async.d.mts +0 -1
  147. package/dist/utils/db.d.mts +1 -0
  148. package/dist/utils/deprecate.d.mts +0 -1
  149. package/dist/utils/error-codes.d.mts +2 -2
  150. package/dist/utils/host.d.mts +27 -1
  151. package/dist/utils/host.mjs +1 -1
  152. package/dist/utils/ip.d.mts +1 -0
  153. package/dist/utils/ip.mjs +1 -1
  154. package/dist/utils/is-api-error.d.mts +0 -1
  155. package/dist/utils/redirect-uri.d.mts +0 -1
  156. package/package.json +10 -2
  157. package/src/context/request-state.ts +26 -6
  158. package/src/context/transaction.ts +37 -18
  159. package/src/db/adapter/factory.ts +24 -18
  160. package/src/db/adapter/get-default-model-name.ts +23 -14
  161. package/src/db/database-index.ts +351 -0
  162. package/src/db/get-tables.ts +89 -9
  163. package/src/db/index.ts +4 -0
  164. package/src/db/internal.ts +12 -0
  165. package/src/db/plugin.ts +3 -1
  166. package/src/db/schema/account.ts +27 -1
  167. package/src/db/schema/user.ts +1 -1
  168. package/src/db/type.ts +12 -0
  169. package/src/oauth2/index.ts +3 -0
  170. package/src/oauth2/oauth-provider.ts +94 -36
  171. package/src/oauth2/verify-id-token.ts +4 -2
  172. package/src/social-providers/apple.ts +2 -1
  173. package/src/social-providers/atlassian.ts +1 -1
  174. package/src/social-providers/cognito.ts +2 -2
  175. package/src/social-providers/discord.ts +1 -1
  176. package/src/social-providers/dropbox.ts +1 -1
  177. package/src/social-providers/facebook.ts +21 -28
  178. package/src/social-providers/figma.ts +1 -1
  179. package/src/social-providers/github.ts +1 -1
  180. package/src/social-providers/gitlab.ts +1 -1
  181. package/src/social-providers/google.ts +2 -1
  182. package/src/social-providers/huggingface.ts +1 -1
  183. package/src/social-providers/kakao.ts +1 -1
  184. package/src/social-providers/kick.ts +1 -1
  185. package/src/social-providers/line.ts +7 -12
  186. package/src/social-providers/linear.ts +1 -1
  187. package/src/social-providers/linkedin.ts +1 -1
  188. package/src/social-providers/microsoft-entra-id.ts +3 -2
  189. package/src/social-providers/naver.ts +1 -1
  190. package/src/social-providers/notion.ts +1 -1
  191. package/src/social-providers/paybin.ts +2 -1
  192. package/src/social-providers/paypal.ts +1 -1
  193. package/src/social-providers/polar.ts +1 -1
  194. package/src/social-providers/railway.ts +1 -1
  195. package/src/social-providers/reddit.ts +1 -1
  196. package/src/social-providers/roblox.ts +1 -1
  197. package/src/social-providers/salesforce.ts +1 -1
  198. package/src/social-providers/slack.ts +1 -1
  199. package/src/social-providers/spotify.ts +1 -1
  200. package/src/social-providers/tiktok.ts +2 -2
  201. package/src/social-providers/twitch.ts +1 -1
  202. package/src/social-providers/twitter.ts +1 -1
  203. package/src/social-providers/vercel.ts +1 -1
  204. package/src/social-providers/vk.ts +2 -2
  205. package/src/social-providers/wechat.ts +1 -1
  206. package/src/social-providers/zoom.ts +1 -1
  207. package/src/types/context.ts +21 -14
  208. package/src/types/init-options.ts +15 -14
@@ -1,5 +1,9 @@
1
1
  import type { JWTVerifyGetKey } from "jose";
2
- import type { Awaitable, LiteralString } from "../types";
2
+ import type {
3
+ Awaitable,
4
+ GenericEndpointContext,
5
+ LiteralString,
6
+ } from "../types";
3
7
 
4
8
  /**
5
9
  * id_token verification config for a social provider.
@@ -51,7 +55,11 @@ export type OAuthIdTokenConfig =
51
55
  * Custom verifier for providers that cannot verify against a local JWKS, such as a
52
56
  * remote verification endpoint (e.g. LINE).
53
57
  */
54
- verify: (token: string, nonce?: string) => Promise<boolean>;
58
+ verify: (
59
+ token: string,
60
+ nonce?: string,
61
+ ctx?: GenericEndpointContext,
62
+ ) => Promise<boolean>;
55
63
  };
56
64
 
57
65
  export interface OAuth2Tokens {
@@ -69,14 +77,52 @@ export interface OAuth2Tokens {
69
77
  raw?: Record<string, unknown> | undefined;
70
78
  }
71
79
 
80
+ /** Mutable local-user attributes normalized from an OAuth provider profile. */
72
81
  export type OAuth2UserInfo = {
73
- id: string | number;
82
+ /** Provider identity belongs in raw profile data and `accountSubject`. */
83
+ id?: never;
74
84
  name?: string | undefined;
75
85
  email?: (string | null) | undefined;
76
86
  image?: string | undefined;
77
87
  emailVerified: boolean;
78
88
  };
79
89
 
90
+ /**
91
+ * Verified provider data available when deriving a stable OAuth account key.
92
+ *
93
+ * Account-key resolvers must use the raw provider profile or verified token
94
+ * response. They never receive the mapped local user, so profile mapping
95
+ * cannot redefine provider identity.
96
+ */
97
+ export interface OAuthAccountKeyContext<Profile extends object = object> {
98
+ tokens: OAuth2Tokens;
99
+ profile: Profile;
100
+ }
101
+
102
+ /**
103
+ * Resolves one part of an account key from a profile returned by the same
104
+ * provider. The method-derived callback keeps that profile pairing intact when
105
+ * providers with different profile shapes share an `OAuthProvider[]`.
106
+ */
107
+ type OAuthAccountKeyResolver<Profile extends object, Value> = {
108
+ resolve(context: OAuthAccountKeyContext<Profile>): Awaitable<Value>;
109
+ }["resolve"];
110
+
111
+ /** Resolves the stable provider subject used to build an OAuth account key. */
112
+ export type OAuthAccountSubject<Profile extends object = object> =
113
+ OAuthAccountKeyResolver<Profile, string | number>;
114
+
115
+ /** Mutable local-user attributes returned by `mapProfileToUser`. */
116
+ export type OAuthMappedUser = {
117
+ /** Provider identity is defined by `accountSubject`, not local user mapping. */
118
+ id?: never;
119
+ name?: string;
120
+ email?: string | null;
121
+ image?: string;
122
+ emailVerified?: boolean;
123
+ [key: string]: unknown;
124
+ };
125
+
80
126
  /**
81
127
  * Request metadata available to provider refresh hooks.
82
128
  *
@@ -90,8 +136,8 @@ export interface OAuthRefreshContext {
90
136
  }
91
137
 
92
138
  export interface OAuthProvider<
93
- T extends Record<string, any> = Record<string, any>,
94
- O extends Record<string, any> = Partial<ProviderOptions>,
139
+ T extends object = object,
140
+ O extends object = Partial<ProviderOptions>,
95
141
  > {
96
142
  id: LiteralString;
97
143
  /**
@@ -127,6 +173,15 @@ export interface OAuthProvider<
127
173
  additionalParams?: Record<string, string> | undefined;
128
174
  }) => Awaitable<URL>;
129
175
  name: string;
176
+ /**
177
+ * Stable subject that identifies the provider account.
178
+ *
179
+ * Read this value from the raw, provider-verified profile. For OpenID
180
+ * Connect providers, use the `sub` claim. For OAuth providers, use the
181
+ * provider's documented immutable user identifier. Never derive this value
182
+ * from `mapProfileToUser`.
183
+ */
184
+ accountSubject: OAuthAccountSubject<T>;
130
185
  validateAuthorizationCode: (data: {
131
186
  code: string;
132
187
  redirectURI: string;
@@ -185,6 +240,16 @@ export interface OAuthProvider<
185
240
  * against this value to prevent authorization server mix-up attacks.
186
241
  */
187
242
  issuer?: string | undefined;
243
+ /**
244
+ * Stable issuer used with the provider subject to recognize an account.
245
+ *
246
+ * Use the validated OpenID Connect issuer for OIDC providers. A resolver is
247
+ * supported for tenant-specific issuers and receives only provider-verified
248
+ * data. OAuth providers without an issuer omit this property and are scoped
249
+ * to the synthetic `local:oauth:<encoded providerId>` issuer, where the
250
+ * provider ID segment is percent-encoded.
251
+ */
252
+ accountIssuer?: string | OAuthAccountKeyResolver<T, string> | undefined;
188
253
  /**
189
254
  * Require shared OAuth redirect routes to bind ID-token verification to an
190
255
  * authorization request nonce. When true, routes generate `idTokenNonce`,
@@ -218,7 +283,15 @@ export interface OAuthProvider<
218
283
  options?: O | undefined;
219
284
  }
220
285
 
221
- export type ProviderOptions<Profile extends Record<string, any> = any> = {
286
+ /**
287
+ * Maps a provider-specific profile while remaining compatible with the erased
288
+ * profile type used by shared OAuth helpers.
289
+ */
290
+ type OAuthProfileMapper<Profile extends object> = {
291
+ map(profile: Profile): Awaitable<OAuthMappedUser>;
292
+ }["map"];
293
+
294
+ export type ProviderOptions<Profile extends object = object> = {
222
295
  /**
223
296
  * The client ID of your application.
224
297
  *
@@ -262,25 +335,28 @@ export type ProviderOptions<Profile extends Record<string, any> = any> = {
262
335
  */
263
336
  disableIdTokenSignIn?: boolean | undefined;
264
337
  /**
265
- * verifyIdToken function to verify the id token
338
+ * verifyIdToken function to verify the id token.
339
+ *
340
+ * The optional endpoint context exposes request metadata to custom
341
+ * verifiers without coupling built-in provider verification to a runtime.
266
342
  */
267
343
  verifyIdToken?:
268
- | ((token: string, nonce?: string) => Promise<boolean>)
344
+ | ((
345
+ token: string,
346
+ nonce?: string,
347
+ ctx?: GenericEndpointContext,
348
+ ) => Promise<boolean>)
269
349
  | undefined;
270
350
  /**
271
351
  * Custom function to get user info from the provider
352
+ *
353
+ * `data` must preserve the declared raw profile shape because account-key
354
+ * resolvers consume it after this hook returns.
272
355
  */
273
356
  getUserInfo?:
274
357
  | ((token: OAuth2Tokens) => Promise<{
275
- user: {
276
- id: string;
277
- name?: string;
278
- email?: string | null;
279
- image?: string;
280
- emailVerified: boolean;
281
- [key: string]: any;
282
- };
283
- data: any;
358
+ user: OAuth2UserInfo & Record<string, unknown>;
359
+ data: Profile;
284
360
  } | null>)
285
361
  | undefined;
286
362
  /**
@@ -293,25 +369,7 @@ export type ProviderOptions<Profile extends Record<string, any> = any> = {
293
369
  * Custom function to map the provider profile to a
294
370
  * user.
295
371
  */
296
- mapProfileToUser?:
297
- | ((profile: Profile) =>
298
- | {
299
- id?: string;
300
- name?: string;
301
- email?: string | null;
302
- image?: string;
303
- emailVerified?: boolean;
304
- [key: string]: any;
305
- }
306
- | Promise<{
307
- id?: string;
308
- name?: string;
309
- email?: string | null;
310
- image?: string;
311
- emailVerified?: boolean;
312
- [key: string]: any;
313
- }>)
314
- | undefined;
372
+ mapProfileToUser?: OAuthProfileMapper<Profile> | undefined;
315
373
  /**
316
374
  * Disable implicit sign up for new users. When set to true for the provider,
317
375
  * sign-in need to be called with with requestSignUp as true to create new users.
@@ -1,4 +1,5 @@
1
1
  import { decodeProtectedHeader, jwtVerify } from "jose";
2
+ import type { GenericEndpointContext } from "../types";
2
3
  import type { OAuthProvider, ProviderOptions } from "./oauth-provider";
3
4
 
4
5
  type ProviderWithIdTokenConfig = Pick<OAuthProvider, "idToken" | "options">;
@@ -59,6 +60,7 @@ export async function verifyProviderIdToken(
59
60
  provider: ProviderWithIdTokenConfig,
60
61
  token: string,
61
62
  nonce?: string,
63
+ ctx?: GenericEndpointContext,
62
64
  ): Promise<boolean> {
63
65
  const options = (provider.options ?? {}) as Partial<ProviderOptions>;
64
66
  if (options.disableIdTokenSignIn) {
@@ -69,14 +71,14 @@ export async function verifyProviderIdToken(
69
71
  // escaping to the caller as a server error.
70
72
  try {
71
73
  if (options.verifyIdToken) {
72
- return await options.verifyIdToken(token, nonce);
74
+ return await options.verifyIdToken(token, nonce, ctx);
73
75
  }
74
76
  const config = provider.idToken;
75
77
  if (!config) {
76
78
  return false;
77
79
  }
78
80
  if ("verify" in config) {
79
- return await config.verify(token, nonce);
81
+ return await config.verify(token, nonce, ctx);
80
82
  }
81
83
  // Opaque (non-JWS) tokens carry no signature to check. They are accepted only when the
82
84
  // provider opts in, in which case getUserInfo resolves identity from the access token via
@@ -82,6 +82,8 @@ export const apple = (options: AppleOptions) => {
82
82
  return {
83
83
  id: "apple",
84
84
  name: "Apple",
85
+ accountSubject: ({ profile }) => profile.sub,
86
+ accountIssuer: "https://appleid.apple.com",
85
87
  async createAuthorizationURL({
86
88
  state,
87
89
  scopes,
@@ -174,7 +176,6 @@ export const apple = (options: AppleOptions) => {
174
176
  const userMap = await options.mapProfileToUser?.(enrichedProfile);
175
177
  return {
176
178
  user: {
177
- id: profile.sub,
178
179
  name: enrichedProfile.name,
179
180
  emailVerified: emailVerified,
180
181
  email: profile.email,
@@ -34,6 +34,7 @@ export const atlassian = (options: AtlassianOptions) => {
34
34
  return {
35
35
  id: "atlassian",
36
36
  name: "Atlassian",
37
+ accountSubject: ({ profile }) => profile.account_id,
37
38
 
38
39
  async createAuthorizationURL({
39
40
  state,
@@ -120,7 +121,6 @@ export const atlassian = (options: AtlassianOptions) => {
120
121
 
121
122
  return {
122
123
  user: {
123
- id: profile.account_id,
124
124
  name: profile.name,
125
125
  email: profile.email,
126
126
  image: profile.picture,
@@ -73,6 +73,8 @@ export const cognito = (options: CognitoOptions) => {
73
73
  return {
74
74
  id: "cognito",
75
75
  name: "Cognito",
76
+ accountSubject: ({ profile }) => profile.sub,
77
+ accountIssuer: `https://cognito-idp.${options.region}.amazonaws.com/${options.userPoolId}`,
76
78
  async createAuthorizationURL({
77
79
  state,
78
80
  scopes,
@@ -184,7 +186,6 @@ export const cognito = (options: CognitoOptions) => {
184
186
 
185
187
  return {
186
188
  user: {
187
- id: profile.sub,
188
189
  name: enrichedProfile.name,
189
190
  email: profile.email,
190
191
  image: profile.picture,
@@ -213,7 +214,6 @@ export const cognito = (options: CognitoOptions) => {
213
214
  const userMap = await options.mapProfileToUser?.(userInfo);
214
215
  return {
215
216
  user: {
216
- id: userInfo.sub,
217
217
  name:
218
218
  userInfo.name ||
219
219
  userInfo.given_name ||
@@ -88,6 +88,7 @@ export const discord = (options: DiscordOptions) => {
88
88
  return {
89
89
  id: "discord",
90
90
  name: "Discord",
91
+ accountSubject: ({ profile }) => profile.id,
91
92
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
92
93
  const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
93
94
  if (scopes) _scopes.push(...scopes);
@@ -159,7 +160,6 @@ export const discord = (options: DiscordOptions) => {
159
160
  const userMap = await options.mapProfileToUser?.(profile);
160
161
  return {
161
162
  user: {
162
- id: profile.id,
163
163
  name: profile.global_name || profile.username || "",
164
164
  email: profile.email,
165
165
  emailVerified: profile.verified,
@@ -31,6 +31,7 @@ export const dropbox = (options: DropboxOptions) => {
31
31
  return {
32
32
  id: "dropbox",
33
33
  name: "Dropbox",
34
+ accountSubject: ({ profile }) => profile.account_id,
34
35
  createAuthorizationURL: async ({
35
36
  state,
36
37
  scopes,
@@ -99,7 +100,6 @@ export const dropbox = (options: DropboxOptions) => {
99
100
  const userMap = await options.mapProfileToUser?.(profile);
100
101
  return {
101
102
  user: {
102
- id: profile.account_id,
103
103
  name: profile.name?.display_name,
104
104
  email: profile.email,
105
105
  emailVerified: profile.email_verified || false,
@@ -9,7 +9,7 @@ import {
9
9
  refreshAccessToken,
10
10
  validateAuthorizationCode,
11
11
  } from "../oauth2";
12
- export interface FacebookProfile {
12
+ export interface FacebookGraphProfile {
13
13
  id: string;
14
14
  name: string;
15
15
  email?: string;
@@ -24,6 +24,17 @@ export interface FacebookProfile {
24
24
  };
25
25
  }
26
26
 
27
+ export interface FacebookLimitedLoginProfile {
28
+ sub: string;
29
+ email: string;
30
+ name: string;
31
+ picture: string;
32
+ }
33
+
34
+ export type FacebookProfile =
35
+ | FacebookGraphProfile
36
+ | FacebookLimitedLoginProfile;
37
+
27
38
  interface FacebookDebugTokenData {
28
39
  app_id?: string;
29
40
  is_valid?: boolean;
@@ -95,6 +106,9 @@ export const facebook = (options: FacebookOptions) => {
95
106
  return {
96
107
  id: "facebook",
97
108
  name: "Facebook",
109
+ accountSubject: ({ profile }) =>
110
+ "sub" in profile ? profile.sub : profile.id,
111
+ accountIssuer: "https://www.facebook.com",
98
112
  async createAuthorizationURL({
99
113
  state,
100
114
  scopes,
@@ -167,38 +181,18 @@ export const facebook = (options: FacebookOptions) => {
167
181
  }
168
182
 
169
183
  if (token.idToken && token.idToken.split(".").length === 3) {
170
- const profile = decodeJwt(token.idToken) as {
171
- sub: string;
172
- email: string;
173
- name: string;
174
- picture: string;
175
- };
176
-
177
- const user = {
178
- id: profile.sub,
179
- name: profile.name,
180
- email: profile.email,
181
- picture: {
182
- data: {
183
- url: profile.picture,
184
- height: 100,
185
- width: 100,
186
- is_silhouette: false,
187
- },
188
- },
189
- };
184
+ const profile = decodeJwt(token.idToken) as FacebookLimitedLoginProfile;
190
185
 
191
186
  // https://developers.facebook.com/docs/facebook-login/limited-login/permissions
192
187
  // Facebook ID token does not include email_verified claim.
193
188
  // We default to false for security consistency.
194
- const userMap = await options.mapProfileToUser?.({
195
- ...user,
196
- email_verified: false,
197
- });
189
+ const userMap = await options.mapProfileToUser?.(profile);
198
190
 
199
191
  return {
200
192
  user: {
201
- ...user,
193
+ name: profile.name,
194
+ email: profile.email,
195
+ image: profile.picture,
202
196
  emailVerified: false,
203
197
  ...userMap,
204
198
  },
@@ -228,7 +222,7 @@ export const facebook = (options: FacebookOptions) => {
228
222
  "picture",
229
223
  ...(options?.fields || []),
230
224
  ];
231
- const { data: profile, error } = await betterFetch<FacebookProfile>(
225
+ const { data: profile, error } = await betterFetch<FacebookGraphProfile>(
232
226
  "https://graph.facebook.com/me?fields=" + fields.join(","),
233
227
  {
234
228
  auth: {
@@ -247,7 +241,6 @@ export const facebook = (options: FacebookOptions) => {
247
241
  const userMap = await options.mapProfileToUser?.(profile);
248
242
  return {
249
243
  user: {
250
- id: profile.id,
251
244
  name: profile.name,
252
245
  email: profile.email,
253
246
  image: profile.picture.data.url,
@@ -24,6 +24,7 @@ export const figma = (options: FigmaOptions) => {
24
24
  return {
25
25
  id: "figma",
26
26
  name: "Figma",
27
+ accountSubject: ({ profile }) => profile.id,
27
28
  async createAuthorizationURL({
28
29
  state,
29
30
  scopes,
@@ -106,7 +107,6 @@ export const figma = (options: FigmaOptions) => {
106
107
 
107
108
  return {
108
109
  user: {
109
- id: profile.id,
110
110
  name: profile.handle,
111
111
  email: profile.email,
112
112
  image: profile.img_url,
@@ -63,6 +63,7 @@ export const github = (options: GithubOptions) => {
63
63
  return {
64
64
  id: "github",
65
65
  name: "GitHub",
66
+ accountSubject: ({ profile }) => profile.id,
66
67
  createAuthorizationURL({
67
68
  state,
68
69
  scopes,
@@ -171,7 +172,6 @@ export const github = (options: GithubOptions) => {
171
172
  const userMap = await options.mapProfileToUser?.(profile);
172
173
  return {
173
174
  user: {
174
- id: profile.id,
175
175
  name: profile.name || profile.login || "",
176
176
  email: profile.email,
177
177
  image: profile.avatar_url,
@@ -81,6 +81,7 @@ export const gitlab = (options: GitlabOptions) => {
81
81
  return {
82
82
  id: issuerId,
83
83
  name: issuerName,
84
+ accountSubject: ({ profile }) => profile.id,
84
85
  createAuthorizationURL: async ({
85
86
  state,
86
87
  scopes,
@@ -142,7 +143,6 @@ export const gitlab = (options: GitlabOptions) => {
142
143
  // We check for it first, then default to false for security consistency.
143
144
  return {
144
145
  user: {
145
- id: profile.id,
146
146
  name: profile.name ?? profile.username ?? "",
147
147
  email: profile.email,
148
148
  image: profile.avatar_url,
@@ -150,6 +150,8 @@ export const google = (options: GoogleOptions) => {
150
150
  return {
151
151
  id: "google",
152
152
  name: "Google",
153
+ accountSubject: ({ profile }) => profile.sub,
154
+ accountIssuer: "https://accounts.google.com",
153
155
  async createAuthorizationURL({
154
156
  state,
155
157
  scopes,
@@ -254,7 +256,6 @@ export const google = (options: GoogleOptions) => {
254
256
  const userMap = await options.mapProfileToUser?.(user);
255
257
  return {
256
258
  user: {
257
- id: user.sub,
258
259
  name: user.name,
259
260
  email: user.email,
260
261
  image: user.picture,
@@ -47,6 +47,7 @@ export const huggingface = (options: HuggingFaceOptions) => {
47
47
  return {
48
48
  id: "huggingface",
49
49
  name: "Hugging Face",
50
+ accountSubject: ({ profile }) => profile.sub,
50
51
  createAuthorizationURL({
51
52
  state,
52
53
  scopes,
@@ -111,7 +112,6 @@ export const huggingface = (options: HuggingFaceOptions) => {
111
112
  const userMap = await options.mapProfileToUser?.(profile);
112
113
  return {
113
114
  user: {
114
- id: profile.sub,
115
115
  name: profile.name || profile.preferred_username || "",
116
116
  email: profile.email,
117
117
  image: profile.picture,
@@ -106,6 +106,7 @@ export const kakao = (options: KakaoOptions) => {
106
106
  return {
107
107
  id: "kakao",
108
108
  name: "Kakao",
109
+ accountSubject: ({ profile }) => profile.id,
109
110
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
110
111
  const _scopes = options.disableDefaultScope
111
112
  ? []
@@ -162,7 +163,6 @@ export const kakao = (options: KakaoOptions) => {
162
163
  const account = profile.kakao_account || {};
163
164
  const kakaoProfile = account.profile || {};
164
165
  const user = {
165
- id: String(profile.id),
166
166
  name: kakaoProfile.nickname || account.name || "",
167
167
  email: account.email,
168
168
  image:
@@ -33,6 +33,7 @@ export const kick = (options: KickOptions) => {
33
33
  return {
34
34
  id: "kick",
35
35
  name: "Kick",
36
+ accountSubject: ({ profile }) => profile.user_id,
36
37
  createAuthorizationURL({
37
38
  state,
38
39
  scopes,
@@ -101,7 +102,6 @@ export const kick = (options: KickOptions) => {
101
102
  // We default to false for security consistency.
102
103
  return {
103
104
  user: {
104
- id: profile.user_id,
105
105
  name: profile.name,
106
106
  email: profile.email,
107
107
  image: profile.profile_picture,
@@ -50,6 +50,8 @@ export const line = (options: LineOptions) => {
50
50
  return {
51
51
  id: "line",
52
52
  name: "LINE",
53
+ accountSubject: ({ profile }) => profile.sub,
54
+ accountIssuer: "https://access.line.me",
53
55
  async createAuthorizationURL({
54
56
  state,
55
57
  scopes,
@@ -142,24 +144,17 @@ export const line = (options: LineOptions) => {
142
144
  profile = data || null;
143
145
  }
144
146
  if (!profile) return null;
145
- const userMap = await options.mapProfileToUser?.(profile as any);
146
- // ID preference order
147
- const id = (profile as any).sub || (profile as any).userId;
148
- const name = (profile as any).name || (profile as any).displayName || "";
149
- const image =
150
- (profile as any).picture || (profile as any).pictureUrl || undefined;
151
- const email = (profile as any).email;
147
+ const userMap = await options.mapProfileToUser?.(profile);
152
148
  return {
153
149
  user: {
154
- id,
155
- name,
156
- email,
157
- image,
150
+ name: profile.name || "",
151
+ email: profile.email,
152
+ image: profile.picture,
158
153
  // LINE does not expose email verification status in ID token/userinfo
159
154
  emailVerified: false,
160
155
  ...userMap,
161
156
  },
162
- data: profile as any,
157
+ data: profile,
163
158
  };
164
159
  },
165
160
  options,
@@ -31,6 +31,7 @@ export const linear = (options: LinearOptions) => {
31
31
  return {
32
32
  id: "linear",
33
33
  name: "Linear",
34
+ accountSubject: ({ profile }) => profile.id,
34
35
  createAuthorizationURL({
35
36
  state,
36
37
  scopes,
@@ -113,7 +114,6 @@ export const linear = (options: LinearOptions) => {
113
114
  // We default to false for security consistency.
114
115
  return {
115
116
  user: {
116
- id: profile.data.viewer.id,
117
117
  name: profile.data.viewer.name,
118
118
  email: profile.data.viewer.email,
119
119
  image: profile.data.viewer.avatarUrl,
@@ -32,6 +32,7 @@ export const linkedin = (options: LinkedInOptions) => {
32
32
  return {
33
33
  id: "linkedin",
34
34
  name: "Linkedin",
35
+ accountSubject: ({ profile }) => profile.sub,
35
36
  createAuthorizationURL: async ({
36
37
  state,
37
38
  scopes,
@@ -97,7 +98,6 @@ export const linkedin = (options: LinkedInOptions) => {
97
98
  const userMap = await options.mapProfileToUser?.(profile);
98
99
  return {
99
100
  user: {
100
- id: profile.sub,
101
101
  name: profile.name,
102
102
  email: profile.email,
103
103
  emailVerified: profile.email_verified ?? false,
@@ -187,6 +187,8 @@ export const microsoft = (options: MicrosoftOptions) => {
187
187
  return {
188
188
  id: "microsoft",
189
189
  name: "Microsoft EntraID",
190
+ accountSubject: ({ profile }) => profile.sub,
191
+ accountIssuer: ({ profile }) => profile.iss,
190
192
  createAuthorizationURL(data) {
191
193
  // Microsoft Entra supports public clients (SPA / native apps with
192
194
  // PKCE only), so clientSecret is intentionally not required here.
@@ -319,7 +321,6 @@ export const microsoft = (options: MicrosoftOptions) => {
319
321
  : false;
320
322
  return {
321
323
  user: {
322
- id: user.sub,
323
324
  name: user.name,
324
325
  email: user.email,
325
326
  image: user.picture,
@@ -351,7 +352,7 @@ export const microsoft = (options: MicrosoftOptions) => {
351
352
  });
352
353
  },
353
354
  options,
354
- } satisfies OAuthProvider;
355
+ } satisfies OAuthProvider<MicrosoftEntraIDProfile>;
355
356
  };
356
357
 
357
358
  export const getMicrosoftPublicKey = async (
@@ -44,6 +44,7 @@ export const naver = (options: NaverOptions) => {
44
44
  return {
45
45
  id: "naver",
46
46
  name: "Naver",
47
+ accountSubject: ({ profile }) => profile.response.id,
47
48
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
48
49
  const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
49
50
  if (options.scope) _scopes.push(...options.scope);
@@ -97,7 +98,6 @@ export const naver = (options: NaverOptions) => {
97
98
  const userMap = await options.mapProfileToUser?.(profile);
98
99
  const res = profile.response || {};
99
100
  const user = {
100
- id: res.id,
101
101
  name: res.name || res.nickname || "",
102
102
  email: res.email,
103
103
  image: res.profile_image,
@@ -28,6 +28,7 @@ export const notion = (options: NotionOptions) => {
28
28
  return {
29
29
  id: "notion",
30
30
  name: "Notion",
31
+ accountSubject: ({ profile }) => profile.id,
31
32
  createAuthorizationURL({
32
33
  state,
33
34
  scopes,
@@ -100,7 +101,6 @@ export const notion = (options: NotionOptions) => {
100
101
  const userMap = await options.mapProfileToUser?.(userProfile);
101
102
  return {
102
103
  user: {
103
- id: userProfile.id,
104
104
  name: userProfile.name || "",
105
105
  email: userProfile.person?.email || null,
106
106
  image: userProfile.avatar_url,