184 lines
5.9 KiB
TypeScript
184 lines
5.9 KiB
TypeScript
import { type 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", "booking"],
|
|
/**
|
|
* 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()
|
|
}
|