From bcbc829e2e48cf4e5b0d8b7738daaffa03828577 Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Thu, 25 Apr 2024 08:54:05 +0200 Subject: [PATCH] fix(WEB-132): absolute seamless login return urls --- app/[lang]/(live)/(public)/login/route.ts | 59 ++++++++++++++++------- env/server.ts | 2 + middleware.ts | 6 +-- middlewares/authRequired.ts | 12 ++++- next.config.js | 4 ++ 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/app/[lang]/(live)/(public)/login/route.ts b/app/[lang]/(live)/(public)/login/route.ts index a444a6225..5d7681bb1 100644 --- a/app/[lang]/(live)/(public)/login/route.ts +++ b/app/[lang]/(live)/(public)/login/route.ts @@ -3,7 +3,7 @@ import { AuthError } from "next-auth" import { Lang } from "@/constants/languages" import { env } from "@/env/server" -import { badRequest, internalServerError } from "@/server/errors/next" +import { badRequest } from "@/server/errors/next" import { signIn } from "@/auth" @@ -11,21 +11,41 @@ export async function GET( request: NextRequest, context: { params: { lang: Lang } } ) { + let redirectHeaders: Headers | undefined = undefined + let redirectTo: string + const returnUrl = request.headers.get("x-returnurl") + if (returnUrl) { + // Seamless login request from Current web + redirectTo = returnUrl + } else { + // Normal login request from New web + redirectTo = + request.cookies.get("redirectTo")?.value || // Cookie gets set by authRequired middleware + request.headers.get("x-redirect-to") || + request.nextUrl.searchParams.get("redirectTo") || + request.headers.get("Referer") || + "" - // If all else fails, always redirect to startpage - const finalDestination = - returnUrl || - request.headers.get("x-redirect-to") || - request.nextUrl.searchParams.get("redirectTo") || - request.headers.get("Referer") || - "/" + // If above fails, always redirect to startpage + if (!redirectTo) { + const proto = request.headers.get("x-forwarded-proto") ?? "http" + const host = + request.headers.get("x-forwarded-host") ?? + request.headers.get("host") ?? + env.URL + redirectTo = `${proto}://${host}/` + } + + // 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" + ) - let redirectTo = finalDestination - if (!returnUrl) { - // This is a regular login request, not a seamless login request - // We should initiate the seamless login flow try { + // Initiate the seamless login flow let redirectUrlValue switch (context.params.lang) { case Lang.da: @@ -48,12 +68,13 @@ export async function GET( break } const redirectUrl = new URL(redirectUrlValue) - redirectUrl.searchParams.set("returnurl", finalDestination) + redirectUrl.searchParams.set("returnurl", redirectTo) redirectTo = redirectUrl.toString() } catch (e) { - console.error("Unable to create URL for seamless login") + console.error( + "Unable to create URL for seamless login, proceeding without it." + ) console.error(e) - return internalServerError() } } @@ -63,7 +84,7 @@ export async function GET( * automatically redirecting to it inside of `signIn`. * https://github.com/nextauthjs/next-auth/blob/3c035ec/packages/next-auth/src/lib/actions.ts#L76 */ - const url = await signIn( + const redirectUrl = await signIn( "curity", { redirectTo, @@ -74,8 +95,10 @@ export async function GET( } ) - if (url) { - return NextResponse.redirect(url) + if (redirectUrl) { + return NextResponse.redirect(redirectUrl, { + headers: redirectHeaders, + }) } } catch (error) { if (error instanceof AuthError) { diff --git a/env/server.ts b/env/server.ts index 2a0efbff0..b565ed448 100644 --- a/env/server.ts +++ b/env/server.ts @@ -34,6 +34,7 @@ export const env = createEnv({ SEAMLESS_LOGIN_FI: z.string(), SEAMLESS_LOGIN_NO: z.string(), SEAMLESS_LOGIN_SV: z.string(), + URL: z.string().optional(), WEBVIEW_ENCRYPTION_KEY: z.string(), }, emptyStringAsUndefined: true, @@ -63,6 +64,7 @@ export const env = createEnv({ SEAMLESS_LOGIN_FI: process.env.SEAMLESS_LOGIN_FI, SEAMLESS_LOGIN_NO: process.env.SEAMLESS_LOGIN_NO, SEAMLESS_LOGIN_SV: process.env.SEAMLESS_LOGIN_SV, + URL: process.env.URL, WEBVIEW_ENCRYPTION_KEY: process.env.WEBVIEW_ENCRYPTION_KEY, }, }) diff --git a/middleware.ts b/middleware.ts index d048ffd2d..c8510a8d9 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,11 +1,11 @@ import { NextMiddleware } from "next/server" -import * as handleAuth from "./middlewares/handleAuth" import * as authRequired from "./middlewares/authRequired" +import * as cmsContent from "./middlewares/cmsContent" import * as currentWebLogin from "./middlewares/currentWebLogin" import * as ensureLang from "./middlewares/ensureLang" -import * as cmsContent from "@/middlewares/cmsContent" -import * as webView from "@/middlewares/webView" +import * as handleAuth from "./middlewares/handleAuth" +import * as webView from "./middlewares/webView" export const middleware: NextMiddleware = async (request, event) => { const middlewares = [ diff --git a/middlewares/authRequired.ts b/middlewares/authRequired.ts index 836cb0979..2e5c5fde2 100644 --- a/middlewares/authRequired.ts +++ b/middlewares/authRequired.ts @@ -1,10 +1,11 @@ import { NextResponse } from "next/server" -import { auth } from "@/auth" import { findLang } from "@/constants/languages" import { authRequired } from "@/constants/routes/authRequired" import { login } from "@/constants/routes/handleAuth" +import { auth } from "@/auth" + import type { NextMiddleware } from "next/server" import type { MiddlewareMatcher } from "@/types/middleware" @@ -44,8 +45,15 @@ export const middleware = auth(async (request) => { return NextResponse.next() } + const headers = new Headers() + headers.append( + "set-cookie", + `redirectTo=${encodeURIComponent(nextUrl.href)}; Path=/; HttpOnly; SameSite=Lax` + ) const loginUrl = login[lang] - return NextResponse.redirect(new URL(loginUrl, request.nextUrl)) + return NextResponse.redirect(new URL(loginUrl, request.nextUrl), { + headers, + }) }) as NextMiddleware // See comment above export const matcher: MiddlewareMatcher = (request) => { diff --git a/next.config.js b/next.config.js index 15832b42a..318c5f938 100644 --- a/next.config.js +++ b/next.config.js @@ -23,6 +23,10 @@ jiti("./env/client") /** @type {import('next').NextConfig} */ const nextConfig = { env: { + URL: + process.env.CONTEXT === "production" + ? process.env.URL + : process.env.DEPLOY_PRIME_URL, NEXTAUTH_URL: (process.env.CONTEXT === "production" ? process.env.URL