UNPKG

@auth/core

Version:

Authentication for the Web.

219 lines (203 loc) 7.04 kB
import { UnknownAction } from "../errors.js" import { SessionStore } from "./cookie.js" import { init } from "./init.js" import renderPage from "./pages/index.js" import * as routes from "./routes/index.js" import type { AuthConfig, ErrorPageParam, RequestInternal, ResponseInternal, } from "../types.js" /** @internal */ export async function AuthInternal< Body extends string | Record<string, any> | any[] >( request: RequestInternal, authOptions: AuthConfig ): Promise<ResponseInternal<Body>> { const { action, providerId, error, method } = request const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck const { options, cookies } = await init({ authOptions, action, providerId, url: request.url, callbackUrl: request.body?.callbackUrl ?? request.query?.callbackUrl, csrfToken: request.body?.csrfToken, cookies: request.cookies, isPost: method === "POST", csrfDisabled, }) const sessionStore = new SessionStore( options.cookies.sessionToken, request, options.logger ) if (method === "GET") { const render = renderPage({ ...options, query: request.query, cookies }) const { pages } = options switch (action) { case "providers": return (await routes.providers(options.providers)) as any case "session": { const session = await routes.session({ sessionStore, options }) if (session.cookies) cookies.push(...session.cookies) // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return { ...session, cookies } as any } case "csrf": { if (csrfDisabled) { options.logger.warn("csrf-disabled") cookies.push({ name: options.cookies.csrfToken.name, value: "", options: { ...options.cookies.csrfToken.options, maxAge: 0 }, }) return { status: 404, cookies } } return { headers: { "Content-Type": "application/json" }, body: { csrfToken: options.csrfToken } as any, cookies, } } case "signin": if (pages.signIn) { let signinUrl = `${pages.signIn}${ pages.signIn.includes("?") ? "&" : "?" }${new URLSearchParams({ callbackUrl: options.callbackUrl })}` if (error) signinUrl = `${signinUrl}&${new URLSearchParams({ error })}` return { redirect: signinUrl, cookies } } return render.signin() case "signout": if (pages.signOut) return { redirect: pages.signOut, cookies } return render.signout() case "callback": if (options.provider) { const callback = await routes.callback({ body: request.body, query: request.query, headers: request.headers, cookies: request.cookies, method, options, sessionStore, }) if (callback.cookies) cookies.push(...callback.cookies) return { ...callback, cookies } } break case "verify-request": if (pages.verifyRequest) { return { redirect: pages.verifyRequest, cookies } } return render.verifyRequest() case "error": // These error messages are displayed in line on the sign in page // TODO: verify these. We should redirect these to signin directly, instead of // first to error and then to signin. if ( [ "Signin", "OAuthCreateAccount", "EmailCreateAccount", "Callback", "OAuthAccountNotLinked", "SessionRequired", ].includes(error as string) ) { return { redirect: `${options.url}/signin?error=${error}`, cookies } } if (pages.error) { return { redirect: `${pages.error}${ pages.error.includes("?") ? "&" : "?" }error=${error}`, cookies, } } return render.error({ error: error as ErrorPageParam }) default: } } else { switch (action) { case "signin": if ((csrfDisabled || options.csrfTokenVerified) && options.provider) { const signin = await routes.signin(request, options) if (signin.cookies) cookies.push(...signin.cookies) return { ...signin, cookies } } return { redirect: `${options.url}/signin?csrf=true`, cookies } case "signout": if (csrfDisabled || options.csrfTokenVerified) { const signout = await routes.signout(sessionStore, options) if (signout.cookies) cookies.push(...signout.cookies) return { ...signout, cookies } } return { redirect: `${options.url}/signout?csrf=true`, cookies } case "callback": if (options.provider) { // Verified CSRF Token required for credentials providers only if ( options.provider.type === "credentials" && !csrfDisabled && !options.csrfTokenVerified ) { return { redirect: `${options.url}/signin?csrf=true`, cookies } } const callback = await routes.callback({ body: request.body, query: request.query, headers: request.headers, cookies: request.cookies, method, options, sessionStore, }) if (callback.cookies) cookies.push(...callback.cookies) return { ...callback, cookies } } break case "session": { if (options.csrfTokenVerified || csrfDisabled) { const session = await routes.session({ options, sessionStore, newSession: request.body?.data, isUpdate: true, }) if (session.cookies) cookies.push(...session.cookies) return { ...session, cookies } as any } // If CSRF token is invalid, return a 400 status code // we should not redirect to a page as this is an API route return { status: 400, cookies } } default: } } throw new UnknownAction(`Cannot handle action: ${action}`) } /** * :::danger * This option is intended for framework authors. * ::: * * Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but * if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by * passing this value to {@link AuthConfig.skipCSRFCheck}. */ export const skipCSRFCheck = Symbol("skip-csrf-check") /** * :::danger * This option is intended for framework authors. * ::: * * Auth.js returns a web standard {@link Response} by default, but * if you are implementing a framework you might want to get access to the raw internal response * by passing this value to {@link AuthConfig.raw}. */ export const raw = Symbol("return-type-raw")