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/index.js CHANGED
@@ -1,5 +1,2029 @@
1
- import{APIError as ir,createRouter as ar}from"better-call";import{APIError as ce}from"better-call";import{z as le}from"zod";import{xchacha20poly1305 as xr}from"@noble/ciphers/chacha";import{bytesToHex as Pr,hexToBytes as vr,utf8ToBytes as Ir}from"@noble/ciphers/utils";import{managedNonce as Or}from"@noble/ciphers/webcrypto";import{sha256 as Cr}from"@noble/hashes/sha256";async function F(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 gt,createMiddleware as ae,createMiddlewareCreator as ht}from"better-call";var de=ae(async()=>({})),z=ht({use:[de,ae(async()=>({}))]}),g=gt({use:[de]});var ue=z({body:le.object({csrfToken:le.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 ce("UNAUTHORIZED",{message:"Invalid CSRF Token"});let i=await F(e.context.secret,n);if(s!==i)throw e.setCookie(e.context.authCookies.csrfToken.name,"",{maxAge:0}),new ce("UNAUTHORIZED",{message:"Invalid CSRF Token"})});import{APIError as I}from"better-call";import{generateCodeVerifier as Nt}from"oslo/oauth2";import{z as x}from"zod";import"arctic";import{parseJWT as At}from"oslo/jwt";import"@better-fetch/fetch";var k=class extends Error{constructor(t,o,r){super(t),this.name="BetterAuthError",this.message=t,this.cause=o}};import{OAuth2Tokens as bt}from"arctic";function yt(e){try{return new URL(e).pathname!=="/"}catch{throw new k(`Invalid base URL: ${e}. Please provide a valid base URL.`)}}function J(e,t="/api/auth"){return yt(e)?e:(t=t.startsWith("/")?t:`/${t}`,`${e}${t}`)}function V(e,t){if(e)return J(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 J(r,t);if(typeof window<"u")return J(window.location.origin,t)}import{betterFetch as wt}from"@better-fetch/fetch";function w(e,t){return t||`${V()}/callback/${e}`}async function R({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 wt(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 bt(i)}var pe=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)=>R({code:o,codeVerifier:r,redirectURI:n||w("apple",e.redirectURI),options:e,tokenEndpoint:t}),async getUserInfo(o){let r=At(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 kt}from"@better-fetch/fetch";import{Discord as Rt}from"arctic";var me=e=>{let t=new Rt(e.clientId,e.clientSecret,w("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)=>R({code:o,codeVerifier:r,redirectURI:n||w("discord",e.redirectURI),options:e,tokenEndpoint:"https://discord.com/api/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await kt("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 Tt}from"@better-fetch/fetch";import{Facebook as xt}from"arctic";var fe=e=>{let t=new xt(e.clientId,e.clientSecret,w("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)=>R({code:o,codeVerifier:r,redirectURI:n||w("facebook",e.redirectURI),options:e,tokenEndpoint:"https://graph.facebook.com/v16.0/oauth/access_token"}),async getUserInfo(o){let{data:r,error:n}=await Tt("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 ge}from"@better-fetch/fetch";import{GitHub as Ut}from"arctic";var he=({clientId:e,clientSecret:t,redirectURI:o})=>{let r=new Ut(e,t,w("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 ge("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:c}=await ge("https://api.github.com/user/emails",{auth:{type:"Bearer",token:n.accessToken()}});c||(s.email=(d.find(l=>l.primary)??d[0])?.email,a=d.find(l=>l.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 vt}from"arctic";import{parseJWT as It}from"oslo/jwt";import{createConsola as Pt}from"consola";var E=Pt({formatOptions:{date:!1,colors:!0,compact:!0},defaults:{tag:"Better Auth"}}),Z=e=>({log:(...t)=>{!e?.disabled&&E.log("",...t)},error:(...t)=>{!e?.disabled&&E.error("",...t)},warn:(...t)=>{!e?.disabled&&E.warn("",...t)},info:(...t)=>{!e?.disabled&&E.info("",...t)},debug:(...t)=>{!e?.disabled&&E.debug("",...t)},box:(...t)=>{!e?.disabled&&E.box("",...t)},success:(...t)=>{!e?.disabled&&E.success("",...t)},break:(...t)=>{!e?.disabled&&console.log(`
2
- `)}}),T=Z();var ye=e=>{let t=new vt(e.clientId,e.clientSecret,w("google",e.redirectURI));return{id:"google",name:"Google",createAuthorizationURL({state:o,scopes:r,codeVerifier:n,redirectURI:s}){if(!e.clientId||!e.clientSecret)throw T.error("Client Id and Client Secret is required for Google. Make sure to provide them in the options."),new k("CLIENT_ID_AND_SECRET_REQUIRED");if(!n)throw new k("codeVerifier is required for Google");let i=r||["email","profile"];return t.createAuthorizationURL(o,n,i)},validateAuthorizationCode:async(o,r,n)=>R({code:o,codeVerifier:r,redirectURI:n||w("google",e.redirectURI),options:e,tokenEndpoint:"https://oauth2.googleapis.com/token"}),async getUserInfo(o){if(!o.idToken)return null;let r=It(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 St}from"@better-fetch/fetch";import{Spotify as Ot}from"arctic";var be=e=>{let t=new Ot(e.clientId,e.clientSecret,w("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)=>R({code:o,codeVerifier:r,redirectURI:n||w("spotify",e.redirectURI),options:e,tokenEndpoint:"https://accounts.spotify.com/api/token"}),async getUserInfo(o){let{data:r,error:n}=await St("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 Lt}from"@better-fetch/fetch";import{Twitch as Ct}from"arctic";var we=e=>{let t=new Ct(e.clientId,e.clientSecret,w("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)=>R({code:o,codeVerifier:r,redirectURI:n||w("twitch",e.redirectURI),options:e,tokenEndpoint:"https://id.twitch.tv/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await Lt("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 Et}from"@better-fetch/fetch";import{Twitter as _t}from"arctic";var Ae=e=>{let t=new _t(e.clientId,e.clientSecret,w("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)=>R({code:o,codeVerifier:r,redirectURI:n||w("twitch",e.redirectURI),options:e,tokenEndpoint:"https://id.twitch.tv/oauth2/token"}),async getUserInfo(o){let{data:r,error:n}=await Et("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 Q={apple:pe,discord:me,facebook:fe,github:he,google:ye,spotify:be,twitch:we,twitter:Ae},ke=Object.keys(Q);import{generateState as Bt}from"oslo/oauth2";import{z as D}from"zod";function Re(e,t,o){let r=Bt();return{state:JSON.stringify({code:r,callbackURL:e,currentURL:t,dontRememberMe:o}),code:r}}function X(e){return D.object({code:D.string(),callbackURL:D.string().optional(),currentURL:D.string().optional(),dontRememberMe:D.boolean().optional()}).safeParse(JSON.parse(e))}import{APIError as jt}from"better-call";var N=(e,t=!1)=>{let o=new Date;return new Date(o.getTime()+(t?e*1e3:e))};import{TimeSpan as qt}from"oslo";function Te(e){let o=!!e.advanced?.useSecureCookies||process.env.NODE_ENV!=="development"&&process.env.NODE_ENV!=="test"?"__Secure-":"",r="better-auth",n=new qt(7,"d").seconds();return{sessionToken:{name:`${o}${r}.session_token`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o,maxAge:n}},csrfToken:{name:`${o?"__Host-":""}${r}.csrf_token`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o,maxAge:60*60*24*7}},state:{name:`${o}${r}.state`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o,maxAge:60*15}},pkCodeVerifier:{name:`${o}${r}.pk_code_verifier`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o,maxAge:60*15}},dontRememberToken:{name:`${o}${r}.dont_remember`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o}},nonce:{name:`${o}${r}.nonce`,options:{httpOnly:!0,sameSite:"lax",path:"/",secure:!!o,maxAge:60*15}}}}function xe(e){let o=!!e.advanced?.useSecureCookies||process.env.NODE_ENV==="production"?"__Secure-":"",r="better-auth";function n(s,i){return{name:process.env.NODE_ENV==="production"?`${o}${r}.${s}`:`${r}.${s}`,options:{secure:!!o,sameSite:"lax",path:"/",maxAge:60*15,...i}}}return n}async function P(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 $(e){e.setCookie(e.context.authCookies.sessionToken.name,"",{maxAge:0}),e.setCookie(e.context.authCookies.dontRememberToken.name,"",{maxAge:0})}import{z as Ue}from"zod";function M(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 Y=new Map;function Dt(e,t){if(!e.request)return"";let{method:o,url:r,headers:n}=e.request,s=e.request.headers.get("User-Agent")||"",i=M(e.request)||"",a=JSON.stringify(n);return`${o}:${r}:${a}:${s}:${i}:${t}`}var ee=()=>g("/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=Dt(e,t),r=Y.get(o);if(r){if(r.expiresAt>Date.now())return e.json(r.data);Y.delete(o)}let n=await e.context.internalAdapter.findSession(t);if(!n||n.session.expiresAt<new Date)return $(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 l=await e.context.internalAdapter.updateSession(n.session.id,{expiresAt:N(e.context.sessionConfig.expiresIn,!0)});if(!l)return $(e),e.json(null,{status:401});let u=(l.expiresAt.valueOf()-Date.now())/1e3;return await P(e,l.id,!1,{maxAge:u}),e.json({session:l,user:n.user})}return Y.set(o,{data:n,expiresAt:Date.now()+5e3}),e.json(n)}catch(t){return e.context.logger.error(t),e.json(null,{status:500})}}),te=async e=>await ee()({...e,_flag:void 0}),_=z(async e=>{let t=await te(e);if(!t?.session)throw new jt("UNAUTHORIZED");return{session:t}}),Pe=()=>g("/user/list-sessions",{method:"GET",use:[_],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)}),ve=g("/user/revoke-session",{method:"POST",body:Ue.object({id:Ue.string()}),use:[_],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})}),Ie=g("/user/revoke-sessions",{method:"POST",use:[_],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 Se=g("/sign-in/social",{method:"POST",requireHeaders:!0,query:x.object({currentURL:x.string().optional()}).optional(),body:x.object({callbackURL:x.string().optional(),provider:x.enum(ke),dontRememberMe:x.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 I("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=Re(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=Nt();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 I("INTERNAL_SERVER_ERROR")}}),Oe=g("/sign-in/email",{method:"POST",body:x.object({email:x.string().email(),password:x.string(),callbackURL:x.string().optional(),dontRememberMe:x.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 I("BAD_REQUEST",{message:"Email and password is not enabled"});let t=await te(e);t&&await e.context.internalAdapter.deleteSession(t.session.id);let{email:o,password:r}=e.body;if(!x.string().email().safeParse(o).success)throw new I("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 I("UNAUTHORIZED",{message:"Invalid email or password"});let i=s.accounts.find(l=>l.providerId==="credential");if(!i)throw e.context.logger.error("Credential account not found",{email:o}),new I("UNAUTHORIZED",{message:"Invalid email or password"});let a=i?.password;if(!a)throw e.context.logger.error("Password not found",{email:o}),new I("UNAUTHORIZED",{message:"Unexpected error"});if(!await e.context.password.verify(a,r))throw e.context.logger.error("Invalid password"),new I("UNAUTHORIZED",{message:"Invalid email or password"});let c=await e.context.internalAdapter.createSession(s.user.id,e.headers,e.body.dontRememberMe);if(!c)throw e.context.logger.error("Failed to create session"),new I("INTERNAL_SERVER_ERROR");return await P(e,c.id,e.body.dontRememberMe),e.json({user:s.user,session:c,redirect:!!e.body.callbackURL,url:e.body.callbackURL})});import{APIError as zt}from"better-call";import{z as H}from"zod";import{z as b}from"zod";var Sn=b.object({id:b.string(),providerId:b.string(),accountId:b.string(),userId:b.string(),accessToken:b.string().nullable().optional(),refreshToken:b.string().nullable().optional(),idToken:b.string().nullable().optional(),expiresAt:b.date().nullable().optional(),password:b.string().optional().nullable()}),Le=b.object({id:b.string(),email:b.string().transform(e=>e.toLowerCase()),emailVerified:b.boolean().default(!1),name:b.string(),image:b.string().optional(),createdAt:b.date().default(new Date),updatedAt:b.date().default(new Date)}),On=b.object({id:b.string(),userId:b.string(),expiresAt:b.date(),ipAddress:b.string().optional(),userAgent:b.string().optional()});import{alphabet as $t,generateRandomString as Ft}from"oslo/crypto";var Ce=()=>Ft(36,$t("a-z","0-9"));var L={isAction:!1};function re(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 Ee=g("/callback/:id",{method:"GET",query:H.object({state:H.string(),code:H.string().optional(),error:H.string().optional()}),metadata:L},async e=>{if(e.query.error||!e.query.code){let y=X(e.query.state).data?.callbackURL||`${e.context.baseURL}/error`;throw e.context.logger.error(e.query.error,e.params.id),e.redirect(`${y}?error=${e.query.error||"oAuth_code_missing"}`)}let t=e.context.socialProviders.find(f=>f.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(f){throw e.context.logger.error(f),e.redirect(`${e.context.baseURL}/error?error=oauth_code_verification_failed`)}let n=await t.getUserInfo(r).then(f=>f?.user),s=Ce(),i=Le.safeParse({...n,id:s}),a=X(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:c,dontRememberMe:l}=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 u=await e.context.internalAdapter.findUserByEmail(n.email),p=u?.user.id;if(u){let f=u.accounts.find(A=>A.providerId===t.id),y=e.context.options.account?.accountLinking?.trustedProviders,h=y?y.includes(t.id):!0;if(!f&&(!n.emailVerified||!h)){let A;try{A=new URL(c||d),A.searchParams.set("error","account_not_linked")}catch{throw e.redirect(`${e.context.baseURL}/error?error=account_not_linked`)}throw e.redirect(A.toString())}if(!f)try{await e.context.internalAdapter.linkAccount({providerId:t.id,accountId:n.id,id:`${t.id}:${n.id}`,userId:u.user.id,...re(r)})}catch(A){throw console.log(A),e.redirect(`${e.context.baseURL}/error?error=failed_linking_account`)}}else try{await e.context.internalAdapter.createOAuthUser(i.data,{...re(r),id:`${t.id}:${n.id}`,providerId:t.id,accountId:n.id,userId:s})}catch{let y=new URL(c||d);throw y.searchParams.set("error","unable_to_create_user"),e.setHeader("Location",y.toString()),e.redirect(y.toString())}if(!p&&!s)throw new zt("INTERNAL_SERVER_ERROR",{message:"Unable to create user"});let m=await e.context.internalAdapter.createSession(p||s,e.request,l);if(!m){let f=new URL(c||d);throw f.searchParams.set("error","unable_to_create_session"),e.redirect(f.toString())}try{await P(e,m.id,l)}catch(f){e.context.logger.error("Unable to set session cookie",f);let y=new URL(c||d);throw y.searchParams.set("error","unable_to_create_session"),e.redirect(y.toString())}throw e.redirect(d)});import{z as oe}from"zod";var _e=g("/sign-out",{method:"POST",body:oe.optional(oe.object({callbackURL:oe.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),$(e),e.json(null,{body:{redirect:!!e.body?.callbackURL,url:e.body?.callbackURL}})):e.json(null)});import{TimeSpan as Vt}from"oslo";import{createJWT as Mt,parseJWT as Ht}from"oslo/jwt";import{validateJWT as Be}from"oslo/jwt";import{z as U}from"zod";var qe=g("/forget-password",{method:"POST",body:U.object({email:U.string().email(),redirectTo:U.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 Mt("HS256",Buffer.from(e.context.secret),{email:o.user.email,redirectTo:e.body.redirectTo},{expiresIn:new Vt(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})}),je=g("/reset-password/:token",{method:"GET"},async e=>{let{token:t}=e.params,o,r=U.object({email:U.string(),redirectTo:U.string()});try{if(o=await Be("HS256",Buffer.from(e.context.secret),t),!o.expiresAt||o.expiresAt<new Date)throw Error("Token expired")}catch{let i=Ht(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}`)}),De=g("/reset-password",{method:"POST",query:U.object({currentURL:U.string()}).optional(),body:U.object({newPassword:U.string(),callbackURL:U.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 Be("HS256",Buffer.from(e.context.secret),t),n=U.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 Gt}from"oslo";import{createJWT as Kt,validateJWT as Wt}from"oslo/jwt";import{z as v}from"zod";async function ne(e,t){return await Kt("HS256",Buffer.from(e),{email:t.toLowerCase()},{expiresIn:new Gt(1,"h"),issuer:"better-auth",subject:"verify-email",audiences:[t],includeIssuedTimestamp:!0})}var Ne=g("/send-verification-email",{method:"POST",query:v.object({currentURL:v.string().optional()}).optional(),body:v.object({email:v.string().email(),callbackURL:v.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 ne(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})}),$e=g("/verify-email",{method:"GET",query:v.object({token:v.string(),callbackURL:v.string().optional()})},async e=>{let{token:t}=e.query,o;try{o=await Wt("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=v.object({email:v.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 S}from"zod";import{alphabet as Jt,generateRandomString as Zt}from"oslo/crypto";import"better-call";var Fe=g("/user/update",{method:"POST",body:S.object({name:S.string().optional(),image:S.string().optional()}),use:[_]},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)}),ze=g("/user/change-password",{method:"POST",body:S.object({newPassword:S.string(),currentPassword:S.string(),revokeOtherSessions:S.boolean().optional()}),use:[_]},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(u=>u.providerId==="credential"&&u.password);if(!d||!d.password)return e.json(null,{status:400,body:{message:"User does not have a password"}});let c=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:c}),r){await e.context.internalAdapter.deleteSessions(n.user.id);let u=await e.context.internalAdapter.createSession(n.user.id,e.headers);if(!u)return e.json(null,{status:500,body:{message:"Failed to create session"}});await P(e,u.id)}return e.json(n.user)}),Ve=g("/user/set-password",{method:"POST",body:S.object({newPassword:S.string()}),use:[_]},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:Zt(32,Jt("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 Qt,generateRandomString as Xt}from"oslo/crypto";var Me=g("/csrf",{method:"GET",metadata:L},async e=>{let t=await e.getSignedCookie(e.context.authCookies.csrfToken.name,e.context.secret);if(t)return{csrfToken:t};let o=Xt(32,Qt("a-z","0-9","A-Z")),r=await F(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 Yt=(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
+ var MissingDependencyError = class extends BetterAuthError {
120
+ constructor(pkgName) {
121
+ super(
122
+ `The package "${pkgName}" is required. Make sure it is installed.`,
123
+ pkgName
124
+ );
125
+ }
126
+ };
127
+
128
+ // src/social-providers/utils.ts
129
+ import { OAuth2Tokens } from "arctic";
130
+
131
+ // src/utils/base-url.ts
132
+ function checkHasPath(url) {
133
+ try {
134
+ const parsedUrl = new URL(url);
135
+ return parsedUrl.pathname !== "/";
136
+ } catch (error2) {
137
+ throw new BetterAuthError(
138
+ `Invalid base URL: ${url}. Please provide a valid base URL.`
139
+ );
140
+ }
141
+ }
142
+ function withPath(url, path = "/api/auth") {
143
+ const hasPath = checkHasPath(url);
144
+ if (hasPath) {
145
+ return url;
146
+ }
147
+ path = path.startsWith("/") ? path : `/${path}`;
148
+ return `${url}${path}`;
149
+ }
150
+ function getBaseURL(url, path) {
151
+ if (url) {
152
+ return withPath(url, path);
153
+ }
154
+ const env = process?.env || {};
155
+ 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);
156
+ if (fromEnv) {
157
+ return withPath(fromEnv, path);
158
+ }
159
+ if (typeof window !== "undefined") {
160
+ return withPath(window.location.origin, path);
161
+ }
162
+ return void 0;
163
+ }
164
+
165
+ // src/social-providers/utils.ts
166
+ import { betterFetch } from "@better-fetch/fetch";
167
+ function getRedirectURI(providerId, redirectURI) {
168
+ return redirectURI || `${getBaseURL()}/callback/${providerId}`;
169
+ }
170
+ async function validateAuthorizationCode({
171
+ code,
172
+ codeVerifier,
173
+ redirectURI,
174
+ options,
175
+ tokenEndpoint
176
+ }) {
177
+ const body = new URLSearchParams();
178
+ body.set("grant_type", "authorization_code");
179
+ body.set("code", code);
180
+ codeVerifier && body.set("code_verifier", codeVerifier);
181
+ body.set("redirect_uri", redirectURI);
182
+ body.set("client_id", options.clientId);
183
+ body.set("client_secret", options.clientSecret);
184
+ const { data, error: error2 } = await betterFetch(tokenEndpoint, {
185
+ method: "POST",
186
+ body,
187
+ headers: {
188
+ "content-type": "application/x-www-form-urlencoded",
189
+ accept: "application/json",
190
+ "user-agent": "better-auth"
191
+ }
192
+ });
193
+ if (error2) {
194
+ throw error2;
195
+ }
196
+ const tokens = new OAuth2Tokens(data);
197
+ return tokens;
198
+ }
199
+
200
+ // src/social-providers/apple.ts
201
+ var apple = (options) => {
202
+ const tokenEndpoint = "https://appleid.apple.com/auth/token";
203
+ return {
204
+ id: "apple",
205
+ name: "Apple",
206
+ createAuthorizationURL({ state, scopes, redirectURI }) {
207
+ const _scope = scopes || ["email", "name", "openid"];
208
+ return new URL(
209
+ `https://appleid.apple.com/auth/authorize?client_id=${options.clientId}&response_type=code&redirect_uri=${redirectURI || options.redirectURI}&scope=${_scope.join(" ")}&state=${state}`
210
+ );
211
+ },
212
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
213
+ return validateAuthorizationCode({
214
+ code,
215
+ codeVerifier,
216
+ redirectURI: redirectURI || getRedirectURI("apple", options.redirectURI),
217
+ options,
218
+ tokenEndpoint
219
+ });
220
+ },
221
+ async getUserInfo(token) {
222
+ const data = parseJWT(token.idToken())?.payload;
223
+ if (!data) {
224
+ return null;
225
+ }
226
+ return {
227
+ user: {
228
+ id: data.sub,
229
+ name: data.name,
230
+ email: data.email,
231
+ emailVerified: data.email_verified === "true"
232
+ },
233
+ data
234
+ };
235
+ }
236
+ };
237
+ };
238
+
239
+ // src/social-providers/discord.ts
240
+ import { betterFetch as betterFetch3 } from "@better-fetch/fetch";
241
+ import { Discord } from "arctic";
242
+ var discord = (options) => {
243
+ const discordArctic = new Discord(
244
+ options.clientId,
245
+ options.clientSecret,
246
+ getRedirectURI("discord", options.redirectURI)
247
+ );
248
+ return {
249
+ id: "discord",
250
+ name: "Discord",
251
+ createAuthorizationURL({ state, scopes }) {
252
+ const _scope = scopes || ["email"];
253
+ return discordArctic.createAuthorizationURL(state, _scope);
254
+ },
255
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
256
+ return validateAuthorizationCode({
257
+ code,
258
+ codeVerifier,
259
+ redirectURI: redirectURI || getRedirectURI("discord", options.redirectURI),
260
+ options,
261
+ tokenEndpoint: "https://discord.com/api/oauth2/token"
262
+ });
263
+ },
264
+ async getUserInfo(token) {
265
+ const { data: profile, error: error2 } = await betterFetch3(
266
+ "https://discord.com/api/users/@me",
267
+ {
268
+ auth: {
269
+ type: "Bearer",
270
+ token: token.accessToken()
271
+ }
272
+ }
273
+ );
274
+ if (error2) {
275
+ return null;
276
+ }
277
+ return {
278
+ user: {
279
+ id: profile.id,
280
+ name: profile.display_name || profile.username || "",
281
+ email: profile.email,
282
+ emailVerified: profile.verified
283
+ },
284
+ data: profile
285
+ };
286
+ }
287
+ };
288
+ };
289
+
290
+ // src/social-providers/facebook.ts
291
+ import { betterFetch as betterFetch4 } from "@better-fetch/fetch";
292
+ import { Facebook } from "arctic";
293
+ var facebook = (options) => {
294
+ const facebookArctic = new Facebook(
295
+ options.clientId,
296
+ options.clientSecret,
297
+ getRedirectURI("facebook", options.redirectURI)
298
+ );
299
+ return {
300
+ id: "facebook",
301
+ name: "Facebook",
302
+ createAuthorizationURL({ state, scopes }) {
303
+ const _scopes = scopes || ["email", "public_profile"];
304
+ return facebookArctic.createAuthorizationURL(state, _scopes);
305
+ },
306
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
307
+ return validateAuthorizationCode({
308
+ code,
309
+ codeVerifier,
310
+ redirectURI: redirectURI || getRedirectURI("facebook", options.redirectURI),
311
+ options,
312
+ tokenEndpoint: "https://graph.facebook.com/v16.0/oauth/access_token"
313
+ });
314
+ },
315
+ async getUserInfo(token) {
316
+ const { data: profile, error: error2 } = await betterFetch4(
317
+ "https://graph.facebook.com/me",
318
+ {
319
+ auth: {
320
+ type: "Bearer",
321
+ token: token.accessToken()
322
+ }
323
+ }
324
+ );
325
+ if (error2) {
326
+ return null;
327
+ }
328
+ return {
329
+ user: {
330
+ id: profile.id,
331
+ name: profile.name,
332
+ email: profile.email,
333
+ emailVerified: profile.email_verified
334
+ },
335
+ data: profile
336
+ };
337
+ }
338
+ };
339
+ };
340
+
341
+ // src/social-providers/github.ts
342
+ import { betterFetch as betterFetch5 } from "@better-fetch/fetch";
343
+ import { GitHub } from "arctic";
344
+ var github = ({
345
+ clientId,
346
+ clientSecret,
347
+ redirectURI
348
+ }) => {
349
+ const githubArctic = new GitHub(
350
+ clientId,
351
+ clientSecret,
352
+ getRedirectURI("github", redirectURI)
353
+ );
354
+ return {
355
+ id: "github",
356
+ name: "Github",
357
+ createAuthorizationURL({ state, scopes }) {
358
+ const _scopes = scopes || ["user:email"];
359
+ return githubArctic.createAuthorizationURL(state, _scopes);
360
+ },
361
+ validateAuthorizationCode: async (state) => {
362
+ return await githubArctic.validateAuthorizationCode(state);
363
+ },
364
+ async getUserInfo(token) {
365
+ const { data: profile, error: error2 } = await betterFetch5(
366
+ "https://api.github.com/user",
367
+ {
368
+ auth: {
369
+ type: "Bearer",
370
+ token: token.accessToken()
371
+ }
372
+ }
373
+ );
374
+ if (error2) {
375
+ return null;
376
+ }
377
+ let emailVerified = false;
378
+ if (!profile.email) {
379
+ const { data, error: error3 } = await betterFetch5("https://api.github.com/user/emails", {
380
+ auth: {
381
+ type: "Bearer",
382
+ token: token.accessToken()
383
+ }
384
+ });
385
+ if (!error3) {
386
+ profile.email = (data.find((e) => e.primary) ?? data[0])?.email;
387
+ emailVerified = data.find((e) => e.email === profile.email)?.verified ?? false;
388
+ }
389
+ }
390
+ return {
391
+ user: {
392
+ id: profile.id,
393
+ name: profile.name,
394
+ email: profile.email,
395
+ image: profile.avatar_url,
396
+ emailVerified,
397
+ createdAt: /* @__PURE__ */ new Date(),
398
+ updatedAt: /* @__PURE__ */ new Date()
399
+ },
400
+ data: profile
401
+ };
402
+ }
403
+ };
404
+ };
405
+
406
+ // src/social-providers/google.ts
407
+ import { Google } from "arctic";
408
+ import { parseJWT as parseJWT2 } from "oslo/jwt";
409
+
410
+ // src/utils/logger.ts
411
+ import { createConsola } from "consola";
412
+ var consola = createConsola({
413
+ formatOptions: {
414
+ date: false,
415
+ colors: true,
416
+ compact: true
417
+ },
418
+ defaults: {
419
+ tag: "Better Auth"
420
+ }
421
+ });
422
+ var createLogger = (options) => {
423
+ return {
424
+ log: (...args) => {
425
+ !options?.disabled && consola.log("", ...args);
426
+ },
427
+ error: (...args) => {
428
+ !options?.disabled && consola.error("", ...args);
429
+ },
430
+ warn: (...args) => {
431
+ !options?.disabled && consola.warn("", ...args);
432
+ },
433
+ info: (...args) => {
434
+ !options?.disabled && consola.info("", ...args);
435
+ },
436
+ debug: (...args) => {
437
+ !options?.disabled && consola.debug("", ...args);
438
+ },
439
+ box: (...args) => {
440
+ !options?.disabled && consola.box("", ...args);
441
+ },
442
+ success: (...args) => {
443
+ !options?.disabled && consola.success("", ...args);
444
+ },
445
+ break: (...args) => {
446
+ !options?.disabled && console.log("\n");
447
+ }
448
+ };
449
+ };
450
+ var logger = createLogger();
451
+
452
+ // src/social-providers/google.ts
453
+ var google = (options) => {
454
+ const googleArctic = new Google(
455
+ options.clientId,
456
+ options.clientSecret,
457
+ getRedirectURI("google", options.redirectURI)
458
+ );
459
+ return {
460
+ id: "google",
461
+ name: "Google",
462
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
463
+ if (!options.clientId || !options.clientSecret) {
464
+ logger.error(
465
+ "Client Id and Client Secret is required for Google. Make sure to provide them in the options."
466
+ );
467
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
468
+ }
469
+ if (!codeVerifier) {
470
+ throw new BetterAuthError("codeVerifier is required for Google");
471
+ }
472
+ const _scopes = scopes || ["email", "profile"];
473
+ const url = googleArctic.createAuthorizationURL(
474
+ state,
475
+ codeVerifier,
476
+ _scopes
477
+ );
478
+ return url;
479
+ },
480
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
481
+ return validateAuthorizationCode({
482
+ code,
483
+ codeVerifier,
484
+ redirectURI: redirectURI || getRedirectURI("google", options.redirectURI),
485
+ options,
486
+ tokenEndpoint: "https://oauth2.googleapis.com/token"
487
+ });
488
+ },
489
+ async getUserInfo(token) {
490
+ if (!token.idToken) {
491
+ return null;
492
+ }
493
+ const user = parseJWT2(token.idToken())?.payload;
494
+ return {
495
+ user: {
496
+ id: user.sub,
497
+ name: user.name,
498
+ email: user.email,
499
+ image: user.picture,
500
+ emailVerified: user.email_verified
501
+ },
502
+ data: user
503
+ };
504
+ }
505
+ };
506
+ };
507
+
508
+ // src/social-providers/spotify.ts
509
+ import { betterFetch as betterFetch6 } from "@better-fetch/fetch";
510
+ import { Spotify } from "arctic";
511
+ var spotify = (options) => {
512
+ const spotifyArctic = new Spotify(
513
+ options.clientId,
514
+ options.clientSecret,
515
+ getRedirectURI("spotify", options.redirectURI)
516
+ );
517
+ return {
518
+ id: "spotify",
519
+ name: "Spotify",
520
+ createAuthorizationURL({ state, scopes }) {
521
+ const _scopes = scopes || ["user-read-email"];
522
+ return spotifyArctic.createAuthorizationURL(state, _scopes);
523
+ },
524
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
525
+ return validateAuthorizationCode({
526
+ code,
527
+ codeVerifier,
528
+ redirectURI: redirectURI || getRedirectURI("spotify", options.redirectURI),
529
+ options,
530
+ tokenEndpoint: "https://accounts.spotify.com/api/token"
531
+ });
532
+ },
533
+ async getUserInfo(token) {
534
+ const { data: profile, error: error2 } = await betterFetch6(
535
+ "https://api.spotify.com/v1/me",
536
+ {
537
+ method: "GET",
538
+ headers: {
539
+ Authorization: `Bearer ${token.accessToken()}`
540
+ }
541
+ }
542
+ );
543
+ if (error2) {
544
+ return null;
545
+ }
546
+ return {
547
+ user: {
548
+ id: profile.id,
549
+ name: profile.display_name,
550
+ email: profile.email,
551
+ image: profile.images[0]?.url,
552
+ emailVerified: false
553
+ },
554
+ data: profile
555
+ };
556
+ }
557
+ };
558
+ };
559
+
560
+ // src/social-providers/twitch.ts
561
+ import { betterFetch as betterFetch7 } from "@better-fetch/fetch";
562
+ import { Twitch } from "arctic";
563
+ var twitch = (options) => {
564
+ const twitchArctic = new Twitch(
565
+ options.clientId,
566
+ options.clientSecret,
567
+ getRedirectURI("twitch", options.redirectURI)
568
+ );
569
+ return {
570
+ id: "twitch",
571
+ name: "Twitch",
572
+ createAuthorizationURL({ state, scopes }) {
573
+ const _scopes = scopes || ["activity:write", "read"];
574
+ return twitchArctic.createAuthorizationURL(state, _scopes);
575
+ },
576
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
577
+ return validateAuthorizationCode({
578
+ code,
579
+ codeVerifier,
580
+ redirectURI: redirectURI || getRedirectURI("twitch", options.redirectURI),
581
+ options,
582
+ tokenEndpoint: "https://id.twitch.tv/oauth2/token"
583
+ });
584
+ },
585
+ async getUserInfo(token) {
586
+ const { data: profile, error: error2 } = await betterFetch7(
587
+ "https://api.twitch.tv/helix/users",
588
+ {
589
+ method: "GET",
590
+ headers: {
591
+ Authorization: `Bearer ${token.accessToken()}`
592
+ }
593
+ }
594
+ );
595
+ if (error2) {
596
+ return null;
597
+ }
598
+ return {
599
+ user: {
600
+ id: profile.sub,
601
+ name: profile.preferred_username,
602
+ email: profile.email,
603
+ image: profile.picture,
604
+ emailVerified: false
605
+ },
606
+ data: profile
607
+ };
608
+ }
609
+ };
610
+ };
611
+
612
+ // src/social-providers/twitter.ts
613
+ import { betterFetch as betterFetch8 } from "@better-fetch/fetch";
614
+ import { Twitter } from "arctic";
615
+ var twitter = (options) => {
616
+ const twitterArctic = new Twitter(
617
+ options.clientId,
618
+ options.clientSecret,
619
+ getRedirectURI("twitter", options.redirectURI)
620
+ );
621
+ return {
622
+ id: "twitter",
623
+ name: "Twitter",
624
+ createAuthorizationURL(data) {
625
+ const _scopes = data.scopes || ["account_info.read"];
626
+ return twitterArctic.createAuthorizationURL(
627
+ data.state,
628
+ data.codeVerifier,
629
+ _scopes
630
+ );
631
+ },
632
+ validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
633
+ return validateAuthorizationCode({
634
+ code,
635
+ codeVerifier,
636
+ redirectURI: redirectURI || getRedirectURI("twitch", options.redirectURI),
637
+ options,
638
+ tokenEndpoint: "https://id.twitch.tv/oauth2/token"
639
+ });
640
+ },
641
+ async getUserInfo(token) {
642
+ const { data: profile, error: error2 } = await betterFetch8(
643
+ "https://api.x.com/2/users/me?user.fields=profile_image_url",
644
+ {
645
+ method: "GET",
646
+ headers: {
647
+ Authorization: `Bearer ${token.accessToken()}`
648
+ }
649
+ }
650
+ );
651
+ if (error2) {
652
+ return null;
653
+ }
654
+ if (!profile.data.email) {
655
+ return null;
656
+ }
657
+ return {
658
+ user: {
659
+ id: profile.data.id,
660
+ name: profile.data.name,
661
+ email: profile.data.email,
662
+ image: profile.data.profile_image_url,
663
+ emailVerified: profile.data.verified || false
664
+ },
665
+ data: profile
666
+ };
667
+ }
668
+ };
669
+ };
670
+
671
+ // src/types/provider.ts
672
+ import "arctic";
673
+
674
+ // src/social-providers/index.ts
675
+ var oAuthProviders = {
676
+ apple,
677
+ discord,
678
+ facebook,
679
+ github,
680
+ google,
681
+ spotify,
682
+ twitch,
683
+ twitter
684
+ };
685
+ var oAuthProviderList = Object.keys(oAuthProviders);
686
+
687
+ // src/utils/state.ts
688
+ import { generateState as generateStateOAuth } from "oslo/oauth2";
689
+ import { z as z2 } from "zod";
690
+ function generateState(callbackURL, currentURL, dontRememberMe) {
691
+ const code = generateStateOAuth();
692
+ const state = JSON.stringify({
693
+ code,
694
+ callbackURL,
695
+ currentURL,
696
+ dontRememberMe
697
+ });
698
+ return { state, code };
699
+ }
700
+ function parseState(state) {
701
+ const data = z2.object({
702
+ code: z2.string(),
703
+ callbackURL: z2.string().optional(),
704
+ currentURL: z2.string().optional(),
705
+ dontRememberMe: z2.boolean().optional()
706
+ }).safeParse(JSON.parse(state));
707
+ return data;
708
+ }
709
+
710
+ // src/api/routes/session.ts
711
+ import { APIError as APIError2 } from "better-call";
712
+
713
+ // src/utils/date.ts
714
+ var getDate = (span, isSeconds = false) => {
715
+ const date = /* @__PURE__ */ new Date();
716
+ return new Date(date.getTime() + (isSeconds ? span * 1e3 : span));
717
+ };
718
+
719
+ // src/utils/cookies.ts
720
+ import { TimeSpan } from "oslo";
721
+ function getCookies(options) {
722
+ const secure = !!options.advanced?.useSecureCookies || process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test";
723
+ const secureCookiePrefix = secure ? "__Secure-" : "";
724
+ const cookiePrefix = "better-auth";
725
+ const sessionMaxAge = new TimeSpan(7, "d").seconds();
726
+ return {
727
+ sessionToken: {
728
+ name: `${secureCookiePrefix}${cookiePrefix}.session_token`,
729
+ options: {
730
+ httpOnly: true,
731
+ sameSite: "lax",
732
+ path: "/",
733
+ secure: !!secureCookiePrefix,
734
+ maxAge: sessionMaxAge
735
+ }
736
+ },
737
+ csrfToken: {
738
+ name: `${secureCookiePrefix ? "__Host-" : ""}${cookiePrefix}.csrf_token`,
739
+ options: {
740
+ httpOnly: true,
741
+ sameSite: "lax",
742
+ path: "/",
743
+ secure: !!secureCookiePrefix,
744
+ maxAge: 60 * 60 * 24 * 7
745
+ }
746
+ },
747
+ state: {
748
+ name: `${secureCookiePrefix}${cookiePrefix}.state`,
749
+ options: {
750
+ httpOnly: true,
751
+ sameSite: "lax",
752
+ path: "/",
753
+ secure: !!secureCookiePrefix,
754
+ maxAge: 60 * 15
755
+ // 15 minutes in seconds
756
+ }
757
+ },
758
+ pkCodeVerifier: {
759
+ name: `${secureCookiePrefix}${cookiePrefix}.pk_code_verifier`,
760
+ options: {
761
+ httpOnly: true,
762
+ sameSite: "lax",
763
+ path: "/",
764
+ secure: !!secureCookiePrefix,
765
+ maxAge: 60 * 15
766
+ // 15 minutes in seconds
767
+ }
768
+ },
769
+ dontRememberToken: {
770
+ name: `${secureCookiePrefix}${cookiePrefix}.dont_remember`,
771
+ options: {
772
+ httpOnly: true,
773
+ sameSite: "lax",
774
+ path: "/",
775
+ secure: !!secureCookiePrefix
776
+ //no max age so it expires when the browser closes
777
+ }
778
+ },
779
+ nonce: {
780
+ name: `${secureCookiePrefix}${cookiePrefix}.nonce`,
781
+ options: {
782
+ httpOnly: true,
783
+ sameSite: "lax",
784
+ path: "/",
785
+ secure: !!secureCookiePrefix,
786
+ maxAge: 60 * 15
787
+ // 15 minutes in seconds
788
+ }
789
+ }
790
+ };
791
+ }
792
+ function createCookieGetter(options) {
793
+ const secure = !!options.advanced?.useSecureCookies || process.env.NODE_ENV === "production";
794
+ const secureCookiePrefix = secure ? "__Secure-" : "";
795
+ const cookiePrefix = "better-auth";
796
+ function getCookie(cookieName, options2) {
797
+ return {
798
+ name: process.env.NODE_ENV === "production" ? `${secureCookiePrefix}${cookiePrefix}.${cookieName}` : `${cookiePrefix}.${cookieName}`,
799
+ options: {
800
+ secure: !!secureCookiePrefix,
801
+ sameSite: "lax",
802
+ path: "/",
803
+ maxAge: 60 * 15,
804
+ // 15 minutes in seconds
805
+ ...options2
806
+ }
807
+ };
808
+ }
809
+ return getCookie;
810
+ }
811
+ async function setSessionCookie(ctx, sessionToken, dontRememberMe, overrides) {
812
+ const options = ctx.context.authCookies.sessionToken.options;
813
+ options.maxAge = dontRememberMe ? void 0 : options.maxAge;
814
+ await ctx.setSignedCookie(
815
+ ctx.context.authCookies.sessionToken.name,
816
+ sessionToken,
817
+ ctx.context.secret,
818
+ options
819
+ );
820
+ if (dontRememberMe) {
821
+ await ctx.setSignedCookie(
822
+ ctx.context.authCookies.dontRememberToken.name,
823
+ "true",
824
+ ctx.context.secret,
825
+ ctx.context.authCookies.dontRememberToken.options
826
+ );
827
+ }
828
+ }
829
+ function deleteSessionCookie(ctx) {
830
+ ctx.setCookie(ctx.context.authCookies.sessionToken.name, "", {
831
+ maxAge: 0
832
+ });
833
+ ctx.setCookie(ctx.context.authCookies.dontRememberToken.name, "", {
834
+ maxAge: 0
835
+ });
836
+ }
837
+
838
+ // src/api/routes/session.ts
839
+ import { z as z3 } from "zod";
840
+
841
+ // src/utils/get-request-ip.ts
842
+ function getIp(req) {
843
+ const testIP = "127.0.0.1";
844
+ if (process.env.NODE_ENV === "test") {
845
+ return testIP;
846
+ }
847
+ const headers = [
848
+ "x-client-ip",
849
+ "x-forwarded-for",
850
+ "cf-connecting-ip",
851
+ "fastly-client-ip",
852
+ "x-real-ip",
853
+ "x-cluster-client-ip",
854
+ "x-forwarded",
855
+ "forwarded-for",
856
+ "forwarded"
857
+ ];
858
+ for (const header of headers) {
859
+ const value = req.headers.get(header);
860
+ if (typeof value === "string") {
861
+ const ip = value.split(",")[0].trim();
862
+ if (ip) return ip;
863
+ }
864
+ }
865
+ return null;
866
+ }
867
+
868
+ // src/api/routes/session.ts
869
+ var sessionCache = /* @__PURE__ */ new Map();
870
+ function getRequestUniqueKey(ctx, token) {
871
+ if (!ctx.request) {
872
+ return "";
873
+ }
874
+ const { method, url, headers } = ctx.request;
875
+ const userAgent = ctx.request.headers.get("User-Agent") || "";
876
+ const ip = getIp(ctx.request) || "";
877
+ const headerString = JSON.stringify(headers);
878
+ const uniqueString = `${method}:${url}:${headerString}:${userAgent}:${ip}:${token}`;
879
+ return uniqueString;
880
+ }
881
+ var getSession = () => createAuthEndpoint(
882
+ "/session",
883
+ {
884
+ method: "GET",
885
+ requireHeaders: true
886
+ },
887
+ async (ctx) => {
888
+ try {
889
+ const sessionCookieToken = await ctx.getSignedCookie(
890
+ ctx.context.authCookies.sessionToken.name,
891
+ ctx.context.secret
892
+ );
893
+ if (!sessionCookieToken) {
894
+ return ctx.json(null, {
895
+ status: 401
896
+ });
897
+ }
898
+ const key = getRequestUniqueKey(ctx, sessionCookieToken);
899
+ const cachedSession = sessionCache.get(key);
900
+ if (cachedSession) {
901
+ if (cachedSession.expiresAt > Date.now()) {
902
+ return ctx.json(
903
+ cachedSession.data
904
+ );
905
+ }
906
+ sessionCache.delete(key);
907
+ }
908
+ const session = await ctx.context.internalAdapter.findSession(sessionCookieToken);
909
+ if (!session || session.session.expiresAt < /* @__PURE__ */ new Date()) {
910
+ deleteSessionCookie(ctx);
911
+ if (session) {
912
+ await ctx.context.internalAdapter.deleteSession(session.session.id);
913
+ }
914
+ return ctx.json(null, {
915
+ status: 401
916
+ });
917
+ }
918
+ const dontRememberMe = await ctx.getSignedCookie(
919
+ ctx.context.authCookies.dontRememberToken.name,
920
+ ctx.context.secret
921
+ );
922
+ if (dontRememberMe) {
923
+ return ctx.json(
924
+ session
925
+ );
926
+ }
927
+ const expiresIn = ctx.context.sessionConfig.expiresIn;
928
+ const updateAge = ctx.context.sessionConfig.updateAge;
929
+ const sessionIsDueToBeUpdatedDate = session.session.expiresAt.valueOf() - expiresIn * 1e3 + updateAge * 1e3;
930
+ const shouldBeUpdated = sessionIsDueToBeUpdatedDate <= Date.now();
931
+ if (shouldBeUpdated) {
932
+ const updatedSession = await ctx.context.internalAdapter.updateSession(
933
+ session.session.id,
934
+ {
935
+ expiresAt: getDate(ctx.context.sessionConfig.expiresIn, true)
936
+ }
937
+ );
938
+ if (!updatedSession) {
939
+ deleteSessionCookie(ctx);
940
+ return ctx.json(null, { status: 401 });
941
+ }
942
+ const maxAge = (updatedSession.expiresAt.valueOf() - Date.now()) / 1e3;
943
+ await setSessionCookie(ctx, updatedSession.id, false, {
944
+ maxAge
945
+ });
946
+ return ctx.json({
947
+ session: updatedSession,
948
+ user: session.user
949
+ });
950
+ }
951
+ sessionCache.set(key, {
952
+ data: session,
953
+ expiresAt: Date.now() + 5e3
954
+ });
955
+ return ctx.json(
956
+ session
957
+ );
958
+ } catch (error2) {
959
+ ctx.context.logger.error(error2);
960
+ return ctx.json(null, { status: 500 });
961
+ }
962
+ }
963
+ );
964
+ var getSessionFromCtx = async (ctx) => {
965
+ const session = await getSession()({
966
+ ...ctx,
967
+ //@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
968
+ _flag: void 0
969
+ });
970
+ return session;
971
+ };
972
+ var sessionMiddleware = createAuthMiddleware(async (ctx) => {
973
+ const session = await getSessionFromCtx(ctx);
974
+ if (!session?.session) {
975
+ throw new APIError2("UNAUTHORIZED");
976
+ }
977
+ return {
978
+ session
979
+ };
980
+ });
981
+ var listSessions = () => createAuthEndpoint(
982
+ "/user/list-sessions",
983
+ {
984
+ method: "GET",
985
+ use: [sessionMiddleware],
986
+ requireHeaders: true
987
+ },
988
+ async (ctx) => {
989
+ const sessions = await ctx.context.adapter.findMany({
990
+ model: ctx.context.tables.session.tableName,
991
+ where: [
992
+ {
993
+ field: "userId",
994
+ value: ctx.context.session.user.id
995
+ }
996
+ ]
997
+ });
998
+ const activeSessions = sessions.filter((session) => {
999
+ return session.expiresAt > /* @__PURE__ */ new Date();
1000
+ });
1001
+ return ctx.json(
1002
+ activeSessions
1003
+ );
1004
+ }
1005
+ );
1006
+ var revokeSession = createAuthEndpoint(
1007
+ "/user/revoke-session",
1008
+ {
1009
+ method: "POST",
1010
+ body: z3.object({
1011
+ id: z3.string()
1012
+ }),
1013
+ use: [sessionMiddleware],
1014
+ requireHeaders: true
1015
+ },
1016
+ async (ctx) => {
1017
+ const id = ctx.body.id;
1018
+ const findSession = await ctx.context.internalAdapter.findSession(id);
1019
+ if (!findSession) {
1020
+ return ctx.json(null, { status: 400 });
1021
+ }
1022
+ if (findSession.session.userId !== ctx.context.session.user.id) {
1023
+ return ctx.json(null, { status: 403 });
1024
+ }
1025
+ try {
1026
+ await ctx.context.internalAdapter.deleteSession(id);
1027
+ } catch (error2) {
1028
+ ctx.context.logger.error(error2);
1029
+ return ctx.json(null, { status: 500 });
1030
+ }
1031
+ return ctx.json({
1032
+ status: true
1033
+ });
1034
+ }
1035
+ );
1036
+ var revokeSessions = createAuthEndpoint(
1037
+ "/user/revoke-sessions",
1038
+ {
1039
+ method: "POST",
1040
+ use: [sessionMiddleware],
1041
+ requireHeaders: true
1042
+ },
1043
+ async (ctx) => {
1044
+ try {
1045
+ await ctx.context.internalAdapter.deleteSessions(
1046
+ ctx.context.session.user.id
1047
+ );
1048
+ } catch (error2) {
1049
+ ctx.context.logger.error(error2);
1050
+ return ctx.json(null, { status: 500 });
1051
+ }
1052
+ return ctx.json({
1053
+ status: true
1054
+ });
1055
+ }
1056
+ );
1057
+
1058
+ // src/api/routes/sign-in.ts
1059
+ var signInOAuth = createAuthEndpoint(
1060
+ "/sign-in/social",
1061
+ {
1062
+ method: "POST",
1063
+ requireHeaders: true,
1064
+ query: z4.object({
1065
+ /**
1066
+ * Redirect to the current URL after the
1067
+ * user has signed in.
1068
+ */
1069
+ currentURL: z4.string().optional()
1070
+ }).optional(),
1071
+ body: z4.object({
1072
+ /**
1073
+ * Callback URL to redirect to after the user has signed in.
1074
+ */
1075
+ callbackURL: z4.string().optional(),
1076
+ /**
1077
+ * OAuth2 provider to use`
1078
+ */
1079
+ provider: z4.enum(oAuthProviderList),
1080
+ /**
1081
+ * If this is true the session will only be valid for the current browser session
1082
+ */
1083
+ dontRememberMe: z4.boolean().default(false).optional()
1084
+ })
1085
+ },
1086
+ async (c) => {
1087
+ const provider = c.context.socialProviders.find(
1088
+ (p) => p.id === c.body.provider
1089
+ );
1090
+ if (!provider) {
1091
+ c.context.logger.error(
1092
+ "Provider not found. Make sure to add the provider to your auth config",
1093
+ {
1094
+ provider: c.body.provider
1095
+ }
1096
+ );
1097
+ throw new APIError3("NOT_FOUND", {
1098
+ message: "Provider not found"
1099
+ });
1100
+ }
1101
+ const cookie = c.context.authCookies;
1102
+ const currentURL = c.query?.currentURL ? new URL(c.query?.currentURL) : null;
1103
+ const callbackURL = c.body.callbackURL?.startsWith("http") ? c.body.callbackURL : `${currentURL?.origin}${c.body.callbackURL || ""}`;
1104
+ const state = generateState(
1105
+ callbackURL || currentURL?.origin || c.context.baseURL,
1106
+ c.query?.currentURL
1107
+ );
1108
+ try {
1109
+ await c.setSignedCookie(
1110
+ cookie.state.name,
1111
+ state.code,
1112
+ c.context.secret,
1113
+ cookie.state.options
1114
+ );
1115
+ const codeVerifier = generateCodeVerifier();
1116
+ await c.setSignedCookie(
1117
+ cookie.pkCodeVerifier.name,
1118
+ codeVerifier,
1119
+ c.context.secret,
1120
+ cookie.pkCodeVerifier.options
1121
+ );
1122
+ const url = provider.createAuthorizationURL({
1123
+ state: state.state,
1124
+ codeVerifier
1125
+ });
1126
+ url.searchParams.set(
1127
+ "redirect_uri",
1128
+ `${c.context.baseURL}/callback/${c.body.provider}`
1129
+ );
1130
+ return {
1131
+ url: url.toString(),
1132
+ state: state.state,
1133
+ codeVerifier,
1134
+ redirect: true
1135
+ };
1136
+ } catch (e) {
1137
+ throw new APIError3("INTERNAL_SERVER_ERROR");
1138
+ }
1139
+ }
1140
+ );
1141
+ var signInEmail = createAuthEndpoint(
1142
+ "/sign-in/email",
1143
+ {
1144
+ method: "POST",
1145
+ body: z4.object({
1146
+ email: z4.string().email(),
1147
+ password: z4.string(),
1148
+ callbackURL: z4.string().optional(),
1149
+ /**
1150
+ * If this is true the session will only be valid for the current browser session
1151
+ * @default false
1152
+ */
1153
+ dontRememberMe: z4.boolean().default(false).optional()
1154
+ })
1155
+ },
1156
+ async (ctx) => {
1157
+ if (!ctx.context.options?.emailAndPassword?.enabled) {
1158
+ ctx.context.logger.error(
1159
+ "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!"
1160
+ );
1161
+ throw new APIError3("BAD_REQUEST", {
1162
+ message: "Email and password is not enabled"
1163
+ });
1164
+ }
1165
+ const currentSession = await getSessionFromCtx(ctx);
1166
+ if (currentSession) {
1167
+ await ctx.context.internalAdapter.deleteSession(
1168
+ currentSession.session.id
1169
+ );
1170
+ }
1171
+ const { email, password } = ctx.body;
1172
+ const checkEmail = z4.string().email().safeParse(email);
1173
+ if (!checkEmail.success) {
1174
+ throw new APIError3("BAD_REQUEST", {
1175
+ message: "Invalid email"
1176
+ });
1177
+ }
1178
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1179
+ if (!user) {
1180
+ await ctx.context.password.hash(password);
1181
+ ctx.context.logger.error("User not found", { email });
1182
+ throw new APIError3("UNAUTHORIZED", {
1183
+ message: "Invalid email or password"
1184
+ });
1185
+ }
1186
+ const credentialAccount = user.accounts.find(
1187
+ (a) => a.providerId === "credential"
1188
+ );
1189
+ if (!credentialAccount) {
1190
+ ctx.context.logger.error("Credential account not found", { email });
1191
+ throw new APIError3("UNAUTHORIZED", {
1192
+ message: "Invalid email or password"
1193
+ });
1194
+ }
1195
+ const currentPassword = credentialAccount?.password;
1196
+ if (!currentPassword) {
1197
+ ctx.context.logger.error("Password not found", { email });
1198
+ throw new APIError3("UNAUTHORIZED", {
1199
+ message: "Unexpected error"
1200
+ });
1201
+ }
1202
+ const validPassword = await ctx.context.password.verify(
1203
+ currentPassword,
1204
+ password
1205
+ );
1206
+ if (!validPassword) {
1207
+ ctx.context.logger.error("Invalid password");
1208
+ throw new APIError3("UNAUTHORIZED", {
1209
+ message: "Invalid email or password"
1210
+ });
1211
+ }
1212
+ const session = await ctx.context.internalAdapter.createSession(
1213
+ user.user.id,
1214
+ ctx.headers,
1215
+ ctx.body.dontRememberMe
1216
+ );
1217
+ if (!session) {
1218
+ ctx.context.logger.error("Failed to create session");
1219
+ throw new APIError3("INTERNAL_SERVER_ERROR");
1220
+ }
1221
+ await setSessionCookie(ctx, session.id, ctx.body.dontRememberMe);
1222
+ return ctx.json({
1223
+ user: user.user,
1224
+ session,
1225
+ redirect: !!ctx.body.callbackURL,
1226
+ url: ctx.body.callbackURL
1227
+ });
1228
+ }
1229
+ );
1230
+
1231
+ // src/api/routes/callback.ts
1232
+ import { APIError as APIError4 } from "better-call";
1233
+ import { z as z6 } from "zod";
1234
+
1235
+ // src/db/schema.ts
1236
+ import { z as z5 } from "zod";
1237
+ var accountSchema = z5.object({
1238
+ id: z5.string(),
1239
+ providerId: z5.string(),
1240
+ accountId: z5.string(),
1241
+ userId: z5.string(),
1242
+ accessToken: z5.string().nullable().optional(),
1243
+ refreshToken: z5.string().nullable().optional(),
1244
+ idToken: z5.string().nullable().optional(),
1245
+ /**
1246
+ * Access token expires at
1247
+ */
1248
+ expiresAt: z5.date().nullable().optional(),
1249
+ /**
1250
+ * Password is only stored in the credential provider
1251
+ */
1252
+ password: z5.string().optional().nullable()
1253
+ });
1254
+ var userSchema = z5.object({
1255
+ id: z5.string(),
1256
+ email: z5.string().transform((val) => val.toLowerCase()),
1257
+ emailVerified: z5.boolean().default(false),
1258
+ name: z5.string(),
1259
+ image: z5.string().optional(),
1260
+ createdAt: z5.date().default(/* @__PURE__ */ new Date()),
1261
+ updatedAt: z5.date().default(/* @__PURE__ */ new Date())
1262
+ });
1263
+ var sessionSchema = z5.object({
1264
+ id: z5.string(),
1265
+ userId: z5.string(),
1266
+ expiresAt: z5.date(),
1267
+ ipAddress: z5.string().optional(),
1268
+ userAgent: z5.string().optional()
1269
+ });
1270
+
1271
+ // src/utils/id.ts
1272
+ import { alphabet, generateRandomString } from "oslo/crypto";
1273
+ var generateId = () => {
1274
+ return generateRandomString(36, alphabet("a-z", "0-9"));
1275
+ };
1276
+
1277
+ // src/utils/hide-metadata.ts
1278
+ var HIDE_METADATA = {
1279
+ isAction: false
1280
+ };
1281
+
1282
+ // src/utils/getAccount.ts
1283
+ function getAccountTokens(tokens) {
1284
+ const accessToken = tokens.accessToken();
1285
+ let refreshToken = tokens.hasRefreshToken() ? tokens.refreshToken() : void 0;
1286
+ let accessTokenExpiresAt = void 0;
1287
+ try {
1288
+ accessTokenExpiresAt = tokens.accessTokenExpiresAt();
1289
+ } catch {
1290
+ }
1291
+ return {
1292
+ accessToken,
1293
+ refreshToken,
1294
+ expiresAt: accessTokenExpiresAt
1295
+ };
1296
+ }
1297
+
1298
+ // src/api/routes/callback.ts
1299
+ var callbackOAuth = createAuthEndpoint(
1300
+ "/callback/:id",
1301
+ {
1302
+ method: "GET",
1303
+ query: z6.object({
1304
+ state: z6.string(),
1305
+ code: z6.string().optional(),
1306
+ error: z6.string().optional()
1307
+ }),
1308
+ metadata: HIDE_METADATA
1309
+ },
1310
+ async (c) => {
1311
+ if (c.query.error || !c.query.code) {
1312
+ const parsedState2 = parseState(c.query.state);
1313
+ const callbackURL2 = parsedState2.data?.callbackURL || `${c.context.baseURL}/error`;
1314
+ c.context.logger.error(c.query.error, c.params.id);
1315
+ throw c.redirect(
1316
+ `${callbackURL2}?error=${c.query.error || "oAuth_code_missing"}`
1317
+ );
1318
+ }
1319
+ const provider = c.context.socialProviders.find(
1320
+ (p) => p.id === c.params.id
1321
+ );
1322
+ if (!provider) {
1323
+ c.context.logger.error(
1324
+ "Oauth provider with id",
1325
+ c.params.id,
1326
+ "not found"
1327
+ );
1328
+ throw c.redirect(
1329
+ `${c.context.baseURL}/error?error=oauth_provider_not_found`
1330
+ );
1331
+ }
1332
+ const codeVerifier = await c.getSignedCookie(
1333
+ c.context.authCookies.pkCodeVerifier.name,
1334
+ c.context.secret
1335
+ );
1336
+ let tokens;
1337
+ try {
1338
+ tokens = await provider.validateAuthorizationCode(
1339
+ c.query.code,
1340
+ codeVerifier,
1341
+ `${c.context.baseURL}/callback/${provider.id}`
1342
+ );
1343
+ } catch (e) {
1344
+ c.context.logger.error(e);
1345
+ throw c.redirect(
1346
+ `${c.context.baseURL}/error?error=oauth_code_verification_failed`
1347
+ );
1348
+ }
1349
+ const user = await provider.getUserInfo(tokens).then((res) => res?.user);
1350
+ const id = generateId();
1351
+ const data = userSchema.safeParse({
1352
+ ...user,
1353
+ id
1354
+ });
1355
+ const parsedState = parseState(c.query.state);
1356
+ if (!parsedState.success) {
1357
+ c.context.logger.error("Unable to parse state");
1358
+ throw c.redirect(
1359
+ `${c.context.baseURL}/error?error=invalid_state_parameter`
1360
+ );
1361
+ }
1362
+ const { callbackURL, currentURL, dontRememberMe } = parsedState.data;
1363
+ if (!user || data.success === false) {
1364
+ throw c.redirect(
1365
+ `${c.context.baseURL}/error?error=oauth_validation_failed`
1366
+ );
1367
+ }
1368
+ if (!callbackURL) {
1369
+ throw c.redirect(
1370
+ `${c.context.baseURL}/error?error=oauth_callback_url_not_found`
1371
+ );
1372
+ }
1373
+ const dbUser = await c.context.internalAdapter.findUserByEmail(user.email);
1374
+ const userId = dbUser?.user.id;
1375
+ if (dbUser) {
1376
+ const hasBeenLinked = dbUser.accounts.find(
1377
+ (a) => a.providerId === provider.id
1378
+ );
1379
+ const trustedProviders = c.context.options.account?.accountLinking?.trustedProviders;
1380
+ const isTrustedProvider = trustedProviders ? trustedProviders.includes(provider.id) : true;
1381
+ if (!hasBeenLinked && (!user.emailVerified || !isTrustedProvider)) {
1382
+ let url;
1383
+ try {
1384
+ url = new URL(currentURL || callbackURL);
1385
+ url.searchParams.set("error", "account_not_linked");
1386
+ } catch (e) {
1387
+ throw c.redirect(
1388
+ `${c.context.baseURL}/error?error=account_not_linked`
1389
+ );
1390
+ }
1391
+ throw c.redirect(url.toString());
1392
+ }
1393
+ if (!hasBeenLinked) {
1394
+ try {
1395
+ await c.context.internalAdapter.linkAccount({
1396
+ providerId: provider.id,
1397
+ accountId: user.id,
1398
+ id: `${provider.id}:${user.id}`,
1399
+ userId: dbUser.user.id,
1400
+ ...getAccountTokens(tokens)
1401
+ });
1402
+ } catch (e) {
1403
+ console.log(e);
1404
+ throw c.redirect(
1405
+ `${c.context.baseURL}/error?error=failed_linking_account`
1406
+ );
1407
+ }
1408
+ }
1409
+ } else {
1410
+ try {
1411
+ await c.context.internalAdapter.createOAuthUser(data.data, {
1412
+ ...getAccountTokens(tokens),
1413
+ id: `${provider.id}:${user.id}`,
1414
+ providerId: provider.id,
1415
+ accountId: user.id,
1416
+ userId: id
1417
+ });
1418
+ } catch (e) {
1419
+ const url = new URL(currentURL || callbackURL);
1420
+ url.searchParams.set("error", "unable_to_create_user");
1421
+ c.setHeader("Location", url.toString());
1422
+ throw c.redirect(url.toString());
1423
+ }
1424
+ }
1425
+ if (!userId && !id)
1426
+ throw new APIError4("INTERNAL_SERVER_ERROR", {
1427
+ message: "Unable to create user"
1428
+ });
1429
+ const session = await c.context.internalAdapter.createSession(
1430
+ userId || id,
1431
+ c.request,
1432
+ dontRememberMe
1433
+ );
1434
+ if (!session) {
1435
+ const url = new URL(currentURL || callbackURL);
1436
+ url.searchParams.set("error", "unable_to_create_session");
1437
+ throw c.redirect(url.toString());
1438
+ }
1439
+ try {
1440
+ await setSessionCookie(c, session.id, dontRememberMe);
1441
+ } catch (e) {
1442
+ c.context.logger.error("Unable to set session cookie", e);
1443
+ const url = new URL(currentURL || callbackURL);
1444
+ url.searchParams.set("error", "unable_to_create_session");
1445
+ throw c.redirect(url.toString());
1446
+ }
1447
+ throw c.redirect(callbackURL);
1448
+ }
1449
+ );
1450
+
1451
+ // src/api/routes/sign-out.ts
1452
+ import { z as z7 } from "zod";
1453
+ var signOut = createAuthEndpoint(
1454
+ "/sign-out",
1455
+ {
1456
+ method: "POST",
1457
+ body: z7.optional(
1458
+ z7.object({
1459
+ callbackURL: z7.string().optional()
1460
+ })
1461
+ )
1462
+ },
1463
+ async (ctx) => {
1464
+ const sessionCookieToken = await ctx.getSignedCookie(
1465
+ ctx.context.authCookies.sessionToken.name,
1466
+ ctx.context.secret
1467
+ );
1468
+ if (!sessionCookieToken) {
1469
+ return ctx.json(null);
1470
+ }
1471
+ await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
1472
+ deleteSessionCookie(ctx);
1473
+ return ctx.json(null, {
1474
+ body: {
1475
+ redirect: !!ctx.body?.callbackURL,
1476
+ url: ctx.body?.callbackURL
1477
+ }
1478
+ });
1479
+ }
1480
+ );
1481
+
1482
+ // src/api/routes/forget-password.ts
1483
+ import { TimeSpan as TimeSpan2 } from "oslo";
1484
+ import { createJWT, parseJWT as parseJWT3 } from "oslo/jwt";
1485
+ import { validateJWT } from "oslo/jwt";
1486
+ import { z as z8 } from "zod";
1487
+ var forgetPassword = createAuthEndpoint(
1488
+ "/forget-password",
1489
+ {
1490
+ method: "POST",
1491
+ body: z8.object({
1492
+ /**
1493
+ * The email address of the user to send a password reset email to.
1494
+ */
1495
+ email: z8.string().email(),
1496
+ /**
1497
+ * The URL to redirect the user to reset their password.
1498
+ * If the token isn't valid or expired, it'll be redirected with a query parameter `?
1499
+ * error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?
1500
+ * token=VALID_TOKEN
1501
+ */
1502
+ redirectTo: z8.string()
1503
+ })
1504
+ },
1505
+ async (ctx) => {
1506
+ if (!ctx.context.options.emailAndPassword?.sendResetPassword) {
1507
+ ctx.context.logger.error(
1508
+ "Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!"
1509
+ );
1510
+ return ctx.json(null, {
1511
+ status: 400,
1512
+ statusText: "RESET_PASSWORD_EMAIL_NOT_SENT",
1513
+ body: {
1514
+ message: "Reset password isn't enabled"
1515
+ }
1516
+ });
1517
+ }
1518
+ const { email } = ctx.body;
1519
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1520
+ if (!user) {
1521
+ return ctx.json(
1522
+ {
1523
+ status: false
1524
+ },
1525
+ {
1526
+ body: {
1527
+ status: true
1528
+ }
1529
+ }
1530
+ );
1531
+ }
1532
+ const token = await createJWT(
1533
+ "HS256",
1534
+ Buffer.from(ctx.context.secret),
1535
+ {
1536
+ email: user.user.email,
1537
+ redirectTo: ctx.body.redirectTo
1538
+ },
1539
+ {
1540
+ expiresIn: new TimeSpan2(1, "h"),
1541
+ issuer: "better-auth",
1542
+ subject: "forget-password",
1543
+ audiences: [user.user.email],
1544
+ includeIssuedTimestamp: true
1545
+ }
1546
+ );
1547
+ const url = `${ctx.context.baseURL}/reset-password/${token}`;
1548
+ await ctx.context.options.emailAndPassword.sendResetPassword(
1549
+ url,
1550
+ user.user
1551
+ );
1552
+ return ctx.json({
1553
+ status: true
1554
+ });
1555
+ }
1556
+ );
1557
+ var forgetPasswordCallback = createAuthEndpoint(
1558
+ "/reset-password/:token",
1559
+ {
1560
+ method: "GET"
1561
+ },
1562
+ async (ctx) => {
1563
+ const { token } = ctx.params;
1564
+ let decodedToken;
1565
+ const schema = z8.object({
1566
+ email: z8.string(),
1567
+ redirectTo: z8.string()
1568
+ });
1569
+ try {
1570
+ decodedToken = await validateJWT(
1571
+ "HS256",
1572
+ Buffer.from(ctx.context.secret),
1573
+ token
1574
+ );
1575
+ if (!decodedToken.expiresAt || decodedToken.expiresAt < /* @__PURE__ */ new Date()) {
1576
+ throw Error("Token expired");
1577
+ }
1578
+ } catch (e) {
1579
+ const decoded = parseJWT3(token);
1580
+ const jwt = schema.safeParse(decoded?.payload);
1581
+ if (jwt.success) {
1582
+ throw ctx.redirect(`${jwt.data?.redirectTo}?error=invalid_token`);
1583
+ } else {
1584
+ throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_token`);
1585
+ }
1586
+ }
1587
+ const { redirectTo } = schema.parse(decodedToken.payload);
1588
+ throw ctx.redirect(`${redirectTo}?token=${token}`);
1589
+ }
1590
+ );
1591
+ var resetPassword = createAuthEndpoint(
1592
+ "/reset-password",
1593
+ {
1594
+ method: "POST",
1595
+ query: z8.object({
1596
+ currentURL: z8.string()
1597
+ }).optional(),
1598
+ body: z8.object({
1599
+ newPassword: z8.string(),
1600
+ callbackURL: z8.string().optional()
1601
+ })
1602
+ },
1603
+ async (ctx) => {
1604
+ const token = ctx.query?.currentURL.split("?token=")[1];
1605
+ if (!token) {
1606
+ return ctx.json(
1607
+ {
1608
+ error: "Invalid token",
1609
+ data: null
1610
+ },
1611
+ {
1612
+ status: 400,
1613
+ statusText: "INVALID_TOKEN",
1614
+ body: {
1615
+ message: "Invalid token"
1616
+ }
1617
+ }
1618
+ );
1619
+ }
1620
+ const { newPassword } = ctx.body;
1621
+ try {
1622
+ const jwt = await validateJWT(
1623
+ "HS256",
1624
+ Buffer.from(ctx.context.secret),
1625
+ token
1626
+ );
1627
+ const email = z8.string().email().parse(jwt.payload.email);
1628
+ const user = await ctx.context.internalAdapter.findUserByEmail(email);
1629
+ if (!user) {
1630
+ return ctx.json(
1631
+ {
1632
+ error: "User not found",
1633
+ data: null
1634
+ },
1635
+ {
1636
+ status: 400,
1637
+ body: {
1638
+ message: "failed to reset password"
1639
+ }
1640
+ }
1641
+ );
1642
+ }
1643
+ if (newPassword.length < (ctx.context.options.emailAndPassword?.minPasswordLength || 8) || newPassword.length > (ctx.context.options.emailAndPassword?.maxPasswordLength || 32)) {
1644
+ return ctx.json(
1645
+ {
1646
+ data: null,
1647
+ error: "password is too short or too long"
1648
+ },
1649
+ {
1650
+ status: 400,
1651
+ statusText: "INVALID_PASSWORD_LENGTH",
1652
+ body: {
1653
+ message: "password is too short or too long"
1654
+ }
1655
+ }
1656
+ );
1657
+ }
1658
+ const hashedPassword = await ctx.context.password.hash(newPassword);
1659
+ const updatedUser = await ctx.context.internalAdapter.updatePassword(
1660
+ user.user.id,
1661
+ hashedPassword
1662
+ );
1663
+ if (!updatedUser) {
1664
+ return ctx.json(null, {
1665
+ status: 400,
1666
+ statusText: "USER_NOT_FOUND",
1667
+ body: {
1668
+ message: "User doesn't have a credential account"
1669
+ }
1670
+ });
1671
+ }
1672
+ return ctx.json(
1673
+ {
1674
+ error: null,
1675
+ data: {
1676
+ status: true,
1677
+ url: ctx.body.callbackURL,
1678
+ redirect: !!ctx.body.callbackURL
1679
+ }
1680
+ },
1681
+ {
1682
+ body: {
1683
+ status: true,
1684
+ url: ctx.body.callbackURL,
1685
+ redirect: !!ctx.body.callbackURL
1686
+ }
1687
+ }
1688
+ );
1689
+ } catch (e) {
1690
+ console.log(e);
1691
+ return ctx.json(
1692
+ {
1693
+ error: "Invalid token",
1694
+ data: null
1695
+ },
1696
+ {
1697
+ status: 400,
1698
+ statusText: "INVALID_TOKEN",
1699
+ body: {
1700
+ message: "Invalid token"
1701
+ }
1702
+ }
1703
+ );
1704
+ }
1705
+ }
1706
+ );
1707
+
1708
+ // src/api/routes/verify-email.ts
1709
+ import { TimeSpan as TimeSpan3 } from "oslo";
1710
+ import { createJWT as createJWT2, validateJWT as validateJWT2 } from "oslo/jwt";
1711
+ import { z as z9 } from "zod";
1712
+ async function createEmailVerificationToken(secret, email) {
1713
+ const token = await createJWT2(
1714
+ "HS256",
1715
+ Buffer.from(secret),
1716
+ {
1717
+ email: email.toLowerCase()
1718
+ },
1719
+ {
1720
+ expiresIn: new TimeSpan3(1, "h"),
1721
+ issuer: "better-auth",
1722
+ subject: "verify-email",
1723
+ audiences: [email],
1724
+ includeIssuedTimestamp: true
1725
+ }
1726
+ );
1727
+ return token;
1728
+ }
1729
+ var sendVerificationEmail = createAuthEndpoint(
1730
+ "/send-verification-email",
1731
+ {
1732
+ method: "POST",
1733
+ query: z9.object({
1734
+ currentURL: z9.string().optional()
1735
+ }).optional(),
1736
+ body: z9.object({
1737
+ email: z9.string().email(),
1738
+ callbackURL: z9.string().optional()
1739
+ })
1740
+ },
1741
+ async (ctx) => {
1742
+ if (!ctx.context.options.emailAndPassword?.sendVerificationEmail) {
1743
+ ctx.context.logger.error(
1744
+ "Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it."
1745
+ );
1746
+ return ctx.json(null, {
1747
+ status: 400,
1748
+ statusText: "VERIFICATION_EMAIL_NOT_SENT",
1749
+ body: {
1750
+ message: "Verification email isn't enabled"
1751
+ }
1752
+ });
1753
+ }
1754
+ const { email } = ctx.body;
1755
+ const token = await createEmailVerificationToken(ctx.context.secret, email);
1756
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || ctx.query?.currentURL || "/"}`;
1757
+ await ctx.context.options.emailAndPassword.sendVerificationEmail(
1758
+ email,
1759
+ url,
1760
+ token
1761
+ );
1762
+ return ctx.json({
1763
+ status: true
1764
+ });
1765
+ }
1766
+ );
1767
+ var verifyEmail = createAuthEndpoint(
1768
+ "/verify-email",
1769
+ {
1770
+ method: "GET",
1771
+ query: z9.object({
1772
+ token: z9.string(),
1773
+ callbackURL: z9.string().optional()
1774
+ })
1775
+ },
1776
+ async (ctx) => {
1777
+ const { token } = ctx.query;
1778
+ let jwt;
1779
+ try {
1780
+ jwt = await validateJWT2("HS256", Buffer.from(ctx.context.secret), token);
1781
+ } catch (e) {
1782
+ ctx.context.logger.error("Failed to verify email", e);
1783
+ return ctx.json(null, {
1784
+ status: 400,
1785
+ statusText: "INVALID_TOKEN",
1786
+ body: {
1787
+ message: "Invalid token"
1788
+ }
1789
+ });
1790
+ }
1791
+ const schema = z9.object({
1792
+ email: z9.string().email()
1793
+ });
1794
+ const parsed = schema.parse(jwt.payload);
1795
+ const user = await ctx.context.internalAdapter.findUserByEmail(
1796
+ parsed.email
1797
+ );
1798
+ if (!user) {
1799
+ return ctx.json(null, {
1800
+ status: 400,
1801
+ statusText: "USER_NOT_FOUND",
1802
+ body: {
1803
+ message: "User not found"
1804
+ }
1805
+ });
1806
+ }
1807
+ const account = user.accounts.find((a) => a.providerId === "credential");
1808
+ if (!account) {
1809
+ throw ctx.redirect;
1810
+ }
1811
+ await ctx.context.internalAdapter.updateUserByEmail(parsed.email, {
1812
+ emailVerified: true
1813
+ });
1814
+ if (ctx.query.callbackURL) {
1815
+ console.log("Redirecting to", ctx.query.callbackURL);
1816
+ throw ctx.redirect("/");
1817
+ }
1818
+ return ctx.json({
1819
+ status: true
1820
+ });
1821
+ }
1822
+ );
1823
+
1824
+ // src/api/routes/update-user.ts
1825
+ import { z as z10 } from "zod";
1826
+ import { alphabet as alphabet2, generateRandomString as generateRandomString2 } from "oslo/crypto";
1827
+ import "better-call";
1828
+ var updateUser = createAuthEndpoint(
1829
+ "/user/update",
1830
+ {
1831
+ method: "POST",
1832
+ body: z10.object({
1833
+ name: z10.string().optional(),
1834
+ image: z10.string().optional()
1835
+ }),
1836
+ use: [sessionMiddleware]
1837
+ },
1838
+ async (ctx) => {
1839
+ const { name, image } = ctx.body;
1840
+ const session = ctx.context.session;
1841
+ if (!image && !name) {
1842
+ return ctx.json(session.user);
1843
+ }
1844
+ const user = await ctx.context.internalAdapter.updateUserByEmail(
1845
+ session.user.email,
1846
+ {
1847
+ name,
1848
+ image
1849
+ }
1850
+ );
1851
+ return ctx.json(user);
1852
+ }
1853
+ );
1854
+ var changePassword = createAuthEndpoint(
1855
+ "/user/change-password",
1856
+ {
1857
+ method: "POST",
1858
+ body: z10.object({
1859
+ /**
1860
+ * The new password to set
1861
+ */
1862
+ newPassword: z10.string(),
1863
+ /**
1864
+ * The current password of the user
1865
+ */
1866
+ currentPassword: z10.string(),
1867
+ /**
1868
+ * revoke all sessions that are not the
1869
+ * current one logged in by the user
1870
+ */
1871
+ revokeOtherSessions: z10.boolean().optional()
1872
+ }),
1873
+ use: [sessionMiddleware]
1874
+ },
1875
+ async (ctx) => {
1876
+ const { newPassword, currentPassword, revokeOtherSessions } = ctx.body;
1877
+ const session = ctx.context.session;
1878
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
1879
+ if (newPassword.length < minPasswordLength) {
1880
+ ctx.context.logger.error("Password is too short");
1881
+ return ctx.json(null, {
1882
+ status: 400,
1883
+ body: { message: "Password is too short" }
1884
+ });
1885
+ }
1886
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
1887
+ if (newPassword.length > maxPasswordLength) {
1888
+ ctx.context.logger.error("Password is too long");
1889
+ return ctx.json(null, {
1890
+ status: 400,
1891
+ body: { message: "Password is too long" }
1892
+ });
1893
+ }
1894
+ const accounts = await ctx.context.internalAdapter.findAccounts(
1895
+ session.user.id
1896
+ );
1897
+ const account = accounts.find(
1898
+ (account2) => account2.providerId === "credential" && account2.password
1899
+ );
1900
+ if (!account || !account.password) {
1901
+ return ctx.json(null, {
1902
+ status: 400,
1903
+ body: { message: "User does not have a password" }
1904
+ });
1905
+ }
1906
+ const passwordHash = await ctx.context.password.hash(newPassword);
1907
+ const verify = await ctx.context.password.verify(
1908
+ account.password,
1909
+ currentPassword
1910
+ );
1911
+ if (!verify) {
1912
+ return ctx.json(null, {
1913
+ status: 400,
1914
+ body: { message: "Invalid password" }
1915
+ });
1916
+ }
1917
+ await ctx.context.internalAdapter.updateAccount(account.id, {
1918
+ password: passwordHash
1919
+ });
1920
+ if (revokeOtherSessions) {
1921
+ await ctx.context.internalAdapter.deleteSessions(session.user.id);
1922
+ const newSession = await ctx.context.internalAdapter.createSession(
1923
+ session.user.id,
1924
+ ctx.headers
1925
+ );
1926
+ if (!newSession) {
1927
+ return ctx.json(null, {
1928
+ status: 500,
1929
+ body: { message: "Failed to create session" }
1930
+ });
1931
+ }
1932
+ await setSessionCookie(ctx, newSession.id);
1933
+ }
1934
+ return ctx.json(session.user);
1935
+ }
1936
+ );
1937
+ var setPassword = createAuthEndpoint(
1938
+ "/user/set-password",
1939
+ {
1940
+ method: "POST",
1941
+ body: z10.object({
1942
+ /**
1943
+ * The new password to set
1944
+ */
1945
+ newPassword: z10.string()
1946
+ }),
1947
+ use: [sessionMiddleware]
1948
+ },
1949
+ async (ctx) => {
1950
+ const { newPassword } = ctx.body;
1951
+ const session = ctx.context.session;
1952
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
1953
+ if (newPassword.length < minPasswordLength) {
1954
+ ctx.context.logger.error("Password is too short");
1955
+ return ctx.json(null, {
1956
+ status: 400,
1957
+ body: { message: "Password is too short" }
1958
+ });
1959
+ }
1960
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
1961
+ if (newPassword.length > maxPasswordLength) {
1962
+ ctx.context.logger.error("Password is too long");
1963
+ return ctx.json(null, {
1964
+ status: 400,
1965
+ body: { message: "Password is too long" }
1966
+ });
1967
+ }
1968
+ const accounts = await ctx.context.internalAdapter.findAccounts(
1969
+ session.user.id
1970
+ );
1971
+ const account = accounts.find(
1972
+ (account2) => account2.providerId === "credential" && account2.password
1973
+ );
1974
+ const passwordHash = await ctx.context.password.hash(newPassword);
1975
+ if (!account) {
1976
+ await ctx.context.internalAdapter.linkAccount({
1977
+ id: generateRandomString2(32, alphabet2("a-z", "0-9", "A-Z")),
1978
+ userId: session.user.id,
1979
+ providerId: "credential",
1980
+ accountId: session.user.id,
1981
+ password: passwordHash
1982
+ });
1983
+ return ctx.json(session.user);
1984
+ }
1985
+ return ctx.json(null, {
1986
+ status: 400,
1987
+ body: { message: "User already has a password" }
1988
+ });
1989
+ }
1990
+ );
1991
+
1992
+ // src/api/routes/csrf.ts
1993
+ import { alphabet as alphabet3, generateRandomString as generateRandomString3 } from "oslo/crypto";
1994
+ var getCSRFToken = createAuthEndpoint(
1995
+ "/csrf",
1996
+ {
1997
+ method: "GET",
1998
+ metadata: HIDE_METADATA
1999
+ },
2000
+ async (ctx) => {
2001
+ const csrfToken = await ctx.getSignedCookie(
2002
+ ctx.context.authCookies.csrfToken.name,
2003
+ ctx.context.secret
2004
+ );
2005
+ if (csrfToken) {
2006
+ return {
2007
+ csrfToken
2008
+ };
2009
+ }
2010
+ const token = generateRandomString3(32, alphabet3("a-z", "0-9", "A-Z"));
2011
+ const hash = await hs256(ctx.context.secret, token);
2012
+ const cookie = `${token}!${hash}`;
2013
+ await ctx.setSignedCookie(
2014
+ ctx.context.authCookies.csrfToken.name,
2015
+ cookie,
2016
+ ctx.context.secret,
2017
+ ctx.context.authCookies.csrfToken.options
2018
+ );
2019
+ return {
2020
+ csrfToken: token
2021
+ };
2022
+ }
2023
+ );
2024
+
2025
+ // src/api/routes/error.ts
2026
+ var html = (errorCode = "Unknown") => `<!DOCTYPE html>
3
2027
  <html lang="en">
4
2028
  <head>
5
2029
  <meta charset="UTF-8">
@@ -76,9 +2100,1650 @@ import{APIError as ir,createRouter as ar}from"better-call";import{APIError as ce
76
2100
  <h1>Better Auth Error</h1>
77
2101
  <p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
78
2102
  <a href="/" id="returnLink" class="btn">Return to Application</a>
79
- <div class="error-code">Error Code: <span id="errorCode">${e}</span></div>
2103
+ <div class="error-code">Error Code: <span id="errorCode">${errorCode}</span></div>
80
2104
  </div>
81
2105
  </body>
82
- </html>`,He=g("/error",{method:"GET",metadata:L},async e=>{let t=new URL(e.request?.url||"").searchParams.get("error")||"Unknown";return new Response(Yt(t),{headers:{"Content-Type":"text/html"}})});var Ge=g("/ok",{method:"GET",metadata:L},async e=>e.json({ok:!0}));import{alphabet as Ke,generateRandomString as We}from"oslo/crypto";import{z as O}from"zod";var Je=g("/sign-up/email",{method:"POST",query:O.object({currentURL:O.string().optional()}).optional(),body:O.object({name:O.string(),email:O.string(),password:O.string(),image:O.string().optional(),callbackURL:O.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(!O.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),c=await e.context.password.hash(r);if(d?.user)return e.json(null,{status:400,body:{message:"User already exists"}});let l=await e.context.internalAdapter.createUser({id:We(32,Ke("a-z","0-9","A-Z")),email:o.toLowerCase(),name:t,image:n,emailVerified:!1,createdAt:new Date,updatedAt:new Date});if(!l)return e.json(null,{status:400,body:{message:"Could not create user"}});await e.context.internalAdapter.linkAccount({id:We(32,Ke("a-z","0-9","A-Z")),userId:l.id,providerId:"credential",accountId:l.id,password:c});let u=await e.context.internalAdapter.createSession(l.id,e.request);if(!u)return e.json(null,{status:400,body:{message:"Could not create session"}});if(await P(e,u.id),e.context.options.emailAndPassword.sendEmailVerificationOnSignUp){let p=await ne(e.context.secret,l.email),m=`${e.context.baseURL}/verify-email?token=${p}&callbackURL=${e.body.callbackURL||e.query?.currentURL||"/"}`;await e.context.options.emailAndPassword.sendVerificationEmail?.(l.email,m,p)}return e.json({user:l,session:u},{body:e.body.callbackURL?{url:e.body.callbackURL,redirect:!0}:{user:l,session:u}})});import se from"chalk";function er(e,t,o){let r=Date.now(),n=t*1e3;return r-o.lastRequest<n&&o.count>=e}function tr(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 rr(e,t){let o=Date.now(),r=t*1e3;return Math.ceil((e+r-o)/1e3)}function or(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){T.error("Error setting rate limit",a)}}}}var Ze=new Map;function nr(e){return e.rateLimit.customStorage?e.rateLimit.customStorage:e.rateLimit.storage==="memory"?{async get(o){return Ze.get(o)},async set(o,r,n){Ze.set(o,r)}}:or(e,e.rateLimit.tableName)}async function Qe(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=M(e)+r,d=sr().find(p=>p.pathMatcher(r));d&&(n=d.window,s=d.max);for(let p of t.options.plugins||[])if(p.rateLimit){let m=p.rateLimit.find(f=>f.pathMatcher(r));if(m){n=m.window,s=m.max;break}}if(t.rateLimit.customRules){let p=t.rateLimit.customRules[r];p&&(n=p.window,s=p.max)}let c=nr(t),l=await c.get(i),u=Date.now();if(!l)await c.set(i,{key:i,count:1,lastRequest:u});else{let p=u-l.lastRequest;if(er(s,n,l)){let m=rr(l.lastRequest,n);return tr(m)}else p>n*1e3?await c.set(i,{...l,count:1,lastRequest:u}):await c.set(i,{...l,count:l.count+1,lastRequest:u})}}function sr(){return[{pathMatcher(t){return t.startsWith("/sign-in")||t.startsWith("/sign-up")},window:10,max:7}]}function ie(e,t){let o=t.plugins?.reduce((a,d)=>({...a,...d.endpoints}),{}),r=t.plugins?.map(a=>a.middlewares?.map(d=>{let c=async l=>d.middleware({...l,context:{...e,...l.context}});return c.path=d.path,c.options=d.middleware.options,c.headers=d.middleware.headers,{path:d.path,middleware:c}})).filter(a=>a!==void 0).flat()||[],s={...{signInOAuth:Se,callbackOAuth:Ee,getCSRFToken:Me,getSession:ee(),signOut:_e,signUpEmail:Je,signInEmail:Oe,forgetPassword:qe,resetPassword:De,verifyEmail:$e,sendVerificationEmail:Ne,changePassword:ze,setPassword:Ve,updateUser:Fe,forgetPasswordCallback:je,listSessions:Pe(),revokeSession:ve,revokeSessions:Ie},...o,ok:Ge,error:He},i={};for(let[a,d]of Object.entries(s))i[a]=async c=>{let l=await e,p=await d({...c,context:{...l,...c.context}});for(let m of t.plugins||[])if(m.hooks?.after){for(let f of m.hooks.after)if(f.matcher(c)){let h=Object.assign(c,{context:{...e,returned:p}}),A=await f.handler(h);A&&"response"in A&&(p=A.response)}}return p},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 Xe=(e,t)=>{let{api:o,middlewares:r}=ie(e,t),n=new URL(e.baseURL).pathname;return ar(o,{extraContext:e,basePath:n,routerMiddleware:[{path:"/**",middleware:ue},...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 Qe(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?T:void 0;if(t.logger?.disabled!==!0)if(s instanceof ir)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")?T?.error(`Please run ${se.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")?T.error(`Please run ${se.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")?T?.error(`Please run ${se.green("npx better-auth migrate")} to create the tables. There are missing tables in your MySQL database.`):i?.error(s)}else i?.error(s)}})};var C=e=>{let t=e.plugins?.reduce((d,c)=>{let l=c.schema;if(!l)return d;for(let[u,p]of Object.entries(l))d[u]={fields:{...d[u]?.fields,...p.fields},tableName:u};return d},{}),o=e.rateLimit?.storage==="database",r={rateLimit:{tableName:e.rateLimit?.tableName||"rateLimit",fields:{key:{type:"string"},count:{type:"number"},lastRequest:{type:"number"}}}},{user:n,session:s,account:i,...a}=t||{};return{user:{tableName:e.user?.modelName||"user",fields:{name:{type:"string",required:!0},email:{type:"string",unique:!0,required:!0},emailVerified:{type:"boolean",defaultValue:()=>!1,required:!0},image:{type:"string",required:!1},createdAt:{type:"date",defaultValue:()=>new Date,required:!0},updatedAt:{type:"date",defaultValue:()=>new Date,required:!0},...n?.fields},order:0},session:{tableName:e.session?.modelName||"session",fields:{expiresAt:{type:"date",required:!0},ipAddress:{type:"string",required:!1},userAgent:{type:"string",required:!1},userId:{type:"string",references:{model:"user",field:"id",onDelete:"cascade"},required:!0},...s?.fields},order:1},account:{tableName:e.account?.modelName||"account",fields:{accountId:{type:"string",required:!0},providerId:{type:"string",required:!0},userId:{type:"string",references:{model:"user",field:"id",onDelete:"cascade"},required:!0},accessToken:{type:"string",required:!1},refreshToken:{type:"string",required:!1},idToken:{type:"string",required:!1},expiresAt:{type:"date",required:!1},password:{type:"string",required:!1},...i?.fields},order:2},...a,...o?r:{}}};import{Kysely as dr}from"kysely";import{MysqlDialect as Ye,PostgresDialect as et,SqliteDialect as tt}from"kysely";var cr=async e=>{if(!e.database)return;if("createDriver"in e.database)return e.database;let t;if("provider"in e.database){let o=e.database.provider,r=e.database?.url?.trim();if(o==="postgres"){let s=(await import("pg").catch(i=>{throw new k("Please install `pg` to use postgres database")})).Pool;t=new et({pool:new s({connectionString:r})})}if(o==="mysql")try{let{createPool:n}=await import("mysql2/promise").catch(a=>{throw new k("Please install `mysql2` to use mysql database")}),s=new URL(r),i=n({host:s.hostname,user:s.username,password:s.password,database:s.pathname.split("/")[1],port:Number(s.port)});t=new Ye({pool:i})}catch(n){if(n instanceof TypeError)throw new k("Invalid database URL")}if(o==="sqlite")try{let n=await import("better-sqlite3").catch(a=>{throw new k("Please install `better-sqlite3` to use sqlite database")}),s=n.default||n;if(!s)throw new k("Failed to import better-sqlite3. Please ensure `better-sqlite3` is properly installed.");let i=new s(r);t=new tt({database:i})}catch(n){throw console.error(n),new k("Failed to initialize SQLite. Please ensure `better-sqlite3` is properly installed.")}}return t},q=async e=>{let t=await cr(e);return t&&new dr({dialect:t})},G=e=>{if("provider"in e.database)return e.database.provider;if("dialect"in e.database){if(e.database.dialect instanceof et)return"postgres";if(e.database.dialect instanceof Ye)return"mysql";if(e.database.dialect instanceof tt)return"sqlite"}return"sqlite"};import"kysely";function lr(e){return e.plugins?.flatMap(o=>Object.keys(o.schema||{}).map(r=>{let s=(o.schema||{})[r];if(!s?.disableMigration)return{tableName:r,fields:s?.fields}}).filter(r=>r!==void 0))||[]}function rt(e){let t=C(e),o=lr(e);return[t.user,t.session,t.account,...o].reduce((n,s)=>(n[s.tableName]={fields:{...n[s.tableName]?.fields,...s.fields}},n),{})}var ur={string:["character varying","text"],number:["int4","integer","bigint","smallint","numeric","real","double precision"],boolean:["bool","boolean"],date:["timestamp","date"]},pr={string:["varchar","text"],number:["integer","int","bigint","smallint","decimal","float","double"],boolean:["boolean"],date:["date","datetime"]},mr={string:["TEXT"],number:["INTEGER","REAL"],boolean:["INTEGER","BOOLEAN"],date:["DATE","INTEGER"]},fr={postgres:ur,mysql:pr,sqlite:mr};function gr(e,t,o){return fr[o][t].map(i=>i.toLowerCase()).includes(e.toLowerCase())}async function ot(e){let t=rt(e),o=G(e),r=await q(e);r||(T.error("Invalid database configuration."),process.exit(1));let n=await r.introspection.getTables(),s=[],i=[];for(let[u,p]of Object.entries(t)){let m=n.find(y=>y.name===u);if(!m){let y=s.findIndex(B=>B.table===u),h={table:u,fields:p.fields,order:p.order||1/0},A=s.findIndex(B=>(B.order||1/0)>h.order);A===-1?y===-1?s.push(h):s[y].fields={...s[y].fields,...p.fields}:s.splice(A,0,h);continue}let f={};for(let[y,h]of Object.entries(p.fields)){let A=m.columns.find(B=>B.name===y);if(!A){f[y]=h;continue}gr(A.dataType,h.type,o)||T.warn(`Field ${y} in table ${u} has a different type in the database. Expected ${h.type} but got ${A.dataType}.`)}Object.keys(f).length>0&&i.push({table:u,fields:f,order:p.order||1/0})}let a=[];function d(u){let p={string:"text",boolean:"boolean",number:"integer",date:"date"};return o==="mysql"&&u==="string"?"varchar(255)":p[u]}if(i.length)for(let u of i)for(let[p,m]of Object.entries(u.fields)){let f=d(m.type),y=r.schema.alterTable(u.table).addColumn(p,f,h=>(h=m.required!==!1?h.notNull():h,m.references&&(h=h.references(`${m.references.model}.${m.references.field}`)),h));a.push(y)}if(s.length)for(let u of s){let p=r.schema.createTable(u.table).addColumn("id",d("string"),m=>m.primaryKey());for(let[m,f]of Object.entries(u.fields)){let y=d(f.type);p=p.addColumn(m,y,h=>(h=f.required!==!1?h.notNull():h,f.references&&(h=h.references(`${f.references.model}.${f.references.field}`)),f.unique&&(h=h.unique()),h))}a.push(p)}async function c(){for(let u of a)await u.execute()}async function l(){return a.map(p=>p.compile().sql).join(`;
2106
+ </html>`;
2107
+ var error = createAuthEndpoint(
2108
+ "/error",
2109
+ {
2110
+ method: "GET",
2111
+ metadata: HIDE_METADATA
2112
+ },
2113
+ async (c) => {
2114
+ const query = new URL(c.request?.url || "").searchParams.get("error") || "Unknown";
2115
+ return new Response(html(query), {
2116
+ headers: {
2117
+ "Content-Type": "text/html"
2118
+ }
2119
+ });
2120
+ }
2121
+ );
2122
+
2123
+ // src/api/routes/ok.ts
2124
+ var ok = createAuthEndpoint(
2125
+ "/ok",
2126
+ {
2127
+ method: "GET",
2128
+ metadata: HIDE_METADATA
2129
+ },
2130
+ async (ctx) => {
2131
+ return ctx.json({
2132
+ ok: true
2133
+ });
2134
+ }
2135
+ );
2136
+
2137
+ // src/api/routes/sign-up.ts
2138
+ import { alphabet as alphabet4, generateRandomString as generateRandomString4 } from "oslo/crypto";
2139
+ import { z as z11 } from "zod";
2140
+ var signUpEmail = createAuthEndpoint(
2141
+ "/sign-up/email",
2142
+ {
2143
+ method: "POST",
2144
+ query: z11.object({
2145
+ currentURL: z11.string().optional()
2146
+ }).optional(),
2147
+ body: z11.object({
2148
+ name: z11.string(),
2149
+ email: z11.string(),
2150
+ password: z11.string(),
2151
+ image: z11.string().optional(),
2152
+ callbackURL: z11.string().optional()
2153
+ })
2154
+ },
2155
+ async (ctx) => {
2156
+ if (!ctx.context.options.emailAndPassword?.enabled) {
2157
+ return ctx.json(null, {
2158
+ status: 400,
2159
+ body: {
2160
+ message: "Email and password is not enabled"
2161
+ }
2162
+ });
2163
+ }
2164
+ const { name, email, password, image } = ctx.body;
2165
+ const isValidEmail = z11.string().email().safeParse(email);
2166
+ if (!isValidEmail.success) {
2167
+ return ctx.json(null, {
2168
+ status: 400,
2169
+ body: {
2170
+ message: "Invalid email address"
2171
+ }
2172
+ });
2173
+ }
2174
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
2175
+ if (password.length < minPasswordLength) {
2176
+ ctx.context.logger.error("Password is too short");
2177
+ return ctx.json(null, {
2178
+ status: 400,
2179
+ body: { message: "Password is too short" }
2180
+ });
2181
+ }
2182
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
2183
+ if (password.length > maxPasswordLength) {
2184
+ ctx.context.logger.error("Password is too long");
2185
+ return ctx.json(null, {
2186
+ status: 400,
2187
+ body: { message: "Password is too long" }
2188
+ });
2189
+ }
2190
+ const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
2191
+ const hash = await ctx.context.password.hash(password);
2192
+ if (dbUser?.user) {
2193
+ return ctx.json(null, {
2194
+ status: 400,
2195
+ body: {
2196
+ message: "User already exists"
2197
+ }
2198
+ });
2199
+ }
2200
+ const createdUser = await ctx.context.internalAdapter.createUser({
2201
+ id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
2202
+ email: email.toLowerCase(),
2203
+ name,
2204
+ image,
2205
+ emailVerified: false,
2206
+ createdAt: /* @__PURE__ */ new Date(),
2207
+ updatedAt: /* @__PURE__ */ new Date()
2208
+ });
2209
+ if (!createdUser) {
2210
+ return ctx.json(null, {
2211
+ status: 400,
2212
+ body: {
2213
+ message: "Could not create user"
2214
+ }
2215
+ });
2216
+ }
2217
+ await ctx.context.internalAdapter.linkAccount({
2218
+ id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
2219
+ userId: createdUser.id,
2220
+ providerId: "credential",
2221
+ accountId: createdUser.id,
2222
+ password: hash
2223
+ });
2224
+ const session = await ctx.context.internalAdapter.createSession(
2225
+ createdUser.id,
2226
+ ctx.request
2227
+ );
2228
+ if (!session) {
2229
+ return ctx.json(null, {
2230
+ status: 400,
2231
+ body: {
2232
+ message: "Could not create session"
2233
+ }
2234
+ });
2235
+ }
2236
+ await setSessionCookie(ctx, session.id);
2237
+ if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {
2238
+ const token = await createEmailVerificationToken(
2239
+ ctx.context.secret,
2240
+ createdUser.email
2241
+ );
2242
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || ctx.query?.currentURL || "/"}`;
2243
+ await ctx.context.options.emailAndPassword.sendVerificationEmail?.(
2244
+ createdUser.email,
2245
+ url,
2246
+ token
2247
+ );
2248
+ }
2249
+ return ctx.json(
2250
+ {
2251
+ user: createdUser,
2252
+ session
2253
+ },
2254
+ {
2255
+ body: ctx.body.callbackURL ? {
2256
+ url: ctx.body.callbackURL,
2257
+ redirect: true
2258
+ } : {
2259
+ user: createdUser,
2260
+ session
2261
+ }
2262
+ }
2263
+ );
2264
+ }
2265
+ );
2266
+
2267
+ // src/api/index.ts
2268
+ import chalk from "chalk";
2269
+
2270
+ // src/api/rate-limiter.ts
2271
+ function shouldRateLimit(max, window2, rateLimitData) {
2272
+ const now = Date.now();
2273
+ const windowInMs = window2 * 1e3;
2274
+ const timeSinceLastRequest = now - rateLimitData.lastRequest;
2275
+ return timeSinceLastRequest < windowInMs && rateLimitData.count >= max;
2276
+ }
2277
+ function rateLimitResponse(retryAfter) {
2278
+ return new Response(
2279
+ JSON.stringify({
2280
+ message: "Too many requests. Please try again later."
2281
+ }),
2282
+ {
2283
+ status: 429,
2284
+ statusText: "Too Many Requests",
2285
+ headers: {
2286
+ "X-Retry-After": retryAfter.toString()
2287
+ }
2288
+ }
2289
+ );
2290
+ }
2291
+ function getRetryAfter(lastRequest, window2) {
2292
+ const now = Date.now();
2293
+ const windowInMs = window2 * 1e3;
2294
+ return Math.ceil((lastRequest + windowInMs - now) / 1e3);
2295
+ }
2296
+ function createDBStorage(ctx, tableName) {
2297
+ const model = tableName ?? "rateLimit";
2298
+ const db = ctx.adapter;
2299
+ return {
2300
+ get: async (key) => {
2301
+ const res = await db.findOne({
2302
+ model,
2303
+ where: [{ field: "key", value: key }]
2304
+ });
2305
+ return res;
2306
+ },
2307
+ set: async (key, value, _update) => {
2308
+ try {
2309
+ if (_update) {
2310
+ await db.update({
2311
+ model: tableName ?? "rateLimit",
2312
+ where: [{ field: "key", value: key }],
2313
+ update: {
2314
+ count: value.count,
2315
+ lastRequest: value.lastRequest
2316
+ }
2317
+ });
2318
+ } else {
2319
+ await db.create({
2320
+ model: tableName ?? "rateLimit",
2321
+ data: {
2322
+ key,
2323
+ count: value.count,
2324
+ lastRequest: value.lastRequest
2325
+ }
2326
+ });
2327
+ }
2328
+ } catch (e) {
2329
+ logger.error("Error setting rate limit", e);
2330
+ }
2331
+ }
2332
+ };
2333
+ }
2334
+ var memory = /* @__PURE__ */ new Map();
2335
+ function getRateLimitStorage(ctx) {
2336
+ if (ctx.rateLimit.customStorage) {
2337
+ return ctx.rateLimit.customStorage;
2338
+ }
2339
+ const storage = ctx.rateLimit.storage;
2340
+ if (storage === "memory") {
2341
+ return {
2342
+ async get(key) {
2343
+ return memory.get(key);
2344
+ },
2345
+ async set(key, value, _update) {
2346
+ memory.set(key, value);
2347
+ }
2348
+ };
2349
+ }
2350
+ return createDBStorage(ctx, ctx.rateLimit.tableName);
2351
+ }
2352
+ async function onRequestRateLimit(req, ctx) {
2353
+ if (!ctx.rateLimit.enabled) {
2354
+ return;
2355
+ }
2356
+ const baseURL = ctx.baseURL;
2357
+ const path = req.url.replace(baseURL, "");
2358
+ let window2 = ctx.rateLimit.window;
2359
+ let max = ctx.rateLimit.max;
2360
+ const key = getIp(req) + path;
2361
+ const specialRules = getDefaultSpecialRules();
2362
+ const specialRule = specialRules.find((rule) => rule.pathMatcher(path));
2363
+ if (specialRule) {
2364
+ window2 = specialRule.window;
2365
+ max = specialRule.max;
2366
+ }
2367
+ for (const plugin of ctx.options.plugins || []) {
2368
+ if (plugin.rateLimit) {
2369
+ const matchedRule = plugin.rateLimit.find(
2370
+ (rule) => rule.pathMatcher(path)
2371
+ );
2372
+ if (matchedRule) {
2373
+ window2 = matchedRule.window;
2374
+ max = matchedRule.max;
2375
+ break;
2376
+ }
2377
+ }
2378
+ }
2379
+ if (ctx.rateLimit.customRules) {
2380
+ const customRule = ctx.rateLimit.customRules[path];
2381
+ if (customRule) {
2382
+ window2 = customRule.window;
2383
+ max = customRule.max;
2384
+ }
2385
+ }
2386
+ const storage = getRateLimitStorage(ctx);
2387
+ const data = await storage.get(key);
2388
+ const now = Date.now();
2389
+ if (!data) {
2390
+ await storage.set(key, {
2391
+ key,
2392
+ count: 1,
2393
+ lastRequest: now
2394
+ });
2395
+ } else {
2396
+ const timeSinceLastRequest = now - data.lastRequest;
2397
+ if (shouldRateLimit(max, window2, data)) {
2398
+ const retryAfter = getRetryAfter(data.lastRequest, window2);
2399
+ return rateLimitResponse(retryAfter);
2400
+ } else if (timeSinceLastRequest > window2 * 1e3) {
2401
+ await storage.set(key, {
2402
+ ...data,
2403
+ count: 1,
2404
+ lastRequest: now
2405
+ });
2406
+ } else {
2407
+ await storage.set(key, {
2408
+ ...data,
2409
+ count: data.count + 1,
2410
+ lastRequest: now
2411
+ });
2412
+ }
2413
+ }
2414
+ }
2415
+ function getDefaultSpecialRules() {
2416
+ const specialRules = [
2417
+ {
2418
+ pathMatcher(path) {
2419
+ return path.startsWith("/sign-in") || path.startsWith("/sign-up");
2420
+ },
2421
+ window: 10,
2422
+ max: 7
2423
+ }
2424
+ ];
2425
+ return specialRules;
2426
+ }
2427
+
2428
+ // src/api/index.ts
2429
+ function getEndpoints(ctx, options) {
2430
+ const pluginEndpoints = options.plugins?.reduce(
2431
+ (acc, plugin) => {
2432
+ return {
2433
+ ...acc,
2434
+ ...plugin.endpoints
2435
+ };
2436
+ },
2437
+ {}
2438
+ );
2439
+ const middlewares = options.plugins?.map(
2440
+ (plugin) => plugin.middlewares?.map((m) => {
2441
+ const middleware = async (context) => {
2442
+ return m.middleware({
2443
+ ...context,
2444
+ context: {
2445
+ ...ctx,
2446
+ ...context.context
2447
+ }
2448
+ });
2449
+ };
2450
+ middleware.path = m.path;
2451
+ middleware.options = m.middleware.options;
2452
+ middleware.headers = m.middleware.headers;
2453
+ return {
2454
+ path: m.path,
2455
+ middleware
2456
+ };
2457
+ })
2458
+ ).filter((plugin) => plugin !== void 0).flat() || [];
2459
+ const baseEndpoints = {
2460
+ signInOAuth,
2461
+ callbackOAuth,
2462
+ getCSRFToken,
2463
+ getSession: getSession(),
2464
+ signOut,
2465
+ signUpEmail,
2466
+ signInEmail,
2467
+ forgetPassword,
2468
+ resetPassword,
2469
+ verifyEmail,
2470
+ sendVerificationEmail,
2471
+ changePassword,
2472
+ setPassword,
2473
+ updateUser,
2474
+ forgetPasswordCallback,
2475
+ listSessions: listSessions(),
2476
+ revokeSession,
2477
+ revokeSessions
2478
+ };
2479
+ const endpoints = {
2480
+ ...baseEndpoints,
2481
+ ...pluginEndpoints,
2482
+ ok,
2483
+ error
2484
+ };
2485
+ let api = {};
2486
+ for (const [key, value] of Object.entries(endpoints)) {
2487
+ api[key] = async (context) => {
2488
+ const c = await ctx;
2489
+ const endpointRes = await value({
2490
+ ...context,
2491
+ context: {
2492
+ ...c,
2493
+ ...context.context
2494
+ }
2495
+ });
2496
+ let response = endpointRes;
2497
+ for (const plugin of options.plugins || []) {
2498
+ if (plugin.hooks?.after) {
2499
+ for (const hook of plugin.hooks.after) {
2500
+ const match = hook.matcher(context);
2501
+ if (match) {
2502
+ const obj = Object.assign(context, {
2503
+ context: {
2504
+ ...ctx,
2505
+ returned: response
2506
+ }
2507
+ });
2508
+ const hookRes = await hook.handler(obj);
2509
+ if (hookRes && "response" in hookRes) {
2510
+ response = hookRes.response;
2511
+ }
2512
+ }
2513
+ }
2514
+ }
2515
+ }
2516
+ return response;
2517
+ };
2518
+ api[key].path = value.path;
2519
+ api[key].method = value.method;
2520
+ api[key].options = value.options;
2521
+ api[key].headers = value.headers;
2522
+ }
2523
+ return {
2524
+ api,
2525
+ middlewares
2526
+ };
2527
+ }
2528
+ var router = (ctx, options) => {
2529
+ const { api, middlewares } = getEndpoints(ctx, options);
2530
+ const basePath = new URL(ctx.baseURL).pathname;
2531
+ return createRouter(api, {
2532
+ extraContext: ctx,
2533
+ basePath,
2534
+ routerMiddleware: [
2535
+ {
2536
+ path: "/**",
2537
+ middleware: csrfMiddleware
2538
+ },
2539
+ ...middlewares
2540
+ ],
2541
+ async onRequest(req) {
2542
+ for (const plugin of ctx.options.plugins || []) {
2543
+ if (plugin.onRequest) {
2544
+ const response = await plugin.onRequest(req, ctx);
2545
+ if (response) {
2546
+ return response;
2547
+ }
2548
+ }
2549
+ }
2550
+ return onRequestRateLimit(req, ctx);
2551
+ },
2552
+ async onResponse(res) {
2553
+ for (const plugin of ctx.options.plugins || []) {
2554
+ if (plugin.onResponse) {
2555
+ const response = await plugin.onResponse(res, ctx);
2556
+ if (response) {
2557
+ return response.response;
2558
+ }
2559
+ }
2560
+ }
2561
+ return res;
2562
+ },
2563
+ onError(e) {
2564
+ const log = options.logger?.verboseLogging ? logger : void 0;
2565
+ if (options.logger?.disabled !== true) {
2566
+ if (e instanceof APIError6) {
2567
+ log?.warn(e);
2568
+ } else {
2569
+ if (typeof e === "object" && e !== null && "message" in e) {
2570
+ const errorMessage = e.message;
2571
+ if (!errorMessage || typeof errorMessage !== "string") {
2572
+ log?.error(e);
2573
+ return;
2574
+ }
2575
+ if (errorMessage.includes("no such table")) {
2576
+ logger?.error(
2577
+ `Please run ${chalk.green(
2578
+ "npx better-auth migrate"
2579
+ )} to create the tables. There are missing tables in your SQLite database.`
2580
+ );
2581
+ } else if (errorMessage.includes("relation") && errorMessage.includes("does not exist")) {
2582
+ logger.error(
2583
+ `Please run ${chalk.green(
2584
+ "npx better-auth migrate"
2585
+ )} to create the tables. There are missing tables in your PostgreSQL database.`
2586
+ );
2587
+ } else if (errorMessage.includes("Table") && errorMessage.includes("doesn't exist")) {
2588
+ logger?.error(
2589
+ `Please run ${chalk.green(
2590
+ "npx better-auth migrate"
2591
+ )} to create the tables. There are missing tables in your MySQL database.`
2592
+ );
2593
+ } else {
2594
+ log?.error(e);
2595
+ }
2596
+ } else {
2597
+ log?.error(e);
2598
+ }
2599
+ }
2600
+ }
2601
+ }
2602
+ });
2603
+ };
2604
+
2605
+ // src/db/get-tables.ts
2606
+ var getAuthTables = (options) => {
2607
+ const pluginSchema = options.plugins?.reduce(
2608
+ (acc, plugin) => {
2609
+ const schema = plugin.schema;
2610
+ if (!schema) return acc;
2611
+ for (const [key, value] of Object.entries(schema)) {
2612
+ acc[key] = {
2613
+ fields: {
2614
+ ...acc[key]?.fields,
2615
+ ...value.fields
2616
+ },
2617
+ tableName: key
2618
+ };
2619
+ }
2620
+ return acc;
2621
+ },
2622
+ {}
2623
+ );
2624
+ const shouldAddRateLimitTable = options.rateLimit?.storage === "database";
2625
+ const rateLimitTable = {
2626
+ rateLimit: {
2627
+ tableName: options.rateLimit?.tableName || "rateLimit",
2628
+ fields: {
2629
+ key: {
2630
+ type: "string"
2631
+ },
2632
+ count: {
2633
+ type: "number"
2634
+ },
2635
+ lastRequest: {
2636
+ type: "number"
2637
+ }
2638
+ }
2639
+ }
2640
+ };
2641
+ const { user, session, account, ...pluginTables } = pluginSchema || {};
2642
+ return {
2643
+ user: {
2644
+ tableName: options.user?.modelName || "user",
2645
+ fields: {
2646
+ name: {
2647
+ type: "string",
2648
+ required: true
2649
+ },
2650
+ email: {
2651
+ type: "string",
2652
+ unique: true,
2653
+ required: true
2654
+ },
2655
+ emailVerified: {
2656
+ type: "boolean",
2657
+ defaultValue: () => false,
2658
+ required: true
2659
+ },
2660
+ image: {
2661
+ type: "string",
2662
+ required: false
2663
+ },
2664
+ createdAt: {
2665
+ type: "date",
2666
+ defaultValue: () => /* @__PURE__ */ new Date(),
2667
+ required: true
2668
+ },
2669
+ updatedAt: {
2670
+ type: "date",
2671
+ defaultValue: () => /* @__PURE__ */ new Date(),
2672
+ required: true
2673
+ },
2674
+ ...user?.fields
2675
+ },
2676
+ order: 0
2677
+ },
2678
+ session: {
2679
+ tableName: options.session?.modelName || "session",
2680
+ fields: {
2681
+ expiresAt: {
2682
+ type: "date",
2683
+ required: true
2684
+ },
2685
+ ipAddress: {
2686
+ type: "string",
2687
+ required: false
2688
+ },
2689
+ userAgent: {
2690
+ type: "string",
2691
+ required: false
2692
+ },
2693
+ userId: {
2694
+ type: "string",
2695
+ references: {
2696
+ model: "user",
2697
+ field: "id",
2698
+ onDelete: "cascade"
2699
+ },
2700
+ required: true
2701
+ },
2702
+ ...session?.fields
2703
+ },
2704
+ order: 1
2705
+ },
2706
+ account: {
2707
+ tableName: options.account?.modelName || "account",
2708
+ fields: {
2709
+ accountId: {
2710
+ type: "string",
2711
+ required: true
2712
+ },
2713
+ providerId: {
2714
+ type: "string",
2715
+ required: true
2716
+ },
2717
+ userId: {
2718
+ type: "string",
2719
+ references: {
2720
+ model: "user",
2721
+ field: "id",
2722
+ onDelete: "cascade"
2723
+ },
2724
+ required: true
2725
+ },
2726
+ accessToken: {
2727
+ type: "string",
2728
+ required: false
2729
+ },
2730
+ refreshToken: {
2731
+ type: "string",
2732
+ required: false
2733
+ },
2734
+ idToken: {
2735
+ type: "string",
2736
+ required: false
2737
+ },
2738
+ expiresAt: {
2739
+ type: "date",
2740
+ required: false
2741
+ },
2742
+ password: {
2743
+ type: "string",
2744
+ required: false
2745
+ },
2746
+ ...account?.fields
2747
+ },
2748
+ order: 2
2749
+ },
2750
+ ...pluginTables,
2751
+ ...shouldAddRateLimitTable ? rateLimitTable : {}
2752
+ };
2753
+ };
2754
+
2755
+ // src/adapters/kysely-adapter/dialect.ts
2756
+ import { Kysely } from "kysely";
2757
+ import {
2758
+ MysqlDialect,
2759
+ PostgresDialect,
2760
+ SqliteDialect
2761
+ } from "kysely";
2762
+ import "execa";
2763
+ import "prompts";
2764
+
2765
+ // src/cli/utils/get-package-manager.ts
2766
+ import { detect } from "@antfu/ni";
2767
+
2768
+ // src/adapters/kysely-adapter/dialect.ts
2769
+ import "ora";
2770
+ var getDialect = async (config2, isCli) => {
2771
+ if (!config2.database) {
2772
+ return void 0;
2773
+ }
2774
+ if ("createDriver" in config2.database) {
2775
+ return config2.database;
2776
+ }
2777
+ let dialect = void 0;
2778
+ if ("provider" in config2.database) {
2779
+ const provider = config2.database.provider;
2780
+ const connectionString = config2.database?.url?.trim();
2781
+ if (provider === "postgres") {
2782
+ const pg = await import("pg").catch(async (e) => {
2783
+ throw new MissingDependencyError("pg");
2784
+ });
2785
+ const Pool = pg.default?.Pool || pg.Pool;
2786
+ const pool = new Pool({
2787
+ connectionString
2788
+ });
2789
+ dialect = new PostgresDialect({
2790
+ pool
2791
+ });
2792
+ }
2793
+ if (provider === "mysql") {
2794
+ try {
2795
+ const { createPool } = await import("mysql2/promise").catch(
2796
+ async (e) => {
2797
+ throw new MissingDependencyError("mysql2");
2798
+ }
2799
+ );
2800
+ const params = new URL(connectionString);
2801
+ const pool = createPool({
2802
+ host: params.hostname,
2803
+ user: params.username,
2804
+ password: params.password,
2805
+ database: params.pathname.split("/")[1],
2806
+ port: Number(params.port)
2807
+ });
2808
+ dialect = new MysqlDialect({ pool });
2809
+ } catch (e) {
2810
+ if (e instanceof TypeError) {
2811
+ throw new BetterAuthError("Invalid database URL");
2812
+ }
2813
+ throw e;
2814
+ }
2815
+ }
2816
+ if (provider === "sqlite") {
2817
+ try {
2818
+ const database = await import("better-sqlite3").catch(async (e) => {
2819
+ throw new MissingDependencyError("better-sqlite3");
2820
+ });
2821
+ const Database = database.default || database;
2822
+ const db = new Database(connectionString);
2823
+ dialect = new SqliteDialect({
2824
+ database: db
2825
+ });
2826
+ } catch (e) {
2827
+ console.error(e);
2828
+ throw new BetterAuthError(
2829
+ "Failed to initialize SQLite. Make sure `better-sqlite3` is properly installed."
2830
+ );
2831
+ }
2832
+ }
2833
+ }
2834
+ return dialect;
2835
+ };
2836
+ var createKyselyAdapter = async (config2, isCli) => {
2837
+ const dialect = await getDialect(config2, isCli);
2838
+ if (!dialect) {
2839
+ return dialect;
2840
+ }
2841
+ const db = new Kysely({
2842
+ dialect
2843
+ });
2844
+ return db;
2845
+ };
2846
+ var getDatabaseType = (config2) => {
2847
+ if ("provider" in config2.database) {
2848
+ return config2.database.provider;
2849
+ }
2850
+ if ("dialect" in config2.database) {
2851
+ if (config2.database.dialect instanceof PostgresDialect) {
2852
+ return "postgres";
2853
+ }
2854
+ if (config2.database.dialect instanceof MysqlDialect) {
2855
+ return "mysql";
2856
+ }
2857
+ if (config2.database.dialect instanceof SqliteDialect) {
2858
+ return "sqlite";
2859
+ }
2860
+ }
2861
+ return "sqlite";
2862
+ };
2863
+
2864
+ // src/cli/utils/get-migration.ts
2865
+ import "kysely";
2866
+
2867
+ // src/cli/utils/get-schema.ts
2868
+ function getPluginTable(config2) {
2869
+ const pluginsMigrations = config2.plugins?.flatMap(
2870
+ (plugin) => Object.keys(plugin.schema || {}).map((key) => {
2871
+ const schema = plugin.schema || {};
2872
+ const table = schema[key];
2873
+ if (table?.disableMigration) {
2874
+ return;
2875
+ }
2876
+ return {
2877
+ tableName: key,
2878
+ fields: table?.fields
2879
+ };
2880
+ }).filter((value) => value !== void 0)
2881
+ ) || [];
2882
+ return pluginsMigrations;
2883
+ }
2884
+ function getSchema(config2) {
2885
+ const baseSchema = getAuthTables(config2);
2886
+ const pluginSchema = getPluginTable(config2);
2887
+ const schema = [
2888
+ baseSchema.user,
2889
+ baseSchema.session,
2890
+ baseSchema.account,
2891
+ ...pluginSchema
2892
+ ].reduce((acc, curr) => {
2893
+ acc[curr.tableName] = {
2894
+ fields: {
2895
+ ...acc[curr.tableName]?.fields,
2896
+ ...curr.fields
2897
+ }
2898
+ };
2899
+ return acc;
2900
+ }, {});
2901
+ return schema;
2902
+ }
2903
+
2904
+ // src/cli/utils/get-migration.ts
2905
+ var postgresMap = {
2906
+ string: ["character varying", "text"],
2907
+ number: [
2908
+ "int4",
2909
+ "integer",
2910
+ "bigint",
2911
+ "smallint",
2912
+ "numeric",
2913
+ "real",
2914
+ "double precision"
2915
+ ],
2916
+ boolean: ["bool", "boolean"],
2917
+ date: ["timestamp", "date"]
2918
+ };
2919
+ var mysqlMap = {
2920
+ string: ["varchar", "text"],
2921
+ number: [
2922
+ "integer",
2923
+ "int",
2924
+ "bigint",
2925
+ "smallint",
2926
+ "decimal",
2927
+ "float",
2928
+ "double"
2929
+ ],
2930
+ boolean: ["boolean"],
2931
+ date: ["date", "datetime"]
2932
+ };
2933
+ var sqliteMap = {
2934
+ string: ["TEXT"],
2935
+ number: ["INTEGER", "REAL"],
2936
+ boolean: ["INTEGER", "BOOLEAN"],
2937
+ // 0 or 1
2938
+ date: ["DATE", "INTEGER"]
2939
+ };
2940
+ var map = {
2941
+ postgres: postgresMap,
2942
+ mysql: mysqlMap,
2943
+ sqlite: sqliteMap
2944
+ };
2945
+ function matchType(columnDataType, fieldType, dbType) {
2946
+ const types = map[dbType];
2947
+ const type = types[fieldType].map((t) => t.toLowerCase());
2948
+ const matches = type.includes(columnDataType.toLowerCase());
2949
+ return matches;
2950
+ }
2951
+ async function getMigrations(config2) {
2952
+ const betterAuthSchema = getSchema(config2);
2953
+ const dbType = getDatabaseType(config2);
2954
+ const db = await createKyselyAdapter(config2);
2955
+ if (!db) {
2956
+ logger.error("Invalid database configuration.");
2957
+ process.exit(1);
2958
+ }
2959
+ const tableMetadata = await db.introspection.getTables();
2960
+ const toBeCreated = [];
2961
+ const toBeAdded = [];
2962
+ for (const [key, value] of Object.entries(betterAuthSchema)) {
2963
+ const table = tableMetadata.find((t) => t.name === key);
2964
+ if (!table) {
2965
+ const tIndex = toBeCreated.findIndex((t) => t.table === key);
2966
+ const tableData = {
2967
+ table: key,
2968
+ fields: value.fields,
2969
+ order: value.order || Infinity
2970
+ };
2971
+ const insertIndex = toBeCreated.findIndex(
2972
+ (t) => (t.order || Infinity) > tableData.order
2973
+ );
2974
+ if (insertIndex === -1) {
2975
+ if (tIndex === -1) {
2976
+ toBeCreated.push(tableData);
2977
+ } else {
2978
+ toBeCreated[tIndex].fields = {
2979
+ ...toBeCreated[tIndex].fields,
2980
+ ...value.fields
2981
+ };
2982
+ }
2983
+ } else {
2984
+ toBeCreated.splice(insertIndex, 0, tableData);
2985
+ }
2986
+ continue;
2987
+ }
2988
+ let toBeAddedFields = {};
2989
+ for (const [fieldName, field] of Object.entries(value.fields)) {
2990
+ const column = table.columns.find((c) => c.name === fieldName);
2991
+ if (!column) {
2992
+ toBeAddedFields[fieldName] = field;
2993
+ continue;
2994
+ }
2995
+ if (matchType(column.dataType, field.type, dbType)) {
2996
+ continue;
2997
+ } else {
2998
+ logger.warn(
2999
+ `Field ${fieldName} in table ${key} has a different type in the database. Expected ${field.type} but got ${column.dataType}.`
3000
+ );
3001
+ }
3002
+ }
3003
+ if (Object.keys(toBeAddedFields).length > 0) {
3004
+ toBeAdded.push({
3005
+ table: key,
3006
+ fields: toBeAddedFields,
3007
+ order: value.order || Infinity
3008
+ });
3009
+ }
3010
+ }
3011
+ const migrations = [];
3012
+ function getType(type) {
3013
+ const typeMap = {
3014
+ string: "text",
3015
+ boolean: "boolean",
3016
+ number: "integer",
3017
+ date: "date"
3018
+ };
3019
+ if (dbType === "mysql" && type === "string") {
3020
+ return "varchar(255)";
3021
+ }
3022
+ return typeMap[type];
3023
+ }
3024
+ if (toBeAdded.length) {
3025
+ for (const table of toBeAdded) {
3026
+ for (const [fieldName, field] of Object.entries(table.fields)) {
3027
+ const type = getType(field.type);
3028
+ const exec = db.schema.alterTable(table.table).addColumn(fieldName, type, (col) => {
3029
+ col = field.required !== false ? col.notNull() : col;
3030
+ if (field.references) {
3031
+ col = col.references(
3032
+ `${field.references.model}.${field.references.field}`
3033
+ );
3034
+ }
3035
+ return col;
3036
+ });
3037
+ migrations.push(exec);
3038
+ }
3039
+ }
3040
+ }
3041
+ if (toBeCreated.length) {
3042
+ for (const table of toBeCreated) {
3043
+ let dbT = db.schema.createTable(table.table).addColumn("id", getType("string"), (col) => col.primaryKey());
3044
+ for (const [fieldName, field] of Object.entries(table.fields)) {
3045
+ const type = getType(field.type);
3046
+ dbT = dbT.addColumn(fieldName, type, (col) => {
3047
+ col = field.required !== false ? col.notNull() : col;
3048
+ if (field.references) {
3049
+ col = col.references(
3050
+ `${field.references.model}.${field.references.field}`
3051
+ );
3052
+ }
3053
+ if (field.unique) {
3054
+ col = col.unique();
3055
+ }
3056
+ return col;
3057
+ });
3058
+ }
3059
+ migrations.push(dbT);
3060
+ }
3061
+ }
3062
+ async function runMigrations() {
3063
+ for (const migration of migrations) {
3064
+ await migration.execute();
3065
+ }
3066
+ }
3067
+ async function compileMigrations() {
3068
+ const compiled = migrations.map((m) => m.compile().sql);
3069
+ return compiled.join(";\n\n");
3070
+ }
3071
+ return { toBeCreated, toBeAdded, runMigrations, compileMigrations };
3072
+ }
3073
+
3074
+ // src/adapters/kysely-adapter/index.ts
3075
+ function convertWhere(w) {
3076
+ if (!w)
3077
+ return {
3078
+ and: null,
3079
+ or: null
3080
+ };
3081
+ const and = w?.filter((w2) => w2.connector === "AND" || !w2.connector).reduce(
3082
+ (acc, w2) => ({
3083
+ ...acc,
3084
+ [w2.field]: w2.value
3085
+ }),
3086
+ {}
3087
+ );
3088
+ const or = w?.filter((w2) => w2.connector === "OR").reduce(
3089
+ (acc, w2) => ({
3090
+ ...acc,
3091
+ [w2.field]: w2.value
3092
+ }),
3093
+ {}
3094
+ );
3095
+ return {
3096
+ and: Object.keys(and).length ? and : null,
3097
+ or: Object.keys(or).length ? or : null
3098
+ };
3099
+ }
3100
+ function transformTo(val, fields, transform) {
3101
+ for (const key in val) {
3102
+ if (val[key] === 0 && fields[key]?.type === "boolean" && transform?.boolean) {
3103
+ val[key] = false;
3104
+ }
3105
+ if (val[key] === 1 && fields[key]?.type === "boolean" && transform?.boolean) {
3106
+ val[key] = true;
3107
+ }
3108
+ if (fields[key]?.type === "date") {
3109
+ if (!(val[key] instanceof Date)) {
3110
+ val[key] = new Date(val[key]);
3111
+ }
3112
+ }
3113
+ }
3114
+ return val;
3115
+ }
3116
+ function transformFrom(val, transform) {
3117
+ for (const key in val) {
3118
+ if (typeof val[key] === "boolean" && transform?.boolean) {
3119
+ val[key] = val[key] ? 1 : 0;
3120
+ }
3121
+ if (val[key] instanceof Date) {
3122
+ val[key] = val[key].toISOString();
3123
+ }
3124
+ }
3125
+ return val;
3126
+ }
3127
+ var kyselyAdapter = (db, config2) => {
3128
+ return {
3129
+ id: "kysely",
3130
+ async create(data) {
3131
+ let { model, data: val, select } = data;
3132
+ if (config2?.transform) {
3133
+ val = transformFrom(val, config2.transform);
3134
+ }
3135
+ let res = await db.insertInto(model).values(val).returningAll().executeTakeFirst();
3136
+ if (config2?.transform) {
3137
+ const schema = config2.transform.schema[model];
3138
+ res = schema ? transformTo(val, schema, config2.transform) : res;
3139
+ }
3140
+ if (select?.length) {
3141
+ const data2 = res ? select.reduce((acc, cur) => {
3142
+ if (res?.[cur]) {
3143
+ return {
3144
+ ...acc,
3145
+ [cur]: res[cur]
3146
+ };
3147
+ }
3148
+ return acc;
3149
+ }, {}) : null;
3150
+ res = data2;
3151
+ }
3152
+ return res;
3153
+ },
3154
+ async findOne(data) {
3155
+ const { model, where, select } = data;
3156
+ const { and, or } = convertWhere(where);
3157
+ let query = db.selectFrom(model).selectAll();
3158
+ if (or) {
3159
+ query = query.where((eb) => eb.or(or));
3160
+ }
3161
+ if (and) {
3162
+ query = query.where((eb) => eb.and(and));
3163
+ }
3164
+ let res = await query.executeTakeFirst();
3165
+ if (select?.length) {
3166
+ const data2 = res ? select.reduce((acc, cur) => {
3167
+ if (res?.[cur]) {
3168
+ return {
3169
+ ...acc,
3170
+ [cur]: res[cur]
3171
+ };
3172
+ }
3173
+ return acc;
3174
+ }, {}) : null;
3175
+ res = data2;
3176
+ }
3177
+ if (config2?.transform) {
3178
+ const schema = config2.transform.schema[model];
3179
+ res = res && schema ? transformTo(res, schema, config2.transform) : res;
3180
+ return res || null;
3181
+ }
3182
+ return res || null;
3183
+ },
3184
+ async findMany(data) {
3185
+ const { model, where } = data;
3186
+ let query = db.selectFrom(model);
3187
+ const { and, or } = convertWhere(where);
3188
+ if (and) {
3189
+ query = query.where((eb) => eb.and(and));
3190
+ }
3191
+ if (or) {
3192
+ query = query.where((eb) => eb.or(or));
3193
+ }
3194
+ const res = await query.selectAll().execute();
3195
+ if (config2?.transform) {
3196
+ const schema = config2.transform.schema[model];
3197
+ return schema ? res.map((v) => transformTo(v, schema, config2.transform)) : res;
3198
+ }
3199
+ return res;
3200
+ },
3201
+ async update(data) {
3202
+ let { model, where, update: val } = data;
3203
+ const { and, or } = convertWhere(where);
3204
+ if (config2?.transform) {
3205
+ val = transformFrom(val, config2.transform);
3206
+ }
3207
+ let query = db.updateTable(model).set(val);
3208
+ if (and) {
3209
+ query = query.where((eb) => eb.and(and));
3210
+ }
3211
+ if (or) {
3212
+ query = query.where((eb) => eb.or(or));
3213
+ }
3214
+ const res = await query.returningAll().executeTakeFirst() || null;
3215
+ if (config2?.transform) {
3216
+ const schema = config2.transform.schema[model];
3217
+ return schema ? transformTo(res, schema, config2.transform) : res;
3218
+ }
3219
+ return res;
3220
+ },
3221
+ async delete(data) {
3222
+ const { model, where } = data;
3223
+ const { and, or } = convertWhere(where);
3224
+ let query = db.deleteFrom(model);
3225
+ if (and) {
3226
+ query = query.where((eb) => eb.and(and));
3227
+ }
3228
+ if (or) {
3229
+ query = query.where((eb) => eb.or(or));
3230
+ }
3231
+ await query.execute();
3232
+ },
3233
+ async createSchema(options) {
3234
+ const { compileMigrations } = await getMigrations(options);
3235
+ const migrations = await compileMigrations();
3236
+ return {
3237
+ code: migrations,
3238
+ fileName: `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString()}.sql`
3239
+ };
3240
+ }
3241
+ };
3242
+ };
3243
+
3244
+ // src/db/utils.ts
3245
+ async function getAdapter(options, isCli) {
3246
+ if (!options.database) {
3247
+ throw new BetterAuthError("Database configuration is required");
3248
+ }
3249
+ if ("create" in options.database) {
3250
+ return options.database;
3251
+ }
3252
+ const db = await createKyselyAdapter(options, isCli);
3253
+ if (!db) {
3254
+ throw new BetterAuthError("Failed to initialize database adapter");
3255
+ }
3256
+ const tables = getAuthTables(options);
3257
+ let schema = {};
3258
+ for (const table of Object.values(tables)) {
3259
+ schema[table.tableName] = table.fields;
3260
+ }
3261
+ return kyselyAdapter(db, {
3262
+ transform: {
3263
+ schema,
3264
+ date: true,
3265
+ boolean: getDatabaseType(options) === "sqlite"
3266
+ }
3267
+ });
3268
+ }
3269
+
3270
+ // src/crypto/password.ts
3271
+ import { scrypt } from "node:crypto";
3272
+ import { decodeHex, encodeHex } from "oslo/encoding";
3273
+ import { constantTimeEqual } from "oslo/crypto";
3274
+ var config = {
3275
+ N: 16384,
3276
+ r: 16,
3277
+ p: 1,
3278
+ dkLen: 64
3279
+ };
3280
+ async function generateKey(password, salt) {
3281
+ return await new Promise((resolve, reject) => {
3282
+ scrypt(
3283
+ password.normalize("NFKC"),
3284
+ salt,
3285
+ config.dkLen,
3286
+ {
3287
+ N: config.N,
3288
+ p: config.p,
3289
+ r: config.r,
3290
+ // errors when 128 * N * r > `maxmem` (approximately)
3291
+ maxmem: 128 * config.N * config.r * 2
3292
+ },
3293
+ (err, buff) => {
3294
+ if (err) return reject(err);
3295
+ return resolve(buff);
3296
+ }
3297
+ );
3298
+ });
3299
+ }
3300
+ var hashPassword = async (password) => {
3301
+ const salt = encodeHex(crypto.getRandomValues(new Uint8Array(16)));
3302
+ const key = await generateKey(password, salt);
3303
+ return `${salt}:${encodeHex(key)}`;
3304
+ };
3305
+ var verifyPassword = async (hash, password) => {
3306
+ const [salt, key] = hash.split(":");
3307
+ const targetKey = await generateKey(password, salt);
3308
+ return constantTimeEqual(targetKey, decodeHex(key));
3309
+ };
3310
+
3311
+ // src/db/internal-adapter.ts
3312
+ import { alphabet as alphabet5, generateRandomString as generateRandomString5 } from "oslo/crypto";
3313
+ var createInternalAdapter = (adapter, options) => {
3314
+ const sessionExpiration = options.session?.expiresIn || 60 * 60 * 24 * 7;
3315
+ const tables = getAuthTables(options);
3316
+ const hooks = options.databaseHooks;
3317
+ async function createWithHooks(data, model) {
3318
+ let actualData = data;
3319
+ if (hooks?.[model]?.create?.before) {
3320
+ const result = await hooks[model].create.before(data);
3321
+ if (result === false) {
3322
+ return null;
3323
+ }
3324
+ const isObject = typeof result === "object";
3325
+ actualData = isObject ? result.data : result;
3326
+ }
3327
+ const created = await adapter.create({
3328
+ model,
3329
+ data
3330
+ });
3331
+ if (hooks?.[model]?.create?.after && created) {
3332
+ await hooks[model].create.after(created);
3333
+ }
3334
+ return created;
3335
+ }
3336
+ return {
3337
+ createOAuthUser: async (user, account) => {
3338
+ try {
3339
+ const createdUser = await createWithHooks(user, "user");
3340
+ const createdAccount = await createWithHooks(account, "account");
3341
+ return {
3342
+ user: createdUser,
3343
+ account: createdAccount
3344
+ };
3345
+ } catch (e) {
3346
+ console.log(e);
3347
+ return null;
3348
+ }
3349
+ },
3350
+ createUser: async (user) => {
3351
+ const createdUser = await createWithHooks(user, "user");
3352
+ return createdUser;
3353
+ },
3354
+ createSession: async (userId, request, dontRememberMe) => {
3355
+ const headers = request instanceof Request ? request.headers : request;
3356
+ const data = {
3357
+ id: generateRandomString5(32, alphabet5("a-z", "0-9", "A-Z")),
3358
+ userId,
3359
+ /**
3360
+ * If the user doesn't want to be remembered
3361
+ * set the session to expire in 1 day.
3362
+ * The cookie will be set to expire at the end of the session
3363
+ */
3364
+ expiresAt: dontRememberMe ? getDate(1e3 * 60 * 60 * 24) : getDate(sessionExpiration, true),
3365
+ ipAddress: headers?.get("x-forwarded-for") || "",
3366
+ userAgent: headers?.get("user-agent") || ""
3367
+ };
3368
+ const session = await createWithHooks(data, "session");
3369
+ return session;
3370
+ },
3371
+ findSession: async (sessionId) => {
3372
+ const session = await adapter.findOne({
3373
+ model: tables.session.tableName,
3374
+ where: [
3375
+ {
3376
+ value: sessionId,
3377
+ field: "id"
3378
+ }
3379
+ ]
3380
+ });
3381
+ if (!session) {
3382
+ return null;
3383
+ }
3384
+ const user = await adapter.findOne({
3385
+ model: tables.user.tableName,
3386
+ where: [
3387
+ {
3388
+ value: session.userId,
3389
+ field: "id"
3390
+ }
3391
+ ]
3392
+ });
3393
+ if (!user) {
3394
+ return null;
3395
+ }
3396
+ return {
3397
+ session,
3398
+ user
3399
+ };
3400
+ },
3401
+ updateSession: async (sessionId, session) => {
3402
+ if (hooks?.session?.update?.before) {
3403
+ const result = await hooks.session.update.before(session);
3404
+ if (result === false) {
3405
+ return null;
3406
+ }
3407
+ session = typeof result === "object" ? result.data : result;
3408
+ }
3409
+ const updatedSession = await adapter.update({
3410
+ model: tables.session.tableName,
3411
+ where: [
3412
+ {
3413
+ field: "id",
3414
+ value: sessionId
3415
+ }
3416
+ ],
3417
+ update: session
3418
+ });
3419
+ if (hooks?.session?.update?.after && updatedSession) {
3420
+ await hooks.session.update.after(updatedSession);
3421
+ }
3422
+ return updatedSession;
3423
+ },
3424
+ deleteSession: async (id) => {
3425
+ const session = await adapter.delete({
3426
+ model: tables.session.tableName,
3427
+ where: [
3428
+ {
3429
+ field: "id",
3430
+ value: id
3431
+ }
3432
+ ]
3433
+ });
3434
+ return session;
3435
+ },
3436
+ deleteSessions: async (userId) => {
3437
+ return await adapter.delete({
3438
+ model: tables.session.tableName,
3439
+ where: [
3440
+ {
3441
+ field: "userId",
3442
+ value: userId
3443
+ }
3444
+ ]
3445
+ });
3446
+ },
3447
+ findUserByEmail: async (email) => {
3448
+ const user = await adapter.findOne({
3449
+ model: tables.user.tableName,
3450
+ where: [
3451
+ {
3452
+ value: email.toLowerCase(),
3453
+ field: "email"
3454
+ }
3455
+ ]
3456
+ });
3457
+ if (!user) return null;
3458
+ const accounts = await adapter.findMany({
3459
+ model: tables.account.tableName,
3460
+ where: [
3461
+ {
3462
+ value: user.id,
3463
+ field: "userId"
3464
+ }
3465
+ ]
3466
+ });
3467
+ return {
3468
+ user,
3469
+ accounts
3470
+ };
3471
+ },
3472
+ findUserById: async (userId) => {
3473
+ const user = await adapter.findOne({
3474
+ model: tables.user.tableName,
3475
+ where: [
3476
+ {
3477
+ field: "id",
3478
+ value: userId
3479
+ }
3480
+ ]
3481
+ });
3482
+ return user;
3483
+ },
3484
+ linkAccount: async (account) => {
3485
+ const _account = await createWithHooks(account, "account");
3486
+ return _account;
3487
+ },
3488
+ updateUserByEmail: async (email, data) => {
3489
+ if (hooks?.user?.update?.before) {
3490
+ const result = await hooks.user.update.before(data);
3491
+ if (result === false) {
3492
+ return null;
3493
+ }
3494
+ data = typeof result === "object" ? result.data : result;
3495
+ }
3496
+ const user = await adapter.update({
3497
+ model: tables.user.tableName,
3498
+ where: [
3499
+ {
3500
+ value: email,
3501
+ field: "email"
3502
+ }
3503
+ ],
3504
+ update: data
3505
+ });
3506
+ if (hooks?.user?.update?.after && user) {
3507
+ await hooks.user.update.after(user);
3508
+ }
3509
+ return user;
3510
+ },
3511
+ updatePassword: async (userId, password) => {
3512
+ const account = await adapter.update({
3513
+ model: tables.account.tableName,
3514
+ where: [
3515
+ {
3516
+ value: userId,
3517
+ field: "userId"
3518
+ },
3519
+ {
3520
+ field: "providerId",
3521
+ value: "credential"
3522
+ }
3523
+ ],
3524
+ update: {
3525
+ password
3526
+ }
3527
+ });
3528
+ return account;
3529
+ },
3530
+ findAccounts: async (userId) => {
3531
+ const accounts = await adapter.findMany({
3532
+ model: tables.account.tableName,
3533
+ where: [
3534
+ {
3535
+ field: "userId",
3536
+ value: userId
3537
+ }
3538
+ ]
3539
+ });
3540
+ return accounts;
3541
+ },
3542
+ updateAccount: async (accountId, data) => {
3543
+ if (hooks?.account?.update?.before) {
3544
+ const result = await hooks.account.update.before(data);
3545
+ if (result === false) {
3546
+ return null;
3547
+ }
3548
+ data = typeof result === "object" ? result.data : result;
3549
+ }
3550
+ const account = await adapter.update({
3551
+ model: tables.account.tableName,
3552
+ where: [
3553
+ {
3554
+ field: "id",
3555
+ value: accountId
3556
+ }
3557
+ ],
3558
+ update: data
3559
+ });
3560
+ if (hooks?.account?.update?.after && account) {
3561
+ await hooks.account.update.after(account);
3562
+ }
3563
+ return account;
3564
+ }
3565
+ };
3566
+ };
3567
+
3568
+ // src/utils/constants.ts
3569
+ var DEFAULT_SECRET = "better-auth-secret-123456789";
3570
+
3571
+ // src/internal-plugins/cross-subdomain/index.ts
3572
+ var crossSubdomainCookies = (options) => {
3573
+ return {
3574
+ id: "cross-subdomain-cookies",
3575
+ async onResponse(response, ctx) {
3576
+ const setCookie = response.headers.get("set-cookie");
3577
+ if (!setCookie) return;
3578
+ const baseURL = ctx.baseURL;
3579
+ const cookieParts = setCookie.split(";");
3580
+ const domain = options?.domainName || new URL(baseURL).hostname;
3581
+ const authCookies = ctx.authCookies;
3582
+ const cookieNamesEligibleForDomain = [
3583
+ authCookies.sessionToken.name,
3584
+ authCookies.csrfToken.name,
3585
+ authCookies.dontRememberToken.name
3586
+ ];
3587
+ if (!cookieNamesEligibleForDomain.some((name) => setCookie.includes(name))) {
3588
+ return;
3589
+ }
3590
+ const updatedCookies = cookieParts.map((part) => {
3591
+ if (!cookieNamesEligibleForDomain.some(
3592
+ (name) => part.toLowerCase().includes(name.toLowerCase())
3593
+ )) {
3594
+ return part;
3595
+ }
3596
+ const trimmedPart = part.trim();
3597
+ if (trimmedPart.toLowerCase().startsWith("domain=")) {
3598
+ return `Domain=${domain}`;
3599
+ }
3600
+ if (!trimmedPart.toLowerCase().includes("domain=")) {
3601
+ return `${trimmedPart}; Domain=${domain}`;
3602
+ }
3603
+ return trimmedPart;
3604
+ }).filter(
3605
+ (part, index, self) => index === self.findIndex((p) => p.split(";")[0] === part.split(";")[0])
3606
+ ).join("; ");
3607
+ response.headers.set("set-cookie", updatedCookies);
3608
+ return {
3609
+ response
3610
+ };
3611
+ }
3612
+ };
3613
+ };
3614
+
3615
+ // src/init.ts
3616
+ var init = async (opts) => {
3617
+ const { options, context } = runPluginInit(opts);
3618
+ const plugins = options.plugins || [];
3619
+ const internalPlugins = getInternalPlugins(options);
3620
+ const adapter = await getAdapter(options);
3621
+ const db = await createKyselyAdapter(options);
3622
+ const baseURL = getBaseURL(options.baseURL, options.basePath) || "";
3623
+ const secret = options.secret || process.env.BETTER_AUTH_SECRET || process.env.AUTH_SECRET || DEFAULT_SECRET;
3624
+ const cookies = getCookies(options);
3625
+ const tables = getAuthTables(options);
3626
+ const socialProviders = Object.keys(options.socialProviders || {}).map((key) => {
3627
+ const value = options.socialProviders?.[key];
3628
+ if (value.enabled === false) {
3629
+ return null;
3630
+ }
3631
+ if (!value.clientId || !value.clientSecret) {
3632
+ logger.warn(
3633
+ `Social provider ${key} is missing clientId or clientSecret`
3634
+ );
3635
+ }
3636
+ return oAuthProviders[key](value);
3637
+ }).filter((x) => x !== null);
3638
+ return {
3639
+ appName: options.appName || "Better Auth",
3640
+ socialProviders,
3641
+ options: {
3642
+ ...options,
3643
+ baseURL: baseURL ? new URL(baseURL).origin : "",
3644
+ basePath: options.basePath || "/api/auth",
3645
+ plugins: plugins.concat(internalPlugins)
3646
+ },
3647
+ tables,
3648
+ baseURL,
3649
+ sessionConfig: {
3650
+ updateAge: options.session?.updateAge || 24 * 60 * 60,
3651
+ // 24 hours
3652
+ expiresIn: options.session?.expiresIn || 60 * 60 * 24 * 7
3653
+ // 7 days
3654
+ },
3655
+ secret,
3656
+ rateLimit: {
3657
+ ...options.rateLimit,
3658
+ enabled: options.rateLimit?.enabled ?? process.env.NODE_ENV !== "development",
3659
+ window: options.rateLimit?.window || 60,
3660
+ max: options.rateLimit?.max || 100,
3661
+ storage: options.rateLimit?.storage || "memory"
3662
+ },
3663
+ authCookies: cookies,
3664
+ logger: createLogger({
3665
+ disabled: options.logger?.disabled || false
3666
+ }),
3667
+ db,
3668
+ password: {
3669
+ hash: options.emailAndPassword?.password?.hash || hashPassword,
3670
+ verify: options.emailAndPassword?.password?.verify || verifyPassword,
3671
+ config: {
3672
+ minPasswordLength: options.emailAndPassword?.minPasswordLength || 8,
3673
+ maxPasswordLength: options.emailAndPassword?.maxPasswordLength || 128
3674
+ }
3675
+ },
3676
+ adapter,
3677
+ internalAdapter: createInternalAdapter(adapter, options),
3678
+ createAuthCookie: createCookieGetter(options),
3679
+ ...context
3680
+ };
3681
+ };
3682
+ function runPluginInit(options) {
3683
+ const plugins = options.plugins || [];
3684
+ let context = {};
3685
+ for (const plugin of plugins) {
3686
+ if (plugin.init) {
3687
+ const result = plugin.init(options);
3688
+ if (typeof result === "object") {
3689
+ if (result.options) {
3690
+ options = {
3691
+ ...options,
3692
+ ...result.options
3693
+ };
3694
+ }
3695
+ context = {
3696
+ ...result
3697
+ };
3698
+ }
3699
+ }
3700
+ }
3701
+ const { options: _, ...rest } = context;
3702
+ return {
3703
+ options,
3704
+ context: rest
3705
+ };
3706
+ }
3707
+ function getInternalPlugins(options) {
3708
+ const plugins = [];
3709
+ if (options.advanced?.crossSubDomainCookies?.enabled) {
3710
+ plugins.push(
3711
+ crossSubdomainCookies({
3712
+ eligibleCookies: options.advanced.crossSubDomainCookies.eligibleCookies
3713
+ })
3714
+ );
3715
+ }
3716
+ return plugins;
3717
+ }
83
3718
 
84
- `)}return{toBeCreated:s,toBeAdded:i,runMigrations:c,compileMigrations:l}}function K(e){if(!e)return{and:null,or:null};let t=e?.filter(r=>r.connector==="AND"||!r.connector).reduce((r,n)=>({...r,[n.field]:n.value}),{}),o=e?.filter(r=>r.connector==="OR").reduce((r,n)=>({...r,[n.field]:n.value}),{});return{and:Object.keys(t).length?t:null,or:Object.keys(o).length?o:null}}function W(e,t,o){for(let r in e)e[r]===0&&t[r]?.type==="boolean"&&o?.boolean&&(e[r]=!1),e[r]===1&&t[r]?.type==="boolean"&&o?.boolean&&(e[r]=!0),t[r]?.type==="date"&&(e[r]instanceof Date||(e[r]=new Date(e[r])));return e}function nt(e,t){for(let o in e)typeof e[o]=="boolean"&&t?.boolean&&(e[o]=e[o]?1:0),e[o]instanceof Date&&(e[o]=e[o].toISOString());return e}var st=(e,t)=>({id:"kysely",async create(o){let{model:r,data:n,select:s}=o;t?.transform&&(n=nt(n,t.transform));let i=await e.insertInto(r).values(n).returningAll().executeTakeFirst();if(t?.transform){let a=t.transform.schema[r];i=a?W(n,a,t.transform):i}return s?.length&&(i=i?s.reduce((d,c)=>i?.[c]?{...d,[c]:i[c]}:d,{}):null),i},async findOne(o){let{model:r,where:n,select:s}=o,{and:i,or:a}=K(n),d=e.selectFrom(r).selectAll();a&&(d=d.where(l=>l.or(a))),i&&(d=d.where(l=>l.and(i)));let c=await d.executeTakeFirst();if(s?.length&&(c=c?s.reduce((u,p)=>c?.[p]?{...u,[p]:c[p]}:u,{}):null),t?.transform){let l=t.transform.schema[r];return c=c&&l?W(c,l,t.transform):c,c||null}return c||null},async findMany(o){let{model:r,where:n}=o,s=e.selectFrom(r),{and:i,or:a}=K(n);i&&(s=s.where(c=>c.and(i))),a&&(s=s.where(c=>c.or(a)));let d=await s.selectAll().execute();if(t?.transform){let c=t.transform.schema[r];return c?d.map(l=>W(l,c,t.transform)):d}return d},async update(o){let{model:r,where:n,update:s}=o,{and:i,or:a}=K(n);t?.transform&&(s=nt(s,t.transform));let d=e.updateTable(r).set(s);i&&(d=d.where(l=>l.and(i))),a&&(d=d.where(l=>l.or(a)));let c=await d.returningAll().executeTakeFirst()||null;if(t?.transform){let l=t.transform.schema[r];return l?W(c,l,t.transform):c}return c},async delete(o){let{model:r,where:n}=o,{and:s,or:i}=K(n),a=e.deleteFrom(r);s&&(a=a.where(d=>d.and(s))),i&&(a=a.where(d=>d.or(i))),await a.execute()},async createSchema(o){let{compileMigrations:r}=await ot(o);return console.log(r),{code:await r(),fileName:`./better-auth_migrations/${new Date().toISOString()}.sql`}}});async function it(e){if(!e.database)throw new k("Database configuration is required");if("create"in e.database)return e.database;let t=await q(e);if(!t)throw new k("Failed to initialize database adapter");let o=C(e),r={};for(let n of Object.values(o))r[n.tableName]=n.fields;return st(t,{transform:{schema:r,date:!0,boolean:G(e)==="sqlite"}})}import{scrypt as hr}from"node:crypto";import{decodeHex as yr,encodeHex as at}from"oslo/encoding";import{constantTimeEqual as br}from"oslo/crypto";var j={N:16384,r:16,p:1,dkLen:64};async function dt(e,t){return await new Promise((o,r)=>{hr(e.normalize("NFKC"),t,j.dkLen,{N:j.N,p:j.p,r:j.r,maxmem:128*j.N*j.r*2},(n,s)=>n?r(n):o(s))})}var ct=async e=>{let t=at(crypto.getRandomValues(new Uint8Array(16))),o=await dt(e,t);return`${t}:${at(o)}`},lt=async(e,t)=>{let[o,r]=e.split(":"),n=await dt(t,o);return br(n,yr(r))};import{alphabet as wr,generateRandomString as Ar}from"oslo/crypto";var ut=(e,t)=>{let o=t.session?.expiresIn||604800,r=C(t),n=t.databaseHooks;async function s(i,a){let d=i;if(n?.[a]?.create?.before){let l=await n[a].create.before(i);if(l===!1)return null;d=typeof l=="object"?l.data:l}let c=await e.create({model:a,data:i});return n?.[a]?.create?.after&&c&&await n[a].create.after(c),c}return{createOAuthUser:async(i,a)=>{try{let d=await s(i,"user"),c=await s(a,"account");return{user:d,account:c}}catch(d){return console.log(d),null}},createUser:async i=>await s(i,"user"),createSession:async(i,a,d)=>{let c=a instanceof Request?a.headers:a,l={id:Ar(32,wr("a-z","0-9","A-Z")),userId:i,expiresAt:d?N(1e3*60*60*24):N(o,!0),ipAddress:c?.get("x-forwarded-for")||"",userAgent:c?.get("user-agent")||""};return await s(l,"session")},findSession:async i=>{let a=await e.findOne({model:r.session.tableName,where:[{value:i,field:"id"}]});if(!a)return null;let d=await e.findOne({model:r.user.tableName,where:[{value:a.userId,field:"id"}]});return d?{session:a,user:d}:null},updateSession:async(i,a)=>{if(n?.session?.update?.before){let c=await n.session.update.before(a);if(c===!1)return null;a=typeof c=="object"?c.data:c}let d=await e.update({model:r.session.tableName,where:[{field:"id",value:i}],update:a});return n?.session?.update?.after&&d&&await n.session.update.after(d),d},deleteSession:async i=>await e.delete({model:r.session.tableName,where:[{field:"id",value:i}]}),deleteSessions:async i=>await e.delete({model:r.session.tableName,where:[{field:"userId",value:i}]}),findUserByEmail:async i=>{let a=await e.findOne({model:r.user.tableName,where:[{value:i.toLowerCase(),field:"email"}]});if(!a)return null;let d=await e.findMany({model:r.account.tableName,where:[{value:a.id,field:"userId"}]});return{user:a,accounts:d}},findUserById:async i=>await e.findOne({model:r.user.tableName,where:[{field:"id",value:i}]}),linkAccount:async i=>await s(i,"account"),updateUserByEmail:async(i,a)=>{if(n?.user?.update?.before){let c=await n.user.update.before(a);if(c===!1)return null;a=typeof c=="object"?c.data:c}let d=await e.update({model:r.user.tableName,where:[{value:i,field:"email"}],update:a});return n?.user?.update?.after&&d&&await n.user.update.after(d),d},updatePassword:async(i,a)=>await e.update({model:r.account.tableName,where:[{value:i,field:"userId"},{field:"providerId",value:"credential"}],update:{password:a}}),findAccounts:async i=>await e.findMany({model:r.account.tableName,where:[{field:"userId",value:i}]}),updateAccount:async(i,a)=>{if(n?.account?.update?.before){let c=await n.account.update.before(a);if(c===!1)return null;a=typeof c=="object"?c.data:c}let d=await e.update({model:r.account.tableName,where:[{field:"id",value:i}],update:a});return n?.account?.update?.after&&d&&await n.account.update.after(d),d}}};var pt="better-auth-secret-123456789";var mt=e=>({id:"cross-subdomain-cookies",async onResponse(t,o){let r=t.headers.get("set-cookie");if(!r)return;let n=o.baseURL,s=r.split(";"),i=e?.domainName||new URL(n).hostname,a=o.authCookies,d=[a.sessionToken.name,a.csrfToken.name,a.dontRememberToken.name];if(!d.some(l=>r.includes(l)))return;let c=s.map(l=>{if(!d.some(p=>l.toLowerCase().includes(p.toLowerCase())))return l;let u=l.trim();return u.toLowerCase().startsWith("domain=")?`Domain=${i}`:u.toLowerCase().includes("domain=")?u:`${u}; Domain=${i}`}).filter((l,u,p)=>u===p.findIndex(m=>m.split(";")[0]===l.split(";")[0])).join("; ");return t.headers.set("set-cookie",c),{response:t}}});var ft=async e=>{let{options:t,context:o}=kr(e),r=t.plugins||[],n=Rr(t),s=await it(t),i=await q(t),a=V(t.baseURL,t.basePath)||"",d=t.secret||process.env.BETTER_AUTH_SECRET||process.env.AUTH_SECRET||pt,c=Te(t),l=C(t),u=Object.keys(t.socialProviders||{}).map(p=>{let m=t.socialProviders?.[p];return m.enabled===!1?null:((!m.clientId||!m.clientSecret)&&T.warn(`Social provider ${p} is missing clientId or clientSecret`),Q[p](m))}).filter(p=>p!==null);return{appName:t.appName||"Better Auth",socialProviders:u,options:{...t,baseURL:a?new URL(a).origin:"",basePath:t.basePath||"/api/auth",plugins:r.concat(n)},tables:l,baseURL:a,sessionConfig:{updateAge:t.session?.updateAge||24*60*60,expiresIn:t.session?.expiresIn||60*60*24*7},secret:d,rateLimit:{...t.rateLimit,enabled:t.rateLimit?.enabled??process.env.NODE_ENV!=="development",window:t.rateLimit?.window||60,max:t.rateLimit?.max||100,storage:t.rateLimit?.storage||"memory"},authCookies:c,logger:Z({disabled:t.logger?.disabled||!1}),db:i,password:{hash:t.emailAndPassword?.password?.hash||ct,verify:t.emailAndPassword?.password?.verify||lt,config:{minPasswordLength:t.emailAndPassword?.minPasswordLength||8,maxPasswordLength:t.emailAndPassword?.maxPasswordLength||128}},adapter:s,internalAdapter:ut(s,t),createAuthCookie:xe(t),...o}};function kr(e){let t=e.plugins||[],o={};for(let s of t)if(s.init){let i=s.init(e);typeof i=="object"&&(i.options&&(e={...e,...i.options}),o={...i})}let{options:r,...n}=o;return{options:e,context:n}}function Rr(e){let t=[];return e.advanced?.crossSubDomainCookies?.enabled&&t.push(mt({eligibleCookies:e.advanced.crossSubDomainCookies.eligibleCookies})),t}var La=e=>{let t=ft(e),{api:o}=ie(t,e);return{handler:async r=>{let n=await t,s=n.options.basePath,i=new URL(r.url);if(!n.options.baseURL){let d=`${i.origin}/api/auth`;n.options.baseURL=d,n.baseURL=d}if(!n.options.baseURL)return new Response("Base URL not set",{status:400});if(i.pathname===s||i.pathname===`${s}/`)return new Response("Welcome to BetterAuth",{status:200});let{handler:a}=Xe(n,e);return a(r)},api:o,options:e,$Infer:{}}};export{La as betterAuth};
3719
+ // src/auth.ts
3720
+ var betterAuth = (options) => {
3721
+ const authContext = init(options);
3722
+ const { api } = getEndpoints(authContext, options);
3723
+ return {
3724
+ handler: async (request) => {
3725
+ const ctx = await authContext;
3726
+ const basePath = ctx.options.basePath;
3727
+ const url = new URL(request.url);
3728
+ if (!ctx.options.baseURL) {
3729
+ const baseURL = `${url.origin}/api/auth`;
3730
+ ctx.options.baseURL = baseURL;
3731
+ ctx.baseURL = baseURL;
3732
+ }
3733
+ if (!ctx.options.baseURL) {
3734
+ return new Response("Base URL not set", { status: 400 });
3735
+ }
3736
+ if (url.pathname === basePath || url.pathname === `${basePath}/`) {
3737
+ return new Response("Welcome to BetterAuth", { status: 200 });
3738
+ }
3739
+ const { handler } = router(ctx, options);
3740
+ return handler(request);
3741
+ },
3742
+ api,
3743
+ options,
3744
+ $Infer: {}
3745
+ };
3746
+ };
3747
+ export {
3748
+ betterAuth
3749
+ };