@depup/better-auth__core 1.6.23-depup.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 (310) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +32 -0
  3. package/changes.json +14 -0
  4. package/dist/api/index.d.mts +281 -0
  5. package/dist/api/index.mjs +92 -0
  6. package/dist/async_hooks/index.d.mts +6 -0
  7. package/dist/async_hooks/index.mjs +20 -0
  8. package/dist/async_hooks/pure.index.d.mts +6 -0
  9. package/dist/async_hooks/pure.index.mjs +33 -0
  10. package/dist/context/endpoint-context.d.mts +18 -0
  11. package/dist/context/endpoint-context.mjs +29 -0
  12. package/dist/context/global.d.mts +6 -0
  13. package/dist/context/global.mjs +36 -0
  14. package/dist/context/index.d.mts +5 -0
  15. package/dist/context/index.mjs +5 -0
  16. package/dist/context/request-state.d.mts +27 -0
  17. package/dist/context/request-state.mjs +47 -0
  18. package/dist/context/transaction.d.mts +27 -0
  19. package/dist/context/transaction.mjs +96 -0
  20. package/dist/db/adapter/factory.d.mts +17 -0
  21. package/dist/db/adapter/factory.mjs +905 -0
  22. package/dist/db/adapter/get-default-field-name.d.mts +18 -0
  23. package/dist/db/adapter/get-default-field-name.mjs +36 -0
  24. package/dist/db/adapter/get-default-model-name.d.mts +12 -0
  25. package/dist/db/adapter/get-default-model-name.mjs +30 -0
  26. package/dist/db/adapter/get-field-attributes.d.mts +26 -0
  27. package/dist/db/adapter/get-field-attributes.mjs +37 -0
  28. package/dist/db/adapter/get-field-name.d.mts +18 -0
  29. package/dist/db/adapter/get-field-name.mjs +31 -0
  30. package/dist/db/adapter/get-id-field.d.mts +36 -0
  31. package/dist/db/adapter/get-id-field.mjs +64 -0
  32. package/dist/db/adapter/get-model-name.d.mts +12 -0
  33. package/dist/db/adapter/get-model-name.mjs +21 -0
  34. package/dist/db/adapter/index.d.mts +615 -0
  35. package/dist/db/adapter/index.mjs +24 -0
  36. package/dist/db/adapter/types.d.mts +105 -0
  37. package/dist/db/adapter/utils.d.mts +7 -0
  38. package/dist/db/adapter/utils.mjs +37 -0
  39. package/dist/db/get-tables.d.mts +6 -0
  40. package/dist/db/get-tables.mjs +266 -0
  41. package/dist/db/index.d.mts +10 -0
  42. package/dist/db/index.mjs +8 -0
  43. package/dist/db/plugin.d.mts +12 -0
  44. package/dist/db/schema/account.d.mts +28 -0
  45. package/dist/db/schema/account.mjs +17 -0
  46. package/dist/db/schema/rate-limit.d.mts +18 -0
  47. package/dist/db/schema/rate-limit.mjs +9 -0
  48. package/dist/db/schema/session.d.mts +23 -0
  49. package/dist/db/schema/session.mjs +12 -0
  50. package/dist/db/schema/shared.d.mts +10 -0
  51. package/dist/db/schema/shared.mjs +9 -0
  52. package/dist/db/schema/user.d.mts +22 -0
  53. package/dist/db/schema/user.mjs +11 -0
  54. package/dist/db/schema/verification.d.mts +21 -0
  55. package/dist/db/schema/verification.mjs +10 -0
  56. package/dist/db/type.d.mts +194 -0
  57. package/dist/env/color-depth.d.mts +4 -0
  58. package/dist/env/color-depth.mjs +86 -0
  59. package/dist/env/env-impl.d.mts +32 -0
  60. package/dist/env/env-impl.mjs +81 -0
  61. package/dist/env/index.d.mts +4 -0
  62. package/dist/env/index.mjs +4 -0
  63. package/dist/env/logger.d.mts +48 -0
  64. package/dist/env/logger.mjs +79 -0
  65. package/dist/error/codes.d.mts +69 -0
  66. package/dist/error/codes.mjs +55 -0
  67. package/dist/error/index.d.mts +26 -0
  68. package/dist/error/index.mjs +27 -0
  69. package/dist/index.d.mts +9 -0
  70. package/dist/index.mjs +1 -0
  71. package/dist/instrumentation/api.mjs +12 -0
  72. package/dist/instrumentation/attributes.d.mts +11 -0
  73. package/dist/instrumentation/attributes.mjs +10 -0
  74. package/dist/instrumentation/index.d.mts +3 -0
  75. package/dist/instrumentation/index.mjs +3 -0
  76. package/dist/instrumentation/noop.mjs +42 -0
  77. package/dist/instrumentation/pure.index.d.mts +7 -0
  78. package/dist/instrumentation/pure.index.mjs +7 -0
  79. package/dist/instrumentation/tracer.d.mts +13 -0
  80. package/dist/instrumentation/tracer.mjs +53 -0
  81. package/dist/oauth2/client-credentials-token.d.mts +56 -0
  82. package/dist/oauth2/client-credentials-token.mjs +64 -0
  83. package/dist/oauth2/create-authorization-url.d.mts +44 -0
  84. package/dist/oauth2/create-authorization-url.mjs +41 -0
  85. package/dist/oauth2/index.d.mts +8 -0
  86. package/dist/oauth2/index.mjs +7 -0
  87. package/dist/oauth2/oauth-provider.d.mts +192 -0
  88. package/dist/oauth2/refresh-access-token.d.mts +54 -0
  89. package/dist/oauth2/refresh-access-token.mjs +73 -0
  90. package/dist/oauth2/reject-redirects.mjs +60 -0
  91. package/dist/oauth2/utils.d.mts +24 -0
  92. package/dist/oauth2/utils.mjs +48 -0
  93. package/dist/oauth2/validate-authorization-code.d.mts +85 -0
  94. package/dist/oauth2/validate-authorization-code.mjs +86 -0
  95. package/dist/oauth2/verify.d.mts +65 -0
  96. package/dist/oauth2/verify.mjs +202 -0
  97. package/dist/social-providers/apple.d.mts +133 -0
  98. package/dist/social-providers/apple.mjs +117 -0
  99. package/dist/social-providers/atlassian.d.mts +70 -0
  100. package/dist/social-providers/atlassian.mjs +80 -0
  101. package/dist/social-providers/cognito.d.mts +85 -0
  102. package/dist/social-providers/cognito.mjs +162 -0
  103. package/dist/social-providers/discord.d.mts +124 -0
  104. package/dist/social-providers/discord.mjs +62 -0
  105. package/dist/social-providers/dropbox.d.mts +69 -0
  106. package/dist/social-providers/dropbox.mjs +72 -0
  107. package/dist/social-providers/facebook.d.mts +79 -0
  108. package/dist/social-providers/facebook.mjs +157 -0
  109. package/dist/social-providers/figma.d.mts +61 -0
  110. package/dist/social-providers/figma.mjs +83 -0
  111. package/dist/social-providers/github.d.mts +102 -0
  112. package/dist/social-providers/github.mjs +92 -0
  113. package/dist/social-providers/gitlab.d.mts +123 -0
  114. package/dist/social-providers/gitlab.mjs +79 -0
  115. package/dist/social-providers/google.d.mts +123 -0
  116. package/dist/social-providers/google.mjs +137 -0
  117. package/dist/social-providers/huggingface.d.mts +83 -0
  118. package/dist/social-providers/huggingface.mjs +73 -0
  119. package/dist/social-providers/index.d.mts +1834 -0
  120. package/dist/social-providers/index.mjs +78 -0
  121. package/dist/social-providers/kakao.d.mts +161 -0
  122. package/dist/social-providers/kakao.mjs +70 -0
  123. package/dist/social-providers/kick.d.mts +73 -0
  124. package/dist/social-providers/kick.mjs +68 -0
  125. package/dist/social-providers/line.d.mts +105 -0
  126. package/dist/social-providers/line.mjs +110 -0
  127. package/dist/social-providers/linear.d.mts +68 -0
  128. package/dist/social-providers/linear.mjs +85 -0
  129. package/dist/social-providers/linkedin.d.mts +67 -0
  130. package/dist/social-providers/linkedin.mjs +73 -0
  131. package/dist/social-providers/microsoft-entra-id.d.mts +174 -0
  132. package/dist/social-providers/microsoft-entra-id.mjs +152 -0
  133. package/dist/social-providers/naver.d.mts +92 -0
  134. package/dist/social-providers/naver.mjs +65 -0
  135. package/dist/social-providers/notion.d.mts +64 -0
  136. package/dist/social-providers/notion.mjs +72 -0
  137. package/dist/social-providers/paybin.d.mts +71 -0
  138. package/dist/social-providers/paybin.mjs +81 -0
  139. package/dist/social-providers/paypal.d.mts +131 -0
  140. package/dist/social-providers/paypal.mjs +188 -0
  141. package/dist/social-providers/polar.d.mts +74 -0
  142. package/dist/social-providers/polar.mjs +71 -0
  143. package/dist/social-providers/railway.d.mts +65 -0
  144. package/dist/social-providers/railway.mjs +74 -0
  145. package/dist/social-providers/reddit.d.mts +62 -0
  146. package/dist/social-providers/reddit.mjs +81 -0
  147. package/dist/social-providers/roblox.d.mts +70 -0
  148. package/dist/social-providers/roblox.mjs +57 -0
  149. package/dist/social-providers/salesforce.d.mts +79 -0
  150. package/dist/social-providers/salesforce.mjs +87 -0
  151. package/dist/social-providers/slack.d.mts +83 -0
  152. package/dist/social-providers/slack.mjs +66 -0
  153. package/dist/social-providers/spotify.d.mts +63 -0
  154. package/dist/social-providers/spotify.mjs +69 -0
  155. package/dist/social-providers/tiktok.d.mts +168 -0
  156. package/dist/social-providers/tiktok.mjs +60 -0
  157. package/dist/social-providers/twitch.d.mts +79 -0
  158. package/dist/social-providers/twitch.mjs +75 -0
  159. package/dist/social-providers/twitter.d.mts +126 -0
  160. package/dist/social-providers/twitter.mjs +85 -0
  161. package/dist/social-providers/vercel.d.mts +62 -0
  162. package/dist/social-providers/vercel.mjs +58 -0
  163. package/dist/social-providers/vk.d.mts +70 -0
  164. package/dist/social-providers/vk.mjs +81 -0
  165. package/dist/social-providers/wechat.d.mts +113 -0
  166. package/dist/social-providers/wechat.mjs +81 -0
  167. package/dist/social-providers/zoom.d.mts +163 -0
  168. package/dist/social-providers/zoom.mjs +69 -0
  169. package/dist/types/context.d.mts +319 -0
  170. package/dist/types/cookie.d.mts +15 -0
  171. package/dist/types/helper.d.mts +10 -0
  172. package/dist/types/index.d.mts +9 -0
  173. package/dist/types/init-options.d.mts +1430 -0
  174. package/dist/types/plugin-client.d.mts +123 -0
  175. package/dist/types/plugin.d.mts +124 -0
  176. package/dist/types/secret.d.mts +11 -0
  177. package/dist/utils/async.d.mts +22 -0
  178. package/dist/utils/async.mjs +32 -0
  179. package/dist/utils/db.d.mts +9 -0
  180. package/dist/utils/db.mjs +15 -0
  181. package/dist/utils/deprecate.d.mts +9 -0
  182. package/dist/utils/deprecate.mjs +16 -0
  183. package/dist/utils/error-codes.d.mts +13 -0
  184. package/dist/utils/error-codes.mjs +10 -0
  185. package/dist/utils/fetch-metadata.d.mts +4 -0
  186. package/dist/utils/fetch-metadata.mjs +6 -0
  187. package/dist/utils/host.d.mts +147 -0
  188. package/dist/utils/host.mjs +295 -0
  189. package/dist/utils/id.d.mts +4 -0
  190. package/dist/utils/id.mjs +7 -0
  191. package/dist/utils/ip.d.mts +77 -0
  192. package/dist/utils/ip.mjs +230 -0
  193. package/dist/utils/is-api-error.d.mts +6 -0
  194. package/dist/utils/is-api-error.mjs +8 -0
  195. package/dist/utils/json.d.mts +4 -0
  196. package/dist/utils/json.mjs +41 -0
  197. package/dist/utils/redirect-uri.d.mts +20 -0
  198. package/dist/utils/redirect-uri.mjs +48 -0
  199. package/dist/utils/string.d.mts +8 -0
  200. package/dist/utils/string.mjs +25 -0
  201. package/dist/utils/url.d.mts +37 -0
  202. package/dist/utils/url.mjs +61 -0
  203. package/package.json +216 -0
  204. package/src/api/index.ts +185 -0
  205. package/src/async_hooks/index.ts +40 -0
  206. package/src/async_hooks/pure.index.ts +46 -0
  207. package/src/context/endpoint-context.ts +50 -0
  208. package/src/context/global.ts +57 -0
  209. package/src/context/index.ts +23 -0
  210. package/src/context/request-state.ts +91 -0
  211. package/src/context/transaction.ts +169 -0
  212. package/src/db/adapter/factory.ts +1740 -0
  213. package/src/db/adapter/get-default-field-name.ts +59 -0
  214. package/src/db/adapter/get-default-model-name.ts +51 -0
  215. package/src/db/adapter/get-field-attributes.ts +62 -0
  216. package/src/db/adapter/get-field-name.ts +43 -0
  217. package/src/db/adapter/get-id-field.ts +150 -0
  218. package/src/db/adapter/get-model-name.ts +36 -0
  219. package/src/db/adapter/index.ts +656 -0
  220. package/src/db/adapter/types.ts +134 -0
  221. package/src/db/adapter/utils.ts +61 -0
  222. package/src/db/get-tables.ts +304 -0
  223. package/src/db/index.ts +43 -0
  224. package/src/db/plugin.ts +11 -0
  225. package/src/db/schema/account.ts +47 -0
  226. package/src/db/schema/rate-limit.ts +36 -0
  227. package/src/db/schema/session.ts +29 -0
  228. package/src/db/schema/shared.ts +7 -0
  229. package/src/db/schema/user.ts +31 -0
  230. package/src/db/schema/verification.ts +28 -0
  231. package/src/db/type.ts +360 -0
  232. package/src/env/color-depth.ts +172 -0
  233. package/src/env/env-impl.ts +123 -0
  234. package/src/env/index.ts +23 -0
  235. package/src/env/logger.ts +145 -0
  236. package/src/error/codes.ts +72 -0
  237. package/src/error/index.ts +44 -0
  238. package/src/index.ts +1 -0
  239. package/src/instrumentation/api.ts +17 -0
  240. package/src/instrumentation/attributes.ts +22 -0
  241. package/src/instrumentation/index.ts +2 -0
  242. package/src/instrumentation/noop.ts +74 -0
  243. package/src/instrumentation/pure.index.ts +31 -0
  244. package/src/instrumentation/tracer.ts +95 -0
  245. package/src/oauth2/client-credentials-token.ts +126 -0
  246. package/src/oauth2/create-authorization-url.ts +89 -0
  247. package/src/oauth2/index.ts +34 -0
  248. package/src/oauth2/oauth-provider.ts +222 -0
  249. package/src/oauth2/refresh-access-token.ts +157 -0
  250. package/src/oauth2/reject-redirects.ts +70 -0
  251. package/src/oauth2/utils.ts +70 -0
  252. package/src/oauth2/validate-authorization-code.ts +190 -0
  253. package/src/oauth2/verify.ts +408 -0
  254. package/src/social-providers/apple.ts +249 -0
  255. package/src/social-providers/atlassian.ts +133 -0
  256. package/src/social-providers/cognito.ts +281 -0
  257. package/src/social-providers/discord.ts +170 -0
  258. package/src/social-providers/dropbox.ts +112 -0
  259. package/src/social-providers/facebook.ts +288 -0
  260. package/src/social-providers/figma.ts +118 -0
  261. package/src/social-providers/github.ts +184 -0
  262. package/src/social-providers/gitlab.ts +155 -0
  263. package/src/social-providers/google.ts +263 -0
  264. package/src/social-providers/huggingface.ts +119 -0
  265. package/src/social-providers/index.ts +132 -0
  266. package/src/social-providers/kakao.ts +179 -0
  267. package/src/social-providers/kick.ts +109 -0
  268. package/src/social-providers/line.ts +169 -0
  269. package/src/social-providers/linear.ts +121 -0
  270. package/src/social-providers/linkedin.ts +110 -0
  271. package/src/social-providers/microsoft-entra-id.ts +391 -0
  272. package/src/social-providers/naver.ts +113 -0
  273. package/src/social-providers/notion.ts +108 -0
  274. package/src/social-providers/paybin.ts +118 -0
  275. package/src/social-providers/paypal.ts +371 -0
  276. package/src/social-providers/polar.ts +111 -0
  277. package/src/social-providers/railway.ts +100 -0
  278. package/src/social-providers/reddit.ts +126 -0
  279. package/src/social-providers/roblox.ts +112 -0
  280. package/src/social-providers/salesforce.ts +159 -0
  281. package/src/social-providers/slack.ts +112 -0
  282. package/src/social-providers/spotify.ts +94 -0
  283. package/src/social-providers/tiktok.ts +211 -0
  284. package/src/social-providers/twitch.ts +112 -0
  285. package/src/social-providers/twitter.ts +199 -0
  286. package/src/social-providers/vercel.ts +87 -0
  287. package/src/social-providers/vk.ts +125 -0
  288. package/src/social-providers/wechat.ts +220 -0
  289. package/src/social-providers/zoom.ts +230 -0
  290. package/src/types/context.ts +460 -0
  291. package/src/types/cookie.ts +10 -0
  292. package/src/types/helper.ts +27 -0
  293. package/src/types/index.ts +40 -0
  294. package/src/types/init-options.ts +1679 -0
  295. package/src/types/plugin-client.ts +143 -0
  296. package/src/types/plugin.ts +163 -0
  297. package/src/types/secret.ts +8 -0
  298. package/src/utils/async.ts +53 -0
  299. package/src/utils/db.ts +20 -0
  300. package/src/utils/deprecate.ts +21 -0
  301. package/src/utils/error-codes.ts +68 -0
  302. package/src/utils/fetch-metadata.ts +3 -0
  303. package/src/utils/host.ts +416 -0
  304. package/src/utils/id.ts +5 -0
  305. package/src/utils/ip.ts +395 -0
  306. package/src/utils/is-api-error.ts +10 -0
  307. package/src/utils/json.ts +56 -0
  308. package/src/utils/redirect-uri.ts +54 -0
  309. package/src/utils/string.ts +40 -0
  310. package/src/utils/url.ts +77 -0
@@ -0,0 +1,110 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import {
4
+ createAuthorizationURL,
5
+ refreshAccessToken,
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
+
9
+ export interface LinkedInProfile {
10
+ sub: string;
11
+ name: string;
12
+ given_name: string;
13
+ family_name: string;
14
+ picture: string;
15
+ locale: {
16
+ country: string;
17
+ language: string;
18
+ };
19
+ email?: string;
20
+ email_verified?: boolean;
21
+ }
22
+
23
+ export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
24
+ clientId: string;
25
+ }
26
+
27
+ export const linkedin = (options: LinkedInOptions) => {
28
+ const authorizationEndpoint =
29
+ "https://www.linkedin.com/oauth/v2/authorization";
30
+ const tokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
31
+
32
+ return {
33
+ id: "linkedin",
34
+ name: "Linkedin",
35
+ createAuthorizationURL: async ({
36
+ state,
37
+ scopes,
38
+ redirectURI,
39
+ loginHint,
40
+ }) => {
41
+ const _scopes = options.disableDefaultScope
42
+ ? []
43
+ : ["profile", "email", "openid"];
44
+ if (options.scope) _scopes.push(...options.scope);
45
+ if (scopes) _scopes.push(...scopes);
46
+ return await createAuthorizationURL({
47
+ id: "linkedin",
48
+ options,
49
+ authorizationEndpoint,
50
+ scopes: _scopes,
51
+ state,
52
+ loginHint,
53
+ redirectURI,
54
+ });
55
+ },
56
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
57
+ return await validateAuthorizationCode({
58
+ code,
59
+ redirectURI,
60
+ options,
61
+ tokenEndpoint,
62
+ });
63
+ },
64
+ refreshAccessToken: options.refreshAccessToken
65
+ ? options.refreshAccessToken
66
+ : async (refreshToken) => {
67
+ return refreshAccessToken({
68
+ refreshToken,
69
+ options: {
70
+ clientId: options.clientId,
71
+ clientKey: options.clientKey,
72
+ clientSecret: options.clientSecret,
73
+ },
74
+ tokenEndpoint,
75
+ });
76
+ },
77
+ async getUserInfo(token) {
78
+ if (options.getUserInfo) {
79
+ return options.getUserInfo(token);
80
+ }
81
+ const { data: profile, error } = await betterFetch<LinkedInProfile>(
82
+ "https://api.linkedin.com/v2/userinfo",
83
+ {
84
+ method: "GET",
85
+ headers: {
86
+ Authorization: `Bearer ${token.accessToken}`,
87
+ },
88
+ },
89
+ );
90
+
91
+ if (error) {
92
+ return null;
93
+ }
94
+
95
+ const userMap = await options.mapProfileToUser?.(profile);
96
+ return {
97
+ user: {
98
+ id: profile.sub,
99
+ name: profile.name,
100
+ email: profile.email,
101
+ emailVerified: profile.email_verified ?? false,
102
+ image: profile.picture,
103
+ ...userMap,
104
+ },
105
+ data: profile,
106
+ };
107
+ },
108
+ options,
109
+ } satisfies OAuthProvider<LinkedInProfile>;
110
+ };
@@ -0,0 +1,391 @@
1
+ import { base64 } from "@better-auth/utils/base64";
2
+ import { betterFetch } from "@better-fetch/fetch";
3
+ import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
4
+ import { logger } from "../env";
5
+ import { APIError, BetterAuthError } from "../error";
6
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
7
+ import {
8
+ createAuthorizationURL,
9
+ getPrimaryClientId,
10
+ refreshAccessToken,
11
+ validateAuthorizationCode,
12
+ } from "../oauth2";
13
+
14
+ /**
15
+ * Microsoft's fixed tenant id for personal (consumer) Microsoft accounts. Every
16
+ * personal-account token carries it as the `tid` claim, so it distinguishes the
17
+ * consumer account class from work/school tenants.
18
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
19
+ */
20
+ const MICROSOFT_CONSUMER_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
21
+
22
+ /**
23
+ * @see [Microsoft Identity Platform - Optional claims reference](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference)
24
+ */
25
+ export interface MicrosoftEntraIDProfile extends Record<string, any> {
26
+ /** Identifies the intended recipient of the token */
27
+ aud: string;
28
+ /** Identifies the issuer, or "authorization server" that constructs and returns the token */
29
+ iss: string;
30
+ /** Indicates when the authentication for the token occurred */
31
+ iat: Date;
32
+ /** Records the identity provider that authenticated the subject of the token */
33
+ idp: string;
34
+ /** Identifies the time before which the JWT can't be accepted for processing */
35
+ nbf: Date;
36
+ /** Identifies the expiration time on or after which the JWT can't be accepted for processing */
37
+ exp: Date;
38
+ /** Code hash included in ID tokens when issued with an OAuth 2.0 authorization code */
39
+ c_hash: string;
40
+ /** Access token hash included in ID tokens when issued with an OAuth 2.0 access token */
41
+ at_hash: string;
42
+ /** Internal claim used to record data for token reuse */
43
+ aio: string;
44
+ /** The primary username that represents the user */
45
+ preferred_username: string;
46
+ /** User's email address */
47
+ email?: string;
48
+ /** Human-readable value that identifies the subject of the token */
49
+ name: string;
50
+ /** Matches the parameter included in the original authorize request */
51
+ nonce: string;
52
+ /** User's profile picture */
53
+ picture: string;
54
+ /** Immutable identifier for the user account */
55
+ oid: string;
56
+ /** Set of roles assigned to the user */
57
+ roles: string[];
58
+ /** Internal claim used to revalidate tokens */
59
+ rh: string;
60
+ /** Subject identifier - unique to application ID */
61
+ sub: string;
62
+ /** Tenant ID the user is signing in to */
63
+ tid: string;
64
+ /** Unique identifier for a session */
65
+ sid: string;
66
+ /** Token identifier claim */
67
+ uti: string;
68
+ /** Indicates if user is in at least one group */
69
+ hasgroups: boolean;
70
+ /** User account status in tenant (0 = member, 1 = guest) */
71
+ acct: 0 | 1;
72
+ /** Auth Context IDs */
73
+ acrs: string;
74
+ /** Time when the user last authenticated */
75
+ auth_time: Date;
76
+ /** User's country/region */
77
+ ctry: string;
78
+ /** IP address of requesting client when inside VNET */
79
+ fwd: string;
80
+ /** Group claims */
81
+ groups: string;
82
+ /** Login hint for SSO */
83
+ login_hint: string;
84
+ /** Resource tenant's country/region */
85
+ tenant_ctry: string;
86
+ /** Region of the resource tenant */
87
+ tenant_region_scope: string;
88
+ /** UserPrincipalName */
89
+ upn: string;
90
+ /** User's verified primary email addresses */
91
+ verified_primary_email: string[];
92
+ /** User's verified secondary email addresses */
93
+ verified_secondary_email: string[];
94
+ /** Whether the user's email is verified (optional claim, must be configured in app registration) */
95
+ email_verified?: boolean | undefined;
96
+ /** VNET specifier information */
97
+ vnet: string;
98
+ /** Client Capabilities */
99
+ xms_cc: string;
100
+ /** Whether user's email domain is verified */
101
+ xms_edov: boolean;
102
+ /** Preferred data location for Multi-Geo tenants */
103
+ xms_pdl: string;
104
+ /** User preferred language */
105
+ xms_pl: string;
106
+ /** Tenant preferred language */
107
+ xms_tpl: string;
108
+ /** Zero-touch Deployment ID */
109
+ ztdid: string;
110
+ /** IP Address */
111
+ ipaddr: string;
112
+ /** On-premises Security Identifier */
113
+ onprem_sid: string;
114
+ /** Password Expiration Time */
115
+ pwd_exp: number;
116
+ /** Change Password URL */
117
+ pwd_url: string;
118
+ /** Inside Corporate Network flag */
119
+ in_corp: string;
120
+ /** User's family name/surname */
121
+ family_name: string;
122
+ /** User's given/first name */
123
+ given_name: string;
124
+ }
125
+
126
+ export interface MicrosoftOptions
127
+ extends ProviderOptions<MicrosoftEntraIDProfile> {
128
+ clientId: string | string[];
129
+ /**
130
+ * The tenant ID of the Microsoft account
131
+ * @default "common"
132
+ */
133
+ tenantId?: string | undefined;
134
+ /**
135
+ * The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
136
+ * @default "https://login.microsoftonline.com"
137
+ */
138
+ authority?: string | undefined;
139
+ /**
140
+ * The size of the profile photo
141
+ * @default 48
142
+ */
143
+ profilePhotoSize?:
144
+ | (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648)
145
+ | undefined;
146
+ /**
147
+ * Disable profile photo
148
+ */
149
+ disableProfilePhoto?: boolean | undefined;
150
+ }
151
+
152
+ export const microsoft = (options: MicrosoftOptions) => {
153
+ const tenant = options.tenantId || "common";
154
+ // Trim any trailing slash so endpoint URLs and the issuer comparison below
155
+ // never produce a double slash (e.g. a configured `https://host/` would make
156
+ // the expected issuer `https://host//<tid>/v2.0` and reject every token). A
157
+ // loop avoids a trailing-slash regex, which is a polynomial-ReDoS shape.
158
+ let authority = options.authority || "https://login.microsoftonline.com";
159
+ while (authority.endsWith("/")) {
160
+ authority = authority.slice(0, -1);
161
+ }
162
+ const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
163
+ const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
164
+ return {
165
+ id: "microsoft",
166
+ name: "Microsoft EntraID",
167
+ createAuthorizationURL(data) {
168
+ // Microsoft Entra supports public clients (SPA / native apps with
169
+ // PKCE only), so clientSecret is intentionally not required here.
170
+ // See https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
171
+ if (!getPrimaryClientId(options.clientId)) {
172
+ logger.error(
173
+ "Client Id is required for Microsoft Entra ID. Make sure to provide it in the options.",
174
+ );
175
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
176
+ }
177
+ const scopes = options.disableDefaultScope
178
+ ? []
179
+ : ["openid", "profile", "email", "User.Read", "offline_access"];
180
+ if (options.scope) scopes.push(...options.scope);
181
+ if (data.scopes) scopes.push(...data.scopes);
182
+ return createAuthorizationURL({
183
+ id: "microsoft",
184
+ options,
185
+ authorizationEndpoint,
186
+ state: data.state,
187
+ codeVerifier: data.codeVerifier,
188
+ scopes,
189
+ redirectURI: data.redirectURI,
190
+ prompt: options.prompt,
191
+ loginHint: data.loginHint,
192
+ });
193
+ },
194
+ validateAuthorizationCode({ code, codeVerifier, redirectURI }) {
195
+ return validateAuthorizationCode({
196
+ code,
197
+ codeVerifier,
198
+ redirectURI,
199
+ options,
200
+ tokenEndpoint,
201
+ });
202
+ },
203
+ async verifyIdToken(token, nonce) {
204
+ if (options.disableIdTokenSignIn) {
205
+ return false;
206
+ }
207
+ if (options.verifyIdToken) {
208
+ return options.verifyIdToken(token, nonce);
209
+ }
210
+
211
+ try {
212
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
213
+ if (!kid || !jwtAlg) return false;
214
+
215
+ const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
216
+ const verifyOptions: {
217
+ algorithms: [string];
218
+ audience: string | string[];
219
+ maxTokenAge: string;
220
+ issuer?: string;
221
+ } = {
222
+ algorithms: [jwtAlg],
223
+ audience: options.clientId,
224
+ maxTokenAge: "1h",
225
+ };
226
+ /**
227
+ * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
228
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
229
+ */
230
+ if (
231
+ tenant !== "common" &&
232
+ tenant !== "organizations" &&
233
+ tenant !== "consumers"
234
+ ) {
235
+ verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
236
+ }
237
+ const { payload: jwtClaims } = await jwtVerify(
238
+ token,
239
+ publicKey,
240
+ verifyOptions,
241
+ );
242
+
243
+ if (nonce && jwtClaims.nonce !== nonce) {
244
+ return false;
245
+ }
246
+
247
+ // The multi-tenant endpoints (common/organizations/consumers) skip
248
+ // jose's issuer check above because the issuer varies per tenant, and
249
+ // the organizations and consumers JWKS sets overlap. Enforce the tenant
250
+ // binding explicitly so a token from a disallowed account class cannot
251
+ // pass: the issuer must name the token's own tenant, and the account
252
+ // class must match the configured restriction.
253
+ // @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
254
+ const tid = jwtClaims.tid;
255
+ if (
256
+ typeof tid !== "string" ||
257
+ jwtClaims.iss !== `${authority}/${tid}/v2.0`
258
+ ) {
259
+ return false;
260
+ }
261
+ if (
262
+ tenant === "organizations" &&
263
+ tid === MICROSOFT_CONSUMER_TENANT_ID
264
+ ) {
265
+ return false;
266
+ }
267
+ if (tenant === "consumers" && tid !== MICROSOFT_CONSUMER_TENANT_ID) {
268
+ return false;
269
+ }
270
+
271
+ return true;
272
+ } catch (error) {
273
+ logger.error("Failed to verify ID token:", error);
274
+ return false;
275
+ }
276
+ },
277
+ async getUserInfo(token) {
278
+ if (options.getUserInfo) {
279
+ return options.getUserInfo(token);
280
+ }
281
+ if (!token.idToken) {
282
+ return null;
283
+ }
284
+ const user = decodeJwt(token.idToken) as MicrosoftEntraIDProfile;
285
+ const profilePhotoSize = options.profilePhotoSize || 48;
286
+ await betterFetch<ArrayBuffer>(
287
+ `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
288
+ {
289
+ headers: {
290
+ Authorization: `Bearer ${token.accessToken}`,
291
+ },
292
+ async onResponse(context) {
293
+ if (options.disableProfilePhoto || !context.response.ok) {
294
+ return;
295
+ }
296
+ try {
297
+ const response = context.response.clone();
298
+ const pictureBuffer = await response.arrayBuffer();
299
+ const pictureBase64 = base64.encode(pictureBuffer);
300
+ user.picture = `data:image/jpeg;base64, ${pictureBase64}`;
301
+ } catch (e) {
302
+ logger.error(
303
+ e && typeof e === "object" && "name" in e
304
+ ? (e.name as string)
305
+ : "",
306
+ e,
307
+ );
308
+ }
309
+ },
310
+ },
311
+ );
312
+ const userMap = await options.mapProfileToUser?.(user);
313
+ // Microsoft Entra ID does NOT include email_verified claim by default.
314
+ // It must be configured as an optional claim in the app registration.
315
+ // We default to false when not provided for security consistency.
316
+ // We can also check verified_primary_email/verified_secondary_email arrays as fallback.
317
+ const emailVerified =
318
+ user.email_verified !== undefined
319
+ ? user.email_verified
320
+ : user.email &&
321
+ (user.verified_primary_email?.includes(user.email) ||
322
+ user.verified_secondary_email?.includes(user.email))
323
+ ? true
324
+ : false;
325
+ return {
326
+ user: {
327
+ id: user.sub,
328
+ name: user.name,
329
+ email: user.email,
330
+ image: user.picture,
331
+ emailVerified,
332
+ ...userMap,
333
+ },
334
+ data: user,
335
+ };
336
+ },
337
+ refreshAccessToken: options.refreshAccessToken
338
+ ? options.refreshAccessToken
339
+ : async (refreshToken) => {
340
+ const scopes = options.disableDefaultScope
341
+ ? []
342
+ : ["openid", "profile", "email", "User.Read", "offline_access"];
343
+ if (options.scope) scopes.push(...options.scope);
344
+
345
+ return refreshAccessToken({
346
+ refreshToken,
347
+ options: {
348
+ clientId: options.clientId,
349
+ clientSecret: options.clientSecret,
350
+ },
351
+ extraParams: {
352
+ scope: scopes.join(" "), // Include the scopes in request to microsoft
353
+ },
354
+ tokenEndpoint,
355
+ });
356
+ },
357
+ options,
358
+ } satisfies OAuthProvider;
359
+ };
360
+
361
+ export const getMicrosoftPublicKey = async (
362
+ kid: string,
363
+ tenant: string,
364
+ authority: string,
365
+ ) => {
366
+ const { data } = await betterFetch<{
367
+ keys: Array<{
368
+ kid: string;
369
+ alg: string;
370
+ kty: string;
371
+ use: string;
372
+ n: string;
373
+ e: string;
374
+ x5c?: string[];
375
+ x5t?: string;
376
+ }>;
377
+ }>(`${authority}/${tenant}/discovery/v2.0/keys`);
378
+
379
+ if (!data?.keys) {
380
+ throw new APIError("BAD_REQUEST", {
381
+ message: "Keys not found",
382
+ });
383
+ }
384
+
385
+ const jwk = data.keys.find((key) => key.kid === kid);
386
+ if (!jwk) {
387
+ throw new Error(`JWK with kid ${kid} not found`);
388
+ }
389
+
390
+ return await importJWK(jwk, jwk.alg);
391
+ };
@@ -0,0 +1,113 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import {
4
+ createAuthorizationURL,
5
+ refreshAccessToken,
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
+
9
+ export interface NaverProfile {
10
+ /** API response result code */
11
+ resultcode: string;
12
+ /** API response message */
13
+ message: string;
14
+ response: {
15
+ /** Unique Naver user identifier */
16
+ id: string;
17
+ /** User nickname */
18
+ nickname: string;
19
+ /** User real name */
20
+ name: string;
21
+ /** User email address */
22
+ email: string;
23
+ /** Gender (F: female, M: male, U: unknown) */
24
+ gender: string;
25
+ /** Age range */
26
+ age: string;
27
+ /** Birthday (MM-DD format) */
28
+ birthday: string;
29
+ /** Birth year */
30
+ birthyear: string;
31
+ /** Profile image URL */
32
+ profile_image: string;
33
+ /** Mobile phone number */
34
+ mobile: string;
35
+ };
36
+ }
37
+
38
+ export interface NaverOptions extends ProviderOptions<NaverProfile> {
39
+ clientId: string;
40
+ }
41
+
42
+ export const naver = (options: NaverOptions) => {
43
+ const tokenEndpoint = "https://nid.naver.com/oauth2.0/token";
44
+ return {
45
+ id: "naver",
46
+ name: "Naver",
47
+ createAuthorizationURL({ state, scopes, redirectURI }) {
48
+ const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
49
+ if (options.scope) _scopes.push(...options.scope);
50
+ if (scopes) _scopes.push(...scopes);
51
+ return createAuthorizationURL({
52
+ id: "naver",
53
+ options,
54
+ authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
55
+ scopes: _scopes,
56
+ state,
57
+ redirectURI,
58
+ });
59
+ },
60
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
61
+ return validateAuthorizationCode({
62
+ code,
63
+ redirectURI,
64
+ options,
65
+ tokenEndpoint,
66
+ });
67
+ },
68
+ refreshAccessToken: options.refreshAccessToken
69
+ ? options.refreshAccessToken
70
+ : async (refreshToken) => {
71
+ return refreshAccessToken({
72
+ refreshToken,
73
+ options: {
74
+ clientId: options.clientId,
75
+ clientKey: options.clientKey,
76
+ clientSecret: options.clientSecret,
77
+ },
78
+ tokenEndpoint,
79
+ });
80
+ },
81
+ async getUserInfo(token) {
82
+ if (options.getUserInfo) {
83
+ return options.getUserInfo(token);
84
+ }
85
+ const { data: profile, error } = await betterFetch<NaverProfile>(
86
+ "https://openapi.naver.com/v1/nid/me",
87
+ {
88
+ headers: {
89
+ Authorization: `Bearer ${token.accessToken}`,
90
+ },
91
+ },
92
+ );
93
+ if (error || !profile || profile.resultcode !== "00") {
94
+ return null;
95
+ }
96
+ const userMap = await options.mapProfileToUser?.(profile);
97
+ const res = profile.response || {};
98
+ const user = {
99
+ id: res.id,
100
+ name: res.name || res.nickname || "",
101
+ email: res.email,
102
+ image: res.profile_image,
103
+ emailVerified: false,
104
+ ...userMap,
105
+ };
106
+ return {
107
+ user,
108
+ data: profile,
109
+ };
110
+ },
111
+ options,
112
+ } satisfies OAuthProvider<NaverProfile>;
113
+ };