@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,184 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import { logger } from "../env";
3
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
+ import {
5
+ createAuthorizationURL,
6
+ getOAuth2Tokens,
7
+ refreshAccessToken,
8
+ } from "../oauth2";
9
+ import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code";
10
+
11
+ export interface GithubProfile {
12
+ login: string;
13
+ id: string;
14
+ node_id: string;
15
+ avatar_url: string;
16
+ gravatar_id: string;
17
+ url: string;
18
+ html_url: string;
19
+ followers_url: string;
20
+ following_url: string;
21
+ gists_url: string;
22
+ starred_url: string;
23
+ subscriptions_url: string;
24
+ organizations_url: string;
25
+ repos_url: string;
26
+ events_url: string;
27
+ received_events_url: string;
28
+ type: string;
29
+ site_admin: boolean;
30
+ name: string;
31
+ company: string;
32
+ blog: string;
33
+ location: string;
34
+ email: string | null;
35
+ hireable: boolean;
36
+ bio: string;
37
+ twitter_username: string;
38
+ public_repos: string;
39
+ public_gists: string;
40
+ followers: string;
41
+ following: string;
42
+ created_at: string;
43
+ updated_at: string;
44
+ private_gists: string;
45
+ total_private_repos: string;
46
+ owned_private_repos: string;
47
+ disk_usage: string;
48
+ collaborators: string;
49
+ two_factor_authentication: boolean;
50
+ plan: {
51
+ name: string;
52
+ space: string;
53
+ private_repos: string;
54
+ collaborators: string;
55
+ };
56
+ }
57
+
58
+ export interface GithubOptions extends ProviderOptions<GithubProfile> {
59
+ clientId: string;
60
+ }
61
+ export const github = (options: GithubOptions) => {
62
+ const tokenEndpoint = "https://github.com/login/oauth/access_token";
63
+ return {
64
+ id: "github",
65
+ name: "GitHub",
66
+ createAuthorizationURL({
67
+ state,
68
+ scopes,
69
+ loginHint,
70
+ codeVerifier,
71
+ redirectURI,
72
+ }) {
73
+ const _scopes = options.disableDefaultScope
74
+ ? []
75
+ : ["read:user", "user:email"];
76
+ if (options.scope) _scopes.push(...options.scope);
77
+ if (scopes) _scopes.push(...scopes);
78
+ return createAuthorizationURL({
79
+ id: "github",
80
+ options,
81
+ authorizationEndpoint: "https://github.com/login/oauth/authorize",
82
+ scopes: _scopes,
83
+ state,
84
+ codeVerifier,
85
+ redirectURI,
86
+ loginHint,
87
+ prompt: options.prompt,
88
+ });
89
+ },
90
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
91
+ const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
92
+ code,
93
+ codeVerifier,
94
+ redirectURI,
95
+ options,
96
+ });
97
+
98
+ const { data, error } = await betterFetch<
99
+ | { access_token: string; token_type: string; scope: string }
100
+ | { error: string; error_description?: string; error_uri?: string }
101
+ >(tokenEndpoint, {
102
+ method: "POST",
103
+ body: body,
104
+ headers: requestHeaders,
105
+ });
106
+
107
+ if (error) {
108
+ logger.error("GitHub OAuth token exchange failed:", error);
109
+ return null;
110
+ }
111
+
112
+ if ("error" in data) {
113
+ logger.error("GitHub OAuth token exchange failed:", data);
114
+ return null;
115
+ }
116
+
117
+ return getOAuth2Tokens(data);
118
+ },
119
+ refreshAccessToken: options.refreshAccessToken
120
+ ? options.refreshAccessToken
121
+ : async (refreshToken) => {
122
+ return refreshAccessToken({
123
+ refreshToken,
124
+ options: {
125
+ clientId: options.clientId,
126
+ clientKey: options.clientKey,
127
+ clientSecret: options.clientSecret,
128
+ },
129
+ tokenEndpoint,
130
+ });
131
+ },
132
+ async getUserInfo(token) {
133
+ if (options.getUserInfo) {
134
+ return options.getUserInfo(token);
135
+ }
136
+ const { data: profile, error } = await betterFetch<GithubProfile>(
137
+ "https://api.github.com/user",
138
+ {
139
+ headers: {
140
+ "User-Agent": "better-auth",
141
+ authorization: `Bearer ${token.accessToken}`,
142
+ },
143
+ },
144
+ );
145
+ if (error) {
146
+ return null;
147
+ }
148
+ const { data: emails } = await betterFetch<
149
+ {
150
+ email: string;
151
+ primary: boolean;
152
+ verified: boolean;
153
+ visibility: "public" | "private";
154
+ }[]
155
+ >("https://api.github.com/user/emails", {
156
+ headers: {
157
+ Authorization: `Bearer ${token.accessToken}`,
158
+ "User-Agent": "better-auth",
159
+ },
160
+ });
161
+
162
+ if (!profile.email && emails) {
163
+ profile.email = (emails.find((e) => e.primary) ?? emails[0])
164
+ ?.email as string;
165
+ }
166
+ const emailVerified =
167
+ emails?.find((e) => e.email === profile.email)?.verified ?? false;
168
+
169
+ const userMap = await options.mapProfileToUser?.(profile);
170
+ return {
171
+ user: {
172
+ id: profile.id,
173
+ name: profile.name || profile.login || "",
174
+ email: profile.email,
175
+ image: profile.avatar_url,
176
+ emailVerified,
177
+ ...userMap,
178
+ },
179
+ data: profile,
180
+ };
181
+ },
182
+ options,
183
+ } satisfies OAuthProvider<GithubProfile>;
184
+ };
@@ -0,0 +1,155 @@
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 GitlabProfile extends Record<string, any> {
10
+ id: number;
11
+ username: string;
12
+ email: string;
13
+ name: string;
14
+ state: string;
15
+ avatar_url: string;
16
+ web_url: string;
17
+ created_at: string;
18
+ bio: string;
19
+ location?: string | undefined;
20
+ public_email: string;
21
+ skype: string;
22
+ linkedin: string;
23
+ twitter: string;
24
+ website_url: string;
25
+ organization: string;
26
+ job_title: string;
27
+ pronouns: string;
28
+ bot: boolean;
29
+ work_information?: string | undefined;
30
+ followers: number;
31
+ following: number;
32
+ local_time: string;
33
+ last_sign_in_at: string;
34
+ confirmed_at: string;
35
+ theme_id: number;
36
+ last_activity_on: string;
37
+ color_scheme_id: number;
38
+ projects_limit: number;
39
+ current_sign_in_at: string;
40
+ identities: Array<{
41
+ provider: string;
42
+ extern_uid: string;
43
+ }>;
44
+ can_create_group: boolean;
45
+ can_create_project: boolean;
46
+ two_factor_enabled: boolean;
47
+ external: boolean;
48
+ private_profile: boolean;
49
+ commit_email: string;
50
+ shared_runners_minutes_limit: number;
51
+ extra_shared_runners_minutes_limit: number;
52
+ email_verified?: boolean | undefined;
53
+ }
54
+
55
+ export interface GitlabOptions extends ProviderOptions<GitlabProfile> {
56
+ clientId: string;
57
+ issuer?: string | undefined;
58
+ }
59
+
60
+ const cleanDoubleSlashes = (input: string = "") => {
61
+ return input
62
+ .split("://")
63
+ .map((str) => str.replace(/\/{2,}/g, "/"))
64
+ .join("://");
65
+ };
66
+
67
+ const issuerToEndpoints = (issuer?: string | undefined) => {
68
+ const baseUrl = issuer || "https://gitlab.com";
69
+ return {
70
+ authorizationEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/authorize`),
71
+ tokenEndpoint: cleanDoubleSlashes(`${baseUrl}/oauth/token`),
72
+ userinfoEndpoint: cleanDoubleSlashes(`${baseUrl}/api/v4/user`),
73
+ };
74
+ };
75
+
76
+ export const gitlab = (options: GitlabOptions) => {
77
+ const { authorizationEndpoint, tokenEndpoint, userinfoEndpoint } =
78
+ issuerToEndpoints(options.issuer);
79
+ const issuerId = "gitlab";
80
+ const issuerName = "Gitlab";
81
+ return {
82
+ id: issuerId,
83
+ name: issuerName,
84
+ createAuthorizationURL: async ({
85
+ state,
86
+ scopes,
87
+ codeVerifier,
88
+ loginHint,
89
+ redirectURI,
90
+ }) => {
91
+ const _scopes = options.disableDefaultScope ? [] : ["read_user"];
92
+ if (options.scope) _scopes.push(...options.scope);
93
+ if (scopes) _scopes.push(...scopes);
94
+ return await createAuthorizationURL({
95
+ id: issuerId,
96
+ options,
97
+ authorizationEndpoint,
98
+ scopes: _scopes,
99
+ state,
100
+ redirectURI,
101
+ codeVerifier,
102
+ loginHint,
103
+ });
104
+ },
105
+ validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
106
+ return validateAuthorizationCode({
107
+ code,
108
+ redirectURI,
109
+ options,
110
+ codeVerifier,
111
+ tokenEndpoint,
112
+ });
113
+ },
114
+ refreshAccessToken: options.refreshAccessToken
115
+ ? options.refreshAccessToken
116
+ : async (refreshToken) => {
117
+ return refreshAccessToken({
118
+ refreshToken,
119
+ options: {
120
+ clientId: options.clientId,
121
+ clientKey: options.clientKey,
122
+ clientSecret: options.clientSecret,
123
+ },
124
+ tokenEndpoint: tokenEndpoint,
125
+ });
126
+ },
127
+ async getUserInfo(token) {
128
+ if (options.getUserInfo) {
129
+ return options.getUserInfo(token);
130
+ }
131
+ const { data: profile, error } = await betterFetch<GitlabProfile>(
132
+ userinfoEndpoint,
133
+ { headers: { authorization: `Bearer ${token.accessToken}` } },
134
+ );
135
+ if (error || profile.state !== "active" || profile.locked) {
136
+ return null;
137
+ }
138
+ const userMap = await options.mapProfileToUser?.(profile);
139
+ // GitLab may provide email_verified claim, but it's not guaranteed.
140
+ // We check for it first, then default to false for security consistency.
141
+ return {
142
+ user: {
143
+ id: profile.id,
144
+ name: profile.name ?? profile.username ?? "",
145
+ email: profile.email,
146
+ image: profile.avatar_url,
147
+ emailVerified: profile.email_verified ?? false,
148
+ ...userMap,
149
+ },
150
+ data: profile,
151
+ };
152
+ },
153
+ options,
154
+ } satisfies OAuthProvider<GitlabProfile>;
155
+ };
@@ -0,0 +1,263 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import type { JWTPayload } from "jose";
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
+ export interface GoogleProfile {
15
+ aud: string;
16
+ azp: string;
17
+ email: string;
18
+ email_verified: boolean;
19
+ exp: number;
20
+ /**
21
+ * The family name of the user, or last name in most
22
+ * Western languages.
23
+ */
24
+ family_name: string;
25
+ /**
26
+ * The given name of the user, or first name in most
27
+ * Western languages.
28
+ */
29
+ given_name: string;
30
+ hd?: string | undefined;
31
+ iat: number;
32
+ iss: string;
33
+ jti?: string | undefined;
34
+ locale?: string | undefined;
35
+ name: string;
36
+ nbf?: number | undefined;
37
+ picture: string;
38
+ sub: string;
39
+ }
40
+
41
+ export interface GoogleOptions extends ProviderOptions<GoogleProfile> {
42
+ clientId: string | string[];
43
+ /**
44
+ * The access type to use for the authorization code request
45
+ */
46
+ accessType?: ("offline" | "online") | undefined;
47
+ /**
48
+ * The display mode to use for the authorization code request
49
+ */
50
+ display?: ("page" | "popup" | "touch" | "wap") | undefined;
51
+ /**
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
+ * Set `hd: "*"` to require any Workspace hosted-domain claim. Sign-in is
57
+ * rejected when the claim is missing or does not satisfy this restriction.
58
+ */
59
+ hd?: string | undefined;
60
+ }
61
+
62
+ const GOOGLE_ID_TOKEN_MAX_AGE = "1h";
63
+
64
+ export interface VerifyGoogleIdTokenOptions {
65
+ token: string;
66
+ audience: string | string[];
67
+ nonce?: string | undefined;
68
+ }
69
+
70
+ /**
71
+ * Verifies a Google ID token against Google's issuer, audience, signature,
72
+ * expiry, and maximum token age.
73
+ */
74
+ export const verifyGoogleIdToken = async ({
75
+ token,
76
+ audience,
77
+ nonce,
78
+ }: VerifyGoogleIdTokenOptions): Promise<JWTPayload | null> => {
79
+ try {
80
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
81
+ if (!kid || !jwtAlg) return null;
82
+
83
+ const publicKey = await getGooglePublicKey(kid);
84
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
85
+ algorithms: [jwtAlg],
86
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
87
+ audience,
88
+ maxTokenAge: GOOGLE_ID_TOKEN_MAX_AGE,
89
+ });
90
+
91
+ if (nonce && jwtClaims.nonce !== nonce) {
92
+ return null;
93
+ }
94
+
95
+ return jwtClaims;
96
+ } catch {
97
+ return null;
98
+ }
99
+ };
100
+
101
+ /**
102
+ * Checks whether Google's verified `hd` claim satisfies the configured hosted
103
+ * domain restriction. `hd: "*"` accepts any Google Workspace hosted domain.
104
+ */
105
+ export const isGoogleHostedDomainAllowed = (
106
+ configuredHostedDomain: string | undefined,
107
+ tokenHostedDomain: unknown,
108
+ ) => {
109
+ if (!configuredHostedDomain) return true;
110
+ if (typeof tokenHostedDomain !== "string" || !tokenHostedDomain) {
111
+ return false;
112
+ }
113
+ if (configuredHostedDomain === "*") return true;
114
+ return tokenHostedDomain === configuredHostedDomain;
115
+ };
116
+
117
+ export const google = (options: GoogleOptions) => {
118
+ return {
119
+ id: "google",
120
+ name: "Google",
121
+ async createAuthorizationURL({
122
+ state,
123
+ scopes,
124
+ codeVerifier,
125
+ redirectURI,
126
+ loginHint,
127
+ display,
128
+ }) {
129
+ if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
130
+ logger.error(
131
+ "Client Id and Client Secret is required for Google. Make sure to provide them in the options.",
132
+ );
133
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
134
+ }
135
+ if (!codeVerifier) {
136
+ throw new BetterAuthError("codeVerifier is required for Google");
137
+ }
138
+ const _scopes = options.disableDefaultScope
139
+ ? []
140
+ : ["email", "profile", "openid"];
141
+ if (options.scope) _scopes.push(...options.scope);
142
+ if (scopes) _scopes.push(...scopes);
143
+ const url = await createAuthorizationURL({
144
+ id: "google",
145
+ options,
146
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
147
+ scopes: _scopes,
148
+ state,
149
+ codeVerifier,
150
+ redirectURI,
151
+ prompt: options.prompt,
152
+ accessType: options.accessType,
153
+ display: display || options.display,
154
+ loginHint,
155
+ hd: options.hd,
156
+ additionalParams: {
157
+ include_granted_scopes: "true",
158
+ },
159
+ });
160
+ return url;
161
+ },
162
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
163
+ return validateAuthorizationCode({
164
+ code,
165
+ codeVerifier,
166
+ redirectURI,
167
+ options,
168
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
169
+ });
170
+ },
171
+ refreshAccessToken: options.refreshAccessToken
172
+ ? options.refreshAccessToken
173
+ : async (refreshToken) => {
174
+ return refreshAccessToken({
175
+ refreshToken,
176
+ options: {
177
+ clientId: options.clientId,
178
+ clientKey: options.clientKey,
179
+ clientSecret: options.clientSecret,
180
+ },
181
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
182
+ });
183
+ },
184
+ async verifyIdToken(token, nonce) {
185
+ if (options.disableIdTokenSignIn) {
186
+ return false;
187
+ }
188
+ if (options.verifyIdToken) {
189
+ return options.verifyIdToken(token, nonce);
190
+ }
191
+
192
+ const jwtClaims = await verifyGoogleIdToken({
193
+ token,
194
+ audience: options.clientId,
195
+ nonce,
196
+ });
197
+ if (!jwtClaims) {
198
+ return false;
199
+ }
200
+
201
+ return isGoogleHostedDomainAllowed(options.hd, jwtClaims.hd);
202
+ },
203
+ async getUserInfo(token) {
204
+ if (options.getUserInfo) {
205
+ return options.getUserInfo(token);
206
+ }
207
+ if (!token.idToken) {
208
+ return null;
209
+ }
210
+ const user = decodeJwt(token.idToken) as GoogleProfile;
211
+ // Enforce the configured hosted domain on the callback profile path.
212
+ // The authorization-time `hd` value is only a UI hint; the verified
213
+ // token/profile claim is the authoritative Workspace signal.
214
+ if (!isGoogleHostedDomainAllowed(options.hd, user.hd)) {
215
+ logger.error(
216
+ `Google sign-in rejected: id token hosted domain (hd) "${
217
+ user.hd ?? "<missing>"
218
+ }" does not satisfy the configured "hd" option "${options.hd}".`,
219
+ );
220
+ return null;
221
+ }
222
+ const userMap = await options.mapProfileToUser?.(user);
223
+ return {
224
+ user: {
225
+ id: user.sub,
226
+ name: user.name,
227
+ email: user.email,
228
+ image: user.picture,
229
+ emailVerified: user.email_verified,
230
+ ...userMap,
231
+ },
232
+ data: user,
233
+ };
234
+ },
235
+ options,
236
+ } satisfies OAuthProvider<GoogleProfile>;
237
+ };
238
+
239
+ export const getGooglePublicKey = async (kid: string) => {
240
+ const { data } = await betterFetch<{
241
+ keys: Array<{
242
+ kid: string;
243
+ alg: string;
244
+ kty: string;
245
+ use: string;
246
+ n: string;
247
+ e: string;
248
+ }>;
249
+ }>("https://www.googleapis.com/oauth2/v3/certs");
250
+
251
+ if (!data?.keys) {
252
+ throw new APIError("BAD_REQUEST", {
253
+ message: "Keys not found",
254
+ });
255
+ }
256
+
257
+ const jwk = data.keys.find((key) => key.kid === kid);
258
+ if (!jwk) {
259
+ throw new Error(`JWK with kid ${kid} not found`);
260
+ }
261
+
262
+ return await importJWK(jwk, jwk.alg);
263
+ };
@@ -0,0 +1,119 @@
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 HuggingFaceProfile {
10
+ sub: string;
11
+ name: string;
12
+ preferred_username: string;
13
+ profile: string;
14
+ picture: string;
15
+ website?: string | undefined;
16
+ email?: string | undefined;
17
+ email_verified?: boolean | undefined;
18
+ isPro: boolean;
19
+ canPay?: boolean | undefined;
20
+ orgs?:
21
+ | {
22
+ sub: string;
23
+ name: string;
24
+ picture: string;
25
+ preferred_username: string;
26
+ isEnterprise: boolean | "plus";
27
+ canPay?: boolean;
28
+ roleInOrg?: "admin" | "write" | "contributor" | "read";
29
+ pendingSSO?: boolean;
30
+ missingMFA?: boolean;
31
+ resourceGroups?: {
32
+ sub: string;
33
+ name: string;
34
+ role: "admin" | "write" | "contributor" | "read";
35
+ }[];
36
+ }
37
+ | undefined;
38
+ }
39
+
40
+ export interface HuggingFaceOptions
41
+ extends ProviderOptions<HuggingFaceProfile> {
42
+ clientId: string;
43
+ }
44
+
45
+ export const huggingface = (options: HuggingFaceOptions) => {
46
+ const tokenEndpoint = "https://huggingface.co/oauth/token";
47
+ return {
48
+ id: "huggingface",
49
+ name: "Hugging Face",
50
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
51
+ const _scopes = options.disableDefaultScope
52
+ ? []
53
+ : ["openid", "profile", "email"];
54
+ if (options.scope) _scopes.push(...options.scope);
55
+ if (scopes) _scopes.push(...scopes);
56
+ return createAuthorizationURL({
57
+ id: "huggingface",
58
+ options,
59
+ authorizationEndpoint: "https://huggingface.co/oauth/authorize",
60
+ scopes: _scopes,
61
+ state,
62
+ codeVerifier,
63
+ redirectURI,
64
+ });
65
+ },
66
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
67
+ return validateAuthorizationCode({
68
+ code,
69
+ codeVerifier,
70
+ redirectURI,
71
+ options,
72
+ tokenEndpoint,
73
+ });
74
+ },
75
+ refreshAccessToken: options.refreshAccessToken
76
+ ? options.refreshAccessToken
77
+ : async (refreshToken) => {
78
+ return refreshAccessToken({
79
+ refreshToken,
80
+ options: {
81
+ clientId: options.clientId,
82
+ clientKey: options.clientKey,
83
+ clientSecret: options.clientSecret,
84
+ },
85
+ tokenEndpoint,
86
+ });
87
+ },
88
+ async getUserInfo(token) {
89
+ if (options.getUserInfo) {
90
+ return options.getUserInfo(token);
91
+ }
92
+ const { data: profile, error } = await betterFetch<HuggingFaceProfile>(
93
+ "https://huggingface.co/oauth/userinfo",
94
+ {
95
+ method: "GET",
96
+ headers: {
97
+ Authorization: `Bearer ${token.accessToken}`,
98
+ },
99
+ },
100
+ );
101
+ if (error) {
102
+ return null;
103
+ }
104
+ const userMap = await options.mapProfileToUser?.(profile);
105
+ return {
106
+ user: {
107
+ id: profile.sub,
108
+ name: profile.name || profile.preferred_username || "",
109
+ email: profile.email,
110
+ image: profile.picture,
111
+ emailVerified: profile.email_verified ?? false,
112
+ ...userMap,
113
+ },
114
+ data: profile,
115
+ };
116
+ },
117
+ options,
118
+ } satisfies OAuthProvider<HuggingFaceProfile>;
119
+ };