import { renderToString } from "preact-render-to-string"
import ErrorPage from "./error.js"
import SigninPage from "./signin.js"
import SignoutPage from "./signout.js"
import css from "./styles.js"
import VerifyRequestPage from "./verify-request.js"
import { UnknownAction } from "../../errors.js"
import type {
InternalOptions,
RequestInternal,
ResponseInternal,
InternalProvider,
PublicProvider,
} from "../../types.js"
import type { Cookie } from "../utils/cookie.js"
function send({
html,
title,
status,
cookies,
theme,
headTags,
}: any): ResponseInternal {
return {
cookies,
status,
headers: { "Content-Type": "text/html" },
body: `
${title}${
headTags ?? ""
}${renderToString(html)}
`,
}
}
type RenderPageParams = {
query?: RequestInternal["query"]
cookies?: Cookie[]
} & Partial<
Pick<
InternalOptions,
"url" | "callbackUrl" | "csrfToken" | "providers" | "theme" | "pages"
>
>
/**
* Unless the user defines their [own pages](https://authjs.dev/reference/core#pages),
* we render a set of default ones, using Preact SSR.
*/
export default function renderPage(params: RenderPageParams) {
const { url, theme, query, cookies, pages, providers } = params
return {
csrf(skip: boolean, options: InternalOptions, cookies: Cookie[]) {
if (!skip) {
return {
headers: { "Content-Type": "application/json" },
body: { csrfToken: options.csrfToken },
cookies,
}
}
options.logger.warn("csrf-disabled")
cookies.push({
name: options.cookies.csrfToken.name,
value: "",
options: { ...options.cookies.csrfToken.options, maxAge: 0 },
})
return { status: 404, cookies }
},
providers(providers: InternalProvider[]) {
return {
headers: { "Content-Type": "application/json" },
body: providers.reduce>(
(acc, { id, name, type, signinUrl, callbackUrl }) => {
acc[id] = { id, name, type, signinUrl, callbackUrl }
return acc
},
{}
),
}
},
signin(providerId?: string, error?: any) {
if (providerId) throw new UnknownAction("Unsupported action")
if (pages?.signIn) {
let signinUrl = `${pages.signIn}${
pages.signIn.includes("?") ? "&" : "?"
}${new URLSearchParams({ callbackUrl: params.callbackUrl ?? "/" })}`
if (error) signinUrl = `${signinUrl}&${new URLSearchParams({ error })}`
return { redirect: signinUrl, cookies }
}
// If we have a webauthn provider with conditional UI and
// a simpleWebAuthnBrowserScript is defined, we need to
// render the script in the page.
const webauthnProvider = providers?.find(
(p): p is InternalProvider<"webauthn"> =>
p.type === "webauthn" &&
p.enableConditionalUI &&
!!p.simpleWebAuthnBrowserVersion
)
let simpleWebAuthnBrowserScript = ""
if (webauthnProvider) {
const { simpleWebAuthnBrowserVersion } = webauthnProvider
simpleWebAuthnBrowserScript = ``
}
return send({
cookies,
theme,
html: SigninPage({
csrfToken: params.csrfToken,
// We only want to render providers
providers: params.providers?.filter(
(provider) =>
// Always render oauth and email type providers
["email", "oauth", "oidc"].includes(provider.type) ||
// Only render credentials type provider if credentials are defined
(provider.type === "credentials" && provider.credentials) ||
// Only render webauthn type provider if formFields are defined
(provider.type === "webauthn" && provider.formFields) ||
// Don't render other provider types
false
),
callbackUrl: params.callbackUrl,
theme: params.theme,
error,
...query,
}),
title: "Sign In",
headTags: simpleWebAuthnBrowserScript,
})
},
signout() {
if (pages?.signOut) return { redirect: pages.signOut, cookies }
return send({
cookies,
theme,
html: SignoutPage({ csrfToken: params.csrfToken, url, theme }),
title: "Sign Out",
})
},
verifyRequest(props?: any) {
if (pages?.verifyRequest)
return { redirect: pages.verifyRequest, cookies }
return send({
cookies,
theme,
html: VerifyRequestPage({ url, theme, ...props }),
title: "Verify Request",
})
},
error(error?: string) {
if (pages?.error) {
return {
redirect: `${pages.error}${
pages.error.includes("?") ? "&" : "?"
}error=${error}`,
cookies,
}
}
return send({
cookies,
theme,
// @ts-expect-error fix error type
...ErrorPage({ url, theme, error }),
title: "Error",
})
},
}
}