better-auth 0.2.3-beta.8 → 0.2.4

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.
package/dist/api.js CHANGED
@@ -1,5 +1,1931 @@
1
- import{APIError as Et,createRouter as Ot}from"better-call";import{APIError as Q}from"better-call";import{z as X}from"zod";import{xchacha20poly1305 as $t}from"@noble/ciphers/chacha";import{bytesToHex as zt,hexToBytes as qt,utf8ToBytes as Vt}from"@noble/ciphers/utils";import{managedNonce as Mt}from"@noble/ciphers/webcrypto";import{sha256 as Ft}from"@noble/hashes/sha256";async function j(e,t){let o=new TextEncoder,r={name:"HMAC",hash:"SHA-256"},n=await crypto.subtle.importKey("raw",o.encode(e),r,!1,["sign","verify"]),s=await crypto.subtle.sign(r.name,n,o.encode(t));return btoa(String.fromCharCode(...new Uint8Array(s)))}import{createEndpointCreator as qe,createMiddleware as Z,createMiddlewareCreator as Ve}from"better-call";var K=Z(async()=>({})),D=Ve({use:[K,Z(async()=>({}))]}),l=qe({use:[K]});var Y=D({body:X.object({csrfToken:X.string().optional()}).optional()},async e=>{if(e.request?.method!=="POST"||e.context.options.advanced?.disableCSRFCheck)return;let t=new URL(e.request.url);if(t.origin===new URL(e.context.baseURL).origin||e.context.options.trustedOrigins?.includes(t.origin))return;let o=e.body?.csrfToken,r=await e.getSignedCookie(e.context.authCookies.csrfToken.name,e.context.secret),[n,s]=r?.split("!")||[null,null];if(!o||!r||!n||!s||r!==o)throw e.setCookie(e.context.authCookies.csrfToken.name,"",{maxAge:0}),new Q("UNAUTHORIZED",{message:"Invalid CSRF Token"});let i=await j(e.context.secret,n);if(s!==i)throw e.setCookie(e.context.authCookies.csrfToken.name,"",{maxAge:0}),new Q("UNAUTHORIZED",{message:"Invalid CSRF Token"})});import{APIError as v}from"better-call";import{generateCodeVerifier as ut}from"oslo/oauth2";import{z as A}from"zod";import"arctic";import{parseJWT as Fe}from"oslo/jwt";import"@better-fetch/fetch";var I=class extends Error{constructor(t,o,r){super(t),this.name="BetterAuthError",this.message=t,this.cause=o}};import{OAuth2Tokens as Me}from"arctic";function Ne(e){try{return new URL(e).pathname!=="/"}catch{throw new I(`Invalid base URL: ${e}. Please provide a valid base URL.`)}}function q(e,t="/api/auth"){return Ne(e)?e:(t=t.startsWith("/")?t:`/${t}`,`${e}${t}`)}function ee(e,t){if(e)return q(e,t);let o=process?.env||{},r=o.BETTER_AUTH_URL||o.NEXT_PUBLIC_BETTER_AUTH_URL||o.PUBLIC_BETTER_AUTH_URL||o.NUXT_PUBLIC_BETTER_AUTH_URL||o.NUXT_PUBLIC_AUTH_URL||(o.BASE_URL!=="/"?o.BASE_URL:void 0);if(r)return q(r,t);if(typeof window<"u")return q(window.location.origin,t)}import{betterFetch as He}from"@better-fetch/fetch";function f(e,t){return t||`${ee()}/callback/${e}`}async function y({code:e,codeVerifier:t,redirectURI:o,options:r,tokenEndpoint:n}){let s=new URLSearchParams;s.set("grant_type","authorization_code"),s.set("code",e),t&&s.set("code_verifier",t),s.set("redirect_uri",o),s.set("client_id",r.clientId),s.set("client_secret",r.clientSecret);let{data:i,error:a}=await He(n,{method:"POST",body:s,headers:{"content-type":"application/x-www-form-urlencoded",accept:"application/json","user-agent":"better-auth"}});if(a)throw a;return new Me(i)}var te=e=>{let t="https://appleid.apple.com/auth/token";return{id:"apple",name:"Apple",createAuthorizationURL({state:o,scopes:r,redirectURI:n}){let s=r||["email","name","openid"];return new URL(`https://appleid.apple.com/auth/authorize?client_id=${e.clientId}&response_type=code&redirect_uri=${n||e.redirectURI}&scope=${s.join(" ")}&state=${o}`)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("apple",e.redirectURI),options:e,tokenEndpoint:t}),async getUserInfo(o){let r=Fe(o.idToken())?.payload;return r?{user:{id:r.sub,name:r.name,email:r.email,emailVerified:r.email_verified==="true"},data:r}:null}}};import{betterFetch as Ge}from"@better-fetch/fetch";import{Discord as We}from"arctic";var re=e=>{let t=new We(e.clientId,e.clientSecret,f("discord",e.redirectURI));return{id:"discord",name:"Discord",createAuthorizationURL({state:o,scopes:r}){let n=r||["email"];return t.createAuthorizationURL(o,n)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("discord",e.redirectURI),options:e,tokenEndpoint:"https://discord.com/api/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await Ge("https://discord.com/api/users/@me",{auth:{type:"Bearer",token:o.accessToken()}});return n?null:{user:{id:r.id,name:r.display_name||r.username||"",email:r.email,emailVerified:r.verified},data:r}}}};import{betterFetch as Je}from"@better-fetch/fetch";import{Facebook as Ze}from"arctic";var oe=e=>{let t=new Ze(e.clientId,e.clientSecret,f("facebook",e.redirectURI));return{id:"facebook",name:"Facebook",createAuthorizationURL({state:o,scopes:r}){let n=r||["email","public_profile"];return t.createAuthorizationURL(o,n)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("facebook",e.redirectURI),options:e,tokenEndpoint:"https://graph.facebook.com/v16.0/oauth/access_token"}),async getUserInfo(o){let{data:r,error:n}=await Je("https://graph.facebook.com/me",{auth:{type:"Bearer",token:o.accessToken()}});return n?null:{user:{id:r.id,name:r.name,email:r.email,emailVerified:r.email_verified},data:r}}}};import{betterFetch as ne}from"@better-fetch/fetch";import{GitHub as Ke}from"arctic";var se=({clientId:e,clientSecret:t,redirectURI:o})=>{let r=new Ke(e,t,f("github",o));return{id:"github",name:"Github",createAuthorizationURL({state:n,scopes:s}){let i=s||["user:email"];return r.createAuthorizationURL(n,i)},validateAuthorizationCode:async n=>await r.validateAuthorizationCode(n),async getUserInfo(n){let{data:s,error:i}=await ne("https://api.github.com/user",{auth:{type:"Bearer",token:n.accessToken()}});if(i)return null;let a=!1;if(!s.email){let{data:d,error:u}=await ne("https://api.github.com/user/emails",{auth:{type:"Bearer",token:n.accessToken()}});u||(s.email=(d.find(c=>c.primary)??d[0])?.email,a=d.find(c=>c.email===s.email)?.verified??!1)}return{user:{id:s.id,name:s.name,email:s.email,image:s.avatar_url,emailVerified:a,createdAt:new Date,updatedAt:new Date},data:s}}}};import{Google as Ye}from"arctic";import{parseJWT as et}from"oslo/jwt";import{createConsola as Qe}from"consola";var L=Qe({formatOptions:{date:!1,colors:!0,compact:!0},defaults:{tag:"Better Auth"}}),Xe=e=>({log:(...t)=>{!e?.disabled&&L.log("",...t)},error:(...t)=>{!e?.disabled&&L.error("",...t)},warn:(...t)=>{!e?.disabled&&L.warn("",...t)},info:(...t)=>{!e?.disabled&&L.info("",...t)},debug:(...t)=>{!e?.disabled&&L.debug("",...t)},box:(...t)=>{!e?.disabled&&L.box("",...t)},success:(...t)=>{!e?.disabled&&L.success("",...t)},break:(...t)=>{!e?.disabled&&console.log(`
2
- `)}}),P=Xe();var ie=e=>{let t=new Ye(e.clientId,e.clientSecret,f("google",e.redirectURI));return{id:"google",name:"Google",createAuthorizationURL({state:o,scopes:r,codeVerifier:n,redirectURI:s}){if(!e.clientId||!e.clientSecret)throw P.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options."),new I("CLIENT_ID_AND_SECRET_REQUIRED");if(!n)throw new I("codeVerifier is required for Google");let i=r||["email","profile"];return t.createAuthorizationURL(o,n,i)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("google",e.redirectURI),options:e,tokenEndpoint:"https://oauth2.googleapis.com/token"}),async getUserInfo(o){if(!o.idToken)return null;let r=et(o.idToken())?.payload;return{user:{id:r.sub,name:r.name,email:r.email,image:r.picture,emailVerified:r.email_verified},data:r}}}};import{betterFetch as tt}from"@better-fetch/fetch";import{Spotify as rt}from"arctic";var ae=e=>{let t=new rt(e.clientId,e.clientSecret,f("spotify",e.redirectURI));return{id:"spotify",name:"Spotify",createAuthorizationURL({state:o,scopes:r}){let n=r||["user-read-email"];return t.createAuthorizationURL(o,n)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("spotify",e.redirectURI),options:e,tokenEndpoint:"https://accounts.spotify.com/api/token"}),async getUserInfo(o){let{data:r,error:n}=await tt("https://api.spotify.com/v1/me",{method:"GET",headers:{Authorization:`Bearer ${o.accessToken()}`}});return n?null:{user:{id:r.id,name:r.display_name,email:r.email,image:r.images[0]?.url,emailVerified:!1},data:r}}}};import{betterFetch as ot}from"@better-fetch/fetch";import{Twitch as nt}from"arctic";var de=e=>{let t=new nt(e.clientId,e.clientSecret,f("twitch",e.redirectURI));return{id:"twitch",name:"Twitch",createAuthorizationURL({state:o,scopes:r}){let n=r||["activity:write","read"];return t.createAuthorizationURL(o,n)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("twitch",e.redirectURI),options:e,tokenEndpoint:"https://id.twitch.tv/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await ot("https://api.twitch.tv/helix/users",{method:"GET",headers:{Authorization:`Bearer ${o.accessToken()}`}});return n?null:{user:{id:r.sub,name:r.preferred_username,email:r.email,image:r.picture,emailVerified:!1},data:r}}}};import{betterFetch as st}from"@better-fetch/fetch";import{Twitter as it}from"arctic";var ce=e=>{let t=new it(e.clientId,e.clientSecret,f("twitter",e.redirectURI));return{id:"twitter",name:"Twitter",createAuthorizationURL(o){let r=o.scopes||["account_info.read"];return t.createAuthorizationURL(o.state,o.codeVerifier,r)},validateAuthorizationCode:async(o,r,n)=>y({code:o,codeVerifier:r,redirectURI:n||f("twitch",e.redirectURI),options:e,tokenEndpoint:"https://id.twitch.tv/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await st("https://api.x.com/2/users/me?user.fields=profile_image_url",{method:"GET",headers:{Authorization:`Bearer ${o.accessToken()}`}});return n||!r.data.email?null:{user:{id:r.data.id,name:r.data.name,email:r.data.email,image:r.data.profile_image_url,emailVerified:r.data.verified||!1},data:r}}}};import"arctic";var at={apple:te,discord:re,facebook:oe,github:se,google:ie,spotify:ae,twitch:de,twitter:ce},le=Object.keys(at);import{generateState as dt}from"oslo/oauth2";import{z as O}from"zod";function ue(e,t,o){let r=dt();return{state:JSON.stringify({code:r,callbackURL:e,currentURL:t,dontRememberMe:o}),code:r}}function V(e){return O.object({code:O.string(),callbackURL:O.string().optional(),currentURL:O.string().optional(),dontRememberMe:O.boolean().optional()}).safeParse(JSON.parse(e))}import{APIError as ct}from"better-call";var pe=(e,t=!1)=>{let o=new Date;return new Date(o.getTime()+(t?e*1e3:e))};import{TimeSpan as Ro}from"oslo";async function U(e,t,o,r){let n=e.context.authCookies.sessionToken.options;n.maxAge=o?void 0:n.maxAge,await e.setSignedCookie(e.context.authCookies.sessionToken.name,t,e.context.secret,n),o&&await e.setSignedCookie(e.context.authCookies.dontRememberToken.name,"true",e.context.secret,e.context.authCookies.dontRememberToken.options)}function C(e){e.setCookie(e.context.authCookies.sessionToken.name,"",{maxAge:0}),e.setCookie(e.context.authCookies.dontRememberToken.name,"",{maxAge:0})}import{z as me}from"zod";function $(e){let t="127.0.0.1";if(process.env.NODE_ENV==="test")return t;let o=["x-client-ip","x-forwarded-for","cf-connecting-ip","fastly-client-ip","x-real-ip","x-cluster-client-ip","x-forwarded","forwarded-for","forwarded"];for(let r of o){let n=e.headers.get(r);if(typeof n=="string"){let s=n.split(",")[0].trim();if(s)return s}}return null}var N=new Map;function lt(e,t){if(!e.request)return"";let{method:o,url:r,headers:n}=e.request,s=e.request.headers.get("User-Agent")||"",i=$(e.request)||"",a=JSON.stringify(n);return`${o}:${r}:${a}:${s}:${i}:${t}`}var M=()=>l("/session",{method:"GET",requireHeaders:!0},async e=>{try{let t=await e.getSignedCookie(e.context.authCookies.sessionToken.name,e.context.secret);if(!t)return e.json(null,{status:401});let o=lt(e,t),r=N.get(o);if(r){if(r.expiresAt>Date.now())return e.json(r.data);N.delete(o)}let n=await e.context.internalAdapter.findSession(t);if(!n||n.session.expiresAt<new Date)return C(e),n&&await e.context.internalAdapter.deleteSession(n.session.id),e.json(null,{status:401});if(await e.getSignedCookie(e.context.authCookies.dontRememberToken.name,e.context.secret))return e.json(n);let i=e.context.sessionConfig.expiresIn,a=e.context.sessionConfig.updateAge;if(n.session.expiresAt.valueOf()-i*1e3+a*1e3<=Date.now()){let c=await e.context.internalAdapter.updateSession(n.session.id,{expiresAt:pe(e.context.sessionConfig.expiresIn,!0)});if(!c)return C(e),e.json(null,{status:401});let m=(c.expiresAt.valueOf()-Date.now())/1e3;return await U(e,c.id,!1,{maxAge:m}),e.json({session:c,user:n.user})}return N.set(o,{data:n,expiresAt:Date.now()+5e3}),e.json(n)}catch(t){return e.context.logger.error(t),e.json(null,{status:500})}}),H=async e=>await M()({...e,_flag:void 0}),E=D(async e=>{let t=await H(e);if(!t?.session)throw new ct("UNAUTHORIZED");return{session:t}}),fe=()=>l("/user/list-sessions",{method:"GET",use:[E],requireHeaders:!0},async e=>{let o=(await e.context.adapter.findMany({model:e.context.tables.session.tableName,where:[{field:"userId",value:e.context.session.user.id}]})).filter(r=>r.expiresAt>new Date);return e.json(o)}),ge=l("/user/revoke-session",{method:"POST",body:me.object({id:me.string()}),use:[E],requireHeaders:!0},async e=>{let t=e.body.id,o=await e.context.internalAdapter.findSession(t);if(!o)return e.json(null,{status:400});if(o.session.userId!==e.context.session.user.id)return e.json(null,{status:403});try{await e.context.internalAdapter.deleteSession(t)}catch(r){return e.context.logger.error(r),e.json(null,{status:500})}return e.json({status:!0})}),he=l("/user/revoke-sessions",{method:"POST",use:[E],requireHeaders:!0},async e=>{try{await e.context.internalAdapter.deleteSessions(e.context.session.user.id)}catch(t){return e.context.logger.error(t),e.json(null,{status:500})}return e.json({status:!0})});var ye=l("/sign-in/social",{method:"POST",requireHeaders:!0,query:A.object({currentURL:A.string().optional()}).optional(),body:A.object({callbackURL:A.string().optional(),provider:A.enum(le),dontRememberMe:A.boolean().default(!1).optional()})},async e=>{let t=e.context.socialProviders.find(i=>i.id===e.body.provider);if(!t)throw e.context.logger.error("Provider not found. Make sure to add the provider to your auth config",{provider:e.body.provider}),new v("NOT_FOUND",{message:"Provider not found"});let o=e.context.authCookies,r=e.query?.currentURL?new URL(e.query?.currentURL):null,n=e.body.callbackURL?.startsWith("http")?e.body.callbackURL:`${r?.origin}${e.body.callbackURL||""}`,s=ue(n||r?.origin||e.context.baseURL,e.query?.currentURL);try{await e.setSignedCookie(o.state.name,s.code,e.context.secret,o.state.options);let i=ut();await e.setSignedCookie(o.pkCodeVerifier.name,i,e.context.secret,o.pkCodeVerifier.options);let a=t.createAuthorizationURL({state:s.state,codeVerifier:i});return a.searchParams.set("redirect_uri",`${e.context.baseURL}/callback/${e.body.provider}`),{url:a.toString(),state:s.state,codeVerifier:i,redirect:!0}}catch{throw new v("INTERNAL_SERVER_ERROR")}}),we=l("/sign-in/email",{method:"POST",body:A.object({email:A.string().email(),password:A.string(),callbackURL:A.string().optional(),dontRememberMe:A.boolean().default(!1).optional()})},async e=>{if(!e.context.options?.emailAndPassword?.enabled)throw e.context.logger.error("Email and password is not enabled. Make sure to enable it in the options on you `auth.ts` file. Check `https://better-auth.com/docs/authentication/email-password` for more!"),new v("BAD_REQUEST",{message:"Email and password is not enabled"});let t=await H(e);t&&await e.context.internalAdapter.deleteSession(t.session.id);let{email:o,password:r}=e.body;if(!A.string().email().safeParse(o).success)throw new v("BAD_REQUEST",{message:"Invalid email"});let s=await e.context.internalAdapter.findUserByEmail(o);if(!s)throw await e.context.password.hash(r),e.context.logger.error("User not found",{email:o}),new v("UNAUTHORIZED",{message:"Invalid email or password"});let i=s.accounts.find(c=>c.providerId==="credential");if(!i)throw e.context.logger.error("Credential account not found",{email:o}),new v("UNAUTHORIZED",{message:"Invalid email or password"});let a=i?.password;if(!a)throw e.context.logger.error("Password not found",{email:o}),new v("UNAUTHORIZED",{message:"Unexpected error"});if(!await e.context.password.verify(a,r))throw e.context.logger.error("Invalid password"),new v("UNAUTHORIZED",{message:"Invalid email or password"});let u=await e.context.internalAdapter.createSession(s.user.id,e.headers,e.body.dontRememberMe);if(!u)throw e.context.logger.error("Failed to create session"),new v("INTERNAL_SERVER_ERROR");return await U(e,u.id,e.body.dontRememberMe),e.json({user:s.user,session:u,redirect:!!e.body.callbackURL,url:e.body.callbackURL})});import{APIError as ft}from"better-call";import{z as B}from"zod";import{z as p}from"zod";var No=p.object({id:p.string(),providerId:p.string(),accountId:p.string(),userId:p.string(),accessToken:p.string().nullable().optional(),refreshToken:p.string().nullable().optional(),idToken:p.string().nullable().optional(),expiresAt:p.date().nullable().optional(),password:p.string().optional().nullable()}),be=p.object({id:p.string(),email:p.string().transform(e=>e.toLowerCase()),emailVerified:p.boolean().default(!1),name:p.string(),image:p.string().optional(),createdAt:p.date().default(new Date),updatedAt:p.date().default(new Date)}),Mo=p.object({id:p.string(),userId:p.string(),expiresAt:p.date(),ipAddress:p.string().optional(),userAgent:p.string().optional()});import{alphabet as pt,generateRandomString as mt}from"oslo/crypto";var Ae=()=>mt(36,pt("a-z","0-9"));var _={isAction:!1};function F(e){let t=e.accessToken(),o=e.hasRefreshToken()?e.refreshToken():void 0,r;try{r=e.accessTokenExpiresAt()}catch{}return{accessToken:t,refreshToken:o,expiresAt:r}}var ke=l("/callback/:id",{method:"GET",query:B.object({state:B.string(),code:B.string().optional(),error:B.string().optional()}),metadata:_},async e=>{if(e.query.error||!e.query.code){let b=V(e.query.state).data?.callbackURL||`${e.context.baseURL}/error`;throw e.context.logger.error(e.query.error,e.params.id),e.redirect(`${b}?error=${e.query.error||"oAuth_code_missing"}`)}let t=e.context.socialProviders.find(h=>h.id===e.params.id);if(!t)throw e.context.logger.error("Oauth provider with id",e.params.id,"not found"),e.redirect(`${e.context.baseURL}/error?error=oauth_provider_not_found`);let o=await e.getSignedCookie(e.context.authCookies.pkCodeVerifier.name,e.context.secret),r;try{r=await t.validateAuthorizationCode(e.query.code,o,`${e.context.baseURL}/callback/${t.id}`)}catch(h){throw e.context.logger.error(h),e.redirect(`${e.context.baseURL}/error?error=oauth_code_verification_failed`)}let n=await t.getUserInfo(r).then(h=>h?.user),s=Ae(),i=be.safeParse({...n,id:s}),a=V(e.query.state);if(!a.success)throw e.context.logger.error("Unable to parse state"),e.redirect(`${e.context.baseURL}/error?error=invalid_state_parameter`);let{callbackURL:d,currentURL:u,dontRememberMe:c}=a.data;if(!n||i.success===!1)throw e.redirect(`${e.context.baseURL}/error?error=oauth_validation_failed`);if(!d)throw e.redirect(`${e.context.baseURL}/error?error=oauth_callback_url_not_found`);let m=await e.context.internalAdapter.findUserByEmail(n.email),g=m?.user.id;if(m){let h=m.accounts.find(R=>R.providerId===t.id),b=e.context.options.account?.accountLinking?.trustedProviders,z=b?b.includes(t.id):!0;if(!h&&(!n.emailVerified||!z)){let R;try{R=new URL(u||d),R.searchParams.set("error","account_not_linked")}catch{throw e.redirect(`${e.context.baseURL}/error?error=account_not_linked`)}throw e.redirect(R.toString())}if(!h)try{await e.context.internalAdapter.linkAccount({providerId:t.id,accountId:n.id,id:`${t.id}:${n.id}`,userId:m.user.id,...F(r)})}catch(R){throw console.log(R),e.redirect(`${e.context.baseURL}/error?error=failed_linking_account`)}}else try{await e.context.internalAdapter.createOAuthUser(i.data,{...F(r),id:`${t.id}:${n.id}`,providerId:t.id,accountId:n.id,userId:s})}catch{let b=new URL(u||d);throw b.searchParams.set("error","unable_to_create_user"),e.setHeader("Location",b.toString()),e.redirect(b.toString())}if(!g&&!s)throw new ft("INTERNAL_SERVER_ERROR",{message:"Unable to create user"});let w=await e.context.internalAdapter.createSession(g||s,e.request,c);if(!w){let h=new URL(u||d);throw h.searchParams.set("error","unable_to_create_session"),e.redirect(h.toString())}try{await U(e,w.id,c)}catch(h){e.context.logger.error("Unable to set session cookie",h);let b=new URL(u||d);throw b.searchParams.set("error","unable_to_create_session"),e.redirect(b.toString())}throw e.redirect(d)});import{z as G}from"zod";var Re=l("/sign-out",{method:"POST",body:G.optional(G.object({callbackURL:G.string().optional()}))},async e=>{let t=await e.getSignedCookie(e.context.authCookies.sessionToken.name,e.context.secret);return t?(await e.context.internalAdapter.deleteSession(t),C(e),e.json(null,{body:{redirect:!!e.body?.callbackURL,url:e.body?.callbackURL}})):e.json(null)});import{TimeSpan as gt}from"oslo";import{createJWT as ht,parseJWT as yt}from"oslo/jwt";import{validateJWT as Ue}from"oslo/jwt";import{z as k}from"zod";var Te=l("/forget-password",{method:"POST",body:k.object({email:k.string().email(),redirectTo:k.string()})},async e=>{if(!e.context.options.emailAndPassword?.sendResetPassword)return e.context.logger.error("Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!"),e.json(null,{status:400,statusText:"RESET_PASSWORD_EMAIL_NOT_SENT",body:{message:"Reset password isn't enabled"}});let{email:t}=e.body,o=await e.context.internalAdapter.findUserByEmail(t);if(!o)return e.json({status:!1},{body:{status:!0}});let r=await ht("HS256",Buffer.from(e.context.secret),{email:o.user.email,redirectTo:e.body.redirectTo},{expiresIn:new gt(1,"h"),issuer:"better-auth",subject:"forget-password",audiences:[o.user.email],includeIssuedTimestamp:!0}),n=`${e.context.baseURL}/reset-password/${r}`;return await e.context.options.emailAndPassword.sendResetPassword(n,o.user),e.json({status:!0})}),Pe=l("/reset-password/:token",{method:"GET"},async e=>{let{token:t}=e.params,o,r=k.object({email:k.string(),redirectTo:k.string()});try{if(o=await Ue("HS256",Buffer.from(e.context.secret),t),!o.expiresAt||o.expiresAt<new Date)throw Error("Token expired")}catch{let i=yt(t),a=r.safeParse(i?.payload);throw a.success?e.redirect(`${a.data?.redirectTo}?error=invalid_token`):e.redirect(`${e.context.baseURL}/error?error=invalid_token`)}let{redirectTo:n}=r.parse(o.payload);throw e.redirect(`${n}?token=${t}`)}),ve=l("/reset-password",{method:"POST",query:k.object({currentURL:k.string()}).optional(),body:k.object({newPassword:k.string(),callbackURL:k.string().optional()})},async e=>{let t=e.query?.currentURL.split("?token=")[1];if(!t)return e.json({error:"Invalid token",data:null},{status:400,statusText:"INVALID_TOKEN",body:{message:"Invalid token"}});let{newPassword:o}=e.body;try{let r=await Ue("HS256",Buffer.from(e.context.secret),t),n=k.string().email().parse(r.payload.email),s=await e.context.internalAdapter.findUserByEmail(n);if(!s)return e.json({error:"User not found",data:null},{status:400,body:{message:"failed to reset password"}});if(o.length<(e.context.options.emailAndPassword?.minPasswordLength||8)||o.length>(e.context.options.emailAndPassword?.maxPasswordLength||32))return e.json({data:null,error:"password is too short or too long"},{status:400,statusText:"INVALID_PASSWORD_LENGTH",body:{message:"password is too short or too long"}});let i=await e.context.password.hash(o);return await e.context.internalAdapter.updatePassword(s.user.id,i)?e.json({error:null,data:{status:!0,url:e.body.callbackURL,redirect:!!e.body.callbackURL}},{body:{status:!0,url:e.body.callbackURL,redirect:!!e.body.callbackURL}}):e.json(null,{status:400,statusText:"USER_NOT_FOUND",body:{message:"User doesn't have a credential account"}})}catch(r){return console.log(r),e.json({error:"Invalid token",data:null},{status:400,statusText:"INVALID_TOKEN",body:{message:"Invalid token"}})}});import{TimeSpan as wt}from"oslo";import{createJWT as bt,validateJWT as At}from"oslo/jwt";import{z as T}from"zod";async function W(e,t){return await bt("HS256",Buffer.from(e),{email:t.toLowerCase()},{expiresIn:new wt(1,"h"),issuer:"better-auth",subject:"verify-email",audiences:[t],includeIssuedTimestamp:!0})}var xe=l("/send-verification-email",{method:"POST",query:T.object({currentURL:T.string().optional()}).optional(),body:T.object({email:T.string().email(),callbackURL:T.string().optional()})},async e=>{if(!e.context.options.emailAndPassword?.sendVerificationEmail)return e.context.logger.error("Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it."),e.json(null,{status:400,statusText:"VERIFICATION_EMAIL_NOT_SENT",body:{message:"Verification email isn't enabled"}});let{email:t}=e.body,o=await W(e.context.secret,t),r=`${e.context.baseURL}/verify-email?token=${o}&callbackURL=${e.body.callbackURL||e.query?.currentURL||"/"}`;return await e.context.options.emailAndPassword.sendVerificationEmail(t,r,o),e.json({status:!0})}),Se=l("/verify-email",{method:"GET",query:T.object({token:T.string(),callbackURL:T.string().optional()})},async e=>{let{token:t}=e.query,o;try{o=await At("HS256",Buffer.from(e.context.secret),t)}catch(a){return e.context.logger.error("Failed to verify email",a),e.json(null,{status:400,statusText:"INVALID_TOKEN",body:{message:"Invalid token"}})}let n=T.object({email:T.string().email()}).parse(o.payload),s=await e.context.internalAdapter.findUserByEmail(n.email);if(!s)return e.json(null,{status:400,statusText:"USER_NOT_FOUND",body:{message:"User not found"}});if(!s.accounts.find(a=>a.providerId==="credential"))throw e.redirect;if(await e.context.internalAdapter.updateUserByEmail(n.email,{emailVerified:!0}),e.query.callbackURL)throw console.log("Redirecting to",e.query.callbackURL),e.redirect("/");return e.json({status:!0})});import{z as x}from"zod";import{alphabet as kt,generateRandomString as Rt}from"oslo/crypto";import"better-call";var _e=l("/user/update",{method:"POST",body:x.object({name:x.string().optional(),image:x.string().optional()}),use:[E]},async e=>{let{name:t,image:o}=e.body,r=e.context.session;if(!o&&!t)return e.json(r.user);let n=await e.context.internalAdapter.updateUserByEmail(r.user.email,{name:t,image:o});return e.json(n)}),Ie=l("/user/change-password",{method:"POST",body:x.object({newPassword:x.string(),currentPassword:x.string(),revokeOtherSessions:x.boolean().optional()}),use:[E]},async e=>{let{newPassword:t,currentPassword:o,revokeOtherSessions:r}=e.body,n=e.context.session,s=e.context.password.config.minPasswordLength;if(t.length<s)return e.context.logger.error("Password is too short"),e.json(null,{status:400,body:{message:"Password is too short"}});let i=e.context.password.config.maxPasswordLength;if(t.length>i)return e.context.logger.error("Password is too long"),e.json(null,{status:400,body:{message:"Password is too long"}});let d=(await e.context.internalAdapter.findAccounts(n.user.id)).find(m=>m.providerId==="credential"&&m.password);if(!d||!d.password)return e.json(null,{status:400,body:{message:"User does not have a password"}});let u=await e.context.password.hash(t);if(!await e.context.password.verify(d.password,o))return e.json(null,{status:400,body:{message:"Invalid password"}});if(await e.context.internalAdapter.updateAccount(d.id,{password:u}),r){await e.context.internalAdapter.deleteSessions(n.user.id);let m=await e.context.internalAdapter.createSession(n.user.id,e.headers);if(!m)return e.json(null,{status:500,body:{message:"Failed to create session"}});await U(e,m.id)}return e.json(n.user)}),Le=l("/user/set-password",{method:"POST",body:x.object({newPassword:x.string()}),use:[E]},async e=>{let{newPassword:t}=e.body,o=e.context.session,r=e.context.password.config.minPasswordLength;if(t.length<r)return e.context.logger.error("Password is too short"),e.json(null,{status:400,body:{message:"Password is too short"}});let n=e.context.password.config.maxPasswordLength;if(t.length>n)return e.context.logger.error("Password is too long"),e.json(null,{status:400,body:{message:"Password is too long"}});let i=(await e.context.internalAdapter.findAccounts(o.user.id)).find(d=>d.providerId==="credential"&&d.password),a=await e.context.password.hash(t);return i?e.json(null,{status:400,body:{message:"User already has a password"}}):(await e.context.internalAdapter.linkAccount({id:Rt(32,kt("a-z","0-9","A-Z")),userId:o.user.id,providerId:"credential",accountId:o.user.id,password:a}),e.json(o.user))});import{alphabet as Ut,generateRandomString as Tt}from"oslo/crypto";var Ee=l("/csrf",{method:"GET",metadata:_},async e=>{let t=await e.getSignedCookie(e.context.authCookies.csrfToken.name,e.context.secret);if(t)return{csrfToken:t};let o=Tt(32,Ut("a-z","0-9","A-Z")),r=await j(e.context.secret,o),n=`${o}!${r}`;return await e.setSignedCookie(e.context.authCookies.csrfToken.name,n,e.context.secret,e.context.authCookies.csrfToken.options),{csrfToken:o}});var Pt=(e="Unknown")=>`<!DOCTYPE html>
1
+ // src/api/index.ts
2
+ import {
3
+ APIError as APIError6,
4
+ createRouter
5
+ } from "better-call";
6
+
7
+ // src/api/middlewares/csrf.ts
8
+ import { APIError } from "better-call";
9
+ import { z } from "zod";
10
+
11
+ // src/crypto/index.ts
12
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
13
+ import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/ciphers/utils";
14
+ import { managedNonce } from "@noble/ciphers/webcrypto";
15
+ import { sha256 } from "@noble/hashes/sha256";
16
+ async function hs256(secretKey, message) {
17
+ const enc = new TextEncoder();
18
+ const algorithm = { name: "HMAC", hash: "SHA-256" };
19
+ const key = await crypto.subtle.importKey(
20
+ "raw",
21
+ enc.encode(secretKey),
22
+ algorithm,
23
+ false,
24
+ ["sign", "verify"]
25
+ );
26
+ const signature = await crypto.subtle.sign(
27
+ algorithm.name,
28
+ key,
29
+ enc.encode(message)
30
+ );
31
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
32
+ }
33
+
34
+ // src/api/call.ts
35
+ import {
36
+ createEndpointCreator,
37
+ createMiddleware,
38
+ createMiddlewareCreator
39
+ } from "better-call";
40
+ var optionsMiddleware = createMiddleware(async () => {
41
+ return {};
42
+ });
43
+ var createAuthMiddleware = createMiddlewareCreator({
44
+ use: [
45
+ optionsMiddleware,
46
+ /**
47
+ * Only use for post hooks
48
+ */
49
+ createMiddleware(async () => {
50
+ return {};
51
+ })
52
+ ]
53
+ });
54
+ var createAuthEndpoint = createEndpointCreator({
55
+ use: [optionsMiddleware]
56
+ });
57
+
58
+ // src/api/middlewares/csrf.ts
59
+ var csrfMiddleware = createAuthMiddleware(
60
+ {
61
+ body: z.object({
62
+ csrfToken: z.string().optional()
63
+ }).optional()
64
+ },
65
+ async (ctx) => {
66
+ if (ctx.request?.method !== "POST" || ctx.context.options.advanced?.disableCSRFCheck) {
67
+ return;
68
+ }
69
+ const url = new URL(ctx.request.url);
70
+ if (url.origin === new URL(ctx.context.baseURL).origin || ctx.context.options.trustedOrigins?.includes(url.origin)) {
71
+ return;
72
+ }
73
+ const csrfToken = ctx.body?.csrfToken;
74
+ const csrfCookie = await ctx.getSignedCookie(
75
+ ctx.context.authCookies.csrfToken.name,
76
+ ctx.context.secret
77
+ );
78
+ const [token, hash] = csrfCookie?.split("!") || [null, null];
79
+ if (!csrfToken || !csrfCookie || !token || !hash || csrfCookie !== csrfToken) {
80
+ ctx.setCookie(ctx.context.authCookies.csrfToken.name, "", {
81
+ maxAge: 0
82
+ });
83
+ throw new APIError("UNAUTHORIZED", {
84
+ message: "Invalid CSRF Token"
85
+ });
86
+ }
87
+ const expectedHash = await hs256(ctx.context.secret, token);
88
+ if (hash !== expectedHash) {
89
+ ctx.setCookie(ctx.context.authCookies.csrfToken.name, "", {
90
+ maxAge: 0
91
+ });
92
+ throw new APIError("UNAUTHORIZED", {
93
+ message: "Invalid CSRF Token"
94
+ });
95
+ }
96
+ }
97
+ );
98
+
99
+ // src/api/routes/sign-in.ts
100
+ import { APIError as APIError3 } from "better-call";
101
+ import { generateCodeVerifier } from "oslo/oauth2";
102
+ import { z as z4 } from "zod";
103
+
104
+ // src/social-providers/apple.ts
105
+ import "arctic";
106
+ import { parseJWT } from "oslo/jwt";
107
+ import "@better-fetch/fetch";
108
+
109
+ // src/error/better-auth-error.ts
110
+ var BetterAuthError = class extends Error {
111
+ constructor(message, cause, docsLink) {
112
+ super(message);
113
+ this.name = "BetterAuthError";
114
+ this.message = message;
115
+ this.cause = cause;
116
+ this.stack = "";
117
+ }
118
+ };
119
+
120
+ // src/social-providers/utils.ts
121
+ import { OAuth2Tokens } from "arctic";
122
+
123
+ // src/utils/base-url.ts
124
+ function checkHasPath(url) {
125
+ try {
126
+ const parsedUrl = new URL(url);
127
+ return parsedUrl.pathname !== "/";
128
+ } catch (error2) {
129
+ throw new BetterAuthError(
130
+ `Invalid base URL: ${url}. Please provide a valid base URL.`
131
+ );
132
+ }
133
+ }
134
+ function withPath(url, path = "/api/auth") {
135
+ const hasPath = checkHasPath(url);
136
+ if (hasPath) {
137
+ return url;
138
+ }
139
+ path = path.startsWith("/") ? path : `/${path}`;
140
+ return `${url}${path}`;
141
+ }
142
+ function getBaseURL(url, path) {
143
+ if (url) {
144
+ return withPath(url, path);
145
+ }
146
+ const env = process?.env || {};
147
+ const fromEnv = env.BETTER_AUTH_URL || env.NEXT_PUBLIC_BETTER_AUTH_URL || env.PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_AUTH_URL || (env.BASE_URL !== "/" ? env.BASE_URL : void 0);
148
+ if (fromEnv) {
149
+ return withPath(fromEnv, path);
150
+ }
151
+ if (typeof window !== "undefined") {
152
+ return withPath(window.location.origin, path);
153
+ }
154
+ return void 0;
155
+ }
156
+
157
+ // src/social-providers/utils.ts
158
+ import { betterFetch } from "@better-fetch/fetch";
159
+ function getRedirectURI(providerId, redirectURI) {
160
+ return redirectURI || `${getBaseURL()}/callback/${providerId}`;
161
+ }
162
+ async function validateAuthorizationCode({
163
+ code,
164
+ codeVerifier,
165
+ redirectURI,
166
+ options,
167
+ tokenEndpoint
168
+ }) {
169
+ const body = new URLSearchParams();
170
+ body.set("grant_type", "authorization_code");
171
+ body.set("code", code);
172
+ codeVerifier && body.set("code_verifier", codeVerifier);
173
+ body.set("redirect_uri", redirectURI);
174
+ body.set("client_id", options.clientId);
175
+ body.set("client_secret", options.clientSecret);
176
+ const { data, error: error2 } = await betterFetch(tokenEndpoint, {
177
+ method: "POST",
178
+ body,
179
+ headers: {
180
+ "content-type": "application/x-www-form-urlencoded",
181
+ accept: "application/json",
182
+ "user-agent": "better-auth"
183
+ }
184
+ });
185
+ if (error2) {
186
+ throw error2;
187
+ }
188
+ const tokens = new OAuth2Tokens(data);
189
+ return tokens;
190
+ }
191
+
192
+ // src/social-providers/apple.ts
193
+ var apple = (options) => {
194
+ const tokenEndpoint = "https://appleid.apple.com/auth/token";
195
+ return {
196
+ id: "apple",
197
+ name: "Apple",
198
+ createAuthorizationURL({ state, scopes, redirectURI }) {
199
+ const _scope = scopes || ["email", "name", "openid"];
200
+ return new URL(
201
+ `https://appleid.apple.com/auth/authorize?client_id=${options.clientId}&response_type=code&redirect_uri=${redirectURI || options.redirectURI}&scope=${_scope.join(" ")}&state=${state}`
202
+ );
203
+ },
204
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
205
+ return validateAuthorizationCode({
206
+ code,
207
+ codeVerifier,
208
+ redirectURI: redirectURI || getRedirectURI("apple", options.redirectURI),
209
+ options,
210
+ tokenEndpoint
211
+ });
212
+ },
213
+ async getUserInfo(token) {
214
+ const data = parseJWT(token.idToken())?.payload;
215
+ if (!data) {
216
+ return null;
217
+ }
218
+ return {
219
+ user: {
220
+ id: data.sub,
221
+ name: data.name,
222
+ email: data.email,
223
+ emailVerified: data.email_verified === "true"
224
+ },
225
+ data
226
+ };
227
+ }
228
+ };
229
+ };
230
+
231
+ // src/social-providers/discord.ts
232
+ import { betterFetch as betterFetch3 } from "@better-fetch/fetch";
233
+ import { Discord } from "arctic";
234
+ var discord = (options) => {
235
+ const discordArctic = new Discord(
236
+ options.clientId,
237
+ options.clientSecret,
238
+ getRedirectURI("discord", options.redirectURI)
239
+ );
240
+ return {
241
+ id: "discord",
242
+ name: "Discord",
243
+ createAuthorizationURL({ state, scopes }) {
244
+ const _scope = scopes || ["email"];
245
+ return discordArctic.createAuthorizationURL(state, _scope);
246
+ },
247
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
248
+ return validateAuthorizationCode({
249
+ code,
250
+ codeVerifier,
251
+ redirectURI: redirectURI || getRedirectURI("discord", options.redirectURI),
252
+ options,
253
+ tokenEndpoint: "https://discord.com/api/oauth2/token"
254
+ });
255
+ },
256
+ async getUserInfo(token) {
257
+ const { data: profile, error: error2 } = await betterFetch3(
258
+ "https://discord.com/api/users/@me",
259
+ {
260
+ auth: {
261
+ type: "Bearer",
262
+ token: token.accessToken()
263
+ }
264
+ }
265
+ );
266
+ if (error2) {
267
+ return null;
268
+ }
269
+ return {
270
+ user: {
271
+ id: profile.id,
272
+ name: profile.display_name || profile.username || "",
273
+ email: profile.email,
274
+ emailVerified: profile.verified
275
+ },
276
+ data: profile
277
+ };
278
+ }
279
+ };
280
+ };
281
+
282
+ // src/social-providers/facebook.ts
283
+ import { betterFetch as betterFetch4 } from "@better-fetch/fetch";
284
+ import { Facebook } from "arctic";
285
+ var facebook = (options) => {
286
+ const facebookArctic = new Facebook(
287
+ options.clientId,
288
+ options.clientSecret,
289
+ getRedirectURI("facebook", options.redirectURI)
290
+ );
291
+ return {
292
+ id: "facebook",
293
+ name: "Facebook",
294
+ createAuthorizationURL({ state, scopes }) {
295
+ const _scopes = scopes || ["email", "public_profile"];
296
+ return facebookArctic.createAuthorizationURL(state, _scopes);
297
+ },
298
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
299
+ return validateAuthorizationCode({
300
+ code,
301
+ codeVerifier,
302
+ redirectURI: redirectURI || getRedirectURI("facebook", options.redirectURI),
303
+ options,
304
+ tokenEndpoint: "https://graph.facebook.com/v16.0/oauth/access_token"
305
+ });
306
+ },
307
+ async getUserInfo(token) {
308
+ const { data: profile, error: error2 } = await betterFetch4(
309
+ "https://graph.facebook.com/me",
310
+ {
311
+ auth: {
312
+ type: "Bearer",
313
+ token: token.accessToken()
314
+ }
315
+ }
316
+ );
317
+ if (error2) {
318
+ return null;
319
+ }
320
+ return {
321
+ user: {
322
+ id: profile.id,
323
+ name: profile.name,
324
+ email: profile.email,
325
+ emailVerified: profile.email_verified
326
+ },
327
+ data: profile
328
+ };
329
+ }
330
+ };
331
+ };
332
+
333
+ // src/social-providers/github.ts
334
+ import { betterFetch as betterFetch5 } from "@better-fetch/fetch";
335
+ import { GitHub } from "arctic";
336
+ var github = ({
337
+ clientId,
338
+ clientSecret,
339
+ redirectURI
340
+ }) => {
341
+ const githubArctic = new GitHub(
342
+ clientId,
343
+ clientSecret,
344
+ getRedirectURI("github", redirectURI)
345
+ );
346
+ return {
347
+ id: "github",
348
+ name: "Github",
349
+ createAuthorizationURL({ state, scopes }) {
350
+ const _scopes = scopes || ["user:email"];
351
+ return githubArctic.createAuthorizationURL(state, _scopes);
352
+ },
353
+ validateAuthorizationCode: async (state) => {
354
+ return await githubArctic.validateAuthorizationCode(state);
355
+ },
356
+ async getUserInfo(token) {
357
+ const { data: profile, error: error2 } = await betterFetch5(
358
+ "https://api.github.com/user",
359
+ {
360
+ auth: {
361
+ type: "Bearer",
362
+ token: token.accessToken()
363
+ }
364
+ }
365
+ );
366
+ if (error2) {
367
+ return null;
368
+ }
369
+ let emailVerified = false;
370
+ if (!profile.email) {
371
+ const { data, error: error3 } = await betterFetch5("https://api.github.com/user/emails", {
372
+ auth: {
373
+ type: "Bearer",
374
+ token: token.accessToken()
375
+ }
376
+ });
377
+ if (!error3) {
378
+ profile.email = (data.find((e) => e.primary) ?? data[0])?.email;
379
+ emailVerified = data.find((e) => e.email === profile.email)?.verified ?? false;
380
+ }
381
+ }
382
+ return {
383
+ user: {
384
+ id: profile.id,
385
+ name: profile.name,
386
+ email: profile.email,
387
+ image: profile.avatar_url,
388
+ emailVerified,
389
+ createdAt: /* @__PURE__ */ new Date(),
390
+ updatedAt: /* @__PURE__ */ new Date()
391
+ },
392
+ data: profile
393
+ };
394
+ }
395
+ };
396
+ };
397
+
398
+ // src/social-providers/google.ts
399
+ import { Google } from "arctic";
400
+ import { parseJWT as parseJWT2 } from "oslo/jwt";
401
+
402
+ // src/utils/logger.ts
403
+ import { createConsola } from "consola";
404
+ var consola = createConsola({
405
+ formatOptions: {
406
+ date: false,
407
+ colors: true,
408
+ compact: true
409
+ },
410
+ defaults: {
411
+ tag: "Better Auth"
412
+ }
413
+ });
414
+ var createLogger = (options) => {
415
+ return {
416
+ log: (...args) => {
417
+ !options?.disabled && consola.log("", ...args);
418
+ },
419
+ error: (...args) => {
420
+ !options?.disabled && consola.error("", ...args);
421
+ },
422
+ warn: (...args) => {
423
+ !options?.disabled && consola.warn("", ...args);
424
+ },
425
+ info: (...args) => {
426
+ !options?.disabled && consola.info("", ...args);
427
+ },
428
+ debug: (...args) => {
429
+ !options?.disabled && consola.debug("", ...args);
430
+ },
431
+ box: (...args) => {
432
+ !options?.disabled && consola.box("", ...args);
433
+ },
434
+ success: (...args) => {
435
+ !options?.disabled && consola.success("", ...args);
436
+ },
437
+ break: (...args) => {
438
+ !options?.disabled && console.log("\n");
439
+ }
440
+ };
441
+ };
442
+ var logger = createLogger();
443
+
444
+ // src/social-providers/google.ts
445
+ var google = (options) => {
446
+ const googleArctic = new Google(
447
+ options.clientId,
448
+ options.clientSecret,
449
+ getRedirectURI("google", options.redirectURI)
450
+ );
451
+ return {
452
+ id: "google",
453
+ name: "Google",
454
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
455
+ if (!options.clientId || !options.clientSecret) {
456
+ logger.error(
457
+ "Client Id and Client Secret is required for Google. Make sure to provide them in the options."
458
+ );
459
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
460
+ }
461
+ if (!codeVerifier) {
462
+ throw new BetterAuthError("codeVerifier is required for Google");
463
+ }
464
+ const _scopes = scopes || ["email", "profile"];
465
+ const url = googleArctic.createAuthorizationURL(
466
+ state,
467
+ codeVerifier,
468
+ _scopes
469
+ );
470
+ return url;
471
+ },
472
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
473
+ return validateAuthorizationCode({
474
+ code,
475
+ codeVerifier,
476
+ redirectURI: redirectURI || getRedirectURI("google", options.redirectURI),
477
+ options,
478
+ tokenEndpoint: "https://oauth2.googleapis.com/token"
479
+ });
480
+ },
481
+ async getUserInfo(token) {
482
+ if (!token.idToken) {
483
+ return null;
484
+ }
485
+ const user = parseJWT2(token.idToken())?.payload;
486
+ return {
487
+ user: {
488
+ id: user.sub,
489
+ name: user.name,
490
+ email: user.email,
491
+ image: user.picture,
492
+ emailVerified: user.email_verified
493
+ },
494
+ data: user
495
+ };
496
+ }
497
+ };
498
+ };
499
+
500
+ // src/social-providers/spotify.ts
501
+ import { betterFetch as betterFetch6 } from "@better-fetch/fetch";
502
+ import { Spotify } from "arctic";
503
+ var spotify = (options) => {
504
+ const spotifyArctic = new Spotify(
505
+ options.clientId,
506
+ options.clientSecret,
507
+ getRedirectURI("spotify", options.redirectURI)
508
+ );
509
+ return {
510
+ id: "spotify",
511
+ name: "Spotify",
512
+ createAuthorizationURL({ state, scopes }) {
513
+ const _scopes = scopes || ["user-read-email"];
514
+ return spotifyArctic.createAuthorizationURL(state, _scopes);
515
+ },
516
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
517
+ return validateAuthorizationCode({
518
+ code,
519
+ codeVerifier,
520
+ redirectURI: redirectURI || getRedirectURI("spotify", options.redirectURI),
521
+ options,
522
+ tokenEndpoint: "https://accounts.spotify.com/api/token"
523
+ });
524
+ },
525
+ async getUserInfo(token) {
526
+ const { data: profile, error: error2 } = await betterFetch6(
527
+ "https://api.spotify.com/v1/me",
528
+ {
529
+ method: "GET",
530
+ headers: {
531
+ Authorization: `Bearer ${token.accessToken()}`
532
+ }
533
+ }
534
+ );
535
+ if (error2) {
536
+ return null;
537
+ }
538
+ return {
539
+ user: {
540
+ id: profile.id,
541
+ name: profile.display_name,
542
+ email: profile.email,
543
+ image: profile.images[0]?.url,
544
+ emailVerified: false
545
+ },
546
+ data: profile
547
+ };
548
+ }
549
+ };
550
+ };
551
+
552
+ // src/social-providers/twitch.ts
553
+ import { betterFetch as betterFetch7 } from "@better-fetch/fetch";
554
+ import { Twitch } from "arctic";
555
+ var twitch = (options) => {
556
+ const twitchArctic = new Twitch(
557
+ options.clientId,
558
+ options.clientSecret,
559
+ getRedirectURI("twitch", options.redirectURI)
560
+ );
561
+ return {
562
+ id: "twitch",
563
+ name: "Twitch",
564
+ createAuthorizationURL({ state, scopes }) {
565
+ const _scopes = scopes || ["activity:write", "read"];
566
+ return twitchArctic.createAuthorizationURL(state, _scopes);
567
+ },
568
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
569
+ return validateAuthorizationCode({
570
+ code,
571
+ codeVerifier,
572
+ redirectURI: redirectURI || getRedirectURI("twitch", options.redirectURI),
573
+ options,
574
+ tokenEndpoint: "https://id.twitch.tv/oauth2/token"
575
+ });
576
+ },
577
+ async getUserInfo(token) {
578
+ const { data: profile, error: error2 } = await betterFetch7(
579
+ "https://api.twitch.tv/helix/users",
580
+ {
581
+ method: "GET",
582
+ headers: {
583
+ Authorization: `Bearer ${token.accessToken()}`
584
+ }
585
+ }
586
+ );
587
+ if (error2) {
588
+ return null;
589
+ }
590
+ return {
591
+ user: {
592
+ id: profile.sub,
593
+ name: profile.preferred_username,
594
+ email: profile.email,
595
+ image: profile.picture,
596
+ emailVerified: false
597
+ },
598
+ data: profile
599
+ };
600
+ }
601
+ };
602
+ };
603
+
604
+ // src/social-providers/twitter.ts
605
+ import { betterFetch as betterFetch8 } from "@better-fetch/fetch";
606
+ import { Twitter } from "arctic";
607
+ var twitter = (options) => {
608
+ const twitterArctic = new Twitter(
609
+ options.clientId,
610
+ options.clientSecret,
611
+ getRedirectURI("twitter", options.redirectURI)
612
+ );
613
+ return {
614
+ id: "twitter",
615
+ name: "Twitter",
616
+ createAuthorizationURL(data) {
617
+ const _scopes = data.scopes || ["account_info.read"];
618
+ return twitterArctic.createAuthorizationURL(
619
+ data.state,
620
+ data.codeVerifier,
621
+ _scopes
622
+ );
623
+ },
624
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
625
+ return validateAuthorizationCode({
626
+ code,
627
+ codeVerifier,
628
+ redirectURI: redirectURI || getRedirectURI("twitch", options.redirectURI),
629
+ options,
630
+ tokenEndpoint: "https://id.twitch.tv/oauth2/token"
631
+ });
632
+ },
633
+ async getUserInfo(token) {
634
+ const { data: profile, error: error2 } = await betterFetch8(
635
+ "https://api.x.com/2/users/me?user.fields=profile_image_url",
636
+ {
637
+ method: "GET",
638
+ headers: {
639
+ Authorization: `Bearer ${token.accessToken()}`
640
+ }
641
+ }
642
+ );
643
+ if (error2) {
644
+ return null;
645
+ }
646
+ if (!profile.data.email) {
647
+ return null;
648
+ }
649
+ return {
650
+ user: {
651
+ id: profile.data.id,
652
+ name: profile.data.name,
653
+ email: profile.data.email,
654
+ image: profile.data.profile_image_url,
655
+ emailVerified: profile.data.verified || false
656
+ },
657
+ data: profile
658
+ };
659
+ }
660
+ };
661
+ };
662
+
663
+ // src/types/provider.ts
664
+ import "arctic";
665
+
666
+ // src/social-providers/index.ts
667
+ var oAuthProviders = {
668
+ apple,
669
+ discord,
670
+ facebook,
671
+ github,
672
+ google,
673
+ spotify,
674
+ twitch,
675
+ twitter
676
+ };
677
+ var oAuthProviderList = Object.keys(oAuthProviders);
678
+
679
+ // src/utils/state.ts
680
+ import { generateState as generateStateOAuth } from "oslo/oauth2";
681
+ import { z as z2 } from "zod";
682
+ function generateState(callbackURL, currentURL, dontRememberMe) {
683
+ const code = generateStateOAuth();
684
+ const state = JSON.stringify({
685
+ code,
686
+ callbackURL,
687
+ currentURL,
688
+ dontRememberMe
689
+ });
690
+ return { state, code };
691
+ }
692
+ function parseState(state) {
693
+ const data = z2.object({
694
+ code: z2.string(),
695
+ callbackURL: z2.string().optional(),
696
+ currentURL: z2.string().optional(),
697
+ dontRememberMe: z2.boolean().optional()
698
+ }).safeParse(JSON.parse(state));
699
+ return data;
700
+ }
701
+
702
+ // src/api/routes/session.ts
703
+ import { APIError as APIError2 } from "better-call";
704
+
705
+ // src/utils/date.ts
706
+ var getDate = (span, isSeconds = false) => {
707
+ const date = /* @__PURE__ */ new Date();
708
+ return new Date(date.getTime() + (isSeconds ? span * 1e3 : span));
709
+ };
710
+
711
+ // src/utils/cookies.ts
712
+ import { TimeSpan } from "oslo";
713
+ async function setSessionCookie(ctx, sessionToken, dontRememberMe, overrides) {
714
+ const options = ctx.context.authCookies.sessionToken.options;
715
+ options.maxAge = dontRememberMe ? void 0 : options.maxAge;
716
+ await ctx.setSignedCookie(
717
+ ctx.context.authCookies.sessionToken.name,
718
+ sessionToken,
719
+ ctx.context.secret,
720
+ options
721
+ );
722
+ if (dontRememberMe) {
723
+ await ctx.setSignedCookie(
724
+ ctx.context.authCookies.dontRememberToken.name,
725
+ "true",
726
+ ctx.context.secret,
727
+ ctx.context.authCookies.dontRememberToken.options
728
+ );
729
+ }
730
+ }
731
+ function deleteSessionCookie(ctx) {
732
+ ctx.setCookie(ctx.context.authCookies.sessionToken.name, "", {
733
+ maxAge: 0
734
+ });
735
+ ctx.setCookie(ctx.context.authCookies.dontRememberToken.name, "", {
736
+ maxAge: 0
737
+ });
738
+ }
739
+
740
+ // src/api/routes/session.ts
741
+ import { z as z3 } from "zod";
742
+
743
+ // src/utils/get-request-ip.ts
744
+ function getIp(req) {
745
+ const testIP = "127.0.0.1";
746
+ if (process.env.NODE_ENV === "test") {
747
+ return testIP;
748
+ }
749
+ const headers = [
750
+ "x-client-ip",
751
+ "x-forwarded-for",
752
+ "cf-connecting-ip",
753
+ "fastly-client-ip",
754
+ "x-real-ip",
755
+ "x-cluster-client-ip",
756
+ "x-forwarded",
757
+ "forwarded-for",
758
+ "forwarded"
759
+ ];
760
+ for (const header of headers) {
761
+ const value = req.headers.get(header);
762
+ if (typeof value === "string") {
763
+ const ip = value.split(",")[0].trim();
764
+ if (ip) return ip;
765
+ }
766
+ }
767
+ return null;
768
+ }
769
+
770
+ // src/api/routes/session.ts
771
+ var sessionCache = /* @__PURE__ */ new Map();
772
+ function getRequestUniqueKey(ctx, token) {
773
+ if (!ctx.request) {
774
+ return "";
775
+ }
776
+ const { method, url, headers } = ctx.request;
777
+ const userAgent = ctx.request.headers.get("User-Agent") || "";
778
+ const ip = getIp(ctx.request) || "";
779
+ const headerString = JSON.stringify(headers);
780
+ const uniqueString = `${method}:${url}:${headerString}:${userAgent}:${ip}:${token}`;
781
+ return uniqueString;
782
+ }
783
+ var getSession = () => createAuthEndpoint(
784
+ "/session",
785
+ {
786
+ method: "GET",
787
+ requireHeaders: true
788
+ },
789
+ async (ctx) => {
790
+ try {
791
+ const sessionCookieToken = await ctx.getSignedCookie(
792
+ ctx.context.authCookies.sessionToken.name,
793
+ ctx.context.secret
794
+ );
795
+ if (!sessionCookieToken) {
796
+ return ctx.json(null, {
797
+ status: 401
798
+ });
799
+ }
800
+ const key = getRequestUniqueKey(ctx, sessionCookieToken);
801
+ const cachedSession = sessionCache.get(key);
802
+ if (cachedSession) {
803
+ if (cachedSession.expiresAt > Date.now()) {
804
+ return ctx.json(
805
+ cachedSession.data
806
+ );
807
+ }
808
+ sessionCache.delete(key);
809
+ }
810
+ const session = await ctx.context.internalAdapter.findSession(sessionCookieToken);
811
+ if (!session || session.session.expiresAt < /* @__PURE__ */ new Date()) {
812
+ deleteSessionCookie(ctx);
813
+ if (session) {
814
+ await ctx.context.internalAdapter.deleteSession(session.session.id);
815
+ }
816
+ return ctx.json(null, {
817
+ status: 401
818
+ });
819
+ }
820
+ const dontRememberMe = await ctx.getSignedCookie(
821
+ ctx.context.authCookies.dontRememberToken.name,
822
+ ctx.context.secret
823
+ );
824
+ if (dontRememberMe) {
825
+ return ctx.json(
826
+ session
827
+ );
828
+ }
829
+ const expiresIn = ctx.context.sessionConfig.expiresIn;
830
+ const updateAge = ctx.context.sessionConfig.updateAge;
831
+ const sessionIsDueToBeUpdatedDate = session.session.expiresAt.valueOf() - expiresIn * 1e3 + updateAge * 1e3;
832
+ const shouldBeUpdated = sessionIsDueToBeUpdatedDate <= Date.now();
833
+ if (shouldBeUpdated) {
834
+ const updatedSession = await ctx.context.internalAdapter.updateSession(
835
+ session.session.id,
836
+ {
837
+ expiresAt: getDate(ctx.context.sessionConfig.expiresIn, true)
838
+ }
839
+ );
840
+ if (!updatedSession) {
841
+ deleteSessionCookie(ctx);
842
+ return ctx.json(null, { status: 401 });
843
+ }
844
+ const maxAge = (updatedSession.expiresAt.valueOf() - Date.now()) / 1e3;
845
+ await setSessionCookie(ctx, updatedSession.id, false, {
846
+ maxAge
847
+ });
848
+ return ctx.json({
849
+ session: updatedSession,
850
+ user: session.user
851
+ });
852
+ }
853
+ sessionCache.set(key, {
854
+ data: session,
855
+ expiresAt: Date.now() + 5e3
856
+ });
857
+ return ctx.json(
858
+ session
859
+ );
860
+ } catch (error2) {
861
+ ctx.context.logger.error(error2);
862
+ return ctx.json(null, { status: 500 });
863
+ }
864
+ }
865
+ );
866
+ var getSessionFromCtx = async (ctx) => {
867
+ const session = await getSession()({
868
+ ...ctx,
869
+ //@ts-expect-error: By default since this request context comes from a router it'll have a `router` flag which force it to be a request object
870
+ _flag: void 0
871
+ });
872
+ return session;
873
+ };
874
+ var sessionMiddleware = createAuthMiddleware(async (ctx) => {
875
+ const session = await getSessionFromCtx(ctx);
876
+ if (!session?.session) {
877
+ throw new APIError2("UNAUTHORIZED");
878
+ }
879
+ return {
880
+ session
881
+ };
882
+ });
883
+ var listSessions = () => createAuthEndpoint(
884
+ "/user/list-sessions",
885
+ {
886
+ method: "GET",
887
+ use: [sessionMiddleware],
888
+ requireHeaders: true
889
+ },
890
+ async (ctx) => {
891
+ const sessions = await ctx.context.adapter.findMany({
892
+ model: ctx.context.tables.session.tableName,
893
+ where: [
894
+ {
895
+ field: "userId",
896
+ value: ctx.context.session.user.id
897
+ }
898
+ ]
899
+ });
900
+ const activeSessions = sessions.filter((session) => {
901
+ return session.expiresAt > /* @__PURE__ */ new Date();
902
+ });
903
+ return ctx.json(
904
+ activeSessions
905
+ );
906
+ }
907
+ );
908
+ var revokeSession = createAuthEndpoint(
909
+ "/user/revoke-session",
910
+ {
911
+ method: "POST",
912
+ body: z3.object({
913
+ id: z3.string()
914
+ }),
915
+ use: [sessionMiddleware],
916
+ requireHeaders: true
917
+ },
918
+ async (ctx) => {
919
+ const id = ctx.body.id;
920
+ const findSession = await ctx.context.internalAdapter.findSession(id);
921
+ if (!findSession) {
922
+ return ctx.json(null, { status: 400 });
923
+ }
924
+ if (findSession.session.userId !== ctx.context.session.user.id) {
925
+ return ctx.json(null, { status: 403 });
926
+ }
927
+ try {
928
+ await ctx.context.internalAdapter.deleteSession(id);
929
+ } catch (error2) {
930
+ ctx.context.logger.error(error2);
931
+ return ctx.json(null, { status: 500 });
932
+ }
933
+ return ctx.json({
934
+ status: true
935
+ });
936
+ }
937
+ );
938
+ var revokeSessions = createAuthEndpoint(
939
+ "/user/revoke-sessions",
940
+ {
941
+ method: "POST",
942
+ use: [sessionMiddleware],
943
+ requireHeaders: true
944
+ },
945
+ async (ctx) => {
946
+ try {
947
+ await ctx.context.internalAdapter.deleteSessions(
948
+ ctx.context.session.user.id
949
+ );
950
+ } catch (error2) {
951
+ ctx.context.logger.error(error2);
952
+ return ctx.json(null, { status: 500 });
953
+ }
954
+ return ctx.json({
955
+ status: true
956
+ });
957
+ }
958
+ );
959
+
960
+ // src/api/routes/sign-in.ts
961
+ var signInOAuth = createAuthEndpoint(
962
+ "/sign-in/social",
963
+ {
964
+ method: "POST",
965
+ requireHeaders: true,
966
+ query: z4.object({
967
+ /**
968
+ * Redirect to the current URL after the
969
+ * user has signed in.
970
+ */
971
+ currentURL: z4.string().optional()
972
+ }).optional(),
973
+ body: z4.object({
974
+ /**
975
+ * Callback URL to redirect to after the user has signed in.
976
+ */
977
+ callbackURL: z4.string().optional(),
978
+ /**
979
+ * OAuth2 provider to use`
980
+ */
981
+ provider: z4.enum(oAuthProviderList),
982
+ /**
983
+ * If this is true the session will only be valid for the current browser session
984
+ */
985
+ dontRememberMe: z4.boolean().default(false).optional()
986
+ })
987
+ },
988
+ async (c) => {
989
+ const provider = c.context.socialProviders.find(
990
+ (p) => p.id === c.body.provider
991
+ );
992
+ if (!provider) {
993
+ c.context.logger.error(
994
+ "Provider not found. Make sure to add the provider to your auth config",
995
+ {
996
+ provider: c.body.provider
997
+ }
998
+ );
999
+ throw new APIError3("NOT_FOUND", {
1000
+ message: "Provider not found"
1001
+ });
1002
+ }
1003
+ const cookie = c.context.authCookies;
1004
+ const currentURL = c.query?.currentURL ? new URL(c.query?.currentURL) : null;
1005
+ const callbackURL = c.body.callbackURL?.startsWith("http") ? c.body.callbackURL : `${currentURL?.origin}${c.body.callbackURL || ""}`;
1006
+ const state = generateState(
1007
+ callbackURL || currentURL?.origin || c.context.baseURL,
1008
+ c.query?.currentURL
1009
+ );
1010
+ try {
1011
+ await c.setSignedCookie(
1012
+ cookie.state.name,
1013
+ state.code,
1014
+ c.context.secret,
1015
+ cookie.state.options
1016
+ );
1017
+ const codeVerifier = generateCodeVerifier();
1018
+ await c.setSignedCookie(
1019
+ cookie.pkCodeVerifier.name,
1020
+ codeVerifier,
1021
+ c.context.secret,
1022
+ cookie.pkCodeVerifier.options
1023
+ );
1024
+ const url = provider.createAuthorizationURL({
1025
+ state: state.state,
1026
+ codeVerifier
1027
+ });
1028
+ url.searchParams.set(
1029
+ "redirect_uri",
1030
+ `${c.context.baseURL}/callback/${c.body.provider}`
1031
+ );
1032
+ return {
1033
+ url: url.toString(),
1034
+ state: state.state,
1035
+ codeVerifier,
1036
+ redirect: true
1037
+ };
1038
+ } catch (e) {
1039
+ throw new APIError3("INTERNAL_SERVER_ERROR");
1040
+ }
1041
+ }
1042
+ );
1043
+ var signInEmail = createAuthEndpoint(
1044
+ "/sign-in/email",
1045
+ {
1046
+ method: "POST",
1047
+ body: z4.object({
1048
+ email: z4.string().email(),
1049
+ password: z4.string(),
1050
+ callbackURL: z4.string().optional(),
1051
+ /**
1052
+ * If this is true the session will only be valid for the current browser session
1053
+ * @default false
1054
+ */
1055
+ dontRememberMe: z4.boolean().default(false).optional()
1056
+ })
1057
+ },
1058
+ async (ctx) => {
1059
+ if (!ctx.context.options?.emailAndPassword?.enabled) {
1060
+ ctx.context.logger.error(
1061
+ "Email and password is not enabled. Make sure to enable it in the options on you `auth.ts` file. Check `https://better-auth.com/docs/authentication/email-password` for more!"
1062
+ );
1063
+ throw new APIError3("BAD_REQUEST", {
1064
+ message: "Email and password is not enabled"
1065
+ });
1066
+ }
1067
+ const currentSession = await getSessionFromCtx(ctx);
1068
+ if (currentSession) {
1069
+ await ctx.context.internalAdapter.deleteSession(
1070
+ currentSession.session.id
1071
+ );
1072
+ }
1073
+ const { email, password } = ctx.body;
1074
+ const checkEmail = z4.string().email().safeParse(email);
1075
+ if (!checkEmail.success) {
1076
+ throw new APIError3("BAD_REQUEST", {
1077
+ message: "Invalid email"
1078
+ });
1079
+ }
1080
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1081
+ if (!user) {
1082
+ await ctx.context.password.hash(password);
1083
+ ctx.context.logger.error("User not found", { email });
1084
+ throw new APIError3("UNAUTHORIZED", {
1085
+ message: "Invalid email or password"
1086
+ });
1087
+ }
1088
+ const credentialAccount = user.accounts.find(
1089
+ (a) => a.providerId === "credential"
1090
+ );
1091
+ if (!credentialAccount) {
1092
+ ctx.context.logger.error("Credential account not found", { email });
1093
+ throw new APIError3("UNAUTHORIZED", {
1094
+ message: "Invalid email or password"
1095
+ });
1096
+ }
1097
+ const currentPassword = credentialAccount?.password;
1098
+ if (!currentPassword) {
1099
+ ctx.context.logger.error("Password not found", { email });
1100
+ throw new APIError3("UNAUTHORIZED", {
1101
+ message: "Unexpected error"
1102
+ });
1103
+ }
1104
+ const validPassword = await ctx.context.password.verify(
1105
+ currentPassword,
1106
+ password
1107
+ );
1108
+ if (!validPassword) {
1109
+ ctx.context.logger.error("Invalid password");
1110
+ throw new APIError3("UNAUTHORIZED", {
1111
+ message: "Invalid email or password"
1112
+ });
1113
+ }
1114
+ const session = await ctx.context.internalAdapter.createSession(
1115
+ user.user.id,
1116
+ ctx.headers,
1117
+ ctx.body.dontRememberMe
1118
+ );
1119
+ if (!session) {
1120
+ ctx.context.logger.error("Failed to create session");
1121
+ throw new APIError3("INTERNAL_SERVER_ERROR");
1122
+ }
1123
+ await setSessionCookie(ctx, session.id, ctx.body.dontRememberMe);
1124
+ return ctx.json({
1125
+ user: user.user,
1126
+ session,
1127
+ redirect: !!ctx.body.callbackURL,
1128
+ url: ctx.body.callbackURL
1129
+ });
1130
+ }
1131
+ );
1132
+
1133
+ // src/api/routes/callback.ts
1134
+ import { APIError as APIError4 } from "better-call";
1135
+ import { z as z6 } from "zod";
1136
+
1137
+ // src/db/schema.ts
1138
+ import { z as z5 } from "zod";
1139
+ var accountSchema = z5.object({
1140
+ id: z5.string(),
1141
+ providerId: z5.string(),
1142
+ accountId: z5.string(),
1143
+ userId: z5.string(),
1144
+ accessToken: z5.string().nullable().optional(),
1145
+ refreshToken: z5.string().nullable().optional(),
1146
+ idToken: z5.string().nullable().optional(),
1147
+ /**
1148
+ * Access token expires at
1149
+ */
1150
+ expiresAt: z5.date().nullable().optional(),
1151
+ /**
1152
+ * Password is only stored in the credential provider
1153
+ */
1154
+ password: z5.string().optional().nullable()
1155
+ });
1156
+ var userSchema = z5.object({
1157
+ id: z5.string(),
1158
+ email: z5.string().transform((val) => val.toLowerCase()),
1159
+ emailVerified: z5.boolean().default(false),
1160
+ name: z5.string(),
1161
+ image: z5.string().optional(),
1162
+ createdAt: z5.date().default(/* @__PURE__ */ new Date()),
1163
+ updatedAt: z5.date().default(/* @__PURE__ */ new Date())
1164
+ });
1165
+ var sessionSchema = z5.object({
1166
+ id: z5.string(),
1167
+ userId: z5.string(),
1168
+ expiresAt: z5.date(),
1169
+ ipAddress: z5.string().optional(),
1170
+ userAgent: z5.string().optional()
1171
+ });
1172
+
1173
+ // src/utils/id.ts
1174
+ import { alphabet, generateRandomString } from "oslo/crypto";
1175
+ var generateId = () => {
1176
+ return generateRandomString(36, alphabet("a-z", "0-9"));
1177
+ };
1178
+
1179
+ // src/utils/hide-metadata.ts
1180
+ var HIDE_METADATA = {
1181
+ isAction: false
1182
+ };
1183
+
1184
+ // src/utils/getAccount.ts
1185
+ function getAccountTokens(tokens) {
1186
+ const accessToken = tokens.accessToken();
1187
+ let refreshToken = tokens.hasRefreshToken() ? tokens.refreshToken() : void 0;
1188
+ let accessTokenExpiresAt = void 0;
1189
+ try {
1190
+ accessTokenExpiresAt = tokens.accessTokenExpiresAt();
1191
+ } catch {
1192
+ }
1193
+ return {
1194
+ accessToken,
1195
+ refreshToken,
1196
+ expiresAt: accessTokenExpiresAt
1197
+ };
1198
+ }
1199
+
1200
+ // src/api/routes/callback.ts
1201
+ var callbackOAuth = createAuthEndpoint(
1202
+ "/callback/:id",
1203
+ {
1204
+ method: "GET",
1205
+ query: z6.object({
1206
+ state: z6.string(),
1207
+ code: z6.string().optional(),
1208
+ error: z6.string().optional()
1209
+ }),
1210
+ metadata: HIDE_METADATA
1211
+ },
1212
+ async (c) => {
1213
+ if (c.query.error || !c.query.code) {
1214
+ const parsedState2 = parseState(c.query.state);
1215
+ const callbackURL2 = parsedState2.data?.callbackURL || `${c.context.baseURL}/error`;
1216
+ c.context.logger.error(c.query.error, c.params.id);
1217
+ throw c.redirect(
1218
+ `${callbackURL2}?error=${c.query.error || "oAuth_code_missing"}`
1219
+ );
1220
+ }
1221
+ const provider = c.context.socialProviders.find(
1222
+ (p) => p.id === c.params.id
1223
+ );
1224
+ if (!provider) {
1225
+ c.context.logger.error(
1226
+ "Oauth provider with id",
1227
+ c.params.id,
1228
+ "not found"
1229
+ );
1230
+ throw c.redirect(
1231
+ `${c.context.baseURL}/error?error=oauth_provider_not_found`
1232
+ );
1233
+ }
1234
+ const codeVerifier = await c.getSignedCookie(
1235
+ c.context.authCookies.pkCodeVerifier.name,
1236
+ c.context.secret
1237
+ );
1238
+ let tokens;
1239
+ try {
1240
+ tokens = await provider.validateAuthorizationCode(
1241
+ c.query.code,
1242
+ codeVerifier,
1243
+ `${c.context.baseURL}/callback/${provider.id}`
1244
+ );
1245
+ } catch (e) {
1246
+ c.context.logger.error(e);
1247
+ throw c.redirect(
1248
+ `${c.context.baseURL}/error?error=oauth_code_verification_failed`
1249
+ );
1250
+ }
1251
+ const user = await provider.getUserInfo(tokens).then((res) => res?.user);
1252
+ const id = generateId();
1253
+ const data = userSchema.safeParse({
1254
+ ...user,
1255
+ id
1256
+ });
1257
+ const parsedState = parseState(c.query.state);
1258
+ if (!parsedState.success) {
1259
+ c.context.logger.error("Unable to parse state");
1260
+ throw c.redirect(
1261
+ `${c.context.baseURL}/error?error=invalid_state_parameter`
1262
+ );
1263
+ }
1264
+ const { callbackURL, currentURL, dontRememberMe } = parsedState.data;
1265
+ if (!user || data.success === false) {
1266
+ throw c.redirect(
1267
+ `${c.context.baseURL}/error?error=oauth_validation_failed`
1268
+ );
1269
+ }
1270
+ if (!callbackURL) {
1271
+ throw c.redirect(
1272
+ `${c.context.baseURL}/error?error=oauth_callback_url_not_found`
1273
+ );
1274
+ }
1275
+ const dbUser = await c.context.internalAdapter.findUserByEmail(user.email);
1276
+ const userId = dbUser?.user.id;
1277
+ if (dbUser) {
1278
+ const hasBeenLinked = dbUser.accounts.find(
1279
+ (a) => a.providerId === provider.id
1280
+ );
1281
+ const trustedProviders = c.context.options.account?.accountLinking?.trustedProviders;
1282
+ const isTrustedProvider = trustedProviders ? trustedProviders.includes(provider.id) : true;
1283
+ if (!hasBeenLinked && (!user.emailVerified || !isTrustedProvider)) {
1284
+ let url;
1285
+ try {
1286
+ url = new URL(currentURL || callbackURL);
1287
+ url.searchParams.set("error", "account_not_linked");
1288
+ } catch (e) {
1289
+ throw c.redirect(
1290
+ `${c.context.baseURL}/error?error=account_not_linked`
1291
+ );
1292
+ }
1293
+ throw c.redirect(url.toString());
1294
+ }
1295
+ if (!hasBeenLinked) {
1296
+ try {
1297
+ await c.context.internalAdapter.linkAccount({
1298
+ providerId: provider.id,
1299
+ accountId: user.id,
1300
+ id: `${provider.id}:${user.id}`,
1301
+ userId: dbUser.user.id,
1302
+ ...getAccountTokens(tokens)
1303
+ });
1304
+ } catch (e) {
1305
+ console.log(e);
1306
+ throw c.redirect(
1307
+ `${c.context.baseURL}/error?error=failed_linking_account`
1308
+ );
1309
+ }
1310
+ }
1311
+ } else {
1312
+ try {
1313
+ await c.context.internalAdapter.createOAuthUser(data.data, {
1314
+ ...getAccountTokens(tokens),
1315
+ id: `${provider.id}:${user.id}`,
1316
+ providerId: provider.id,
1317
+ accountId: user.id,
1318
+ userId: id
1319
+ });
1320
+ } catch (e) {
1321
+ const url = new URL(currentURL || callbackURL);
1322
+ url.searchParams.set("error", "unable_to_create_user");
1323
+ c.setHeader("Location", url.toString());
1324
+ throw c.redirect(url.toString());
1325
+ }
1326
+ }
1327
+ if (!userId && !id)
1328
+ throw new APIError4("INTERNAL_SERVER_ERROR", {
1329
+ message: "Unable to create user"
1330
+ });
1331
+ const session = await c.context.internalAdapter.createSession(
1332
+ userId || id,
1333
+ c.request,
1334
+ dontRememberMe
1335
+ );
1336
+ if (!session) {
1337
+ const url = new URL(currentURL || callbackURL);
1338
+ url.searchParams.set("error", "unable_to_create_session");
1339
+ throw c.redirect(url.toString());
1340
+ }
1341
+ try {
1342
+ await setSessionCookie(c, session.id, dontRememberMe);
1343
+ } catch (e) {
1344
+ c.context.logger.error("Unable to set session cookie", e);
1345
+ const url = new URL(currentURL || callbackURL);
1346
+ url.searchParams.set("error", "unable_to_create_session");
1347
+ throw c.redirect(url.toString());
1348
+ }
1349
+ throw c.redirect(callbackURL);
1350
+ }
1351
+ );
1352
+
1353
+ // src/api/routes/sign-out.ts
1354
+ import { z as z7 } from "zod";
1355
+ var signOut = createAuthEndpoint(
1356
+ "/sign-out",
1357
+ {
1358
+ method: "POST",
1359
+ body: z7.optional(
1360
+ z7.object({
1361
+ callbackURL: z7.string().optional()
1362
+ })
1363
+ )
1364
+ },
1365
+ async (ctx) => {
1366
+ const sessionCookieToken = await ctx.getSignedCookie(
1367
+ ctx.context.authCookies.sessionToken.name,
1368
+ ctx.context.secret
1369
+ );
1370
+ if (!sessionCookieToken) {
1371
+ return ctx.json(null);
1372
+ }
1373
+ await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
1374
+ deleteSessionCookie(ctx);
1375
+ return ctx.json(null, {
1376
+ body: {
1377
+ redirect: !!ctx.body?.callbackURL,
1378
+ url: ctx.body?.callbackURL
1379
+ }
1380
+ });
1381
+ }
1382
+ );
1383
+
1384
+ // src/api/routes/forget-password.ts
1385
+ import { TimeSpan as TimeSpan2 } from "oslo";
1386
+ import { createJWT, parseJWT as parseJWT3 } from "oslo/jwt";
1387
+ import { validateJWT } from "oslo/jwt";
1388
+ import { z as z8 } from "zod";
1389
+ var forgetPassword = createAuthEndpoint(
1390
+ "/forget-password",
1391
+ {
1392
+ method: "POST",
1393
+ body: z8.object({
1394
+ /**
1395
+ * The email address of the user to send a password reset email to.
1396
+ */
1397
+ email: z8.string().email(),
1398
+ /**
1399
+ * The URL to redirect the user to reset their password.
1400
+ * If the token isn't valid or expired, it'll be redirected with a query parameter `?
1401
+ * error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?
1402
+ * token=VALID_TOKEN
1403
+ */
1404
+ redirectTo: z8.string()
1405
+ })
1406
+ },
1407
+ async (ctx) => {
1408
+ if (!ctx.context.options.emailAndPassword?.sendResetPassword) {
1409
+ ctx.context.logger.error(
1410
+ "Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!"
1411
+ );
1412
+ return ctx.json(null, {
1413
+ status: 400,
1414
+ statusText: "RESET_PASSWORD_EMAIL_NOT_SENT",
1415
+ body: {
1416
+ message: "Reset password isn't enabled"
1417
+ }
1418
+ });
1419
+ }
1420
+ const { email } = ctx.body;
1421
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1422
+ if (!user) {
1423
+ return ctx.json(
1424
+ {
1425
+ status: false
1426
+ },
1427
+ {
1428
+ body: {
1429
+ status: true
1430
+ }
1431
+ }
1432
+ );
1433
+ }
1434
+ const token = await createJWT(
1435
+ "HS256",
1436
+ Buffer.from(ctx.context.secret),
1437
+ {
1438
+ email: user.user.email,
1439
+ redirectTo: ctx.body.redirectTo
1440
+ },
1441
+ {
1442
+ expiresIn: new TimeSpan2(1, "h"),
1443
+ issuer: "better-auth",
1444
+ subject: "forget-password",
1445
+ audiences: [user.user.email],
1446
+ includeIssuedTimestamp: true
1447
+ }
1448
+ );
1449
+ const url = `${ctx.context.baseURL}/reset-password/${token}`;
1450
+ await ctx.context.options.emailAndPassword.sendResetPassword(
1451
+ url,
1452
+ user.user
1453
+ );
1454
+ return ctx.json({
1455
+ status: true
1456
+ });
1457
+ }
1458
+ );
1459
+ var forgetPasswordCallback = createAuthEndpoint(
1460
+ "/reset-password/:token",
1461
+ {
1462
+ method: "GET"
1463
+ },
1464
+ async (ctx) => {
1465
+ const { token } = ctx.params;
1466
+ let decodedToken;
1467
+ const schema = z8.object({
1468
+ email: z8.string(),
1469
+ redirectTo: z8.string()
1470
+ });
1471
+ try {
1472
+ decodedToken = await validateJWT(
1473
+ "HS256",
1474
+ Buffer.from(ctx.context.secret),
1475
+ token
1476
+ );
1477
+ if (!decodedToken.expiresAt || decodedToken.expiresAt < /* @__PURE__ */ new Date()) {
1478
+ throw Error("Token expired");
1479
+ }
1480
+ } catch (e) {
1481
+ const decoded = parseJWT3(token);
1482
+ const jwt = schema.safeParse(decoded?.payload);
1483
+ if (jwt.success) {
1484
+ throw ctx.redirect(`${jwt.data?.redirectTo}?error=invalid_token`);
1485
+ } else {
1486
+ throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_token`);
1487
+ }
1488
+ }
1489
+ const { redirectTo } = schema.parse(decodedToken.payload);
1490
+ throw ctx.redirect(`${redirectTo}?token=${token}`);
1491
+ }
1492
+ );
1493
+ var resetPassword = createAuthEndpoint(
1494
+ "/reset-password",
1495
+ {
1496
+ method: "POST",
1497
+ query: z8.object({
1498
+ currentURL: z8.string()
1499
+ }).optional(),
1500
+ body: z8.object({
1501
+ newPassword: z8.string(),
1502
+ callbackURL: z8.string().optional()
1503
+ })
1504
+ },
1505
+ async (ctx) => {
1506
+ const token = ctx.query?.currentURL.split("?token=")[1];
1507
+ if (!token) {
1508
+ return ctx.json(
1509
+ {
1510
+ error: "Invalid token",
1511
+ data: null
1512
+ },
1513
+ {
1514
+ status: 400,
1515
+ statusText: "INVALID_TOKEN",
1516
+ body: {
1517
+ message: "Invalid token"
1518
+ }
1519
+ }
1520
+ );
1521
+ }
1522
+ const { newPassword } = ctx.body;
1523
+ try {
1524
+ const jwt = await validateJWT(
1525
+ "HS256",
1526
+ Buffer.from(ctx.context.secret),
1527
+ token
1528
+ );
1529
+ const email = z8.string().email().parse(jwt.payload.email);
1530
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1531
+ if (!user) {
1532
+ return ctx.json(
1533
+ {
1534
+ error: "User not found",
1535
+ data: null
1536
+ },
1537
+ {
1538
+ status: 400,
1539
+ body: {
1540
+ message: "failed to reset password"
1541
+ }
1542
+ }
1543
+ );
1544
+ }
1545
+ if (newPassword.length < (ctx.context.options.emailAndPassword?.minPasswordLength || 8) || newPassword.length > (ctx.context.options.emailAndPassword?.maxPasswordLength || 32)) {
1546
+ return ctx.json(
1547
+ {
1548
+ data: null,
1549
+ error: "password is too short or too long"
1550
+ },
1551
+ {
1552
+ status: 400,
1553
+ statusText: "INVALID_PASSWORD_LENGTH",
1554
+ body: {
1555
+ message: "password is too short or too long"
1556
+ }
1557
+ }
1558
+ );
1559
+ }
1560
+ const hashedPassword = await ctx.context.password.hash(newPassword);
1561
+ const updatedUser = await ctx.context.internalAdapter.updatePassword(
1562
+ user.user.id,
1563
+ hashedPassword
1564
+ );
1565
+ if (!updatedUser) {
1566
+ return ctx.json(null, {
1567
+ status: 400,
1568
+ statusText: "USER_NOT_FOUND",
1569
+ body: {
1570
+ message: "User doesn't have a credential account"
1571
+ }
1572
+ });
1573
+ }
1574
+ return ctx.json(
1575
+ {
1576
+ error: null,
1577
+ data: {
1578
+ status: true,
1579
+ url: ctx.body.callbackURL,
1580
+ redirect: !!ctx.body.callbackURL
1581
+ }
1582
+ },
1583
+ {
1584
+ body: {
1585
+ status: true,
1586
+ url: ctx.body.callbackURL,
1587
+ redirect: !!ctx.body.callbackURL
1588
+ }
1589
+ }
1590
+ );
1591
+ } catch (e) {
1592
+ console.log(e);
1593
+ return ctx.json(
1594
+ {
1595
+ error: "Invalid token",
1596
+ data: null
1597
+ },
1598
+ {
1599
+ status: 400,
1600
+ statusText: "INVALID_TOKEN",
1601
+ body: {
1602
+ message: "Invalid token"
1603
+ }
1604
+ }
1605
+ );
1606
+ }
1607
+ }
1608
+ );
1609
+
1610
+ // src/api/routes/verify-email.ts
1611
+ import { TimeSpan as TimeSpan3 } from "oslo";
1612
+ import { createJWT as createJWT2, validateJWT as validateJWT2 } from "oslo/jwt";
1613
+ import { z as z9 } from "zod";
1614
+ async function createEmailVerificationToken(secret, email) {
1615
+ const token = await createJWT2(
1616
+ "HS256",
1617
+ Buffer.from(secret),
1618
+ {
1619
+ email: email.toLowerCase()
1620
+ },
1621
+ {
1622
+ expiresIn: new TimeSpan3(1, "h"),
1623
+ issuer: "better-auth",
1624
+ subject: "verify-email",
1625
+ audiences: [email],
1626
+ includeIssuedTimestamp: true
1627
+ }
1628
+ );
1629
+ return token;
1630
+ }
1631
+ var sendVerificationEmail = createAuthEndpoint(
1632
+ "/send-verification-email",
1633
+ {
1634
+ method: "POST",
1635
+ query: z9.object({
1636
+ currentURL: z9.string().optional()
1637
+ }).optional(),
1638
+ body: z9.object({
1639
+ email: z9.string().email(),
1640
+ callbackURL: z9.string().optional()
1641
+ })
1642
+ },
1643
+ async (ctx) => {
1644
+ if (!ctx.context.options.emailAndPassword?.sendVerificationEmail) {
1645
+ ctx.context.logger.error(
1646
+ "Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it."
1647
+ );
1648
+ return ctx.json(null, {
1649
+ status: 400,
1650
+ statusText: "VERIFICATION_EMAIL_NOT_SENT",
1651
+ body: {
1652
+ message: "Verification email isn't enabled"
1653
+ }
1654
+ });
1655
+ }
1656
+ const { email } = ctx.body;
1657
+ const token = await createEmailVerificationToken(ctx.context.secret, email);
1658
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || ctx.query?.currentURL || "/"}`;
1659
+ await ctx.context.options.emailAndPassword.sendVerificationEmail(
1660
+ email,
1661
+ url,
1662
+ token
1663
+ );
1664
+ return ctx.json({
1665
+ status: true
1666
+ });
1667
+ }
1668
+ );
1669
+ var verifyEmail = createAuthEndpoint(
1670
+ "/verify-email",
1671
+ {
1672
+ method: "GET",
1673
+ query: z9.object({
1674
+ token: z9.string(),
1675
+ callbackURL: z9.string().optional()
1676
+ })
1677
+ },
1678
+ async (ctx) => {
1679
+ const { token } = ctx.query;
1680
+ let jwt;
1681
+ try {
1682
+ jwt = await validateJWT2("HS256", Buffer.from(ctx.context.secret), token);
1683
+ } catch (e) {
1684
+ ctx.context.logger.error("Failed to verify email", e);
1685
+ return ctx.json(null, {
1686
+ status: 400,
1687
+ statusText: "INVALID_TOKEN",
1688
+ body: {
1689
+ message: "Invalid token"
1690
+ }
1691
+ });
1692
+ }
1693
+ const schema = z9.object({
1694
+ email: z9.string().email()
1695
+ });
1696
+ const parsed = schema.parse(jwt.payload);
1697
+ const user = await ctx.context.internalAdapter.findUserByEmail(
1698
+ parsed.email
1699
+ );
1700
+ if (!user) {
1701
+ return ctx.json(null, {
1702
+ status: 400,
1703
+ statusText: "USER_NOT_FOUND",
1704
+ body: {
1705
+ message: "User not found"
1706
+ }
1707
+ });
1708
+ }
1709
+ const account = user.accounts.find((a) => a.providerId === "credential");
1710
+ if (!account) {
1711
+ throw ctx.redirect;
1712
+ }
1713
+ await ctx.context.internalAdapter.updateUserByEmail(parsed.email, {
1714
+ emailVerified: true
1715
+ });
1716
+ if (ctx.query.callbackURL) {
1717
+ console.log("Redirecting to", ctx.query.callbackURL);
1718
+ throw ctx.redirect("/");
1719
+ }
1720
+ return ctx.json({
1721
+ status: true
1722
+ });
1723
+ }
1724
+ );
1725
+
1726
+ // src/api/routes/update-user.ts
1727
+ import { z as z10 } from "zod";
1728
+ import { alphabet as alphabet2, generateRandomString as generateRandomString2 } from "oslo/crypto";
1729
+ import "better-call";
1730
+ var updateUser = createAuthEndpoint(
1731
+ "/user/update",
1732
+ {
1733
+ method: "POST",
1734
+ body: z10.object({
1735
+ name: z10.string().optional(),
1736
+ image: z10.string().optional()
1737
+ }),
1738
+ use: [sessionMiddleware]
1739
+ },
1740
+ async (ctx) => {
1741
+ const { name, image } = ctx.body;
1742
+ const session = ctx.context.session;
1743
+ if (!image && !name) {
1744
+ return ctx.json(session.user);
1745
+ }
1746
+ const user = await ctx.context.internalAdapter.updateUserByEmail(
1747
+ session.user.email,
1748
+ {
1749
+ name,
1750
+ image
1751
+ }
1752
+ );
1753
+ return ctx.json(user);
1754
+ }
1755
+ );
1756
+ var changePassword = createAuthEndpoint(
1757
+ "/user/change-password",
1758
+ {
1759
+ method: "POST",
1760
+ body: z10.object({
1761
+ /**
1762
+ * The new password to set
1763
+ */
1764
+ newPassword: z10.string(),
1765
+ /**
1766
+ * The current password of the user
1767
+ */
1768
+ currentPassword: z10.string(),
1769
+ /**
1770
+ * revoke all sessions that are not the
1771
+ * current one logged in by the user
1772
+ */
1773
+ revokeOtherSessions: z10.boolean().optional()
1774
+ }),
1775
+ use: [sessionMiddleware]
1776
+ },
1777
+ async (ctx) => {
1778
+ const { newPassword, currentPassword, revokeOtherSessions } = ctx.body;
1779
+ const session = ctx.context.session;
1780
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
1781
+ if (newPassword.length < minPasswordLength) {
1782
+ ctx.context.logger.error("Password is too short");
1783
+ return ctx.json(null, {
1784
+ status: 400,
1785
+ body: { message: "Password is too short" }
1786
+ });
1787
+ }
1788
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
1789
+ if (newPassword.length > maxPasswordLength) {
1790
+ ctx.context.logger.error("Password is too long");
1791
+ return ctx.json(null, {
1792
+ status: 400,
1793
+ body: { message: "Password is too long" }
1794
+ });
1795
+ }
1796
+ const accounts = await ctx.context.internalAdapter.findAccounts(
1797
+ session.user.id
1798
+ );
1799
+ const account = accounts.find(
1800
+ (account2) => account2.providerId === "credential" && account2.password
1801
+ );
1802
+ if (!account || !account.password) {
1803
+ return ctx.json(null, {
1804
+ status: 400,
1805
+ body: { message: "User does not have a password" }
1806
+ });
1807
+ }
1808
+ const passwordHash = await ctx.context.password.hash(newPassword);
1809
+ const verify = await ctx.context.password.verify(
1810
+ account.password,
1811
+ currentPassword
1812
+ );
1813
+ if (!verify) {
1814
+ return ctx.json(null, {
1815
+ status: 400,
1816
+ body: { message: "Invalid password" }
1817
+ });
1818
+ }
1819
+ await ctx.context.internalAdapter.updateAccount(account.id, {
1820
+ password: passwordHash
1821
+ });
1822
+ if (revokeOtherSessions) {
1823
+ await ctx.context.internalAdapter.deleteSessions(session.user.id);
1824
+ const newSession = await ctx.context.internalAdapter.createSession(
1825
+ session.user.id,
1826
+ ctx.headers
1827
+ );
1828
+ if (!newSession) {
1829
+ return ctx.json(null, {
1830
+ status: 500,
1831
+ body: { message: "Failed to create session" }
1832
+ });
1833
+ }
1834
+ await setSessionCookie(ctx, newSession.id);
1835
+ }
1836
+ return ctx.json(session.user);
1837
+ }
1838
+ );
1839
+ var setPassword = createAuthEndpoint(
1840
+ "/user/set-password",
1841
+ {
1842
+ method: "POST",
1843
+ body: z10.object({
1844
+ /**
1845
+ * The new password to set
1846
+ */
1847
+ newPassword: z10.string()
1848
+ }),
1849
+ use: [sessionMiddleware]
1850
+ },
1851
+ async (ctx) => {
1852
+ const { newPassword } = ctx.body;
1853
+ const session = ctx.context.session;
1854
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
1855
+ if (newPassword.length < minPasswordLength) {
1856
+ ctx.context.logger.error("Password is too short");
1857
+ return ctx.json(null, {
1858
+ status: 400,
1859
+ body: { message: "Password is too short" }
1860
+ });
1861
+ }
1862
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
1863
+ if (newPassword.length > maxPasswordLength) {
1864
+ ctx.context.logger.error("Password is too long");
1865
+ return ctx.json(null, {
1866
+ status: 400,
1867
+ body: { message: "Password is too long" }
1868
+ });
1869
+ }
1870
+ const accounts = await ctx.context.internalAdapter.findAccounts(
1871
+ session.user.id
1872
+ );
1873
+ const account = accounts.find(
1874
+ (account2) => account2.providerId === "credential" && account2.password
1875
+ );
1876
+ const passwordHash = await ctx.context.password.hash(newPassword);
1877
+ if (!account) {
1878
+ await ctx.context.internalAdapter.linkAccount({
1879
+ id: generateRandomString2(32, alphabet2("a-z", "0-9", "A-Z")),
1880
+ userId: session.user.id,
1881
+ providerId: "credential",
1882
+ accountId: session.user.id,
1883
+ password: passwordHash
1884
+ });
1885
+ return ctx.json(session.user);
1886
+ }
1887
+ return ctx.json(null, {
1888
+ status: 400,
1889
+ body: { message: "User already has a password" }
1890
+ });
1891
+ }
1892
+ );
1893
+
1894
+ // src/api/routes/csrf.ts
1895
+ import { alphabet as alphabet3, generateRandomString as generateRandomString3 } from "oslo/crypto";
1896
+ var getCSRFToken = createAuthEndpoint(
1897
+ "/csrf",
1898
+ {
1899
+ method: "GET",
1900
+ metadata: HIDE_METADATA
1901
+ },
1902
+ async (ctx) => {
1903
+ const csrfToken = await ctx.getSignedCookie(
1904
+ ctx.context.authCookies.csrfToken.name,
1905
+ ctx.context.secret
1906
+ );
1907
+ if (csrfToken) {
1908
+ return {
1909
+ csrfToken
1910
+ };
1911
+ }
1912
+ const token = generateRandomString3(32, alphabet3("a-z", "0-9", "A-Z"));
1913
+ const hash = await hs256(ctx.context.secret, token);
1914
+ const cookie = `${token}!${hash}`;
1915
+ await ctx.setSignedCookie(
1916
+ ctx.context.authCookies.csrfToken.name,
1917
+ cookie,
1918
+ ctx.context.secret,
1919
+ ctx.context.authCookies.csrfToken.options
1920
+ );
1921
+ return {
1922
+ csrfToken: token
1923
+ };
1924
+ }
1925
+ );
1926
+
1927
+ // src/api/routes/error.ts
1928
+ var html = (errorCode = "Unknown") => `<!DOCTYPE html>
3
1929
  <html lang="en">
4
1930
  <head>
5
1931
  <meta charset="UTF-8">
@@ -76,7 +2002,535 @@ import{APIError as Et,createRouter as Ot}from"better-call";import{APIError as Q}
76
2002
  <h1>Better Auth Error</h1>
77
2003
  <p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
78
2004
  <a href="/" id="returnLink" class="btn">Return to Application</a>
79
- <div class="error-code">Error Code: <span id="errorCode">${e}</span></div>
2005
+ <div class="error-code">Error Code: <span id="errorCode">${errorCode}</span></div>
80
2006
  </div>
81
2007
  </body>
82
- </html>`,Oe=l("/error",{method:"GET",metadata:_},async e=>{let t=new URL(e.request?.url||"").searchParams.get("error")||"Unknown";return new Response(Pt(t),{headers:{"Content-Type":"text/html"}})});var Ce=l("/ok",{method:"GET",metadata:_},async e=>e.json({ok:!0}));import{alphabet as je,generateRandomString as De}from"oslo/crypto";import{z as S}from"zod";var $e=l("/sign-up/email",{method:"POST",query:S.object({currentURL:S.string().optional()}).optional(),body:S.object({name:S.string(),email:S.string(),password:S.string(),image:S.string().optional(),callbackURL:S.string().optional()})},async e=>{if(!e.context.options.emailAndPassword?.enabled)return e.json(null,{status:400,body:{message:"Email and password is not enabled"}});let{name:t,email:o,password:r,image:n}=e.body;if(!S.string().email().safeParse(o).success)return e.json(null,{status:400,body:{message:"Invalid email address"}});let i=e.context.password.config.minPasswordLength;if(r.length<i)return e.context.logger.error("Password is too short"),e.json(null,{status:400,body:{message:"Password is too short"}});let a=e.context.password.config.maxPasswordLength;if(r.length>a)return e.context.logger.error("Password is too long"),e.json(null,{status:400,body:{message:"Password is too long"}});let d=await e.context.internalAdapter.findUserByEmail(o),u=await e.context.password.hash(r);if(d?.user)return e.json(null,{status:400,body:{message:"User already exists"}});let c=await e.context.internalAdapter.createUser({id:De(32,je("a-z","0-9","A-Z")),email:o.toLowerCase(),name:t,image:n,emailVerified:!1,createdAt:new Date,updatedAt:new Date});if(!c)return e.json(null,{status:400,body:{message:"Could not create user"}});await e.context.internalAdapter.linkAccount({id:De(32,je("a-z","0-9","A-Z")),userId:c.id,providerId:"credential",accountId:c.id,password:u});let m=await e.context.internalAdapter.createSession(c.id,e.request);if(!m)return e.json(null,{status:400,body:{message:"Could not create session"}});if(await U(e,m.id),e.context.options.emailAndPassword.sendEmailVerificationOnSignUp){let g=await W(e.context.secret,c.email),w=`${e.context.baseURL}/verify-email?token=${g}&callbackURL=${e.body.callbackURL||e.query?.currentURL||"/"}`;await e.context.options.emailAndPassword.sendVerificationEmail?.(c.email,w,g)}return e.json({user:c,session:m},{body:e.body.callbackURL?{url:e.body.callbackURL,redirect:!0}:{user:c,session:m}})});import J from"chalk";function vt(e,t,o){let r=Date.now(),n=t*1e3;return r-o.lastRequest<n&&o.count>=e}function xt(e){return new Response(JSON.stringify({message:"Too many requests. Please try again later."}),{status:429,statusText:"Too Many Requests",headers:{"X-Retry-After":e.toString()}})}function St(e,t){let o=Date.now(),r=t*1e3;return Math.ceil((e+r-o)/1e3)}function _t(e,t){let o=t??"rateLimit",r=e.adapter;return{get:async n=>await r.findOne({model:o,where:[{field:"key",value:n}]}),set:async(n,s,i)=>{try{i?await r.update({model:t??"rateLimit",where:[{field:"key",value:n}],update:{count:s.count,lastRequest:s.lastRequest}}):await r.create({model:t??"rateLimit",data:{key:n,count:s.count,lastRequest:s.lastRequest}})}catch(a){P.error("Error setting rate limit",a)}}}}var Be=new Map;function It(e){return e.rateLimit.customStorage?e.rateLimit.customStorage:e.rateLimit.storage==="memory"?{async get(o){return Be.get(o)},async set(o,r,n){Be.set(o,r)}}:_t(e,e.rateLimit.tableName)}async function ze(e,t){if(!t.rateLimit.enabled)return;let o=t.baseURL,r=e.url.replace(o,""),n=t.rateLimit.window,s=t.rateLimit.max,i=$(e)+r,d=Lt().find(g=>g.pathMatcher(r));d&&(n=d.window,s=d.max);for(let g of t.options.plugins||[])if(g.rateLimit){let w=g.rateLimit.find(h=>h.pathMatcher(r));if(w){n=w.window,s=w.max;break}}if(t.rateLimit.customRules){let g=t.rateLimit.customRules[r];g&&(n=g.window,s=g.max)}let u=It(t),c=await u.get(i),m=Date.now();if(!c)await u.set(i,{key:i,count:1,lastRequest:m});else{let g=m-c.lastRequest;if(vt(s,n,c)){let w=St(c.lastRequest,n);return xt(w)}else g>n*1e3?await u.set(i,{...c,count:1,lastRequest:m}):await u.set(i,{...c,count:c.count+1,lastRequest:m})}}function Lt(){return[{pathMatcher(t){return t.startsWith("/sign-in")||t.startsWith("/sign-up")},window:10,max:7}]}function Ct(e,t){let o=t.plugins?.reduce((a,d)=>({...a,...d.endpoints}),{}),r=t.plugins?.map(a=>a.middlewares?.map(d=>{let u=async c=>d.middleware({...c,context:{...e,...c.context}});return u.path=d.path,u.options=d.middleware.options,u.headers=d.middleware.headers,{path:d.path,middleware:u}})).filter(a=>a!==void 0).flat()||[],s={...{signInOAuth:ye,callbackOAuth:ke,getCSRFToken:Ee,getSession:M(),signOut:Re,signUpEmail:$e,signInEmail:we,forgetPassword:Te,resetPassword:ve,verifyEmail:Se,sendVerificationEmail:xe,changePassword:Ie,setPassword:Le,updateUser:_e,forgetPasswordCallback:Pe,listSessions:fe(),revokeSession:ge,revokeSessions:he},...o,ok:Ce,error:Oe},i={};for(let[a,d]of Object.entries(s))i[a]=async u=>{let c=await e,g=await d({...u,context:{...c,...u.context}});for(let w of t.plugins||[])if(w.hooks?.after){for(let h of w.hooks.after)if(h.matcher(u)){let z=Object.assign(u,{context:{...e,returned:g}}),R=await h.handler(z);R&&"response"in R&&(g=R.response)}}return g},i[a].path=d.path,i[a].method=d.method,i[a].options=d.options,i[a].headers=d.headers;return{api:i,middlewares:r}}var Ns=(e,t)=>{let{api:o,middlewares:r}=Ct(e,t),n=new URL(e.baseURL).pathname;return Ot(o,{extraContext:e,basePath:n,routerMiddleware:[{path:"/**",middleware:Y},...r],async onRequest(s){for(let i of e.options.plugins||[])if(i.onRequest){let a=await i.onRequest(s,e);if(a)return a}return ze(s,e)},async onResponse(s){for(let i of e.options.plugins||[])if(i.onResponse){let a=await i.onResponse(s,e);if(a)return a.response}return s},onError(s){let i=t.logger?.verboseLogging?P:void 0;if(t.logger?.disabled!==!0)if(s instanceof Et)i?.warn(s);else if(typeof s=="object"&&s!==null&&"message"in s){let a=s.message;if(!a||typeof a!="string"){i?.error(s);return}a.includes("no such table")?P?.error(`Please run ${J.green("npx better-auth migrate")} to create the tables. There are missing tables in your SQLite database.`):a.includes("relation")&&a.includes("does not exist")?P.error(`Please run ${J.green("npx better-auth migrate")} to create the tables. There are missing tables in your PostgreSQL database.`):a.includes("Table")&&a.includes("doesn't exist")?P?.error(`Please run ${J.green("npx better-auth migrate")} to create the tables. There are missing tables in your MySQL database.`):i?.error(s)}else i?.error(s)}})};export{ke as callbackOAuth,Ie as changePassword,l as createAuthEndpoint,D as createAuthMiddleware,W as createEmailVerificationToken,Y as csrfMiddleware,Oe as error,Te as forgetPassword,Pe as forgetPasswordCallback,Ee as getCSRFToken,Ct as getEndpoints,M as getSession,H as getSessionFromCtx,fe as listSessions,Ce as ok,K as optionsMiddleware,ve as resetPassword,ge as revokeSession,he as revokeSessions,Ns as router,xe as sendVerificationEmail,E as sessionMiddleware,Le as setPassword,we as signInEmail,ye as signInOAuth,Re as signOut,$e as signUpEmail,_e as updateUser,Se as verifyEmail};
2008
+ </html>`;
2009
+ var error = createAuthEndpoint(
2010
+ "/error",
2011
+ {
2012
+ method: "GET",
2013
+ metadata: HIDE_METADATA
2014
+ },
2015
+ async (c) => {
2016
+ const query = new URL(c.request?.url || "").searchParams.get("error") || "Unknown";
2017
+ return new Response(html(query), {
2018
+ headers: {
2019
+ "Content-Type": "text/html"
2020
+ }
2021
+ });
2022
+ }
2023
+ );
2024
+
2025
+ // src/api/routes/ok.ts
2026
+ var ok = createAuthEndpoint(
2027
+ "/ok",
2028
+ {
2029
+ method: "GET",
2030
+ metadata: HIDE_METADATA
2031
+ },
2032
+ async (ctx) => {
2033
+ return ctx.json({
2034
+ ok: true
2035
+ });
2036
+ }
2037
+ );
2038
+
2039
+ // src/api/routes/sign-up.ts
2040
+ import { alphabet as alphabet4, generateRandomString as generateRandomString4 } from "oslo/crypto";
2041
+ import { z as z11 } from "zod";
2042
+ var signUpEmail = createAuthEndpoint(
2043
+ "/sign-up/email",
2044
+ {
2045
+ method: "POST",
2046
+ query: z11.object({
2047
+ currentURL: z11.string().optional()
2048
+ }).optional(),
2049
+ body: z11.object({
2050
+ name: z11.string(),
2051
+ email: z11.string(),
2052
+ password: z11.string(),
2053
+ image: z11.string().optional(),
2054
+ callbackURL: z11.string().optional()
2055
+ })
2056
+ },
2057
+ async (ctx) => {
2058
+ if (!ctx.context.options.emailAndPassword?.enabled) {
2059
+ return ctx.json(null, {
2060
+ status: 400,
2061
+ body: {
2062
+ message: "Email and password is not enabled"
2063
+ }
2064
+ });
2065
+ }
2066
+ const { name, email, password, image } = ctx.body;
2067
+ const isValidEmail = z11.string().email().safeParse(email);
2068
+ if (!isValidEmail.success) {
2069
+ return ctx.json(null, {
2070
+ status: 400,
2071
+ body: {
2072
+ message: "Invalid email address"
2073
+ }
2074
+ });
2075
+ }
2076
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
2077
+ if (password.length < minPasswordLength) {
2078
+ ctx.context.logger.error("Password is too short");
2079
+ return ctx.json(null, {
2080
+ status: 400,
2081
+ body: { message: "Password is too short" }
2082
+ });
2083
+ }
2084
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
2085
+ if (password.length > maxPasswordLength) {
2086
+ ctx.context.logger.error("Password is too long");
2087
+ return ctx.json(null, {
2088
+ status: 400,
2089
+ body: { message: "Password is too long" }
2090
+ });
2091
+ }
2092
+ const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
2093
+ const hash = await ctx.context.password.hash(password);
2094
+ if (dbUser?.user) {
2095
+ return ctx.json(null, {
2096
+ status: 400,
2097
+ body: {
2098
+ message: "User already exists"
2099
+ }
2100
+ });
2101
+ }
2102
+ const createdUser = await ctx.context.internalAdapter.createUser({
2103
+ id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
2104
+ email: email.toLowerCase(),
2105
+ name,
2106
+ image,
2107
+ emailVerified: false,
2108
+ createdAt: /* @__PURE__ */ new Date(),
2109
+ updatedAt: /* @__PURE__ */ new Date()
2110
+ });
2111
+ if (!createdUser) {
2112
+ return ctx.json(null, {
2113
+ status: 400,
2114
+ body: {
2115
+ message: "Could not create user"
2116
+ }
2117
+ });
2118
+ }
2119
+ await ctx.context.internalAdapter.linkAccount({
2120
+ id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
2121
+ userId: createdUser.id,
2122
+ providerId: "credential",
2123
+ accountId: createdUser.id,
2124
+ password: hash
2125
+ });
2126
+ const session = await ctx.context.internalAdapter.createSession(
2127
+ createdUser.id,
2128
+ ctx.request
2129
+ );
2130
+ if (!session) {
2131
+ return ctx.json(null, {
2132
+ status: 400,
2133
+ body: {
2134
+ message: "Could not create session"
2135
+ }
2136
+ });
2137
+ }
2138
+ await setSessionCookie(ctx, session.id);
2139
+ if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {
2140
+ const token = await createEmailVerificationToken(
2141
+ ctx.context.secret,
2142
+ createdUser.email
2143
+ );
2144
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || ctx.query?.currentURL || "/"}`;
2145
+ await ctx.context.options.emailAndPassword.sendVerificationEmail?.(
2146
+ createdUser.email,
2147
+ url,
2148
+ token
2149
+ );
2150
+ }
2151
+ return ctx.json(
2152
+ {
2153
+ user: createdUser,
2154
+ session
2155
+ },
2156
+ {
2157
+ body: ctx.body.callbackURL ? {
2158
+ url: ctx.body.callbackURL,
2159
+ redirect: true
2160
+ } : {
2161
+ user: createdUser,
2162
+ session
2163
+ }
2164
+ }
2165
+ );
2166
+ }
2167
+ );
2168
+
2169
+ // src/api/index.ts
2170
+ import chalk from "chalk";
2171
+
2172
+ // src/api/rate-limiter.ts
2173
+ function shouldRateLimit(max, window2, rateLimitData) {
2174
+ const now = Date.now();
2175
+ const windowInMs = window2 * 1e3;
2176
+ const timeSinceLastRequest = now - rateLimitData.lastRequest;
2177
+ return timeSinceLastRequest < windowInMs && rateLimitData.count >= max;
2178
+ }
2179
+ function rateLimitResponse(retryAfter) {
2180
+ return new Response(
2181
+ JSON.stringify({
2182
+ message: "Too many requests. Please try again later."
2183
+ }),
2184
+ {
2185
+ status: 429,
2186
+ statusText: "Too Many Requests",
2187
+ headers: {
2188
+ "X-Retry-After": retryAfter.toString()
2189
+ }
2190
+ }
2191
+ );
2192
+ }
2193
+ function getRetryAfter(lastRequest, window2) {
2194
+ const now = Date.now();
2195
+ const windowInMs = window2 * 1e3;
2196
+ return Math.ceil((lastRequest + windowInMs - now) / 1e3);
2197
+ }
2198
+ function createDBStorage(ctx, tableName) {
2199
+ const model = tableName ?? "rateLimit";
2200
+ const db = ctx.adapter;
2201
+ return {
2202
+ get: async (key) => {
2203
+ const res = await db.findOne({
2204
+ model,
2205
+ where: [{ field: "key", value: key }]
2206
+ });
2207
+ return res;
2208
+ },
2209
+ set: async (key, value, _update) => {
2210
+ try {
2211
+ if (_update) {
2212
+ await db.update({
2213
+ model: tableName ?? "rateLimit",
2214
+ where: [{ field: "key", value: key }],
2215
+ update: {
2216
+ count: value.count,
2217
+ lastRequest: value.lastRequest
2218
+ }
2219
+ });
2220
+ } else {
2221
+ await db.create({
2222
+ model: tableName ?? "rateLimit",
2223
+ data: {
2224
+ key,
2225
+ count: value.count,
2226
+ lastRequest: value.lastRequest
2227
+ }
2228
+ });
2229
+ }
2230
+ } catch (e) {
2231
+ logger.error("Error setting rate limit", e);
2232
+ }
2233
+ }
2234
+ };
2235
+ }
2236
+ var memory = /* @__PURE__ */ new Map();
2237
+ function getRateLimitStorage(ctx) {
2238
+ if (ctx.rateLimit.customStorage) {
2239
+ return ctx.rateLimit.customStorage;
2240
+ }
2241
+ const storage = ctx.rateLimit.storage;
2242
+ if (storage === "memory") {
2243
+ return {
2244
+ async get(key) {
2245
+ return memory.get(key);
2246
+ },
2247
+ async set(key, value, _update) {
2248
+ memory.set(key, value);
2249
+ }
2250
+ };
2251
+ }
2252
+ return createDBStorage(ctx, ctx.rateLimit.tableName);
2253
+ }
2254
+ async function onRequestRateLimit(req, ctx) {
2255
+ if (!ctx.rateLimit.enabled) {
2256
+ return;
2257
+ }
2258
+ const baseURL = ctx.baseURL;
2259
+ const path = req.url.replace(baseURL, "");
2260
+ let window2 = ctx.rateLimit.window;
2261
+ let max = ctx.rateLimit.max;
2262
+ const key = getIp(req) + path;
2263
+ const specialRules = getDefaultSpecialRules();
2264
+ const specialRule = specialRules.find((rule) => rule.pathMatcher(path));
2265
+ if (specialRule) {
2266
+ window2 = specialRule.window;
2267
+ max = specialRule.max;
2268
+ }
2269
+ for (const plugin of ctx.options.plugins || []) {
2270
+ if (plugin.rateLimit) {
2271
+ const matchedRule = plugin.rateLimit.find(
2272
+ (rule) => rule.pathMatcher(path)
2273
+ );
2274
+ if (matchedRule) {
2275
+ window2 = matchedRule.window;
2276
+ max = matchedRule.max;
2277
+ break;
2278
+ }
2279
+ }
2280
+ }
2281
+ if (ctx.rateLimit.customRules) {
2282
+ const customRule = ctx.rateLimit.customRules[path];
2283
+ if (customRule) {
2284
+ window2 = customRule.window;
2285
+ max = customRule.max;
2286
+ }
2287
+ }
2288
+ const storage = getRateLimitStorage(ctx);
2289
+ const data = await storage.get(key);
2290
+ const now = Date.now();
2291
+ if (!data) {
2292
+ await storage.set(key, {
2293
+ key,
2294
+ count: 1,
2295
+ lastRequest: now
2296
+ });
2297
+ } else {
2298
+ const timeSinceLastRequest = now - data.lastRequest;
2299
+ if (shouldRateLimit(max, window2, data)) {
2300
+ const retryAfter = getRetryAfter(data.lastRequest, window2);
2301
+ return rateLimitResponse(retryAfter);
2302
+ } else if (timeSinceLastRequest > window2 * 1e3) {
2303
+ await storage.set(key, {
2304
+ ...data,
2305
+ count: 1,
2306
+ lastRequest: now
2307
+ });
2308
+ } else {
2309
+ await storage.set(key, {
2310
+ ...data,
2311
+ count: data.count + 1,
2312
+ lastRequest: now
2313
+ });
2314
+ }
2315
+ }
2316
+ }
2317
+ function getDefaultSpecialRules() {
2318
+ const specialRules = [
2319
+ {
2320
+ pathMatcher(path) {
2321
+ return path.startsWith("/sign-in") || path.startsWith("/sign-up");
2322
+ },
2323
+ window: 10,
2324
+ max: 7
2325
+ }
2326
+ ];
2327
+ return specialRules;
2328
+ }
2329
+
2330
+ // src/api/index.ts
2331
+ function getEndpoints(ctx, options) {
2332
+ const pluginEndpoints = options.plugins?.reduce(
2333
+ (acc, plugin) => {
2334
+ return {
2335
+ ...acc,
2336
+ ...plugin.endpoints
2337
+ };
2338
+ },
2339
+ {}
2340
+ );
2341
+ const middlewares = options.plugins?.map(
2342
+ (plugin) => plugin.middlewares?.map((m) => {
2343
+ const middleware = async (context) => {
2344
+ return m.middleware({
2345
+ ...context,
2346
+ context: {
2347
+ ...ctx,
2348
+ ...context.context
2349
+ }
2350
+ });
2351
+ };
2352
+ middleware.path = m.path;
2353
+ middleware.options = m.middleware.options;
2354
+ middleware.headers = m.middleware.headers;
2355
+ return {
2356
+ path: m.path,
2357
+ middleware
2358
+ };
2359
+ })
2360
+ ).filter((plugin) => plugin !== void 0).flat() || [];
2361
+ const baseEndpoints = {
2362
+ signInOAuth,
2363
+ callbackOAuth,
2364
+ getCSRFToken,
2365
+ getSession: getSession(),
2366
+ signOut,
2367
+ signUpEmail,
2368
+ signInEmail,
2369
+ forgetPassword,
2370
+ resetPassword,
2371
+ verifyEmail,
2372
+ sendVerificationEmail,
2373
+ changePassword,
2374
+ setPassword,
2375
+ updateUser,
2376
+ forgetPasswordCallback,
2377
+ listSessions: listSessions(),
2378
+ revokeSession,
2379
+ revokeSessions
2380
+ };
2381
+ const endpoints = {
2382
+ ...baseEndpoints,
2383
+ ...pluginEndpoints,
2384
+ ok,
2385
+ error
2386
+ };
2387
+ let api = {};
2388
+ for (const [key, value] of Object.entries(endpoints)) {
2389
+ api[key] = async (context) => {
2390
+ const c = await ctx;
2391
+ const endpointRes = await value({
2392
+ ...context,
2393
+ context: {
2394
+ ...c,
2395
+ ...context.context
2396
+ }
2397
+ });
2398
+ let response = endpointRes;
2399
+ for (const plugin of options.plugins || []) {
2400
+ if (plugin.hooks?.after) {
2401
+ for (const hook of plugin.hooks.after) {
2402
+ const match = hook.matcher(context);
2403
+ if (match) {
2404
+ const obj = Object.assign(context, {
2405
+ context: {
2406
+ ...ctx,
2407
+ returned: response
2408
+ }
2409
+ });
2410
+ const hookRes = await hook.handler(obj);
2411
+ if (hookRes && "response" in hookRes) {
2412
+ response = hookRes.response;
2413
+ }
2414
+ }
2415
+ }
2416
+ }
2417
+ }
2418
+ return response;
2419
+ };
2420
+ api[key].path = value.path;
2421
+ api[key].method = value.method;
2422
+ api[key].options = value.options;
2423
+ api[key].headers = value.headers;
2424
+ }
2425
+ return {
2426
+ api,
2427
+ middlewares
2428
+ };
2429
+ }
2430
+ var router = (ctx, options) => {
2431
+ const { api, middlewares } = getEndpoints(ctx, options);
2432
+ const basePath = new URL(ctx.baseURL).pathname;
2433
+ return createRouter(api, {
2434
+ extraContext: ctx,
2435
+ basePath,
2436
+ routerMiddleware: [
2437
+ {
2438
+ path: "/**",
2439
+ middleware: csrfMiddleware
2440
+ },
2441
+ ...middlewares
2442
+ ],
2443
+ async onRequest(req) {
2444
+ for (const plugin of ctx.options.plugins || []) {
2445
+ if (plugin.onRequest) {
2446
+ const response = await plugin.onRequest(req, ctx);
2447
+ if (response) {
2448
+ return response;
2449
+ }
2450
+ }
2451
+ }
2452
+ return onRequestRateLimit(req, ctx);
2453
+ },
2454
+ async onResponse(res) {
2455
+ for (const plugin of ctx.options.plugins || []) {
2456
+ if (plugin.onResponse) {
2457
+ const response = await plugin.onResponse(res, ctx);
2458
+ if (response) {
2459
+ return response.response;
2460
+ }
2461
+ }
2462
+ }
2463
+ return res;
2464
+ },
2465
+ onError(e) {
2466
+ const log = options.logger?.verboseLogging ? logger : void 0;
2467
+ if (options.logger?.disabled !== true) {
2468
+ if (e instanceof APIError6) {
2469
+ log?.warn(e);
2470
+ } else {
2471
+ if (typeof e === "object" && e !== null && "message" in e) {
2472
+ const errorMessage = e.message;
2473
+ if (!errorMessage || typeof errorMessage !== "string") {
2474
+ log?.error(e);
2475
+ return;
2476
+ }
2477
+ if (errorMessage.includes("no such table")) {
2478
+ logger?.error(
2479
+ `Please run ${chalk.green(
2480
+ "npx better-auth migrate"
2481
+ )} to create the tables. There are missing tables in your SQLite database.`
2482
+ );
2483
+ } else if (errorMessage.includes("relation") && errorMessage.includes("does not exist")) {
2484
+ logger.error(
2485
+ `Please run ${chalk.green(
2486
+ "npx better-auth migrate"
2487
+ )} to create the tables. There are missing tables in your PostgreSQL database.`
2488
+ );
2489
+ } else if (errorMessage.includes("Table") && errorMessage.includes("doesn't exist")) {
2490
+ logger?.error(
2491
+ `Please run ${chalk.green(
2492
+ "npx better-auth migrate"
2493
+ )} to create the tables. There are missing tables in your MySQL database.`
2494
+ );
2495
+ } else {
2496
+ log?.error(e);
2497
+ }
2498
+ } else {
2499
+ log?.error(e);
2500
+ }
2501
+ }
2502
+ }
2503
+ }
2504
+ });
2505
+ };
2506
+ export {
2507
+ callbackOAuth,
2508
+ changePassword,
2509
+ createAuthEndpoint,
2510
+ createAuthMiddleware,
2511
+ createEmailVerificationToken,
2512
+ csrfMiddleware,
2513
+ error,
2514
+ forgetPassword,
2515
+ forgetPasswordCallback,
2516
+ getCSRFToken,
2517
+ getEndpoints,
2518
+ getSession,
2519
+ getSessionFromCtx,
2520
+ listSessions,
2521
+ ok,
2522
+ optionsMiddleware,
2523
+ resetPassword,
2524
+ revokeSession,
2525
+ revokeSessions,
2526
+ router,
2527
+ sendVerificationEmail,
2528
+ sessionMiddleware,
2529
+ setPassword,
2530
+ signInEmail,
2531
+ signInOAuth,
2532
+ signOut,
2533
+ signUpEmail,
2534
+ updateUser,
2535
+ verifyEmail
2536
+ };