faire-auth 1.0.0

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 (221) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +34 -0
  3. package/dist/access-B5bBI4uT.mjs +39 -0
  4. package/dist/access-Crt-IJ2P.cjs +51 -0
  5. package/dist/adapter-factory-BXei1QRX.mjs +881 -0
  6. package/dist/adapter-factory-DT-_2aHJ.cjs +893 -0
  7. package/dist/adapters/drizzle-adapter/index.cjs +208 -0
  8. package/dist/adapters/drizzle-adapter/index.d.cts +48 -0
  9. package/dist/adapters/drizzle-adapter/index.d.mts +48 -0
  10. package/dist/adapters/drizzle-adapter/index.mjs +207 -0
  11. package/dist/adapters/index.cjs +23 -0
  12. package/dist/adapters/index.d.cts +157 -0
  13. package/dist/adapters/index.d.mts +157 -0
  14. package/dist/adapters/index.mjs +17 -0
  15. package/dist/adapters/kysely-adapter/index.cjs +11 -0
  16. package/dist/adapters/kysely-adapter/index.d.cts +52 -0
  17. package/dist/adapters/kysely-adapter/index.d.mts +52 -0
  18. package/dist/adapters/kysely-adapter/index.mjs +9 -0
  19. package/dist/adapters/memory-adapter/index.cjs +9 -0
  20. package/dist/adapters/memory-adapter/index.d.cts +15 -0
  21. package/dist/adapters/memory-adapter/index.d.mts +15 -0
  22. package/dist/adapters/memory-adapter/index.mjs +9 -0
  23. package/dist/adapters/mongodb-adapter/index.cjs +9 -0
  24. package/dist/adapters/mongodb-adapter/index.d.cts +4 -0
  25. package/dist/adapters/mongodb-adapter/index.d.mts +4 -0
  26. package/dist/adapters/mongodb-adapter/index.mjs +9 -0
  27. package/dist/adapters/prisma-adapter/index.cjs +156 -0
  28. package/dist/adapters/prisma-adapter/index.d.cts +35 -0
  29. package/dist/adapters/prisma-adapter/index.d.mts +35 -0
  30. package/dist/adapters/prisma-adapter/index.mjs +155 -0
  31. package/dist/adapters/test.cjs +797 -0
  32. package/dist/adapters/test.d.cts +82 -0
  33. package/dist/adapters/test.d.mts +82 -0
  34. package/dist/adapters/test.mjs +793 -0
  35. package/dist/api/index.cjs +20 -0
  36. package/dist/api/index.d.cts +3 -0
  37. package/dist/api/index.d.mts +3 -0
  38. package/dist/api/index.mjs +20 -0
  39. package/dist/api/middleware/index.cjs +16 -0
  40. package/dist/api/middleware/index.d.cts +16 -0
  41. package/dist/api/middleware/index.d.mts +16 -0
  42. package/dist/api/middleware/index.mjs +11 -0
  43. package/dist/api-Bm89BLgN.cjs +1899 -0
  44. package/dist/api-_B7qO28y.mjs +1892 -0
  45. package/dist/auth-Bo9PDt5j.cjs +739 -0
  46. package/dist/auth-Ds4AG4h4.mjs +709 -0
  47. package/dist/bearer-0SeEmaS4.mjs +54 -0
  48. package/dist/bearer-CHeREcGT.cjs +60 -0
  49. package/dist/bun-sqlite-dialect-CfkBo2v5.mjs +155 -0
  50. package/dist/bun-sqlite-dialect-wuaC2-gh.cjs +156 -0
  51. package/dist/chunk-Ceigfy4P.mjs +39 -0
  52. package/dist/chunk-oakRccI5.cjs +60 -0
  53. package/dist/client/index.cjs +22 -0
  54. package/dist/client/index.d.cts +8 -0
  55. package/dist/client/index.d.mts +8 -0
  56. package/dist/client/index.mjs +19 -0
  57. package/dist/client/lynx/index.cjs +79 -0
  58. package/dist/client/lynx/index.d.cts +156 -0
  59. package/dist/client/lynx/index.d.mts +156 -0
  60. package/dist/client/lynx/index.mjs +76 -0
  61. package/dist/client/plugins/index.cjs +11 -0
  62. package/dist/client/plugins/index.d.cts +17 -0
  63. package/dist/client/plugins/index.d.mts +17 -0
  64. package/dist/client/plugins/index.mjs +10 -0
  65. package/dist/client/react/index.cjs +79 -0
  66. package/dist/client/react/index.d.cts +156 -0
  67. package/dist/client/react/index.d.mts +156 -0
  68. package/dist/client/react/index.mjs +76 -0
  69. package/dist/client/solid/index.cjs +51 -0
  70. package/dist/client/solid/index.d.cts +90 -0
  71. package/dist/client/solid/index.d.mts +90 -0
  72. package/dist/client/solid/index.mjs +50 -0
  73. package/dist/client/svelte/index.cjs +28 -0
  74. package/dist/client/svelte/index.d.cts +110 -0
  75. package/dist/client/svelte/index.d.mts +110 -0
  76. package/dist/client/svelte/index.mjs +28 -0
  77. package/dist/client/vue/index.cjs +68 -0
  78. package/dist/client/vue/index.d.cts +123 -0
  79. package/dist/client/vue/index.d.mts +123 -0
  80. package/dist/client/vue/index.mjs +67 -0
  81. package/dist/cookies-B5xeA6i8.cjs +456 -0
  82. package/dist/cookies-Ch1JfQIz.mjs +367 -0
  83. package/dist/crypto/index.cjs +11 -0
  84. package/dist/crypto/index.d.cts +22 -0
  85. package/dist/crypto/index.d.mts +22 -0
  86. package/dist/crypto/index.mjs +5 -0
  87. package/dist/crypto-CcTxYH82.cjs +57 -0
  88. package/dist/crypto-CyQRFtkq.mjs +41 -0
  89. package/dist/db/index.cjs +40 -0
  90. package/dist/db/index.d.cts +4 -0
  91. package/dist/db/index.d.mts +4 -0
  92. package/dist/db/index.mjs +16 -0
  93. package/dist/db-BTKyYu-_.cjs +1180 -0
  94. package/dist/db-xiJtoDgz.mjs +1119 -0
  95. package/dist/defu-B0Svx_x4.cjs +34 -0
  96. package/dist/defu-SZkpHhbR.mjs +28 -0
  97. package/dist/email-verification-C-AsfJsb.cjs +247 -0
  98. package/dist/email-verification-CWhj2qfb.mjs +228 -0
  99. package/dist/hono-BEq1NYVX.cjs +526 -0
  100. package/dist/hono-Cdukwu5K.cjs +13 -0
  101. package/dist/hono-CoKYWnXA.mjs +7 -0
  102. package/dist/hono-Covz10jU.mjs +508 -0
  103. package/dist/hono-DOM31WQz.d.cts +18 -0
  104. package/dist/hono-DuE14mmj.d.mts +18 -0
  105. package/dist/index-B-WeOMzg.d.cts +13350 -0
  106. package/dist/index-B09P9p5_.d.cts +33 -0
  107. package/dist/index-B5_0QTLg.d.mts +3 -0
  108. package/dist/index-B7SKyR_a.d.cts +3 -0
  109. package/dist/index-BVHscLQl.d.cts +992 -0
  110. package/dist/index-C2-2QDyS.d.cts +35 -0
  111. package/dist/index-CTwrVluH.d.mts +41 -0
  112. package/dist/index-CWAqWfoZ.d.cts +27 -0
  113. package/dist/index-CYSShrFx.d.mts +27 -0
  114. package/dist/index-Qu-iJGNr.d.mts +992 -0
  115. package/dist/index-cs4Sh0En.d.cts +41 -0
  116. package/dist/index-jlessPI0.d.mts +13350 -0
  117. package/dist/index-ppgcFe12.d.mts +33 -0
  118. package/dist/index-to0RO38U.d.mts +35 -0
  119. package/dist/index.cjs +85 -0
  120. package/dist/index.d.cts +5 -0
  121. package/dist/index.d.mts +5 -0
  122. package/dist/index.mjs +27 -0
  123. package/dist/integrations/next-js.cjs +50 -0
  124. package/dist/integrations/next-js.d.cts +23 -0
  125. package/dist/integrations/next-js.d.mts +23 -0
  126. package/dist/integrations/next-js.mjs +49 -0
  127. package/dist/integrations/node.cjs +139 -0
  128. package/dist/integrations/node.d.cts +22 -0
  129. package/dist/integrations/node.d.mts +22 -0
  130. package/dist/integrations/node.mjs +134 -0
  131. package/dist/integrations/react-start.cjs +37 -0
  132. package/dist/integrations/react-start.d.cts +17 -0
  133. package/dist/integrations/react-start.d.mts +17 -0
  134. package/dist/integrations/react-start.mjs +37 -0
  135. package/dist/integrations/solid-start.cjs +14 -0
  136. package/dist/integrations/solid-start.d.cts +13 -0
  137. package/dist/integrations/solid-start.d.mts +13 -0
  138. package/dist/integrations/solid-start.mjs +13 -0
  139. package/dist/integrations/svelte-kit.cjs +52 -0
  140. package/dist/integrations/svelte-kit.d.cts +42 -0
  141. package/dist/integrations/svelte-kit.d.mts +42 -0
  142. package/dist/integrations/svelte-kit.mjs +49 -0
  143. package/dist/ip-B5SwXaMB.cjs +54 -0
  144. package/dist/ip-BjNYspCH.mjs +36 -0
  145. package/dist/json-B1v2qRTQ.mjs +24 -0
  146. package/dist/json-CcJ2HjGo.cjs +30 -0
  147. package/dist/kysely-adapter-Bxi7sPdG.cjs +288 -0
  148. package/dist/kysely-adapter-DRuoT_X8.mjs +270 -0
  149. package/dist/logged-CyGfp8_H.mjs +187 -0
  150. package/dist/logged-DQK3C64b.cjs +193 -0
  151. package/dist/memory-adapter-BlnDIAAX.cjs +154 -0
  152. package/dist/memory-adapter-Db3tsJTn.mjs +148 -0
  153. package/dist/middleware-CE6OhDkq.mjs +29 -0
  154. package/dist/middleware-CWvRH4EW.cjs +47 -0
  155. package/dist/mongodb-adapter-C_qGQED5.mjs +291 -0
  156. package/dist/mongodb-adapter-C_qfK87x.cjs +297 -0
  157. package/dist/node-sqlite-dialect-BfBzyOAg.mjs +155 -0
  158. package/dist/node-sqlite-dialect-mJWC8LWQ.cjs +156 -0
  159. package/dist/oauth2/index.cjs +25 -0
  160. package/dist/oauth2/index.d.cts +42 -0
  161. package/dist/oauth2/index.d.mts +42 -0
  162. package/dist/oauth2/index.mjs +16 -0
  163. package/dist/oauth2-B8M0lW9l.mjs +159 -0
  164. package/dist/oauth2-BLeOEi7Y.cjs +177 -0
  165. package/dist/origin-check-8Gajklvh.mjs +228 -0
  166. package/dist/origin-check-id9lTnqZ.cjs +246 -0
  167. package/dist/parser-CdncAl9i.cjs +83 -0
  168. package/dist/parser-CeAer-RM.mjs +71 -0
  169. package/dist/passkey-B6N20LSS.cjs +512 -0
  170. package/dist/passkey-DJsR_GX3.mjs +505 -0
  171. package/dist/plugins/access/index.cjs +4 -0
  172. package/dist/plugins/access/index.d.cts +2 -0
  173. package/dist/plugins/access/index.d.mts +2 -0
  174. package/dist/plugins/access/index.mjs +3 -0
  175. package/dist/plugins/bearer/index.cjs +7 -0
  176. package/dist/plugins/bearer/index.d.cts +4 -0
  177. package/dist/plugins/bearer/index.d.mts +4 -0
  178. package/dist/plugins/bearer/index.mjs +7 -0
  179. package/dist/plugins/index.cjs +49 -0
  180. package/dist/plugins/index.d.cts +9326 -0
  181. package/dist/plugins/index.d.mts +9326 -0
  182. package/dist/plugins/index.mjs +26 -0
  183. package/dist/plugins/passkey/index.cjs +15 -0
  184. package/dist/plugins/passkey/index.d.cts +4 -0
  185. package/dist/plugins/passkey/index.d.mts +4 -0
  186. package/dist/plugins/passkey/index.mjs +15 -0
  187. package/dist/plugins-C1lm2Wj3.cjs +5254 -0
  188. package/dist/plugins-Dai1QkRU.mjs +5199 -0
  189. package/dist/request-1NL_1lyJ.mjs +58 -0
  190. package/dist/request-CPNlp-DK.cjs +100 -0
  191. package/dist/schema-D-qjygfE.cjs +179 -0
  192. package/dist/schema-DTIwLLmf.mjs +125 -0
  193. package/dist/session-De9POJTW.cjs +641 -0
  194. package/dist/session-rH8ougDl.mjs +538 -0
  195. package/dist/social-providers/index.cjs +10 -0
  196. package/dist/social-providers/index.d.cts +2 -0
  197. package/dist/social-providers/index.d.mts +2 -0
  198. package/dist/social-providers/index.mjs +5 -0
  199. package/dist/social-providers-DcFNUT0S.cjs +13 -0
  200. package/dist/social-providers-DoYJhBe3.mjs +9 -0
  201. package/dist/state--f7fwIGh.cjs +99 -0
  202. package/dist/state-CqJfCIl9.mjs +86 -0
  203. package/dist/test-utils/index.cjs +27492 -0
  204. package/dist/test-utils/index.d.cts +4584 -0
  205. package/dist/test-utils/index.d.mts +4584 -0
  206. package/dist/test-utils/index.mjs +27490 -0
  207. package/dist/types/index.cjs +0 -0
  208. package/dist/types/index.d.cts +3 -0
  209. package/dist/types/index.d.mts +3 -0
  210. package/dist/types/index.mjs +1 -0
  211. package/dist/url-BoUuoIwZ.mjs +88 -0
  212. package/dist/url-D2yvKaM7.cjs +118 -0
  213. package/dist/utils/cookies.cjs +51 -0
  214. package/dist/utils/cookies.d.cts +179 -0
  215. package/dist/utils/cookies.d.mts +179 -0
  216. package/dist/utils/cookies.mjs +7 -0
  217. package/dist/utils-44aDDP8f.cjs +2 -0
  218. package/dist/utils-D4vZd1-9.mjs +4 -0
  219. package/dist/vanilla-Bu2PmFzE.cjs +30 -0
  220. package/dist/vanilla-TfHqeiC7.mjs +25 -0
  221. package/package.json +470 -0
@@ -0,0 +1,1892 @@
1
+ import { t as contextStorage } from "./hono-CoKYWnXA.mjs";
2
+ import { r as runWithTransaction, t as getIp } from "./ip-BjNYspCH.mjs";
3
+ import { n as crypto_exports } from "./crypto-CyQRFtkq.mjs";
4
+ import { t as safeJSONParse } from "./json-B1v2qRTQ.mjs";
5
+ import { n as cookies_exports, o as deleteSessionCookie, y as setSessionCookie } from "./cookies-Ch1JfQIz.mjs";
6
+ import { r as createMiddleware } from "./middleware-CE6OhDkq.mjs";
7
+ import { _ as initContextMiddleware, a as requestOnlySessionMiddleware, c as revokeSessions, d as createAPI, f as createEndpoint, g as initHooksMiddleware, h as initErrorHandler, i as listSessions, l as sensitiveSessionMiddleware, m as setDefaultExecutionCtx, n as getSession, o as revokeOtherSessions, p as setRenderer, s as revokeSession, t as freshSessionMiddleware, u as sessionMiddleware } from "./session-rH8ougDl.mjs";
8
+ import { n as originCheck, r as wildcardMatch, t as initOriginCheckMiddleware } from "./origin-check-8Gajklvh.mjs";
9
+ import { n as sendVerificationEmail, r as verifyEmail, t as createEmailVerificationToken } from "./email-verification-CWhj2qfb.mjs";
10
+ import { n as decryptOAuthToken, r as setTokenUtil, t as handleOAuthUserInfo } from "./oauth2-B8M0lW9l.mjs";
11
+ import { n as parseState, t as generateState } from "./state-CqJfCIl9.mjs";
12
+ import { t as social_providers_exports } from "./social-providers-DoYJhBe3.mjs";
13
+ import { r as generateId } from "./utils-D4vZd1-9.mjs";
14
+ import { c as parseUserInput } from "./schema-DTIwLLmf.mjs";
15
+ import { isDevelopment } from "@faire-auth/core/env";
16
+ import { BASE_ERROR_CODES } from "@faire-auth/core/error";
17
+ import { OpenAPIHono, callbackURLSchema, createRoute, emailSchema, nameSchema, passwordSchema, req, res } from "@faire-auth/core/factory";
18
+ import { Definitions, False, Routes, SCHEMAS, True, isPromise } from "@faire-auth/core/static";
19
+ import { buildSchemas, getDate } from "@faire-auth/core/utils";
20
+ import { Hono } from "hono";
21
+ import { RegExpRouter } from "hono/router/reg-exp-router";
22
+ import * as z$1 from "zod";
23
+ import { looseUserSchema } from "@faire-auth/core/db";
24
+
25
+ //#region src/api/schema/account.ts
26
+ const socialProviderListEnum = z$1.enum(social_providers_exports.socialProviderList).or(z$1.string());
27
+ const linkAccountSchema = z$1.object({
28
+ callbackURL: callbackURLSchema(true).openapi({ description: "The URL to redirect to after the user has signed in" }),
29
+ provider: socialProviderListEnum,
30
+ idToken: z$1.object({
31
+ token: z$1.string(),
32
+ nonce: z$1.string().optional(),
33
+ accessToken: z$1.string().optional(),
34
+ refreshToken: z$1.string().optional(),
35
+ scopes: z$1.array(z$1.string().min(1)).optional()
36
+ }).optional(),
37
+ scopes: z$1.array(z$1.string().min(1)).optional().openapi({ description: "Additional scopes to request when linking the account" }),
38
+ errorCallbackURL: callbackURLSchema(true).openapi({ description: "The URL to redirect to if there is an error during the link process" })
39
+ });
40
+ const unlinkAccountSchema = z$1.object({
41
+ providerId: z$1.string(),
42
+ accountId: z$1.string().optional()
43
+ });
44
+ const getAccessTokenSchema = z$1.object({
45
+ providerId: z$1.string().openapi({ description: "The provider ID for the OAuth provider" }),
46
+ accountId: z$1.string().optional().openapi({ description: "The account ID associated with the refresh token" }),
47
+ userId: z$1.string().optional().openapi({ description: "The user ID associated with the account" })
48
+ });
49
+ const refreshAccessTokenSchema = z$1.object({
50
+ providerId: z$1.string().openapi({ description: "The provider ID for the OAuth provider" }),
51
+ accountId: z$1.string().optional().openapi({ description: "The account ID associated with the refresh token" }),
52
+ userId: z$1.string().optional().openapi({ description: "The user ID associated with the account" })
53
+ });
54
+ const getAccountInfoSchema = z$1.object({ accountId: z$1.string().openapi({ description: "The provider given account id for which to get the account info" }) });
55
+
56
+ //#endregion
57
+ //#region src/api/schema/sign-in.ts
58
+ const signInSocialSchema = z$1.object({
59
+ callbackURL: callbackURLSchema(true).openapi({ description: "Callback URL to redirect to after the user has signed in" }),
60
+ newUserCallbackURL: callbackURLSchema(true),
61
+ errorCallbackURL: callbackURLSchema(true).openapi({ description: "Callback URL to redirect to if an error happens" }),
62
+ provider: socialProviderListEnum,
63
+ disableRedirect: z$1.boolean().optional().openapi({ description: "Disable automatic redirection to the provider. Useful for handling the redirection yourself" }),
64
+ idToken: z$1.optional(z$1.object({
65
+ token: z$1.string().openapi({ description: "ID token from the provider" }),
66
+ nonce: z$1.string().optional().openapi({ description: "Nonce used to generate the token" }),
67
+ accessToken: z$1.string().optional().openapi({ description: "Access token from the provider" }),
68
+ refreshToken: z$1.string().optional().openapi({ description: "Refresh token from the provider" }),
69
+ expiresAt: z$1.number().optional().openapi({ description: "Expiry date of the token" })
70
+ })).openapi({ description: "ID token from the provider to sign in the user with id token" }),
71
+ scopes: z$1.array(z$1.string().min(1)).optional().openapi({ description: "Array of scopes to request from the provider. This will override the default scopes passed." }),
72
+ requestSignUp: z$1.boolean().optional().openapi({ description: "Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider" }),
73
+ loginHint: z$1.string().optional().openapi({ description: "The login hint to use for the authorization code request" })
74
+ });
75
+ const signInEmailSchema = z$1.object({
76
+ email: emailSchema.openapi({ description: "Email of the user" }),
77
+ password: passwordSchema.openapi({ description: "Password of the user" }),
78
+ callbackURL: callbackURLSchema(true).openapi({ description: "Callback URL to use as a redirect for email verification" }),
79
+ rememberMe: z$1.boolean().default(true).openapi({ description: "If this is false, the session will not be remembered. Default is `true`." })
80
+ });
81
+
82
+ //#endregion
83
+ //#region src/api/routes/sign-in.ts
84
+ const signInSocialRoute = createRoute({
85
+ operationId: Routes.SIGN_IN_SOCIAL,
86
+ method: "post",
87
+ path: "/sign-in/social",
88
+ description: "Sign in with a social provider",
89
+ request: req().bdy(signInSocialSchema).bld(),
90
+ responses: res(SCHEMAS[Definitions.SIGN_IN_UP].default, "Returns either session details or redirect URL").rdr().err(401, "Invalid token or user not found").err(404, "Provider or id token not supported").zod().bld()
91
+ });
92
+ const signInSocial = createEndpoint(signInSocialRoute, (options) => async (ctx) => {
93
+ const { provider: requestedProvider, idToken, requestSignUp, callbackURL, scopes, loginHint, disableRedirect } = ctx.req.valid("json");
94
+ const context = ctx.get("context");
95
+ const provider = context.socialProviders.find((p) => p.id === requestedProvider);
96
+ if (!provider) {
97
+ context.logger.error("Provider not found. Make sure to add the provider in your auth config", { provider: requestedProvider });
98
+ return ctx.render({
99
+ success: False,
100
+ message: BASE_ERROR_CODES.PROVIDER_NOT_FOUND
101
+ }, 404);
102
+ }
103
+ if (idToken) {
104
+ if (!provider.verifyIdToken) {
105
+ context.logger.error("Provider does not support id token verification", { provider: requestedProvider });
106
+ return ctx.render({
107
+ success: False,
108
+ message: BASE_ERROR_CODES.ID_TOKEN_NOT_SUPPORTED
109
+ }, 404);
110
+ }
111
+ const { token, nonce } = idToken;
112
+ if (!await provider.verifyIdToken(token, nonce)) {
113
+ context.logger.error("Invalid id token", { provider: requestedProvider });
114
+ return ctx.render({
115
+ success: False,
116
+ message: BASE_ERROR_CODES.INVALID_TOKEN
117
+ }, 401);
118
+ }
119
+ const userInfo = await provider.getUserInfo({
120
+ idToken: token,
121
+ ...idToken.accessToken != null && { accessToken: idToken.accessToken },
122
+ ...idToken.refreshToken != null && { refreshToken: idToken.refreshToken }
123
+ });
124
+ if (!userInfo?.user) {
125
+ context.logger.error("Failed to get user info", { provider: requestedProvider });
126
+ return ctx.render({
127
+ success: False,
128
+ message: BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO
129
+ }, 401);
130
+ }
131
+ if (userInfo.user.email == null) {
132
+ context.logger.error("User email not found", { provider: requestedProvider });
133
+ return ctx.render({
134
+ success: False,
135
+ message: BASE_ERROR_CODES.USER_EMAIL_NOT_FOUND
136
+ }, 401);
137
+ }
138
+ const data = await handleOAuthUserInfo(ctx, {
139
+ userInfo: {
140
+ ...userInfo.user,
141
+ email: userInfo.user.email,
142
+ id: userInfo.user.id.toString(),
143
+ ...userInfo.user.name && { name: userInfo.user.name },
144
+ ...userInfo.user.image && { image: userInfo.user.image },
145
+ emailVerified: userInfo.user.emailVerified
146
+ },
147
+ account: {
148
+ providerId: provider.id,
149
+ accountId: userInfo.user.id.toString(),
150
+ ...idToken.accessToken && { accessToken: idToken.accessToken }
151
+ },
152
+ ...callbackURL && { callbackURL },
153
+ disableSignUp: provider.disableImplicitSignUp === true && requestSignUp !== true || provider.disableSignUp === true
154
+ }, options);
155
+ if (data.type === "redirect") return data.response;
156
+ if (data.type === "error") return ctx.render({
157
+ success: False,
158
+ message: data.reason.replaceAll("_", " ")
159
+ }, 401);
160
+ await setSessionCookie(ctx, options, data.data);
161
+ return ctx.render({
162
+ success: True,
163
+ redirect: False,
164
+ token: data.data.session.token,
165
+ user: data.data.user
166
+ }, 200);
167
+ }
168
+ const { codeVerifier, state } = await generateState(ctx, void 0, options);
169
+ const url = await provider.createAuthorizationURL({
170
+ state,
171
+ codeVerifier,
172
+ redirectURI: `${context.baseURL}/callback/${provider.id}`,
173
+ ...scopes && scopes.length > 0 && { scopes },
174
+ ...loginHint != null && loginHint !== "" && { loginHint }
175
+ });
176
+ if (disableRedirect === true) return ctx.render({
177
+ success: False,
178
+ redirect: False
179
+ }, 200);
180
+ return ctx.render({
181
+ success: False,
182
+ redirect: True,
183
+ url: url.toString()
184
+ }, 200);
185
+ });
186
+ const signInEmailRoute = createRoute({
187
+ operationId: Routes.SIGN_IN_EMAIL,
188
+ method: "post",
189
+ path: "/sign-in/email",
190
+ description: "Sign in with email and password",
191
+ request: req().bdy(signInEmailSchema).bld(),
192
+ responses: res(SCHEMAS[Definitions.SIGN_IN_UP].default, "Returns either session details or redirect URL").err(400, "Email and password login not enabled in options").err(401, "Invalid email or password").err(403, "Email not verified").zod().err(500, "Failed to create session").bld()
193
+ });
194
+ const signInEmail = createEndpoint(signInEmailRoute, (options) => async (ctx) => {
195
+ const context = ctx.get("context");
196
+ if (!(options.emailAndPassword?.enabled ?? false)) {
197
+ context.logger.error("Email and password is not enabled. Make sure to enable it in the options on you `auth.ts` file. Check `https://faire-auth.com/docs/authentication/email-password` for more!");
198
+ return ctx.render({
199
+ success: False,
200
+ message: "Email and password is not enabled"
201
+ }, 400);
202
+ }
203
+ const { email, password, rememberMe, callbackURL } = ctx.req.valid("json");
204
+ const user = await context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
205
+ if (!user) {
206
+ await context.password.hash(password);
207
+ context.logger.error("User not found", { email });
208
+ return ctx.render({
209
+ success: False,
210
+ message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD
211
+ }, 401);
212
+ }
213
+ const credentialAccount = user.accounts.find((a) => a.providerId === "credential");
214
+ if (!credentialAccount) {
215
+ context.logger.error("Credential account not found", { email });
216
+ return ctx.render({
217
+ success: False,
218
+ message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD
219
+ }, 401);
220
+ }
221
+ const currentPassword = credentialAccount.password;
222
+ if (currentPassword == null) {
223
+ context.logger.error("Password not found", { email });
224
+ return ctx.render({
225
+ success: False,
226
+ message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD
227
+ }, 401);
228
+ }
229
+ if (!await context.password.verify({
230
+ hash: currentPassword,
231
+ password
232
+ })) {
233
+ context.logger.error("Invalid password");
234
+ return ctx.render({
235
+ success: False,
236
+ message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD
237
+ }, 401);
238
+ }
239
+ if (options.emailAndPassword?.requireEmailVerification === true && !user.user.emailVerified) {
240
+ if (!options.emailVerification?.sendVerificationEmail) return ctx.render({
241
+ success: False,
242
+ message: BASE_ERROR_CODES.EMAIL_NOT_VERIFIED
243
+ }, 403);
244
+ const token = await createEmailVerificationToken(context.secret, user.user.email, void 0, options.emailVerification.expiresIn);
245
+ const url = `${context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL ?? "/"}`;
246
+ await options.emailVerification.sendVerificationEmail({
247
+ user: user.user,
248
+ url,
249
+ token
250
+ }, ctx);
251
+ return ctx.render({
252
+ success: False,
253
+ message: BASE_ERROR_CODES.EMAIL_NOT_VERIFIED
254
+ }, 403);
255
+ }
256
+ const session = await context.internalAdapter.createSession(user.user.id, !rememberMe);
257
+ if (session == null) {
258
+ context.logger.error("Failed to create session");
259
+ return ctx.render({
260
+ success: False,
261
+ message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION
262
+ }, 500);
263
+ }
264
+ await setSessionCookie(ctx, options, {
265
+ session,
266
+ user: user.user
267
+ }, !rememberMe);
268
+ if (callbackURL != null) return ctx.render({
269
+ success: True,
270
+ redirect: True,
271
+ url: callbackURL,
272
+ token: session.token,
273
+ user: user.user
274
+ }, 200);
275
+ return ctx.render({
276
+ success: True,
277
+ redirect: False,
278
+ token: session.token,
279
+ user: user.user
280
+ }, 200);
281
+ });
282
+
283
+ //#endregion
284
+ //#region src/api/schema/callback.ts
285
+ const callbackSchema = z$1.object({
286
+ code: z$1.string(),
287
+ error: z$1.string(),
288
+ device_id: z$1.string(),
289
+ error_description: z$1.string(),
290
+ state: z$1.string(),
291
+ user: z$1.string()
292
+ }).partial();
293
+ const oauthCallbackParamsSchema = z$1.object({ id: z$1.string().openapi({
294
+ param: {
295
+ name: "id",
296
+ in: "path"
297
+ },
298
+ description: "OAuth provider ID"
299
+ }) });
300
+ const oauthCallbackQuerySchema = callbackSchema;
301
+
302
+ //#endregion
303
+ //#region src/api/routes/callback.ts
304
+ const handleCallbackOAuth = async (ctx, paramsId, { code, error: error$1, device_id, error_description, state, user } = {}, options) => {
305
+ const context = ctx.get("context");
306
+ const defaultErrorURL = options.onAPIError?.errorURL ?? `${context.baseURL}/error`;
307
+ if (error$1 != null) return ctx.redirect(`${defaultErrorURL}?error=${error$1}&error_description=${error_description}`, 302);
308
+ if (state == null) {
309
+ context.logger.error("State not found", error$1);
310
+ return ctx.redirect(`${defaultErrorURL}?error=state_not_found`, 302);
311
+ }
312
+ const res$1 = await parseState(ctx, options);
313
+ if (res$1.type === "redirect") return res$1.response;
314
+ const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp } = res$1.data;
315
+ const redirectOnError = (error$2) => {
316
+ let url = errorURL ?? defaultErrorURL;
317
+ if (url.includes("?")) url = `${url}&error=${error$2}`;
318
+ else url = `${url}?error=${error$2}`;
319
+ return ctx.redirect(url, 302);
320
+ };
321
+ if (code == null) {
322
+ context.logger.error("Code not found");
323
+ return redirectOnError("no_code");
324
+ }
325
+ const provider = context.socialProviders.find((p) => p.id === paramsId);
326
+ if (!provider) {
327
+ context.logger.error("Oauth provider with id", paramsId, "not found");
328
+ return redirectOnError("oauth_provider_not_found");
329
+ }
330
+ let tokens;
331
+ try {
332
+ tokens = await provider.validateAuthorizationCode({
333
+ code,
334
+ codeVerifier,
335
+ ...device_id != null ? { deviceId: device_id } : {},
336
+ redirectURI: `${context.baseURL}/callback/${provider.id}`
337
+ });
338
+ } catch (e) {
339
+ context.logger.error("", e);
340
+ return redirectOnError("invalid_code");
341
+ }
342
+ const userInfo = await provider.getUserInfo({
343
+ ...tokens,
344
+ ...user && { user: safeJSONParse(user) }
345
+ }).then((res$2) => res$2?.user);
346
+ if (!userInfo) {
347
+ context.logger.error("Unable to get user info");
348
+ return redirectOnError("unable_to_get_user_info");
349
+ }
350
+ if (!callbackURL) {
351
+ context.logger.error("No callback URL found");
352
+ return redirectOnError("no_callback_url");
353
+ }
354
+ if (link) {
355
+ if (!(options.account?.accountLinking?.trustedProviders)?.includes(provider.id) && !userInfo.emailVerified || options.account?.accountLinking?.enabled === false) {
356
+ context.logger.error("Unable to link account - untrusted provider");
357
+ return redirectOnError("unable_to_link_account");
358
+ }
359
+ const existingAccount = await context.internalAdapter.findAccount(userInfo.id.toString());
360
+ if (existingAccount) {
361
+ if (existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError("account_already_linked_to_different_user");
362
+ const updateData = Object.fromEntries(Object.entries({
363
+ idToken: tokens.idToken,
364
+ accessToken: await setTokenUtil(tokens.accessToken, ctx, options),
365
+ refreshToken: await setTokenUtil(tokens.refreshToken, ctx, options),
366
+ accessTokenExpiresAt: tokens.accessTokenExpiresAt,
367
+ refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,
368
+ ...tokens.scopes && tokens.scopes.length > 0 && { scope: tokens.scopes.join(",") }
369
+ }).filter(([_, value]) => value !== void 0));
370
+ await context.internalAdapter.updateAccount(existingAccount.id, updateData);
371
+ } else if (await context.internalAdapter.createAccount({
372
+ userId: link.userId,
373
+ providerId: provider.id,
374
+ accountId: userInfo.id,
375
+ ...tokens,
376
+ accessToken: await setTokenUtil(tokens.accessToken, ctx, options),
377
+ refreshToken: await setTokenUtil(tokens.refreshToken, ctx, options),
378
+ ...tokens.scopes && tokens.scopes.length > 0 && { scope: tokens.scopes.join(",") }
379
+ }) == null) return redirectOnError("unable_to_link_account");
380
+ let toRedirectTo$1;
381
+ try {
382
+ toRedirectTo$1 = callbackURL.toString();
383
+ } catch {
384
+ toRedirectTo$1 = callbackURL;
385
+ }
386
+ return ctx.redirect(toRedirectTo$1, 302);
387
+ }
388
+ if (userInfo.email == null) {
389
+ context.logger.error("Provider did not return email. This could be due to misconfiguration in the provider settings.");
390
+ return redirectOnError("email_not_found");
391
+ }
392
+ const result = await handleOAuthUserInfo(ctx, {
393
+ userInfo: {
394
+ ...userInfo,
395
+ id: userInfo.id.toString(),
396
+ email: userInfo.email,
397
+ name: userInfo.name ?? userInfo.email
398
+ },
399
+ account: {
400
+ providerId: provider.id,
401
+ accountId: userInfo.id.toString(),
402
+ ...tokens,
403
+ ...tokens.scopes && tokens.scopes.length > 0 && { scope: tokens.scopes.join(",") }
404
+ },
405
+ callbackURL,
406
+ disableSignUp: provider.disableImplicitSignUp === true && !(requestSignUp ?? false) || provider.options?.disableSignUp === true,
407
+ overrideUserInfo: provider.options?.overrideUserInfoOnSignIn ?? false
408
+ }, options);
409
+ if (result.type === "redirect") return result.response;
410
+ if (result.type === "error") {
411
+ context.logger.error(result.reason);
412
+ return redirectOnError(result.reason);
413
+ }
414
+ const { session, user: resultUser } = result.data;
415
+ await setSessionCookie(ctx, options, {
416
+ session,
417
+ user: resultUser
418
+ });
419
+ let toRedirectTo;
420
+ try {
421
+ toRedirectTo = (result.isRegister === true ? newUserURL ?? callbackURL : callbackURL).toString();
422
+ } catch {
423
+ toRedirectTo = result.isRegister === true ? newUserURL ?? callbackURL : callbackURL;
424
+ }
425
+ return ctx.redirect(toRedirectTo, 302);
426
+ };
427
+ const getOAuthCallbackRoute = createRoute({
428
+ operationId: Routes.OAUTH_CALLBACK_GET,
429
+ isAction: false,
430
+ method: "get",
431
+ path: "/callback/:id",
432
+ description: "Handle OAuth callback",
433
+ request: req().qry(oauthCallbackQuerySchema).prm(oauthCallbackParamsSchema).bld(),
434
+ responses: res().rdr("Redirect to success or error URL").err(400).bld()
435
+ });
436
+ const getOAuthCallback = createEndpoint(getOAuthCallbackRoute, (options) => async (ctx) => {
437
+ const query = ctx.req.valid("query");
438
+ const { id: paramsId } = ctx.req.valid("param");
439
+ return handleCallbackOAuth(ctx, paramsId, query, options);
440
+ });
441
+ const postOAuthCallbackRoute = createRoute({
442
+ operationId: Routes.OAUTH_CALLBACK_POST,
443
+ isAction: false,
444
+ method: "post",
445
+ path: "/callback/:id",
446
+ description: "Handle OAuth callback (POST)",
447
+ request: req().bdy(callbackSchema.optional()).prm(oauthCallbackParamsSchema).bld(),
448
+ responses: res().rdr("Redirect to success or error URL").err(400).zod().bld()
449
+ });
450
+ const postOAuthCallback = createEndpoint(postOAuthCallbackRoute, (options) => async (ctx) => {
451
+ const body = ctx.req.valid("json");
452
+ const { id: paramsId } = ctx.req.valid("param");
453
+ return handleCallbackOAuth(ctx, paramsId, body, options);
454
+ });
455
+
456
+ //#endregion
457
+ //#region src/api/routes/sign-out.ts
458
+ const signOutRoute = createRoute({
459
+ operationId: Routes.SIGN_OUT,
460
+ method: "post",
461
+ path: "/sign-out",
462
+ description: "Sign out the current user",
463
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).err(400).bld()
464
+ });
465
+ const signOut = createEndpoint(signOutRoute, (_options) => async (ctx) => {
466
+ const context = ctx.get("context");
467
+ const sessionCookieToken = await (0, cookies_exports.getSignedCookie)(ctx, context.secret, context.authCookies.sessionToken.name);
468
+ if (sessionCookieToken == null || sessionCookieToken === false) {
469
+ deleteSessionCookie(ctx);
470
+ return ctx.render({
471
+ success: False,
472
+ message: BASE_ERROR_CODES.FAILED_TO_GET_SESSION
473
+ }, 400);
474
+ }
475
+ await context.internalAdapter.deleteSession(sessionCookieToken);
476
+ deleteSessionCookie(ctx);
477
+ return ctx.render({ success: True }, 200);
478
+ });
479
+
480
+ //#endregion
481
+ //#region src/api/schema/reset-password.ts
482
+ const requestPasswordResetSchema = z$1.object({
483
+ email: emailSchema.openapi({ description: "The email address of the user to send the password reset email to" }),
484
+ redirectTo: callbackURLSchema(true).openapi({ description: "The URL to redirect the user to reset their password. If the token isn't valid or expired, it'll be redirected with a query parameter `?error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?token=VALID_TOKEN`" })
485
+ });
486
+ const requestPasswordResetCallbackParamsSchema = z$1.object({ token: z$1.string().openapi({ param: {
487
+ in: "path",
488
+ name: "token"
489
+ } }) });
490
+ const requestPasswordResetCallbackQuerySchema = z$1.object({ callbackURL: callbackURLSchema().openapi({
491
+ param: {
492
+ in: "query",
493
+ name: "callbackURL"
494
+ },
495
+ description: "The URL to redirect the user to reset their password"
496
+ }) });
497
+ const resetPasswordSchema = z$1.object({
498
+ newPassword: passwordSchema.openapi({ description: "The new password to set" }),
499
+ token: z$1.string().optional().openapi({ description: "The token to reset the password" })
500
+ });
501
+ const resetPasswordQuerySchema = z$1.object({ token: z$1.string().optional().openapi({ param: {
502
+ in: "query",
503
+ name: "token"
504
+ } }) });
505
+
506
+ //#endregion
507
+ //#region src/api/routes/reset-password.ts
508
+ const redirectError = (ctx, callbackURL, query) => {
509
+ const url = callbackURL != null ? new URL(callbackURL, ctx.get("context").baseURL) : new URL(`${ctx.get("context").baseURL}/error`);
510
+ if (query) Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v));
511
+ return url.href;
512
+ };
513
+ const redirectCallback = (ctx, callbackURL, query) => {
514
+ const url = new URL(callbackURL, ctx.get("context").baseURL);
515
+ if (query) Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v));
516
+ return url.href;
517
+ };
518
+ const requestPasswordResetRoute = createRoute({
519
+ operationId: Routes.REQUEST_PASSWORD_RESET,
520
+ method: "post",
521
+ path: "/request-password-reset",
522
+ description: "Send a password reset email to the user",
523
+ request: req().bdy(requestPasswordResetSchema).bld(),
524
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).err(400, "Reset password not enabled in options").zod().bld()
525
+ });
526
+ const requestPasswordReset = createEndpoint(requestPasswordResetRoute, (options) => async (ctx) => {
527
+ const context = ctx.get("context");
528
+ if (!options.emailAndPassword?.sendResetPassword) {
529
+ context.logger.error("Reset password isn't enabled. Please add an emailAndPassword and sendResetPassword function to your auth config!");
530
+ return ctx.render({
531
+ success: False,
532
+ message: "Reset password isn't enabled"
533
+ }, 400);
534
+ }
535
+ const { email, redirectTo } = ctx.req.valid("json");
536
+ const user = await context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
537
+ if (!user) {
538
+ context.logger.error("Reset Password: User not found", { email });
539
+ return ctx.render({
540
+ success: True,
541
+ message: "If this email exists in our system, check your email for the reset link"
542
+ }, 200);
543
+ }
544
+ const expiresAt = getDate(options.emailAndPassword.resetPasswordTokenExpiresIn ?? 3600 * 1, "sec");
545
+ const verificationToken = generateId(24);
546
+ await context.internalAdapter.createVerificationValue({
547
+ value: user.user.id,
548
+ identifier: `reset-password:${verificationToken}`,
549
+ expiresAt
550
+ });
551
+ const callbackURL = redirectTo != null ? encodeURIComponent(redirectTo) : void 0;
552
+ const url = `${context.baseURL}/reset-password/${verificationToken}${callbackURL != null ? `?callbackURL=${callbackURL}` : ""}`;
553
+ await options.emailAndPassword.sendResetPassword({
554
+ user: user.user,
555
+ url,
556
+ token: verificationToken
557
+ }, ctx);
558
+ return ctx.render({ success: True }, 200);
559
+ });
560
+ const requestPasswordResetCallbackRoute = createRoute({
561
+ operationId: Routes.REQUEST_PASSWORD_RESET_CALLBACK,
562
+ method: "get",
563
+ path: "/reset-password/:token",
564
+ description: "Redirects the user to the callback URL with the token",
565
+ middleware: [originCheck((ctx) => ctx.req.query("callbackURL"))],
566
+ request: req().prm(requestPasswordResetCallbackParamsSchema).qry(requestPasswordResetCallbackQuerySchema).bld(),
567
+ responses: res().rdr().bld()
568
+ });
569
+ const requestPasswordResetCallback = createEndpoint(requestPasswordResetCallbackRoute, (_options) => async (ctx) => {
570
+ const { token } = ctx.req.valid("param");
571
+ const { callbackURL } = ctx.req.valid("query");
572
+ if (!token || !callbackURL) return ctx.redirect(redirectError(ctx, callbackURL, { error: "INVALID_TOKEN" }), 302);
573
+ const verification = await ctx.get("context").internalAdapter.findVerificationValue(`reset-password:${token}`);
574
+ if (!verification || verification.expiresAt < /* @__PURE__ */ new Date()) return ctx.redirect(redirectError(ctx, callbackURL, { error: "INVALID_TOKEN" }), 302);
575
+ return ctx.redirect(redirectCallback(ctx, callbackURL, { token }), 302);
576
+ });
577
+ const resetPasswordRoute = createRoute({
578
+ operationId: Routes.RESET_PASSWORD,
579
+ method: "post",
580
+ path: "/reset-password",
581
+ description: "Reset the password for a user",
582
+ request: req().qry(resetPasswordQuerySchema).bdy(resetPasswordSchema).bld(),
583
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).err(400, "Invalid token or password").zod().bld()
584
+ });
585
+ const resetPassword = createEndpoint(resetPasswordRoute, (options) => async (ctx) => {
586
+ const token = ctx.req.valid("json").token || ctx.req.valid("query").token;
587
+ if (token == null || token === "") return ctx.render({
588
+ success: False,
589
+ message: BASE_ERROR_CODES.INVALID_TOKEN
590
+ }, 400);
591
+ const { newPassword } = ctx.req.valid("json");
592
+ const context = ctx.get("context");
593
+ const minLength = context.password.config.minPasswordLength;
594
+ const maxLength = context.password.config.maxPasswordLength;
595
+ if (newPassword.length < minLength) return ctx.render({
596
+ success: False,
597
+ message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT
598
+ }, 400);
599
+ if (newPassword.length > maxLength) return ctx.render({
600
+ success: False,
601
+ message: BASE_ERROR_CODES.PASSWORD_TOO_LONG
602
+ }, 400);
603
+ const id = `reset-password:${token}`;
604
+ const verification = await context.internalAdapter.findVerificationValue(id);
605
+ if (!verification || verification.expiresAt < /* @__PURE__ */ new Date()) return ctx.render({
606
+ success: False,
607
+ message: BASE_ERROR_CODES.INVALID_TOKEN
608
+ }, 400);
609
+ const userId = verification.value;
610
+ const hashedPassword = await context.password.hash(newPassword);
611
+ if (!(await context.internalAdapter.findAccounts(userId)).find((ac) => ac.providerId === "credential")) await context.internalAdapter.createAccount({
612
+ userId,
613
+ providerId: "credential",
614
+ password: hashedPassword,
615
+ accountId: userId
616
+ });
617
+ else await context.internalAdapter.updatePassword(userId, hashedPassword);
618
+ await context.internalAdapter.deleteVerificationValue(verification.id);
619
+ if (options.emailAndPassword?.onPasswordReset) {
620
+ const user = await context.internalAdapter.findUserById(userId);
621
+ if (user) await options.emailAndPassword.onPasswordReset({ user }, ctx.req);
622
+ }
623
+ if (options.emailAndPassword?.revokeSessionsOnPasswordReset) await context.internalAdapter.deleteSessions(userId);
624
+ return ctx.render({ success: True }, 200);
625
+ });
626
+
627
+ //#endregion
628
+ //#region src/api/schema/update-user.ts
629
+ const updateUserSchema = looseUserSchema.omit({
630
+ id: true,
631
+ createdAt: true,
632
+ updatedAt: true,
633
+ emailVerified: true,
634
+ email: true
635
+ });
636
+ const changePasswordSchema = z$1.object({
637
+ newPassword: passwordSchema.openapi({ description: "The new password to set" }),
638
+ currentPassword: passwordSchema.openapi({ description: "The current password" }),
639
+ revokeOtherSessions: z$1.boolean().optional().openapi({ description: "Revoke all other sessions" })
640
+ });
641
+ const setPasswordSchema = z$1.object({ newPassword: passwordSchema });
642
+ const deleteUserSchema = z$1.object({
643
+ callbackURL: callbackURLSchema(true).openapi({ description: "The callback URL to redirect to after the user is deleted" }),
644
+ password: passwordSchema.optional().openapi({ description: "The password of the user. If the password isn't provided, session freshness will be checked." }),
645
+ token: z$1.string().optional().openapi({ description: "The token to delete the user. If the token is provided, the user will be deleted" })
646
+ });
647
+ const deleteUserCallbackQuerySchema = z$1.object({
648
+ token: z$1.string().openapi({ param: {
649
+ in: "query",
650
+ name: "token"
651
+ } }),
652
+ callbackURL: callbackURLSchema(true).openapi({ param: {
653
+ in: "query",
654
+ name: "callbackURL"
655
+ } })
656
+ });
657
+ const changeEmailSchema = z$1.object({
658
+ newEmail: emailSchema.openapi({ description: "The new email to set" }),
659
+ callbackURL: callbackURLSchema(true).openapi({ description: "The URL to redirect to after email verification" })
660
+ });
661
+
662
+ //#endregion
663
+ //#region src/api/routes/update-user.ts
664
+ const updateUserRoute = createRoute({
665
+ operationId: Routes.UPDATE_USER,
666
+ method: "post",
667
+ path: "/update-user",
668
+ description: "Update the current user",
669
+ middleware: [sessionMiddleware()],
670
+ request: req().bdy(updateUserSchema).bld(),
671
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).zod().err(500, "Failed to update user").bld()
672
+ });
673
+ const updateUser = createEndpoint(updateUserRoute, (options) => async (ctx) => {
674
+ const { name, image,...rest } = ctx.req.valid("json");
675
+ if (image === void 0 && name === void 0 && Object.keys(rest).length === 0) return ctx.render({ success: True }, 200);
676
+ const context = ctx.get("context");
677
+ const session = ctx.get("session");
678
+ const additionalFields = await parseUserInput(options, rest, "update");
679
+ if (additionalFields instanceof Response) return additionalFields;
680
+ const user = await context.internalAdapter.updateUser(session.user.id, {
681
+ ...name != null && { name },
682
+ ...image !== void 0 && { image },
683
+ ...additionalFields
684
+ });
685
+ if (user == null) return ctx.render({
686
+ success: False,
687
+ message: BASE_ERROR_CODES.FAILED_TO_UPDATE_USER
688
+ }, 500);
689
+ /**
690
+ * Update the session cookie with the new user data
691
+ */
692
+ await setSessionCookie(ctx, options, {
693
+ session: session.session,
694
+ user
695
+ });
696
+ return ctx.render({ success: True }, 200);
697
+ });
698
+ const changePasswordRoute = createRoute({
699
+ operationId: Routes.CHANGE_PASSWORD,
700
+ method: "post",
701
+ path: "/change-password",
702
+ description: "Change the password of the user",
703
+ middleware: [sensitiveSessionMiddleware],
704
+ request: req().bdy(changePasswordSchema).bld(),
705
+ responses: res(SCHEMAS[Definitions.SIGN_IN_UP].default).err(400, "Invalid password").err(404, "Account not found").zod().err(500, "Failed to get session").bld()
706
+ });
707
+ const changePassword = createEndpoint(changePasswordRoute, (options) => async (ctx) => {
708
+ const context = ctx.get("context");
709
+ const session = ctx.get("session");
710
+ const { newPassword, currentPassword, revokeOtherSessions: revokeOtherSessions$1 } = ctx.req.valid("json");
711
+ const { minPasswordLength } = context.password.config;
712
+ if (newPassword.length < minPasswordLength) {
713
+ context.logger.error("Password is too short");
714
+ return ctx.render({
715
+ success: False,
716
+ message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT
717
+ }, 400);
718
+ }
719
+ const { maxPasswordLength } = context.password.config;
720
+ if (newPassword.length > maxPasswordLength) {
721
+ context.logger.error("Password is too long");
722
+ return ctx.render({
723
+ success: False,
724
+ message: BASE_ERROR_CODES.PASSWORD_TOO_LONG
725
+ }, 400);
726
+ }
727
+ const account = (await context.internalAdapter.findAccounts(session.user.id)).find((account$1) => account$1.providerId === "credential" && account$1.password != null);
728
+ if (account?.password == null) return ctx.render({
729
+ success: False,
730
+ message: BASE_ERROR_CODES.CREDENTIAL_ACCOUNT_NOT_FOUND
731
+ }, 404);
732
+ const passwordHash = await context.password.hash(newPassword);
733
+ if (!await context.password.verify({
734
+ hash: account.password,
735
+ password: currentPassword
736
+ })) return ctx.render({
737
+ success: False,
738
+ message: BASE_ERROR_CODES.INVALID_PASSWORD
739
+ }, 400);
740
+ await context.internalAdapter.updateAccount(account.id, { password: passwordHash });
741
+ let token = session.session.token;
742
+ if (revokeOtherSessions$1 === true) {
743
+ await context.internalAdapter.deleteSessions(session.user.id);
744
+ const newSession = await context.internalAdapter.createSession(session.user.id);
745
+ if (newSession == null) return ctx.render({
746
+ success: False,
747
+ message: BASE_ERROR_CODES.FAILED_TO_GET_SESSION
748
+ }, 500);
749
+ await setSessionCookie(ctx, options, {
750
+ session: newSession,
751
+ user: session.user
752
+ });
753
+ token = newSession.token;
754
+ }
755
+ return ctx.render({
756
+ redirect: False,
757
+ success: True,
758
+ token,
759
+ user: session.user
760
+ }, 200);
761
+ });
762
+ const setPasswordRoute = createRoute({
763
+ operationId: Routes.SET_PASSWORD,
764
+ hide: true,
765
+ SERVER_ONLY: true,
766
+ method: "post",
767
+ path: "/set-password",
768
+ middleware: [sensitiveSessionMiddleware],
769
+ request: req().bdy(setPasswordSchema).bld(),
770
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).err(400, "Invalid password or user already has password").zod().bld()
771
+ });
772
+ const setPassword = createEndpoint(setPasswordRoute, (_options) => async (ctx) => {
773
+ const context = ctx.get("context");
774
+ const session = ctx.get("session");
775
+ const { newPassword } = ctx.req.valid("json");
776
+ const { minPasswordLength } = context.password.config;
777
+ if (newPassword.length < minPasswordLength) {
778
+ context.logger.error("Password is too short");
779
+ return ctx.render({
780
+ success: False,
781
+ message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT
782
+ }, 400);
783
+ }
784
+ const { maxPasswordLength } = context.password.config;
785
+ if (newPassword.length > maxPasswordLength) {
786
+ context.logger.error("Password is too long");
787
+ return ctx.render({
788
+ success: False,
789
+ message: BASE_ERROR_CODES.PASSWORD_TOO_LONG
790
+ }, 400);
791
+ }
792
+ const account = (await context.internalAdapter.findAccounts(session.user.id)).find((account$1) => account$1.providerId === "credential" && account$1.password != null);
793
+ const passwordHash = await context.password.hash(newPassword);
794
+ if (!account) {
795
+ await context.internalAdapter.linkAccount({
796
+ userId: session.user.id,
797
+ providerId: "credential",
798
+ accountId: session.user.id,
799
+ password: passwordHash
800
+ });
801
+ return ctx.render({ success: True }, 200);
802
+ }
803
+ return ctx.render({
804
+ success: False,
805
+ message: "User already has a password"
806
+ }, 400);
807
+ });
808
+ const deleteUserRoute = createRoute({
809
+ operationId: Routes.DELETE_USER,
810
+ method: "post",
811
+ path: "/delete-user",
812
+ description: "Delete the user",
813
+ middleware: [sensitiveSessionMiddleware],
814
+ request: req().bdy(deleteUserSchema).bld(),
815
+ responses: res(SCHEMAS[Definitions.SUCCESS].default, "User deletion processed successfully").rdr("Redirect to callback URL").err(400, "Account not found or invalid password").err(404, "Delete user is disabled").zod().bld()
816
+ });
817
+ const deleteUser = createEndpoint(deleteUserRoute, (options) => async (ctx) => {
818
+ const context = ctx.get("context");
819
+ if (options.user?.deleteUser?.enabled !== true) {
820
+ context.logger.error("Delete user is disabled. Enable it in the options");
821
+ return ctx.render({
822
+ success: False,
823
+ message: "Delete user is disabled"
824
+ }, 404);
825
+ }
826
+ const session = ctx.get("session");
827
+ const api = ctx.get("api");
828
+ const { callbackURL, password, token } = ctx.req.valid("json");
829
+ let canDelete = false;
830
+ const account = (await context.internalAdapter.findAccounts(session.user.id)).find((account$1) => account$1.providerId === "credential" && account$1.password != null);
831
+ if (password != null) {
832
+ if (account?.password == null) return ctx.render({
833
+ success: False,
834
+ message: BASE_ERROR_CODES.CREDENTIAL_ACCOUNT_NOT_FOUND
835
+ }, 400);
836
+ if (!await context.password.verify({
837
+ hash: account.password,
838
+ password
839
+ })) return ctx.render({
840
+ success: False,
841
+ message: BASE_ERROR_CODES.INVALID_PASSWORD
842
+ }, 400);
843
+ canDelete = true;
844
+ }
845
+ if (token != null) return await api.deleteUserCallback({ query: { token } }, ctx).then((res$1) => res$1 instanceof Response ? res$1 : ctx.render(res$1, res$1.success === true ? 200 : "errors" in res$1 ? 422 : 404));
846
+ if (options.user?.deleteUser?.sendDeleteAccountVerification != null) {
847
+ if (account?.password != null && !canDelete) return ctx.render({
848
+ success: False,
849
+ message: BASE_ERROR_CODES.USER_ALREADY_HAS_PASSWORD
850
+ }, 400);
851
+ const token$1 = (0, crypto_exports.generateRandomString)(32, "0-9", "a-z");
852
+ await context.internalAdapter.createVerificationValue({
853
+ value: session.user.id,
854
+ identifier: `delete-account-${token$1}`,
855
+ expiresAt: new Date(Date.now() + (options.user.deleteUser.deleteTokenExpiresIn ?? 3600 * 24) * 1e3)
856
+ });
857
+ const url = `${context.baseURL}/delete-user/callback?token=${token$1}${callbackURL ? `&callbackURL=${callbackURL}` : ""}`;
858
+ await options.user.deleteUser.sendDeleteAccountVerification({
859
+ user: session.user,
860
+ url,
861
+ token: token$1
862
+ }, ctx);
863
+ return ctx.render({
864
+ success: True,
865
+ message: "Verification email sent"
866
+ }, 200);
867
+ }
868
+ if (!password && context.sessionConfig.freshAge !== 0) {
869
+ const currentAge = session.session.createdAt.getTime();
870
+ const freshAge = context.sessionConfig.freshAge * 1e3;
871
+ if (Date.now() - currentAge > freshAge * 1e3) return ctx.render({
872
+ success: False,
873
+ message: BASE_ERROR_CODES.SESSION_EXPIRED
874
+ }, 400);
875
+ canDelete = true;
876
+ }
877
+ if (!canDelete) return ctx.render({
878
+ success: False,
879
+ message: "User cannot be deleted. please provide a password or token"
880
+ }, 400);
881
+ if (options.user?.deleteUser?.beforeDelete) await options.user.deleteUser.beforeDelete(session.user, ctx.req);
882
+ await context.internalAdapter.deleteUser(session.user.id);
883
+ await context.internalAdapter.deleteSessions(session.user.id);
884
+ await context.internalAdapter.deleteAccounts(session.user.id);
885
+ deleteSessionCookie(ctx);
886
+ if (options.user?.deleteUser?.afterDelete) await options.user.deleteUser.afterDelete(session.user, ctx.req);
887
+ return ctx.render({
888
+ success: True,
889
+ message: "User deleted"
890
+ }, 200);
891
+ });
892
+ const deleteUserCallbackRoute = createRoute({
893
+ operationId: Routes.DELETE_USER_CALLBACK,
894
+ method: "get",
895
+ path: "/delete-user/callback",
896
+ description: "Callback to complete user deletion with verification token",
897
+ middleware: [originCheck((ctx) => ctx.req.query("callbackURL")), sessionMiddleware()],
898
+ request: req().qry(deleteUserCallbackQuerySchema).bld(),
899
+ responses: res(SCHEMAS[Definitions.SUCCESS].default, "User successfully deleted").rdr("Redirect to callback URL").err(404, "Invalid token or delete user is disabled").zod().bld()
900
+ });
901
+ const deleteUserCallback = createEndpoint(deleteUserCallbackRoute, (options) => async (ctx) => {
902
+ const context = ctx.get("context");
903
+ if (!(options.user?.deleteUser?.enabled ?? false)) {
904
+ context.logger.error("Delete user is disabled. Enable it in the options");
905
+ return ctx.render({
906
+ success: False,
907
+ message: "Delete user is disabled"
908
+ }, 404);
909
+ }
910
+ const session = ctx.get("session");
911
+ const { token: requestedToken, callbackURL } = ctx.req.valid("query");
912
+ const token = await context.internalAdapter.findVerificationValue(`delete-account-${requestedToken}`);
913
+ if (!token || token.expiresAt < /* @__PURE__ */ new Date()) {
914
+ context.logger.info(requestedToken);
915
+ return ctx.render({
916
+ success: False,
917
+ message: BASE_ERROR_CODES.INVALID_TOKEN
918
+ }, 404);
919
+ }
920
+ if (token.value !== session.user.id) return ctx.render({
921
+ success: False,
922
+ message: BASE_ERROR_CODES.INVALID_TOKEN
923
+ }, 404);
924
+ const { beforeDelete } = options.user?.deleteUser ?? {};
925
+ if (beforeDelete) await beforeDelete(session.user, ctx.req);
926
+ await context.internalAdapter.deleteUser(session.user.id);
927
+ await context.internalAdapter.deleteSessions(session.user.id);
928
+ await context.internalAdapter.deleteAccounts(session.user.id);
929
+ await context.internalAdapter.deleteVerificationValue(token.id);
930
+ deleteSessionCookie(ctx);
931
+ const { afterDelete } = options.user?.deleteUser ?? {};
932
+ if (afterDelete) await afterDelete(session.user, ctx.req);
933
+ if (callbackURL != null) return ctx.redirect(callbackURL);
934
+ return ctx.render({
935
+ success: True,
936
+ message: "User deleted"
937
+ }, 200);
938
+ });
939
+ const changeEmailRoute = createRoute({
940
+ operationId: Routes.CHANGE_EMAIL,
941
+ method: "post",
942
+ path: "/change-email",
943
+ middleware: [sensitiveSessionMiddleware],
944
+ request: req().bdy(changeEmailSchema).bld(),
945
+ responses: res(SCHEMAS[Definitions.SUCCESS].default, "Email change request processed successfully").err(400, "Change email is disabled or user already exists").zod().bld()
946
+ });
947
+ const changeEmail = createEndpoint(changeEmailRoute, (options) => async (ctx) => {
948
+ const context = ctx.get("context");
949
+ if (options.user?.changeEmail?.enabled !== true) {
950
+ context.logger.error("Change email is disabled.");
951
+ return ctx.render({
952
+ success: False,
953
+ message: "Change email is disabled"
954
+ }, 400);
955
+ }
956
+ const session = ctx.get("session");
957
+ let { callbackURL, newEmail } = ctx.req.valid("json");
958
+ newEmail = newEmail.toLowerCase();
959
+ if (newEmail === session.user.email) {
960
+ context.logger.error("Email is the same");
961
+ return ctx.render({
962
+ success: False,
963
+ message: "Email is the same"
964
+ }, 400);
965
+ }
966
+ if (await context.internalAdapter.findUserByEmail(newEmail)) {
967
+ context.logger.error("Email already exists");
968
+ return ctx.render({
969
+ success: False,
970
+ message: "Couldn't update your email"
971
+ }, 400);
972
+ }
973
+ /**
974
+ * If the email is not verified, we can update the email
975
+ */
976
+ if (!session.user.emailVerified) {
977
+ if (await context.internalAdapter.findUserByEmail(newEmail)) return ctx.render({
978
+ success: False,
979
+ message: BASE_ERROR_CODES.USER_ALREADY_EXISTS
980
+ }, 400);
981
+ await context.internalAdapter.updateUserByEmail(session.user.email, {
982
+ email: newEmail,
983
+ emailVerified: false
984
+ });
985
+ await setSessionCookie(ctx, options, {
986
+ session: session.session,
987
+ user: {
988
+ ...session.user,
989
+ email: newEmail
990
+ }
991
+ });
992
+ if (options.emailVerification?.sendVerificationEmail) {
993
+ const token$1 = await createEmailVerificationToken(context.secret, newEmail, void 0, options.emailVerification.expiresIn);
994
+ const url$1 = `${context.baseURL}/verify-email?token=${token$1}&callbackURL=${callbackURL ?? "/"}`;
995
+ await options.emailVerification.sendVerificationEmail({
996
+ user: session.user,
997
+ url: url$1,
998
+ token: token$1
999
+ }, ctx);
1000
+ }
1001
+ return ctx.render({ success: True }, 200);
1002
+ }
1003
+ /**
1004
+ * If the email is verified, we need to send a verification email
1005
+ */
1006
+ if (options.user?.changeEmail?.sendChangeEmailVerification == null) {
1007
+ context.logger.error("Verification email isn't enabled.");
1008
+ return ctx.render({
1009
+ success: False,
1010
+ message: "Verification email isn't enabled"
1011
+ }, 400);
1012
+ }
1013
+ const token = await createEmailVerificationToken(context.secret, session.user.email, newEmail, options.emailVerification?.expiresIn);
1014
+ const url = `${context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL ?? "/"}`;
1015
+ await options.user.changeEmail.sendChangeEmailVerification({
1016
+ user: session.user,
1017
+ newEmail,
1018
+ url,
1019
+ token
1020
+ }, ctx);
1021
+ return ctx.render({ success: True }, 200);
1022
+ });
1023
+
1024
+ //#endregion
1025
+ //#region src/api/routes/error.ts
1026
+ const sanitize = (input) => input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
1027
+ const html = (errorCode = "Unknown", errorDescription) => `<!DOCTYPE html>
1028
+ <html lang="en">
1029
+ <head>
1030
+ <meta charset="UTF-8">
1031
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1032
+ <title>Authentication Error</title>
1033
+ <style>
1034
+ :root {
1035
+ --bg-color: #f8f9fa;
1036
+ --text-color: #212529;
1037
+ --accent-color: #000000;
1038
+ --error-color: #dc3545;
1039
+ --border-color: #e9ecef;
1040
+ }
1041
+ body {
1042
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1043
+ background-color: var(--bg-color);
1044
+ color: var(--text-color);
1045
+ display: flex;
1046
+ justify-content: center;
1047
+ align-items: center;
1048
+ height: 100vh;
1049
+ margin: 0;
1050
+ line-height: 1.5;
1051
+ }
1052
+ .error-container {
1053
+ background-color: #ffffff;
1054
+ border-radius: 12px;
1055
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
1056
+ padding: 2.5rem;
1057
+ text-align: center;
1058
+ max-width: 90%;
1059
+ width: 400px;
1060
+ }
1061
+ h1 {
1062
+ color: var(--error-color);
1063
+ font-size: 1.75rem;
1064
+ margin-bottom: 1rem;
1065
+ font-weight: 600;
1066
+ }
1067
+ p {
1068
+ margin-bottom: 1.5rem;
1069
+ color: #495057;
1070
+ }
1071
+ .btn {
1072
+ background-color: var(--accent-color);
1073
+ color: #ffffff;
1074
+ text-decoration: none;
1075
+ padding: 0.75rem 1.5rem;
1076
+ border-radius: 6px;
1077
+ transition: all 0.3s ease;
1078
+ display: inline-block;
1079
+ font-weight: 500;
1080
+ border: 2px solid var(--accent-color);
1081
+ }
1082
+ .btn:hover {
1083
+ background-color: #131721;
1084
+ }
1085
+ .error-code {
1086
+ font-size: 0.875rem;
1087
+ color: #6c757d;
1088
+ margin-top: 1.5rem;
1089
+ padding-top: 1.5rem;
1090
+ border-top: 1px solid var(--border-color);
1091
+ }
1092
+ .icon {
1093
+ font-size: 3rem;
1094
+ margin-bottom: 1rem;
1095
+ }
1096
+ .error-description {
1097
+ font-size: 0.95rem;
1098
+ color: #6c757d;
1099
+ margin: -1rem 0 1.5rem;
1100
+ }
1101
+ </style>
1102
+ </head>
1103
+ <body>
1104
+ <div class="error-container">
1105
+ <div class="icon">!</div>
1106
+ <h1>Faire Auth Error</h1>
1107
+ <p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
1108
+ ${errorDescription ? `<div class="error-description">${sanitize(errorDescription)}</div>` : ""}
1109
+ <a href="/" id="returnLink" class="btn">Return to Application</a>
1110
+ <div class="error-code">Error Code: <span id="errorCode">${sanitize(errorCode)}</span></div>
1111
+ </div>
1112
+ </body>
1113
+ </html>`;
1114
+ const errorQuerySchema = z$1.object({
1115
+ error: z$1.string().optional(),
1116
+ error_description: z$1.string().optional()
1117
+ });
1118
+ const errorRoute = createRoute({
1119
+ operationId: Routes.ERROR,
1120
+ hide: true,
1121
+ isAction: false,
1122
+ method: "get",
1123
+ path: "/error",
1124
+ description: "Displays an error page",
1125
+ request: req().qry(errorQuerySchema).bld(),
1126
+ responses: res(z$1.string().openapi({ description: "The HTML content of the error page" }), "A HTML error page", "text/html").zod().bld()
1127
+ });
1128
+ const error = createEndpoint(errorRoute, (_options) => async (ctx) => {
1129
+ const { error: error$1, error_description: _error_description } = ctx.req.valid("query");
1130
+ return ctx.html(html(error$1), 200);
1131
+ });
1132
+
1133
+ //#endregion
1134
+ //#region src/api/routes/ok.ts
1135
+ const okRoute = createRoute({
1136
+ operationId: Routes.OK,
1137
+ hide: true,
1138
+ isAction: false,
1139
+ method: "get",
1140
+ path: "/ok",
1141
+ description: "Check if the API is working",
1142
+ responses: res(SCHEMAS[Definitions.SUCCESS].default, "API is working").bld()
1143
+ });
1144
+ const ok = createEndpoint(okRoute, (_options) => async (ctx) => ctx.render({ success: True }, 200));
1145
+
1146
+ //#endregion
1147
+ //#region src/api/schema/sign-up.ts
1148
+ const signUpEmailSchema = z$1.looseObject({
1149
+ name: nameSchema,
1150
+ email: emailSchema.openapi({ description: "The email of the user" }),
1151
+ password: passwordSchema.openapi({ description: "The password of the user" }),
1152
+ image: z$1.url().nullish().openapi({ description: "The profile image URL of the user" }),
1153
+ callbackURL: callbackURLSchema(true).openapi({ description: "The URL to use for email verification callback" }),
1154
+ rememberMe: z$1.boolean().default(true).openapi({ description: "If this is false, the session will not be remembered. Default is `true`." })
1155
+ });
1156
+
1157
+ //#endregion
1158
+ //#region src/api/routes/sign-up.ts
1159
+ const signUpEmailRoute = createRoute({
1160
+ operationId: Routes.SIGN_UP_EMAIL,
1161
+ method: "post",
1162
+ path: "/sign-up/email",
1163
+ description: "Sign up a user using email and password",
1164
+ request: req().bdy(signUpEmailSchema).bld(),
1165
+ responses: res(SCHEMAS[Definitions.SIGN_IN_UP].default, "Successfully created user").err(400, "Email and password sign up is not enabled, invalid password, or user already exists").zod().bld()
1166
+ });
1167
+ const signUpEmail = createEndpoint(signUpEmailRoute, (options) => async (ctx) => {
1168
+ if (!(options.emailAndPassword?.enabled ?? false) || options.emailAndPassword?.disableSignUp === true) return ctx.render({
1169
+ success: False,
1170
+ message: "Email and password sign up is not enabled"
1171
+ }, 400);
1172
+ const { name, email, password, image, callbackURL, rememberMe,...additionalFields } = ctx.req.valid("json");
1173
+ const context = ctx.get("context");
1174
+ const { minPasswordLength, maxPasswordLength } = context.password.config;
1175
+ if (password.length < minPasswordLength) {
1176
+ context.logger.error("Password is too short");
1177
+ return ctx.render({
1178
+ success: False,
1179
+ message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT
1180
+ }, 400);
1181
+ }
1182
+ if (password.length > maxPasswordLength) {
1183
+ context.logger.error("Password is too long");
1184
+ return ctx.render({
1185
+ success: False,
1186
+ message: BASE_ERROR_CODES.PASSWORD_TOO_LONG
1187
+ }, 400);
1188
+ }
1189
+ return runWithTransaction(context.adapter, async () => {
1190
+ if ((await context.internalAdapter.findUserByEmail(email))?.user) {
1191
+ context.logger.info(`Sign-up attempt for existing email: ${email}`);
1192
+ return ctx.render({
1193
+ success: False,
1194
+ message: BASE_ERROR_CODES.USER_ALREADY_EXISTS
1195
+ }, 400);
1196
+ }
1197
+ const additionalData = await parseUserInput(options, additionalFields, "create");
1198
+ if (additionalData instanceof Response) return additionalData;
1199
+ /**
1200
+ * Hash the password
1201
+ *
1202
+ * This is done prior to creating the user
1203
+ * to ensure that any plugin that
1204
+ * may break the hashing should break
1205
+ * before the user is created.
1206
+ */
1207
+ const hash = await context.password.hash(password);
1208
+ let createdUser;
1209
+ try {
1210
+ createdUser = await context.internalAdapter.createUser({
1211
+ email: email.toLowerCase(),
1212
+ name,
1213
+ image,
1214
+ ...additionalData,
1215
+ emailVerified: false
1216
+ });
1217
+ } catch (e) {
1218
+ if (isDevelopment()) context.logger.error("Failed to create user", e);
1219
+ return ctx.render({
1220
+ success: False,
1221
+ message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER
1222
+ }, 400);
1223
+ }
1224
+ if (createdUser == null) return ctx.render({
1225
+ success: False,
1226
+ message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER
1227
+ }, 400);
1228
+ await context.internalAdapter.linkAccount({
1229
+ userId: createdUser.id,
1230
+ providerId: "credential",
1231
+ accountId: createdUser.id,
1232
+ password: hash
1233
+ });
1234
+ if (options.emailVerification?.sendOnSignUp === true || options.emailAndPassword?.requireEmailVerification === true) {
1235
+ const token = await createEmailVerificationToken(context.secret, createdUser.email, void 0, options.emailVerification?.expiresIn);
1236
+ const url = `${context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL ?? "/"}`;
1237
+ await options.emailVerification?.sendVerificationEmail?.({
1238
+ user: createdUser,
1239
+ url,
1240
+ token
1241
+ }, ctx);
1242
+ }
1243
+ if (options.emailAndPassword?.autoSignIn === false || options.emailAndPassword?.requireEmailVerification === true) return ctx.render({
1244
+ success: True,
1245
+ redirect: False,
1246
+ user: createdUser
1247
+ }, 200);
1248
+ const session = await context.internalAdapter.createSession(createdUser.id, rememberMe === false);
1249
+ if (session == null) return ctx.render({
1250
+ success: False,
1251
+ message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION
1252
+ }, 400);
1253
+ await setSessionCookie(ctx, options, {
1254
+ session,
1255
+ user: createdUser
1256
+ }, rememberMe === false);
1257
+ return ctx.render({
1258
+ success: True,
1259
+ redirect: False,
1260
+ token: session.token,
1261
+ user: createdUser
1262
+ }, 200);
1263
+ });
1264
+ });
1265
+
1266
+ //#endregion
1267
+ //#region src/api/routes/account.ts
1268
+ const listAccountsRoute = createRoute({
1269
+ operationId: Routes.LIST_ACCOUNTS,
1270
+ method: "get",
1271
+ path: "/list-accounts",
1272
+ description: "List all accounts linked to the user",
1273
+ middleware: [sessionMiddleware()],
1274
+ responses: res(SCHEMAS[Definitions.ACCOUNTS_LIST].default).bld()
1275
+ });
1276
+ const listAccounts = createEndpoint(listAccountsRoute, (_options) => async (ctx) => {
1277
+ const context = ctx.get("context");
1278
+ const session = ctx.get("session");
1279
+ const accounts = await context.internalAdapter.findAccounts(session.user.id);
1280
+ return ctx.render(accounts.map((a) => ({
1281
+ id: a.id,
1282
+ providerId: a.providerId,
1283
+ createdAt: a.createdAt,
1284
+ updatedAt: a.updatedAt,
1285
+ accountId: a.accountId,
1286
+ scopes: a.scope ? a.scope.split(",") : []
1287
+ })), 200);
1288
+ });
1289
+ const linkAccountRoute = createRoute({
1290
+ operationId: Routes.LINK_ACCOUNT,
1291
+ method: "post",
1292
+ path: "/link-social",
1293
+ description: "Link a social account to the user",
1294
+ middleware: [sessionMiddleware()],
1295
+ request: req().bdy(linkAccountSchema).bld(),
1296
+ responses: res(SCHEMAS[Definitions.REDIRECT_URL].default).err(401, "Invalid token, failed to get user info, email not found, linking not allowed, or different emails not allowed").err(404, "Provider not found").err(417, "Unable to create account").zod().bld()
1297
+ });
1298
+ const linkAccount = createEndpoint(linkAccountRoute, (options) => async (ctx) => {
1299
+ const { scopes, provider: requestedProvider, idToken } = ctx.req.valid("json");
1300
+ const context = ctx.get("context");
1301
+ const session = ctx.get("session");
1302
+ const provider = context.socialProviders.find((p) => p.id === requestedProvider);
1303
+ if (!provider) {
1304
+ context.logger.error("Provider not found. Make sure to add the provider in your auth config", { provider: requestedProvider });
1305
+ return ctx.render({
1306
+ success: False,
1307
+ message: BASE_ERROR_CODES.PROVIDER_NOT_FOUND
1308
+ }, 404);
1309
+ }
1310
+ if (idToken) {
1311
+ if (!provider.verifyIdToken) {
1312
+ context.logger.error("Provider does not support id token verification", { provider: requestedProvider });
1313
+ return ctx.render({
1314
+ success: False,
1315
+ message: BASE_ERROR_CODES.ID_TOKEN_NOT_SUPPORTED
1316
+ }, 404);
1317
+ }
1318
+ const { token, nonce } = idToken;
1319
+ if (!await provider.verifyIdToken(token, nonce)) {
1320
+ context.logger.error("Invalid id token", { provider: requestedProvider });
1321
+ return ctx.render({
1322
+ success: False,
1323
+ message: BASE_ERROR_CODES.INVALID_TOKEN
1324
+ }, 401);
1325
+ }
1326
+ const linkingUserInfo = await provider.getUserInfo({
1327
+ idToken: token,
1328
+ accessToken: idToken.accessToken,
1329
+ refreshToken: idToken.refreshToken
1330
+ });
1331
+ if (!linkingUserInfo || !linkingUserInfo?.user) {
1332
+ context.logger.error("Failed to get user info", { provider: requestedProvider });
1333
+ return ctx.render({
1334
+ success: False,
1335
+ message: BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO
1336
+ }, 401);
1337
+ }
1338
+ const linkingUserId = String(linkingUserInfo.user.id);
1339
+ if (!linkingUserInfo.user.email) {
1340
+ context.logger.error("User email not found", { provider: requestedProvider });
1341
+ return ctx.render({
1342
+ success: False,
1343
+ message: BASE_ERROR_CODES.USER_EMAIL_NOT_FOUND
1344
+ }, 401);
1345
+ }
1346
+ if ((await context.internalAdapter.findAccounts(session.user.id)).find((a) => a.providerId === provider.id && a.accountId === linkingUserId)) return ctx.render({ redirect: False }, 200);
1347
+ if (!(options.account?.accountLinking?.trustedProviders)?.includes(provider.id) && !linkingUserInfo.user.emailVerified || options.account?.accountLinking?.enabled === false) return ctx.render({
1348
+ success: False,
1349
+ message: "Account not linked - linking not allowed"
1350
+ }, 401);
1351
+ if (linkingUserInfo.user.email !== session.user.email && options.account?.accountLinking?.allowDifferentEmails !== true) return ctx.render({
1352
+ success: False,
1353
+ message: "Account not linked - different emails not allowed"
1354
+ }, 401);
1355
+ try {
1356
+ await context.internalAdapter.createAccount({
1357
+ userId: session.user.id,
1358
+ providerId: provider.id,
1359
+ accountId: linkingUserId,
1360
+ accessToken: idToken.accessToken,
1361
+ idToken: token,
1362
+ refreshToken: idToken.refreshToken,
1363
+ ...idToken.scopes && idToken.scopes.length > 0 && { scope: idToken.scopes.join(",") }
1364
+ });
1365
+ } catch (e) {
1366
+ return ctx.render({
1367
+ success: False,
1368
+ message: "Account not linked - unable to create account"
1369
+ }, 417);
1370
+ }
1371
+ if (options.account?.accountLinking?.updateUserInfoOnLink === true && (linkingUserInfo.user?.name || linkingUserInfo.user?.image)) try {
1372
+ await context.internalAdapter.updateUser(session.user.id, {
1373
+ ...linkingUserInfo.user?.name && { name: linkingUserInfo.user.name },
1374
+ ...linkingUserInfo.user?.image && { image: linkingUserInfo.user.image }
1375
+ });
1376
+ } catch (e) {
1377
+ console.warn("Could not update user - " + e.toString());
1378
+ }
1379
+ return ctx.render({ redirect: False }, 200);
1380
+ }
1381
+ const state = await generateState(ctx, {
1382
+ userId: session.user.id,
1383
+ email: session.user.email
1384
+ }, options);
1385
+ const url = await provider.createAuthorizationURL({
1386
+ state: state.state,
1387
+ codeVerifier: state.codeVerifier,
1388
+ redirectURI: `${context.baseURL}/callback/${provider.id}`,
1389
+ ...scopes && { scopes }
1390
+ });
1391
+ return ctx.render({
1392
+ redirect: True,
1393
+ url: url.toString()
1394
+ }, 200);
1395
+ });
1396
+ const unlinkAccountRoute = createRoute({
1397
+ operationId: Routes.UNLINK_ACCOUNT,
1398
+ method: "post",
1399
+ path: "/unlink-account",
1400
+ description: "Unlink an account",
1401
+ middleware: [freshSessionMiddleware],
1402
+ request: req().bdy(unlinkAccountSchema).bld(),
1403
+ responses: res(SCHEMAS[Definitions.SUCCESS].default).err(400, "Account not found or failed to unlink").zod().bld()
1404
+ });
1405
+ const unlinkAccount = createEndpoint(unlinkAccountRoute, (options) => async (ctx) => {
1406
+ const { providerId, accountId } = ctx.req.valid("json");
1407
+ const context = ctx.get("context");
1408
+ const session = ctx.get("session");
1409
+ const accounts = await context.internalAdapter.findAccounts(session.user.id);
1410
+ if (accounts.length === 1 && options.account?.accountLinking?.allowUnlinkingAll !== true) return ctx.render({
1411
+ success: False,
1412
+ message: BASE_ERROR_CODES.FAILED_TO_UNLINK_LAST_ACCOUNT
1413
+ }, 400);
1414
+ const accountExist = accounts.find((account) => accountId != null ? account.accountId === accountId && account.providerId === providerId : account.providerId === providerId);
1415
+ if (!accountExist) return ctx.render({
1416
+ success: False,
1417
+ message: BASE_ERROR_CODES.ACCOUNT_NOT_FOUND
1418
+ }, 400);
1419
+ await context.internalAdapter.deleteAccount(accountExist.id);
1420
+ return ctx.render({ success: True }, 200);
1421
+ });
1422
+ const getAccessTokenRoute = createRoute({
1423
+ operationId: Routes.GET_ACCESS_TOKEN,
1424
+ method: "post",
1425
+ path: "/get-access-token",
1426
+ description: "Get a valid access token, doing a refresh if needed",
1427
+ middleware: [requestOnlySessionMiddleware],
1428
+ request: req().bdy(getAccessTokenSchema).bld(),
1429
+ responses: res(SCHEMAS[Definitions.ACCESS_TOKEN].default, "A valid access token").err(400, "Invalid refresh token or provider configuration").zod().bld()
1430
+ });
1431
+ const getAccessToken = createEndpoint(getAccessTokenRoute, (options) => async (ctx) => {
1432
+ const { providerId, accountId, userId } = ctx.req.valid("json");
1433
+ if (userId && ctx.get("isServer") !== true) return ctx.render({ success: False }, 401);
1434
+ const resolvedUserId = ctx.get("session")?.user.id ?? userId;
1435
+ if (resolvedUserId == null) return ctx.render({
1436
+ success: False,
1437
+ message: `Either userId or session is required`
1438
+ }, 400);
1439
+ const context = ctx.get("context");
1440
+ if (!context.socialProviders.find((p) => p.id === providerId)) return ctx.render({
1441
+ success: False,
1442
+ message: `Provider ${providerId} is not supported.`
1443
+ }, 400);
1444
+ const account = (await context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId != null ? acc.id === accountId && acc.providerId === providerId : acc.providerId === providerId);
1445
+ if (!account) return ctx.render({
1446
+ success: False,
1447
+ message: "Account not found"
1448
+ }, 400);
1449
+ const provider = context.socialProviders.find((p) => p.id === providerId);
1450
+ if (!provider) return ctx.render({
1451
+ success: False,
1452
+ message: `Provider ${providerId} not found.`
1453
+ }, 400);
1454
+ try {
1455
+ let newTokens = null;
1456
+ const accessTokenExpired = account.accessTokenExpiresAt && new Date(account.accessTokenExpiresAt).getTime() - Date.now() < 5e3;
1457
+ if (account.refreshToken && accessTokenExpired && provider.refreshAccessToken) {
1458
+ newTokens = await provider.refreshAccessToken(account.refreshToken);
1459
+ await context.internalAdapter.updateAccount(account.id, {
1460
+ ...newTokens.accessToken && {
1461
+ accessToken: await setTokenUtil(newTokens.accessToken, ctx, options),
1462
+ ...newTokens.accessTokenExpiresAt && { accessTokenExpiresAt: newTokens.accessTokenExpiresAt }
1463
+ },
1464
+ ...newTokens.refreshToken && {
1465
+ refreshToken: await setTokenUtil(newTokens.refreshToken, ctx, options),
1466
+ ...newTokens.refreshTokenExpiresAt && { refreshTokenExpiresAt: newTokens.refreshTokenExpiresAt }
1467
+ }
1468
+ });
1469
+ }
1470
+ const tokens = {
1471
+ accessToken: await decryptOAuthToken(newTokens?.accessToken ?? account.accessToken ?? "", ctx, options),
1472
+ ...newTokens?.accessTokenExpiresAt != null || account.accessTokenExpiresAt != null ? { accessTokenExpiresAt: newTokens?.accessTokenExpiresAt ?? account.accessTokenExpiresAt } : {},
1473
+ scopes: account.scope ? account.scope.split(",") : [],
1474
+ ...(newTokens?.idToken != null && newTokens.idToken !== "" || account.idToken != null && account.idToken !== "") && { idToken: newTokens?.idToken || account.idToken }
1475
+ };
1476
+ return ctx.render(tokens, 200);
1477
+ } catch {
1478
+ return ctx.render({
1479
+ success: False,
1480
+ message: "Failed to get a valid access token"
1481
+ }, 400);
1482
+ }
1483
+ });
1484
+ const refreshAccessTokenRoute = createRoute({
1485
+ operationId: Routes.REFRESH_ACCESS_TOKEN,
1486
+ method: "post",
1487
+ path: "/refresh-token",
1488
+ description: "Refresh the access token using a refresh token",
1489
+ middleware: [sessionMiddleware()],
1490
+ request: req().bdy(refreshAccessTokenSchema).bld(),
1491
+ responses: res(SCHEMAS[Definitions.ACCESS_TOKEN].default, "Access token refreshed successfully").err(400, "Invalid refresh token or provider configuration").zod().bld()
1492
+ });
1493
+ const refreshAccessToken = createEndpoint(refreshAccessTokenRoute, (options) => async (ctx) => {
1494
+ const context = ctx.get("context");
1495
+ const session = ctx.get("session");
1496
+ const { providerId, accountId, userId } = ctx.req.valid("json");
1497
+ const resolvedUserId = userId ?? session.user.id;
1498
+ if (!context.socialProviders.find((p) => p.id === providerId)) return ctx.render({
1499
+ success: False,
1500
+ message: `Provider ${providerId} is not supported.`
1501
+ }, 400);
1502
+ const account = (await context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId != null ? acc.id === accountId && acc.providerId === providerId : acc.providerId === providerId);
1503
+ if (account?.refreshToken == null) return ctx.render({
1504
+ success: False,
1505
+ message: "Account not found"
1506
+ }, 400);
1507
+ const provider = context.socialProviders.find((p) => p.id === providerId);
1508
+ if (!provider) return ctx.render({
1509
+ success: False,
1510
+ message: `Provider ${providerId} not found.`
1511
+ }, 400);
1512
+ if (!provider.refreshAccessToken) return ctx.render({
1513
+ success: False,
1514
+ message: `Provider ${providerId} does not support token refreshing.`
1515
+ }, 400);
1516
+ try {
1517
+ const tokens = await provider.refreshAccessToken(account.refreshToken);
1518
+ await context.internalAdapter.updateAccount(account.id, {
1519
+ ...tokens.accessToken && {
1520
+ accessToken: await setTokenUtil(tokens.accessToken, ctx, options),
1521
+ ...tokens.accessTokenExpiresAt && { accessTokenExpiresAt: tokens.accessTokenExpiresAt }
1522
+ },
1523
+ ...tokens.refreshToken && {
1524
+ refreshToken: await setTokenUtil(tokens.refreshToken, ctx, options),
1525
+ ...tokens.refreshTokenExpiresAt && { refreshTokenExpiresAt: tokens.refreshTokenExpiresAt }
1526
+ }
1527
+ });
1528
+ return ctx.render(tokens, 200);
1529
+ } catch {
1530
+ return ctx.render({
1531
+ success: False,
1532
+ message: "Failed to refresh access token"
1533
+ }, 400);
1534
+ }
1535
+ });
1536
+ const getAccountInfoRoute = createRoute({
1537
+ operationId: Routes.GET_ACCOUNT_INFO,
1538
+ method: "post",
1539
+ path: "/account-info",
1540
+ description: "Get the account info provided by the provider",
1541
+ middleware: [sessionMiddleware()],
1542
+ request: req().bdy(getAccountInfoSchema).bld(),
1543
+ responses: res(SCHEMAS[Definitions.ACCOUNT_INFO].default).err(400, "Account not found").zod().err(500, "Provider not configured or failed to get user info").bld()
1544
+ });
1545
+ const getAccountInfo = createEndpoint(getAccountInfoRoute, (_options) => async (ctx) => {
1546
+ const { accountId } = ctx.req.valid("json");
1547
+ const context = ctx.get("context");
1548
+ const session = ctx.get("session");
1549
+ const api = ctx.get("api");
1550
+ const account = await context.internalAdapter.findAccount(accountId);
1551
+ if (!account || account.userId !== session.user.id) return ctx.render({
1552
+ success: False,
1553
+ message: "Account not found"
1554
+ }, 400);
1555
+ const provider = context.socialProviders.find((p) => p.id === account.providerId);
1556
+ if (!provider) return ctx.render({
1557
+ success: False,
1558
+ message: `Provider account provider is ${account.providerId} but it is not configured`
1559
+ }, 500);
1560
+ const tokensResponse = await api.getAccessToken({ json: {
1561
+ accountId: account.id,
1562
+ providerId: account.providerId,
1563
+ userId: account.userId
1564
+ } }, ctx);
1565
+ if (tokensResponse.success === false) return ctx.render(tokensResponse, 500);
1566
+ const info = await provider.getUserInfo({
1567
+ ...tokensResponse.data,
1568
+ ...tokensResponse.data.accessTokenExpiresAt != null && { accessTokenExpiresAt: new Date(tokensResponse.data.accessTokenExpiresAt) },
1569
+ ...tokensResponse.data.refreshTokenExpiresAt != null && { refreshTokenExpiresAt: new Date(tokensResponse.data.refreshTokenExpiresAt) }
1570
+ });
1571
+ if (info == null) return ctx.render({
1572
+ success: False,
1573
+ message: "Failed to get user info"
1574
+ }, 500);
1575
+ return ctx.render(info, 200);
1576
+ });
1577
+
1578
+ //#endregion
1579
+ //#region src/api/endpoints.ts
1580
+ const staticRouteMap = {
1581
+ [Routes.SET_PASSWORD]: setPassword,
1582
+ [Routes.CHANGE_EMAIL]: changeEmail,
1583
+ [Routes.CHANGE_PASSWORD]: changePassword,
1584
+ [Routes.DELETE_USER]: deleteUser,
1585
+ [Routes.DELETE_USER_CALLBACK]: deleteUserCallback,
1586
+ [Routes.ERROR]: error,
1587
+ [Routes.GET_ACCESS_TOKEN]: getAccessToken,
1588
+ [Routes.GET_ACCOUNT_INFO]: getAccountInfo,
1589
+ [Routes.GET_SESSION]: getSession,
1590
+ [Routes.LINK_ACCOUNT]: linkAccount,
1591
+ [Routes.LIST_ACCOUNTS]: listAccounts,
1592
+ [Routes.LIST_SESSIONS]: listSessions,
1593
+ [Routes.OAUTH_CALLBACK_GET]: getOAuthCallback,
1594
+ [Routes.OAUTH_CALLBACK_POST]: postOAuthCallback,
1595
+ [Routes.OK]: ok,
1596
+ [Routes.REFRESH_ACCESS_TOKEN]: refreshAccessToken,
1597
+ [Routes.REQUEST_PASSWORD_RESET]: requestPasswordReset,
1598
+ [Routes.REQUEST_PASSWORD_RESET_CALLBACK]: requestPasswordResetCallback,
1599
+ [Routes.RESET_PASSWORD]: resetPassword,
1600
+ [Routes.REVOKE_OTHER_SESSIONS]: revokeOtherSessions,
1601
+ [Routes.REVOKE_SESSION]: revokeSession,
1602
+ [Routes.REVOKE_SESSIONS]: revokeSessions,
1603
+ [Routes.SEND_VERIFICATION_EMAIL]: sendVerificationEmail,
1604
+ [Routes.SIGN_IN_EMAIL]: signInEmail,
1605
+ [Routes.SIGN_IN_SOCIAL]: signInSocial,
1606
+ [Routes.SIGN_OUT]: signOut,
1607
+ [Routes.SIGN_UP_EMAIL]: signUpEmail,
1608
+ [Routes.UNLINK_ACCOUNT]: unlinkAccount,
1609
+ [Routes.UPDATE_USER]: updateUser,
1610
+ [Routes.VERIFY_EMAIL]: verifyEmail
1611
+ };
1612
+ const resolveHooks = (options) => {
1613
+ const routeHooks = { ...options.routeHooks };
1614
+ options.plugins?.forEach((p) => {
1615
+ if (p.routeHooks) Object.assign(routeHooks, p.routeHooks);
1616
+ });
1617
+ return routeHooks;
1618
+ };
1619
+ const resolveRoutes = (options) => {
1620
+ const routeMap = { ...staticRouteMap };
1621
+ options.plugins?.forEach((p) => {
1622
+ if (p.routes) Object.assign(routeMap, p.routes);
1623
+ });
1624
+ return routeMap;
1625
+ };
1626
+ const createEndpoints = (options) => {
1627
+ const routeMap = resolveRoutes(options);
1628
+ const routeHooks = resolveHooks(options);
1629
+ return {
1630
+ endpoints: Object.fromEntries(Object.entries(routeMap).map(([k, v]) => [k, v(options)])),
1631
+ hooks: routeHooks
1632
+ };
1633
+ };
1634
+ const createArgs = (endpoints, builtSchemas, context, routeHooks) => {
1635
+ const groups = {
1636
+ priv: [],
1637
+ pub: []
1638
+ };
1639
+ for (const [k, v] of Object.entries(endpoints)) {
1640
+ const args = v.toArgs(builtSchemas, context, endpoints, routeHooks[k]);
1641
+ groups[args[0].SERVER_ONLY === true ? "priv" : "pub"].push(args);
1642
+ }
1643
+ return groups;
1644
+ };
1645
+
1646
+ //#endregion
1647
+ //#region src/api/middleware/intercept.ts
1648
+ const runIntercept = (ctx, hooks) => {
1649
+ let response = void 0;
1650
+ for (const hook of hooks) try {
1651
+ const result = hook(ctx);
1652
+ if (result instanceof Response) response = result;
1653
+ } catch (error$1) {
1654
+ ctx.error = error$1;
1655
+ }
1656
+ return response;
1657
+ };
1658
+ const getHooks = (options) => {
1659
+ const beforeHooks = [];
1660
+ const afterHooks = [];
1661
+ options.plugins?.forEach((p) => {
1662
+ if (p.onRequest) beforeHooks.push(p.onRequest);
1663
+ if (p.onResponse) afterHooks.push(p.onResponse);
1664
+ });
1665
+ return {
1666
+ beforeHooks,
1667
+ afterHooks
1668
+ };
1669
+ };
1670
+ /**
1671
+ * Initializes the intercept middleware.
1672
+ * @template O - The options type.
1673
+ * @param options - The FaireAuth options.
1674
+ * @returns The middleware function.
1675
+ */
1676
+ const initInterceptMiddleware = (options) => {
1677
+ const { beforeHooks, afterHooks } = getHooks(options);
1678
+ return createMiddleware()(async (ctx, next) => {
1679
+ const before = runIntercept(ctx, beforeHooks);
1680
+ if (before instanceof Response) return before;
1681
+ await next();
1682
+ const after = runIntercept(ctx, afterHooks);
1683
+ if (after instanceof Response) ctx.res = after;
1684
+ });
1685
+ };
1686
+
1687
+ //#endregion
1688
+ //#region src/api/middleware/on-disabled.ts
1689
+ /**
1690
+ * Initializes the middleware to handle disabled paths.
1691
+ * @param options - The FaireAuth options.
1692
+ * @returns The middleware function.
1693
+ */
1694
+ const initHandleDisabledMiddleware = (options) => {
1695
+ let disabledPaths = null;
1696
+ return createMiddleware()(async (ctx, next) => {
1697
+ disabledPaths ??= options.disabledPaths ?? [];
1698
+ if (disabledPaths.includes(ctx.get("path"))) return ctx.notFound();
1699
+ return await next();
1700
+ });
1701
+ };
1702
+
1703
+ //#endregion
1704
+ //#region src/api/middleware/rate-limit.ts
1705
+ const shouldRateLimit = (max, window, rateLimitData) => {
1706
+ const now = Date.now();
1707
+ const windowInMs = window * 1e3;
1708
+ return now - rateLimitData.lastRequest < windowInMs && rateLimitData.count >= max;
1709
+ };
1710
+ const rateLimitResponse = (ctx, retryAfter) => {
1711
+ ctx.header("X-Retry-After", retryAfter.toString());
1712
+ return ctx.json({
1713
+ status: False,
1714
+ message: "Too many requests. Please try again later."
1715
+ }, 429);
1716
+ };
1717
+ const getRetryAfter = (lastRequest, window) => {
1718
+ const now = Date.now();
1719
+ const windowInMs = window * 1e3;
1720
+ return Math.ceil((lastRequest + windowInMs - now) / 1e3);
1721
+ };
1722
+ const createDBStorage = (ctx) => {
1723
+ const model = "rateLimit";
1724
+ const db = ctx.adapter;
1725
+ return {
1726
+ get: async (key) => {
1727
+ const res$1 = db.findMany({
1728
+ model,
1729
+ where: [{
1730
+ field: "key",
1731
+ value: key
1732
+ }]
1733
+ });
1734
+ const data = isPromise(res$1) ? (await res$1)[0] : res$1[0];
1735
+ if (typeof data?.lastRequest === "bigint") data.lastRequest = Number(data.lastRequest);
1736
+ return data;
1737
+ },
1738
+ set: async (key, value, _update) => {
1739
+ try {
1740
+ if (_update === true) {
1741
+ const res$1 = db.updateMany({
1742
+ model: "rateLimit",
1743
+ where: [{
1744
+ field: "key",
1745
+ value: key
1746
+ }],
1747
+ update: {
1748
+ count: value.count,
1749
+ lastRequest: value.lastRequest
1750
+ }
1751
+ });
1752
+ if (isPromise(res$1)) await res$1;
1753
+ } else {
1754
+ const res$1 = await db.create({
1755
+ model: "rateLimit",
1756
+ data: {
1757
+ key,
1758
+ count: value.count,
1759
+ lastRequest: value.lastRequest
1760
+ }
1761
+ });
1762
+ if (isPromise(res$1)) await res$1;
1763
+ }
1764
+ } catch (e) {
1765
+ ctx.logger.error("Error setting rate limit", e);
1766
+ }
1767
+ }
1768
+ };
1769
+ };
1770
+ const memory = /* @__PURE__ */ new Map();
1771
+ /**
1772
+ * Gets the rate limit storage based on the configuration.
1773
+ * @param context - The auth context with rateLimit, adapter, and logger.
1774
+ * @param options - The FaireAuth options with rateLimit and secondaryStorage.
1775
+ * @returns The storage object with get and set methods.
1776
+ */
1777
+ const getRateLimitStorage = (config, context, options) => {
1778
+ if (options.rateLimit?.customStorage) return options.rateLimit.customStorage;
1779
+ if (config.storage === "secondary-storage") return {
1780
+ get: (key) => {
1781
+ const res$1 = options.secondaryStorage?.get(key);
1782
+ return isPromise(res$1) ? res$1.then((r) => r ? safeJSONParse(r) : void 0) : res$1 ? safeJSONParse(res$1) : void 0;
1783
+ },
1784
+ set: (key, value) => options.secondaryStorage?.set(key, JSON.stringify(value))
1785
+ };
1786
+ if (config.storage === "memory") return {
1787
+ get: (key) => memory.get(key),
1788
+ set: (key, value, _update) => memory.set(key, value)
1789
+ };
1790
+ return createDBStorage(context);
1791
+ };
1792
+ const getDefaultSpecialRules = () => [{
1793
+ pathMatcher: (path) => path.startsWith("/sign-in") || path.startsWith("/sign-up") || path.startsWith("/change-password") || path.startsWith("/change-email"),
1794
+ window: 10,
1795
+ max: 3
1796
+ }];
1797
+ /**
1798
+ * Initializes the rate limit middleware.
1799
+ * @param options - The FaireAuth options.
1800
+ * @returns The middleware function.
1801
+ */
1802
+ const initRateLimitMiddleware = (options, rateLimit) => {
1803
+ const specialRules = getDefaultSpecialRules();
1804
+ return createMiddleware()(async (ctx, next) => {
1805
+ if (!rateLimit.enabled) return await next();
1806
+ const ip = getIp(ctx.req, options);
1807
+ if (!ip) return await next();
1808
+ const context = ctx.get("context");
1809
+ const path = ctx.get("path");
1810
+ const key = ip + path;
1811
+ let window = rateLimit.window;
1812
+ let max = rateLimit.max;
1813
+ const specialRule = specialRules.find((rule) => rule.pathMatcher(path));
1814
+ if (specialRule) {
1815
+ window = specialRule.window;
1816
+ max = specialRule.max;
1817
+ }
1818
+ for (const plugin of options.plugins ?? []) if (plugin.rateLimit) {
1819
+ const matchedRule = plugin.rateLimit.find((rule) => rule.pathMatcher(path));
1820
+ if (matchedRule) {
1821
+ window = matchedRule.window;
1822
+ max = matchedRule.max;
1823
+ break;
1824
+ }
1825
+ }
1826
+ if (rateLimit.customRules) {
1827
+ const _path = Object.keys(rateLimit.customRules).find((p) => p.includes("*") ? wildcardMatch(p)(path) : p === path);
1828
+ if (_path) {
1829
+ const customRule = rateLimit.customRules[_path];
1830
+ const res$2 = typeof customRule === "function" ? customRule(ctx.req) : customRule;
1831
+ const resolved = isPromise(res$2) ? await res$2 : res$2;
1832
+ if (resolved) {
1833
+ window = resolved.window;
1834
+ max = resolved.max;
1835
+ }
1836
+ if (resolved === false) return await next();
1837
+ }
1838
+ }
1839
+ const storage = getRateLimitStorage(rateLimit, context, options);
1840
+ const res$1 = storage.get(key);
1841
+ const now = Date.now();
1842
+ const data = isPromise(res$1) ? await res$1 : res$1;
1843
+ if (!data) {
1844
+ const res2 = storage.set(key, {
1845
+ key,
1846
+ count: 1,
1847
+ lastRequest: now
1848
+ });
1849
+ if (isPromise(res2)) await res2;
1850
+ } else {
1851
+ const timeSinceLastRequest = now - data.lastRequest;
1852
+ if (shouldRateLimit(max, window, data)) return rateLimitResponse(ctx, getRetryAfter(data.lastRequest, window));
1853
+ if (timeSinceLastRequest > window * 1e3) {
1854
+ const res3 = storage.set(key, {
1855
+ ...data,
1856
+ count: 1,
1857
+ lastRequest: now
1858
+ }, true);
1859
+ if (isPromise(res3)) await res3;
1860
+ } else {
1861
+ const res3 = storage.set(key, {
1862
+ ...data,
1863
+ count: data.count + 1,
1864
+ lastRequest: now
1865
+ }, true);
1866
+ if (isPromise(res3)) await res3;
1867
+ }
1868
+ }
1869
+ return await next();
1870
+ });
1871
+ };
1872
+
1873
+ //#endregion
1874
+ //#region src/api/index.ts
1875
+ const router = (context, options) => {
1876
+ const schemas = buildSchemas(SCHEMAS, options);
1877
+ const { endpoints, hooks } = createEndpoints(options);
1878
+ const { pub } = createArgs(endpoints, schemas, context, hooks);
1879
+ const api = createAPI(endpoints);
1880
+ let app = new OpenAPIHono(options).openapi(...pub[0]).openapi(...pub[1]).openapi(...pub[2]).openapi(...pub[3]).openapi(...pub[4]).openapi(...pub[5]).openapi(...pub[6]).openapi(...pub[7]).openapi(...pub[8]).openapi(...pub[9]).openapi(...pub[10]).openapi(...pub[11]).openapi(...pub[12]).openapi(...pub[13]).openapi(...pub[14]).openapi(...pub[15]).openapi(...pub[16]).openapi(...pub[17]).openapi(...pub[18]).openapi(...pub[19]).openapi(...pub[20]).openapi(...pub[21]).openapi(...pub[22]).openapi(...pub[23]).openapi(...pub[24]).openapi(...pub[25]).openapi(...pub[26]).openapi(...pub[27]).openapi(...pub[28]);
1881
+ pub.slice(29).forEach((route) => app = app.openapi(...route));
1882
+ return {
1883
+ api,
1884
+ app: new Hono({
1885
+ router: new RegExpRouter(),
1886
+ ...options.hono?.init
1887
+ }).basePath(options.basePath).use(setDefaultExecutionCtx, setRenderer, initContextMiddleware(options, context, endpoints), contextStorage(), initHandleDisabledMiddleware(options), initInterceptMiddleware(options), initRateLimitMiddleware(options, context.rateLimit), initOriginCheckMiddleware(options, context), initHooksMiddleware(options)).onError(initErrorHandler(options)).route("/", app)
1888
+ };
1889
+ };
1890
+
1891
+ //#endregion
1892
+ export { router as t };