@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,108 @@
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 NotionProfile {
10
+ object: "user";
11
+ id: string;
12
+ type: "person" | "bot";
13
+ name?: string | undefined;
14
+ avatar_url?: string | undefined;
15
+ person?:
16
+ | {
17
+ email?: string;
18
+ }
19
+ | undefined;
20
+ }
21
+
22
+ export interface NotionOptions extends ProviderOptions<NotionProfile> {
23
+ clientId: string;
24
+ }
25
+
26
+ export const notion = (options: NotionOptions) => {
27
+ const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
28
+ return {
29
+ id: "notion",
30
+ name: "Notion",
31
+ createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
32
+ const _scopes: string[] = options.disableDefaultScope ? [] : [];
33
+ if (options.scope) _scopes.push(...options.scope);
34
+ if (scopes) _scopes.push(...scopes);
35
+ return createAuthorizationURL({
36
+ id: "notion",
37
+ options,
38
+ authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
39
+ scopes: _scopes,
40
+ state,
41
+ redirectURI,
42
+ loginHint,
43
+ additionalParams: {
44
+ owner: "user",
45
+ },
46
+ });
47
+ },
48
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
49
+ return validateAuthorizationCode({
50
+ code,
51
+ redirectURI,
52
+ options,
53
+ tokenEndpoint,
54
+ authentication: "basic",
55
+ });
56
+ },
57
+ refreshAccessToken: options.refreshAccessToken
58
+ ? options.refreshAccessToken
59
+ : async (refreshToken) => {
60
+ return refreshAccessToken({
61
+ refreshToken,
62
+ options: {
63
+ clientId: options.clientId,
64
+ clientKey: options.clientKey,
65
+ clientSecret: options.clientSecret,
66
+ },
67
+ tokenEndpoint,
68
+ });
69
+ },
70
+ async getUserInfo(token) {
71
+ if (options.getUserInfo) {
72
+ return options.getUserInfo(token);
73
+ }
74
+ const { data: profile, error } = await betterFetch<{
75
+ bot: {
76
+ owner: {
77
+ user: NotionProfile;
78
+ };
79
+ };
80
+ }>("https://api.notion.com/v1/users/me", {
81
+ headers: {
82
+ Authorization: `Bearer ${token.accessToken}`,
83
+ "Notion-Version": "2022-06-28",
84
+ },
85
+ });
86
+ if (error || !profile) {
87
+ return null;
88
+ }
89
+ const userProfile = profile.bot?.owner?.user;
90
+ if (!userProfile) {
91
+ return null;
92
+ }
93
+ const userMap = await options.mapProfileToUser?.(userProfile);
94
+ return {
95
+ user: {
96
+ id: userProfile.id,
97
+ name: userProfile.name || "",
98
+ email: userProfile.person?.email || null,
99
+ image: userProfile.avatar_url,
100
+ emailVerified: false,
101
+ ...userMap,
102
+ },
103
+ data: userProfile,
104
+ };
105
+ },
106
+ options,
107
+ } satisfies OAuthProvider<NotionProfile>;
108
+ };
@@ -0,0 +1,118 @@
1
+ import { decodeJwt } from "jose";
2
+ import { logger } from "../env";
3
+ import { BetterAuthError } from "../error";
4
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
+ import {
6
+ createAuthorizationURL,
7
+ refreshAccessToken,
8
+ validateAuthorizationCode,
9
+ } from "../oauth2";
10
+
11
+ export interface PaybinProfile {
12
+ sub: string;
13
+ email: string;
14
+ email_verified?: boolean | undefined;
15
+ name?: string | undefined;
16
+ preferred_username?: string | undefined;
17
+ picture?: string | undefined;
18
+ given_name?: string | undefined;
19
+ family_name?: string | undefined;
20
+ }
21
+
22
+ export interface PaybinOptions extends ProviderOptions<PaybinProfile> {
23
+ clientId: string;
24
+ /**
25
+ * The issuer URL of your Paybin OAuth server
26
+ * @default "https://idp.paybin.io"
27
+ */
28
+ issuer?: string | undefined;
29
+ }
30
+
31
+ export const paybin = (options: PaybinOptions) => {
32
+ const issuer = options.issuer || "https://idp.paybin.io";
33
+ const authorizationEndpoint = `${issuer}/oauth2/authorize`;
34
+ const tokenEndpoint = `${issuer}/oauth2/token`;
35
+
36
+ return {
37
+ id: "paybin",
38
+ name: "Paybin",
39
+ async createAuthorizationURL({
40
+ state,
41
+ scopes,
42
+ codeVerifier,
43
+ redirectURI,
44
+ loginHint,
45
+ }) {
46
+ if (!options.clientId || !options.clientSecret) {
47
+ logger.error(
48
+ "Client Id and Client Secret is required for Paybin. Make sure to provide them in the options.",
49
+ );
50
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
51
+ }
52
+ if (!codeVerifier) {
53
+ throw new BetterAuthError("codeVerifier is required for Paybin");
54
+ }
55
+ const _scopes = options.disableDefaultScope
56
+ ? []
57
+ : ["openid", "email", "profile"];
58
+ if (options.scope) _scopes.push(...options.scope);
59
+ if (scopes) _scopes.push(...scopes);
60
+ const url = await createAuthorizationURL({
61
+ id: "paybin",
62
+ options,
63
+ authorizationEndpoint,
64
+ scopes: _scopes,
65
+ state,
66
+ codeVerifier,
67
+ redirectURI,
68
+ prompt: options.prompt,
69
+ loginHint,
70
+ });
71
+ return url;
72
+ },
73
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
74
+ return validateAuthorizationCode({
75
+ code,
76
+ codeVerifier,
77
+ redirectURI,
78
+ options,
79
+ tokenEndpoint,
80
+ });
81
+ },
82
+ refreshAccessToken: options.refreshAccessToken
83
+ ? options.refreshAccessToken
84
+ : async (refreshToken) => {
85
+ return refreshAccessToken({
86
+ refreshToken,
87
+ options: {
88
+ clientId: options.clientId,
89
+ clientKey: options.clientKey,
90
+ clientSecret: options.clientSecret,
91
+ },
92
+ tokenEndpoint,
93
+ });
94
+ },
95
+ async getUserInfo(token) {
96
+ if (options.getUserInfo) {
97
+ return options.getUserInfo(token);
98
+ }
99
+ if (!token.idToken) {
100
+ return null;
101
+ }
102
+ const user = decodeJwt(token.idToken) as PaybinProfile;
103
+ const userMap = await options.mapProfileToUser?.(user);
104
+ return {
105
+ user: {
106
+ id: user.sub,
107
+ name: user.name || user.preferred_username || "",
108
+ email: user.email,
109
+ image: user.picture,
110
+ emailVerified: user.email_verified || false,
111
+ ...userMap,
112
+ },
113
+ data: user,
114
+ };
115
+ },
116
+ options,
117
+ } satisfies OAuthProvider<PaybinProfile>;
118
+ };
@@ -0,0 +1,371 @@
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 { createAuthorizationURL } from "../oauth2";
8
+
9
+ /**
10
+ * ID token signing algorithms advertised by PayPal's OpenID configuration.
11
+ * Anything outside this allowlist is rejected so each token is only ever
12
+ * verified with the algorithm it was issued for.
13
+ *
14
+ * @see https://www.paypal.com/.well-known/openid-configuration
15
+ */
16
+ const PAYPAL_ID_TOKEN_ALGORITHMS = ["RS256", "HS256"] as const;
17
+
18
+ export interface PayPalProfile {
19
+ sub?: string | undefined;
20
+ user_id: string;
21
+ name: string;
22
+ given_name: string;
23
+ family_name: string;
24
+ middle_name?: string | undefined;
25
+ picture?: string | undefined;
26
+ email: string;
27
+ email_verified: boolean;
28
+ gender?: string | undefined;
29
+ birthdate?: string | undefined;
30
+ zoneinfo?: string | undefined;
31
+ locale?: string | undefined;
32
+ phone_number?: string | undefined;
33
+ address?:
34
+ | {
35
+ street_address?: string;
36
+ locality?: string;
37
+ region?: string;
38
+ postal_code?: string;
39
+ country?: string;
40
+ }
41
+ | undefined;
42
+ verified_account?: boolean | undefined;
43
+ account_type?: string | undefined;
44
+ age_range?: string | undefined;
45
+ payer_id?: string | undefined;
46
+ }
47
+
48
+ export interface PayPalTokenResponse {
49
+ scope?: string | undefined;
50
+ access_token: string;
51
+ refresh_token?: string | undefined;
52
+ token_type: "Bearer";
53
+ id_token?: string | undefined;
54
+ expires_in: number;
55
+ nonce?: string | undefined;
56
+ }
57
+
58
+ export interface PayPalOptions extends ProviderOptions<PayPalProfile> {
59
+ clientId: string;
60
+ /**
61
+ * PayPal environment - 'sandbox' for testing, 'live' for production
62
+ * @default 'sandbox'
63
+ */
64
+ environment?: ("sandbox" | "live") | undefined;
65
+ /**
66
+ * Whether to request shipping address information
67
+ * @default false
68
+ */
69
+ requestShippingAddress?: boolean | undefined;
70
+ }
71
+
72
+ export const paypal = (options: PayPalOptions) => {
73
+ const environment = options.environment || "sandbox";
74
+ const isSandbox = environment === "sandbox";
75
+
76
+ const authorizationEndpoint = isSandbox
77
+ ? "https://www.sandbox.paypal.com/signin/authorize"
78
+ : "https://www.paypal.com/signin/authorize";
79
+
80
+ const tokenEndpoint = isSandbox
81
+ ? "https://api-m.sandbox.paypal.com/v1/oauth2/token"
82
+ : "https://api-m.paypal.com/v1/oauth2/token";
83
+
84
+ const userInfoEndpoint = isSandbox
85
+ ? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo"
86
+ : "https://api-m.paypal.com/v1/identity/oauth2/userinfo";
87
+
88
+ /**
89
+ * Issuer and JWKS endpoints used to cryptographically verify ID tokens.
90
+ *
91
+ * @see https://www.paypal.com/.well-known/openid-configuration
92
+ */
93
+ const issuer = isSandbox
94
+ ? "https://www.sandbox.paypal.com"
95
+ : "https://www.paypal.com";
96
+
97
+ const jwksEndpoint = isSandbox
98
+ ? "https://api.sandbox.paypal.com/v1/oauth2/certs"
99
+ : "https://api.paypal.com/v1/oauth2/certs";
100
+
101
+ return {
102
+ id: "paypal",
103
+ name: "PayPal",
104
+ async createAuthorizationURL({ state, codeVerifier, redirectURI }) {
105
+ if (!options.clientId || !options.clientSecret) {
106
+ logger.error(
107
+ "Client Id and Client Secret is required for PayPal. Make sure to provide them in the options.",
108
+ );
109
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
110
+ }
111
+
112
+ /**
113
+ * Log in with PayPal doesn't use traditional OAuth2 scopes
114
+ * Instead, permissions are configured in the PayPal Developer Dashboard
115
+ * We don't pass any scopes to avoid "invalid scope" errors
116
+ **/
117
+
118
+ const _scopes: string[] = [];
119
+
120
+ const url = await createAuthorizationURL({
121
+ id: "paypal",
122
+ options,
123
+ authorizationEndpoint,
124
+ scopes: _scopes,
125
+ state,
126
+ codeVerifier,
127
+ redirectURI,
128
+ prompt: options.prompt,
129
+ });
130
+ return url;
131
+ },
132
+
133
+ validateAuthorizationCode: async ({ code, redirectURI }) => {
134
+ /**
135
+ * PayPal requires Basic Auth for token exchange
136
+ **/
137
+
138
+ const credentials = base64.encode(
139
+ `${options.clientId}:${options.clientSecret}`,
140
+ );
141
+
142
+ try {
143
+ const response = await betterFetch(tokenEndpoint, {
144
+ method: "POST",
145
+ headers: {
146
+ Authorization: `Basic ${credentials}`,
147
+ Accept: "application/json",
148
+ "Accept-Language": "en_US",
149
+ "Content-Type": "application/x-www-form-urlencoded",
150
+ },
151
+ body: new URLSearchParams({
152
+ grant_type: "authorization_code",
153
+ code: code,
154
+ redirect_uri: redirectURI,
155
+ }).toString(),
156
+ });
157
+
158
+ if (!response.data) {
159
+ throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
160
+ }
161
+
162
+ const data = response.data as PayPalTokenResponse;
163
+
164
+ const result = {
165
+ accessToken: data.access_token,
166
+ refreshToken: data.refresh_token,
167
+ accessTokenExpiresAt: data.expires_in
168
+ ? new Date(Date.now() + data.expires_in * 1000)
169
+ : undefined,
170
+ idToken: data.id_token,
171
+ };
172
+
173
+ return result;
174
+ } catch (error) {
175
+ logger.error("PayPal token exchange failed:", error);
176
+ throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
177
+ }
178
+ },
179
+
180
+ refreshAccessToken: options.refreshAccessToken
181
+ ? options.refreshAccessToken
182
+ : async (refreshToken) => {
183
+ const credentials = base64.encode(
184
+ `${options.clientId}:${options.clientSecret}`,
185
+ );
186
+
187
+ try {
188
+ const response = await betterFetch(tokenEndpoint, {
189
+ method: "POST",
190
+ headers: {
191
+ Authorization: `Basic ${credentials}`,
192
+ Accept: "application/json",
193
+ "Accept-Language": "en_US",
194
+ "Content-Type": "application/x-www-form-urlencoded",
195
+ },
196
+ body: new URLSearchParams({
197
+ grant_type: "refresh_token",
198
+ refresh_token: refreshToken,
199
+ }).toString(),
200
+ });
201
+
202
+ if (!response.data) {
203
+ throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
204
+ }
205
+
206
+ const data = response.data as any;
207
+ return {
208
+ accessToken: data.access_token,
209
+ refreshToken: data.refresh_token,
210
+ accessTokenExpiresAt: data.expires_in
211
+ ? new Date(Date.now() + data.expires_in * 1000)
212
+ : undefined,
213
+ };
214
+ } catch (error) {
215
+ logger.error("PayPal token refresh failed:", error);
216
+ throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
217
+ }
218
+ },
219
+
220
+ async verifyIdToken(token, nonce) {
221
+ if (options.disableIdTokenSignIn) {
222
+ return false;
223
+ }
224
+ if (options.verifyIdToken) {
225
+ return options.verifyIdToken(token, nonce);
226
+ }
227
+
228
+ // Cryptographically verify the ID token. Decoding alone is not enough:
229
+ // the signature, issuer, audience and expiration must all be checked
230
+ // before the token's claims can be relied on as proof of identity.
231
+ // See https://www.paypal.com/.well-known/openid-configuration
232
+
233
+ try {
234
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
235
+ if (!jwtAlg) return false;
236
+ if (
237
+ !PAYPAL_ID_TOKEN_ALGORITHMS.includes(
238
+ jwtAlg as (typeof PAYPAL_ID_TOKEN_ALGORITHMS)[number],
239
+ )
240
+ ) {
241
+ return false;
242
+ }
243
+
244
+ // PayPal can sign ID tokens either asymmetrically (RS256, verified
245
+ // against the published JWKS) or symmetrically (HS256, verified with
246
+ // the client secret). Selecting the key by algorithm keeps the two
247
+ // paths separate so each algorithm is only verified with its
248
+ // corresponding key type.
249
+ const key =
250
+ jwtAlg === "HS256"
251
+ ? new TextEncoder().encode(options.clientSecret)
252
+ : kid
253
+ ? await getPayPalPublicKey(kid, jwksEndpoint)
254
+ : undefined;
255
+ if (!key) return false;
256
+
257
+ const { payload: jwtClaims } = await jwtVerify(token, key, {
258
+ algorithms: [jwtAlg],
259
+ issuer,
260
+ audience: options.clientId,
261
+ maxTokenAge: "1h",
262
+ });
263
+
264
+ if (nonce && jwtClaims.nonce !== nonce) {
265
+ return false;
266
+ }
267
+
268
+ return true;
269
+ } catch (error) {
270
+ logger.error("Failed to verify PayPal ID token:", error);
271
+ return false;
272
+ }
273
+ },
274
+
275
+ async getUserInfo(token) {
276
+ if (options.getUserInfo) {
277
+ return options.getUserInfo(token);
278
+ }
279
+
280
+ if (!token.accessToken) {
281
+ logger.error("Access token is required to fetch PayPal user info");
282
+ return null;
283
+ }
284
+
285
+ try {
286
+ const response = await betterFetch<PayPalProfile>(
287
+ `${userInfoEndpoint}?schema=paypalv1.1`,
288
+ {
289
+ headers: {
290
+ Authorization: `Bearer ${token.accessToken}`,
291
+ Accept: "application/json",
292
+ },
293
+ },
294
+ );
295
+
296
+ if (!response.data) {
297
+ logger.error("Failed to fetch user info from PayPal");
298
+ return null;
299
+ }
300
+
301
+ const userInfo = response.data;
302
+ if (token.idToken) {
303
+ let idTokenSubject: string | undefined;
304
+ try {
305
+ idTokenSubject = decodeJwt(token.idToken).sub;
306
+ } catch (error) {
307
+ logger.error("Failed to decode PayPal ID token:", error);
308
+ return null;
309
+ }
310
+
311
+ // OIDC binds UserInfo to the ID Token with `sub`. Keep `user_id`
312
+ // as the account id below for existing PayPal account mappings.
313
+ const userInfoSubject = userInfo.sub ?? userInfo.user_id;
314
+ if (!idTokenSubject || userInfoSubject !== idTokenSubject) {
315
+ logger.error(
316
+ "PayPal user info subject does not match ID token subject",
317
+ );
318
+ return null;
319
+ }
320
+ }
321
+
322
+ const userMap = await options.mapProfileToUser?.(userInfo);
323
+
324
+ const result = {
325
+ user: {
326
+ id: userInfo.user_id,
327
+ name: userInfo.name,
328
+ email: userInfo.email,
329
+ image: userInfo.picture,
330
+ emailVerified: userInfo.email_verified,
331
+ ...userMap,
332
+ },
333
+ data: userInfo,
334
+ };
335
+
336
+ return result;
337
+ } catch (error) {
338
+ logger.error("Failed to fetch user info from PayPal:", error);
339
+ return null;
340
+ }
341
+ },
342
+
343
+ options,
344
+ } satisfies OAuthProvider<PayPalProfile>;
345
+ };
346
+
347
+ export const getPayPalPublicKey = async (kid: string, jwksUri: string) => {
348
+ const { data } = await betterFetch<{
349
+ keys: Array<{
350
+ kid: string;
351
+ alg: string;
352
+ kty: string;
353
+ use: string;
354
+ n: string;
355
+ e: string;
356
+ }>;
357
+ }>(jwksUri);
358
+
359
+ if (!data?.keys) {
360
+ throw new APIError("BAD_REQUEST", {
361
+ message: "Keys not found",
362
+ });
363
+ }
364
+
365
+ const jwk = data.keys.find((key) => key.kid === kid);
366
+ if (!jwk) {
367
+ throw new Error(`JWK with kid ${kid} not found`);
368
+ }
369
+
370
+ return await importJWK(jwk, jwk.alg);
371
+ };
@@ -0,0 +1,111 @@
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 PolarProfile {
10
+ id: string;
11
+ email: string;
12
+ username: string;
13
+ avatar_url: string;
14
+ github_username?: string | undefined;
15
+ account_id?: string | undefined;
16
+ public_name?: string | undefined;
17
+ email_verified?: boolean | undefined;
18
+ profile_settings?:
19
+ | {
20
+ profile_settings_enabled?: boolean;
21
+ profile_settings_public_name?: string;
22
+ profile_settings_public_avatar?: string;
23
+ profile_settings_public_bio?: string;
24
+ profile_settings_public_location?: string;
25
+ profile_settings_public_website?: string;
26
+ profile_settings_public_twitter?: string;
27
+ profile_settings_public_github?: string;
28
+ profile_settings_public_email?: string;
29
+ }
30
+ | undefined;
31
+ }
32
+
33
+ export interface PolarOptions extends ProviderOptions<PolarProfile> {}
34
+
35
+ export const polar = (options: PolarOptions) => {
36
+ const tokenEndpoint = "https://api.polar.sh/v1/oauth2/token";
37
+ return {
38
+ id: "polar",
39
+ name: "Polar",
40
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
41
+ const _scopes = options.disableDefaultScope
42
+ ? []
43
+ : ["openid", "profile", "email"];
44
+ if (options.scope) _scopes.push(...options.scope);
45
+ if (scopes) _scopes.push(...scopes);
46
+ return createAuthorizationURL({
47
+ id: "polar",
48
+ options,
49
+ authorizationEndpoint: "https://polar.sh/oauth2/authorize",
50
+ scopes: _scopes,
51
+ state,
52
+ codeVerifier,
53
+ redirectURI,
54
+ prompt: options.prompt,
55
+ });
56
+ },
57
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
58
+ return validateAuthorizationCode({
59
+ code,
60
+ codeVerifier,
61
+ redirectURI,
62
+ options,
63
+ tokenEndpoint,
64
+ });
65
+ },
66
+ refreshAccessToken: options.refreshAccessToken
67
+ ? options.refreshAccessToken
68
+ : async (refreshToken) => {
69
+ return refreshAccessToken({
70
+ refreshToken,
71
+ options: {
72
+ clientId: options.clientId,
73
+ clientKey: options.clientKey,
74
+ clientSecret: options.clientSecret,
75
+ },
76
+ tokenEndpoint,
77
+ });
78
+ },
79
+ async getUserInfo(token) {
80
+ if (options.getUserInfo) {
81
+ return options.getUserInfo(token);
82
+ }
83
+ const { data: profile, error } = await betterFetch<PolarProfile>(
84
+ "https://api.polar.sh/v1/oauth2/userinfo",
85
+ {
86
+ headers: {
87
+ Authorization: `Bearer ${token.accessToken}`,
88
+ },
89
+ },
90
+ );
91
+ if (error) {
92
+ return null;
93
+ }
94
+ const userMap = await options.mapProfileToUser?.(profile);
95
+ // Polar may provide email_verified claim, but it's not guaranteed.
96
+ // We check for it first, then default to false for security consistency.
97
+ return {
98
+ user: {
99
+ id: profile.id,
100
+ name: profile.public_name || profile.username || "",
101
+ email: profile.email,
102
+ image: profile.avatar_url,
103
+ emailVerified: profile.email_verified ?? false,
104
+ ...userMap,
105
+ },
106
+ data: profile,
107
+ };
108
+ },
109
+ options,
110
+ } satisfies OAuthProvider<PolarProfile>;
111
+ };