Files
web/app/[lang]/(live)/(public)/login/route.ts
Michael Zetterberg 4a846540c3 feat: improve handling of deployment env vars
These are now defined in Netlify UI for dedicated environments (test, stage, production):

AUTH_URL
NEXTAUTH_URL
PUBLIC_URL

Code now falls back to incoming request host. Mainly used for
deployment previews which do not have Akamai in front, meaning
we do not need the above workaround as incoming request host
matches the actual public facing host. When Akamai is in front,
we lose the public facing host in Netlify's routing layer as they
internally use `x-forwarded-for` and we can't claim it for our usage.
2024-10-15 17:03:36 +02:00

184 lines
5.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server"
import { AuthError } from "next-auth"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { internalServerError } from "@/server/errors/next"
import { getPublicURL } from "@/server/utils"
import { signIn } from "@/auth"
export async function GET(
request: NextRequest,
context: { params: { lang: Lang } }
) {
const publicURL = getPublicURL(request)
let redirectHeaders: Headers | undefined = undefined
let redirectTo: string
const returnUrl = request.headers.get("x-returnurl")
const isSeamless = request.headers.get("x-login-source") === "seamless"
const isMFA = request.headers.get("x-login-source") === "mfa"
const isSeamlessMagicLink =
request.headers.get("x-login-source") === "seamless-magiclink"
console.log(
`[login] source: ${request.headers.get("x-login-source") || "normal"}`
)
const redirectToCookieValue = request.cookies.get("redirectTo")?.value // Cookie gets set by authRequired middleware
const redirectToSearchParamValue =
request.nextUrl.searchParams.get("redirectTo")
const redirectToFallback = "/"
console.log(`[login] redirectTo cookie value: ${redirectToCookieValue}`)
console.log(
`[login] redirectTo search param value: ${redirectToSearchParamValue}`
)
if (isSeamless || isSeamlessMagicLink || isMFA) {
if (returnUrl) {
redirectTo = returnUrl
} else {
console.log(
`[login] missing returnUrl, using fallback: ${redirectToFallback}`
)
redirectTo = redirectToFallback
}
} else {
redirectTo =
redirectToCookieValue || redirectToSearchParamValue || redirectToFallback
// Make relative URL to absolute URL
if (redirectTo.startsWith("/")) {
console.log(`[login] make redirectTo absolute, from ${redirectTo}`)
redirectTo = new URL(redirectTo, publicURL).href
console.log(`[login] make redirectTo absolute, to ${redirectTo}`)
}
// Clean up cookie from authRequired middleware
redirectHeaders = new Headers()
redirectHeaders.append(
"set-cookie",
"redirectTo=; Expires=Thu, 01 Jan 1970 00:00:00 UTC; Path=/; HttpOnly; SameSite=Lax"
)
try {
// Initiate the seamless login flow
let redirectUrlValue
switch (context.params.lang) {
case Lang.da:
redirectUrlValue = env.SEAMLESS_LOGIN_DA
break
case Lang.de:
redirectUrlValue = env.SEAMLESS_LOGIN_DE
break
case Lang.en:
redirectUrlValue = env.SEAMLESS_LOGIN_EN
break
case Lang.fi:
redirectUrlValue = env.SEAMLESS_LOGIN_FI
break
case Lang.no:
redirectUrlValue = env.SEAMLESS_LOGIN_NO
break
case Lang.sv:
redirectUrlValue = env.SEAMLESS_LOGIN_SV
break
}
const redirectUrl = new URL(redirectUrlValue)
console.log(`[login] creating redirect to seamless login: ${redirectUrl}`)
redirectUrl.searchParams.set("returnurl", redirectTo)
console.log(
`[login] returnurl for seamless login: ${redirectUrl.searchParams.get("returnurl")}`
)
redirectTo = redirectUrl.toString()
/** Set cookie with redirect Url to appropriately redirect user when using magic link login */
redirectHeaders.append(
"set-cookie",
"magicLinkRedirectTo=" +
redirectTo +
"; Max-Age=300; Path=/; HttpOnly; SameSite=Lax"
)
} catch (e) {
console.error(
"[login] unable to create URL for seamless login, proceeding without it.",
e
)
}
}
try {
console.log(`[login] final redirectUrl: ${redirectTo}`)
console.log({ login_env: process.env })
/** Record<string, any> is next-auth typings */
const params: Record<string, any> = {
ui_locales: context.params.lang,
scope: ["openid", "profile"],
/**
* The `acr_values` param is used to make Curity display the proper login
* page for Scandic. Without the parameter Curity presents some choices
* to the user which we do not want.
*/
acr_values: "acr",
/**
* Both of the below two params are required to send for initiating login as well
* because user might choose to do Email link login.
* */
// The `for_origin` param is used to make Curity email login functionality working.
for_origin: publicURL,
// This is new param set for differentiate between the Magic link login of New web and current web
version: "2",
}
if (isMFA) {
// Append profile_update scope for MFA
params.scope.push("profile_update")
/**
* The below acr value is required as for New Web same Curity Client is used for MFA
* while in current web it is being setup using different Curity Client
*/
params.acr_values =
"urn:se:curity:authentication:otp-authenticator:OTP-Authenticator_web"
} else if (isSeamlessMagicLink) {
params.acr_values = "abc"
}
params.scope = params.scope.join(" ")
/**
* Passing `redirect: false` to `signIn` will return the URL instead of
* automatically redirecting to it inside of `signIn`.
* https://github.com/nextauthjs/next-auth/blob/3c035ec/packages/next-auth/src/lib/actions.ts#L76
*/
const redirectUrl = await signIn(
"curity",
{
redirectTo,
redirect: false,
},
params
)
if (redirectUrl) {
const redirectOpts = {
headers: redirectHeaders,
}
console.log(`[login] redirecting to: ${redirectUrl}`, redirectOpts)
return NextResponse.redirect(redirectUrl, redirectOpts)
} else {
console.error(`[login] missing redirectUrl reponse from signIn()`)
}
} catch (error) {
if (error instanceof AuthError) {
console.error({ signInAuthError: error })
} else {
console.error({ signInError: error })
}
}
return internalServerError()
}