From 4a846540c3bad6f21e1264aa3c9d187087b30a8a Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Mon, 14 Oct 2024 20:43:53 +0200 Subject: [PATCH] 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. --- .env | 4 -- app/[lang]/(live)/(protected)/logout/route.ts | 5 +- app/[lang]/(live)/(public)/login/route.ts | 9 ++- .../(live)/(public)/verifymagiclink/route.ts | 9 ++- app/api/web/add-card-callback/[lang]/route.ts | 5 +- app/api/web/envtest-dynamic-edge/route.ts | 38 ---------- .../web/envtest-dynamic-load-file/route.ts | 39 ---------- app/api/web/envtest-dynamic-load/route.ts | 38 ---------- app/api/web/envtest-dynamic-override/route.ts | 39 ---------- app/api/web/envtest-dynamic/route.ts | 36 ---------- app/api/web/envtest-static-load-file/route.ts | 37 ---------- app/api/web/envtest-static-load/route.ts | 36 ---------- app/api/web/envtest-static-override/route.ts | 37 ---------- app/api/web/envtest-static/route.ts | 34 --------- .../payment-callback/[lang]/[status]/route.ts | 9 +-- auth.ts | 3 +- env/server.ts | 6 +- middlewares/authRequired.ts | 12 +--- middlewares/currentWebLogout.ts | 6 +- middlewares/myPages.ts | 13 +--- middlewares/utils.ts | 19 +---- netlify.toml | 3 - package.json | 3 +- server/routers/user/utils.ts | 19 +++-- server/utils.ts | 27 +++++++ update-dotenv.mjs | 71 ------------------- 26 files changed, 72 insertions(+), 485 deletions(-) delete mode 100644 .env delete mode 100644 app/api/web/envtest-dynamic-edge/route.ts delete mode 100644 app/api/web/envtest-dynamic-load-file/route.ts delete mode 100644 app/api/web/envtest-dynamic-load/route.ts delete mode 100644 app/api/web/envtest-dynamic-override/route.ts delete mode 100644 app/api/web/envtest-dynamic/route.ts delete mode 100644 app/api/web/envtest-static-load-file/route.ts delete mode 100644 app/api/web/envtest-static-load/route.ts delete mode 100644 app/api/web/envtest-static-override/route.ts delete mode 100644 app/api/web/envtest-static/route.ts delete mode 100644 update-dotenv.mjs diff --git a/.env b/.env deleted file mode 100644 index d116a87a8..000000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ -# See update-dotenv.mjs -AUTH_URL="REPLACE-ON-NETLIFY-BUILD" -NEXTAUTH_URL="REPLACE-ON-NETLIFY-BUILD" -PUBLIC_URL="REPLACED-ON-NETLIFY-BUILD" diff --git a/app/[lang]/(live)/(protected)/logout/route.ts b/app/[lang]/(live)/(protected)/logout/route.ts index 2bb2f0a52..db32e1c48 100644 --- a/app/[lang]/(live)/(protected)/logout/route.ts +++ b/app/[lang]/(live)/(protected)/logout/route.ts @@ -4,6 +4,7 @@ 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 { signOut } from "@/auth" @@ -11,6 +12,8 @@ export async function GET( request: NextRequest, context: { params: { lang: Lang } } ) { + const publicURL = getPublicURL(request) + let redirectTo: string = "" const returnUrl = request.headers.get("x-returnurl") @@ -39,7 +42,7 @@ export async function GET( // Make relative URL to absolute URL if (redirectTo.startsWith("/")) { console.log(`[logout] make redirectTo absolute, from ${redirectTo}`) - redirectTo = new URL(redirectTo, env.PUBLIC_URL).href + redirectTo = new URL(redirectTo, publicURL).href console.log(`[logout] make redirectTo absolute, to ${redirectTo}`) } diff --git a/app/[lang]/(live)/(public)/login/route.ts b/app/[lang]/(live)/(public)/login/route.ts index b038205e6..2d91c0d5f 100644 --- a/app/[lang]/(live)/(public)/login/route.ts +++ b/app/[lang]/(live)/(public)/login/route.ts @@ -4,6 +4,7 @@ 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" @@ -11,9 +12,7 @@ export async function GET( request: NextRequest, context: { params: { lang: Lang } } ) { - if (!env.PUBLIC_URL) { - throw internalServerError("No value for env.PUBLIC_URL") - } + const publicURL = getPublicURL(request) let redirectHeaders: Headers | undefined = undefined let redirectTo: string @@ -54,7 +53,7 @@ export async function GET( // Make relative URL to absolute URL if (redirectTo.startsWith("/")) { console.log(`[login] make redirectTo absolute, from ${redirectTo}`) - redirectTo = new URL(redirectTo, env.PUBLIC_URL).href + redirectTo = new URL(redirectTo, publicURL).href console.log(`[login] make redirectTo absolute, to ${redirectTo}`) } @@ -131,7 +130,7 @@ export async function GET( * because user might choose to do Email link login. * */ // The `for_origin` param is used to make Curity email login functionality working. - for_origin: env.PUBLIC_URL, + for_origin: publicURL, // This is new param set for differentiate between the Magic link login of New web and current web version: "2", } diff --git a/app/[lang]/(live)/(public)/verifymagiclink/route.ts b/app/[lang]/(live)/(public)/verifymagiclink/route.ts index 12c4b132f..81fa277c3 100644 --- a/app/[lang]/(live)/(public)/verifymagiclink/route.ts +++ b/app/[lang]/(live)/(public)/verifymagiclink/route.ts @@ -5,6 +5,7 @@ import { Lang } from "@/constants/languages" import { login } from "@/constants/routes/handleAuth" import { env } from "@/env/server" import { badRequest, internalServerError } from "@/server/errors/next" +import { getPublicURL } from "@/server/utils" import { signIn } from "@/auth" @@ -12,9 +13,7 @@ export async function GET( request: NextRequest, context: { params: { lang: Lang } } ) { - if (!env.PUBLIC_URL) { - throw internalServerError("No value for env.PUBLIC_URL") - } + const publicURL = getPublicURL(request) const loginKey = request.nextUrl.searchParams.get("loginKey") if (!loginKey) { @@ -44,7 +43,7 @@ export async function GET( console.log( `[verifymagiclink] make redirectTo absolute, from ${redirectTo}` ) - redirectTo = new URL(redirectTo, env.PUBLIC_URL).href + redirectTo = new URL(redirectTo, publicURL).href console.log(`[verifymagiclink] make redirectTo absolute, to ${redirectTo}`) } @@ -69,7 +68,7 @@ export async function GET( ui_locales: context.params.lang, scope: ["openid", "profile"].join(" "), loginKey: loginKey, - for_origin: env.PUBLIC_URL, + for_origin: publicURL, acr_values: "abc", version: "2", } diff --git a/app/api/web/add-card-callback/[lang]/route.ts b/app/api/web/add-card-callback/[lang]/route.ts index 63eb19942..e664124ed 100644 --- a/app/api/web/add-card-callback/[lang]/route.ts +++ b/app/api/web/add-card-callback/[lang]/route.ts @@ -4,14 +4,17 @@ import { env } from "process" import { Lang } from "@/constants/languages" import { profile } from "@/constants/routes/myPages" import { serverClient } from "@/lib/trpc/server" +import { getPublicURL } from "@/server/utils" export async function GET( request: NextRequest, { params }: { params: { lang: string } } ) { + const publicURL = getPublicURL(request) + console.log(`[add-card] callback started`) const lang = params.lang as Lang - const returnUrl = new URL(`${env.PUBLIC_URL}/${profile[lang ?? Lang.en]}`) + const returnUrl = new URL(`${publicURL}/${profile[lang ?? Lang.en]}`) try { const searchParams = request.nextUrl.searchParams diff --git a/app/api/web/envtest-dynamic-edge/route.ts b/app/api/web/envtest-dynamic-edge/route.ts deleted file mode 100644 index d2a12f7bf..000000000 --- a/app/api/web/envtest-dynamic-edge/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} - -export const dynamic = "force-dynamic" - -export const runtime = "edge" diff --git a/app/api/web/envtest-dynamic-load-file/route.ts b/app/api/web/envtest-dynamic-load-file/route.ts deleted file mode 100644 index 115f12f77..000000000 --- a/app/api/web/envtest-dynamic-load-file/route.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { config } from "dotenv" -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -config({ path: "./.env" }) - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} - -export const dynamic = "force-dynamic" diff --git a/app/api/web/envtest-dynamic-load/route.ts b/app/api/web/envtest-dynamic-load/route.ts deleted file mode 100644 index 18c964526..000000000 --- a/app/api/web/envtest-dynamic-load/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import "dotenv/config" - -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} - -export const dynamic = "force-dynamic" diff --git a/app/api/web/envtest-dynamic-override/route.ts b/app/api/web/envtest-dynamic-override/route.ts deleted file mode 100644 index 4d45b8e5f..000000000 --- a/app/api/web/envtest-dynamic-override/route.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { config } from "dotenv" -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -config({ debug: true, override: true }) - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} - -export const dynamic = "force-dynamic" diff --git a/app/api/web/envtest-dynamic/route.ts b/app/api/web/envtest-dynamic/route.ts deleted file mode 100644 index 386817938..000000000 --- a/app/api/web/envtest-dynamic/route.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} - -export const dynamic = "force-dynamic" diff --git a/app/api/web/envtest-static-load-file/route.ts b/app/api/web/envtest-static-load-file/route.ts deleted file mode 100644 index f1a83978a..000000000 --- a/app/api/web/envtest-static-load-file/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { config } from "dotenv" -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -config({ path: "./.env" }) - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} diff --git a/app/api/web/envtest-static-load/route.ts b/app/api/web/envtest-static-load/route.ts deleted file mode 100644 index 4e360a891..000000000 --- a/app/api/web/envtest-static-load/route.ts +++ /dev/null @@ -1,36 +0,0 @@ -import "dotenv/config" - -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} diff --git a/app/api/web/envtest-static-override/route.ts b/app/api/web/envtest-static-override/route.ts deleted file mode 100644 index d844016c5..000000000 --- a/app/api/web/envtest-static-override/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { config } from "dotenv" -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -config({ debug: true, override: true }) - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} diff --git a/app/api/web/envtest-static/route.ts b/app/api/web/envtest-static/route.ts deleted file mode 100644 index 1c71b77ca..000000000 --- a/app/api/web/envtest-static/route.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NextResponse } from "next/server" - -import { env } from "@/env/server" - -import type { NextRequest } from "next/server" - -export async function GET(request: NextRequest) { - const e = process.env - console.log({ process_env: process.env }) - - const urlVar = "PUBLIC_URL" - const nextAuthUrlVar = "NEXTAUTH_URL" - const nextAuthUrlVar2 = "AUTH_URL" - const envTestVar = "ENVTEST" - - const values = { - env_url: env.PUBLIC_URL, - static_url: process.env.PUBLIC_URL, - dynamic_url: e[urlVar], - env_envtest: env.ENVTEST, - static_envtest: process.env.ENVTEST, - dynamic_envtest: e[envTestVar], - env_nextauth: env.NEXTAUTH_URL, - static_nextauth: process.env.NEXTAUTH_URL, - dynamic_nextauth: e[nextAuthUrlVar], - env_nextauth2: env.AUTH_URL, - static_nextauth2: process.env.AUTH_URL, - dynamic_nextauth2: e[nextAuthUrlVar2], - } - - console.log(values) - - return NextResponse.json(values) -} diff --git a/app/api/web/payment-callback/[lang]/[status]/route.ts b/app/api/web/payment-callback/[lang]/[status]/route.ts index 0b8133c43..4154aff14 100644 --- a/app/api/web/payment-callback/[lang]/[status]/route.ts +++ b/app/api/web/payment-callback/[lang]/[status]/route.ts @@ -6,20 +6,21 @@ import { bookingConfirmation, payment, } from "@/constants/routes/hotelReservation" +import { getPublicURL } from "@/server/utils" export async function GET( request: NextRequest, { params }: { params: { lang: string; status: string } } ): Promise { + const publicURL = getPublicURL(request) + console.log(`[payment-callback] callback started`) const lang = params.lang as Lang const status = params.status - const returnUrl = new URL(`${env.PUBLIC_URL}/${payment[lang]}`) + const returnUrl = new URL(`${publicURL}/${payment[lang]}`) if (status === "success") { - const confirmationUrl = new URL( - `${env.PUBLIC_URL}/${bookingConfirmation[lang]}` - ) + const confirmationUrl = new URL(`${publicURL}/${bookingConfirmation[lang]}`) console.log(`[payment-callback] redirecting to: ${confirmationUrl}`) return NextResponse.redirect(confirmationUrl) } diff --git a/auth.ts b/auth.ts index 0de343626..3adc360f8 100644 --- a/auth.ts +++ b/auth.ts @@ -109,6 +109,7 @@ const curityProvider = { } satisfies OIDCConfig export const config = { + basePath: "/api/web/auth", debug: env.NEXTAUTH_DEBUG, providers: [curityProvider], redirectProxyUrl: env.NEXTAUTH_REDIRECT_PROXY_URL, @@ -233,4 +234,4 @@ export const { auth, signIn, signOut, -} = NextAuth(config) \ No newline at end of file +} = NextAuth(config) diff --git a/env/server.ts b/env/server.ts index b32150a1d..c4b9355b7 100644 --- a/env/server.ts +++ b/env/server.ts @@ -37,15 +37,15 @@ export const env = createEnv({ .default("false"), NEXTAUTH_REDIRECT_PROXY_URL: z.string().optional(), NEXTAUTH_SECRET: z.string(), - NEXTAUTH_URL: z.string().optional(), - AUTH_URL: z.string().optional(), + NEXTAUTH_URL: z.string().default(""), + AUTH_URL: z.string().default(""), NODE_ENV: z.enum(["development", "test", "production"]), PRINT_QUERY: z .string() .refine((s) => s === "true" || s === "false") .transform((s) => s === "true") .default("false"), - PUBLIC_URL: z.string().optional(), + PUBLIC_URL: z.string().default(""), REVALIDATE_SECRET: z.string(), SALESFORCE_PREFERENCE_BASE_URL: z.string(), SEAMLESS_LOGIN_DA: z.string(), diff --git a/middlewares/authRequired.ts b/middlewares/authRequired.ts index 0e50330ee..09781ac56 100644 --- a/middlewares/authRequired.ts +++ b/middlewares/authRequired.ts @@ -2,8 +2,7 @@ import { NextResponse } from "next/server" import { authRequired, mfaRequired } from "@/constants/routes/authRequired" import { login } from "@/constants/routes/handleAuth" -import { env } from "@/env/server" -import { internalServerError } from "@/server/errors/next" +import { getPublicNextURL } from "@/server/utils" import { auth } from "@/auth" import { findLang } from "@/utils/languages" @@ -44,14 +43,7 @@ export const middleware = auth(async (request) => { const isLoggedIn = !!request.auth const hasError = request.auth?.error - if (!env.PUBLIC_URL) { - throw internalServerError("Missing value for env.PUBLIC_URL") - } - - const publicUrl = new URL(env.PUBLIC_URL) - const nextUrlPublic = nextUrl.clone() - nextUrlPublic.host = publicUrl.host - nextUrlPublic.hostname = publicUrl.hostname + const nextUrlPublic = getPublicNextURL(request) /** * Function to validate MFA from token data diff --git a/middlewares/currentWebLogout.ts b/middlewares/currentWebLogout.ts index c358012f2..2e52346a2 100644 --- a/middlewares/currentWebLogout.ts +++ b/middlewares/currentWebLogout.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server" import { env } from "@/env/server" import { badRequest, internalServerError } from "@/server/errors/next" +import { getPublicURL } from "@/server/utils" import { findLang } from "@/utils/languages" @@ -16,10 +17,7 @@ export const middleware: NextMiddleware = (request) => { } const lang = findLang(request.nextUrl.pathname)! - if (!env.PUBLIC_URL) { - throw internalServerError("No value for env.PUBLIC_URL") - } - const redirectTo = env.PUBLIC_URL + const redirectTo = getPublicURL(request) const headers = new Headers(request.headers) headers.set("x-returnurl", redirectTo) diff --git a/middlewares/myPages.ts b/middlewares/myPages.ts index 28c375f13..6e27f10a9 100644 --- a/middlewares/myPages.ts +++ b/middlewares/myPages.ts @@ -8,6 +8,7 @@ import { } from "@/constants/routes/myPages" import { env } from "@/env/server" import { internalServerError, notFound } from "@/server/errors/next" +import { getPublicNextURL } from "@/server/utils" import { findLang } from "@/utils/languages" @@ -23,17 +24,9 @@ export const middleware: NextMiddleware = async (request) => { const myPagesRoot = myPages[lang] if (nextUrl.pathname === myPagesRoot) { - if (!env.PUBLIC_URL) { - throw internalServerError("Missing value for env.PUBLIC_URL") - } - - const publicUrl = new URL(env.PUBLIC_URL) - const nextUrlClone = nextUrl.clone() - nextUrlClone.host = publicUrl.host - nextUrlClone.hostname = publicUrl.hostname - + const nextUrlPublic = getPublicNextURL(request) const overviewUrl = overview[lang] - const redirectUrl = new URL(overviewUrl, nextUrlClone) + const redirectUrl = new URL(overviewUrl, nextUrlPublic) console.log(`[myPages] redirecting to: ${redirectUrl}`) return NextResponse.redirect(redirectUrl) } diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 91e06a68d..18714d28f 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -1,7 +1,5 @@ -import { stringify } from "querystring" - import { Lang } from "@/constants/languages" -import { env } from "@/env/server" +import { getPublicNextURL } from "@/server/utils" import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" @@ -11,18 +9,7 @@ import type { NextRequest } from "next/server" export function getDefaultRequestHeaders(request: NextRequest) { const lang = findLang(request.nextUrl.pathname)! - - let nextUrl - if (env.PUBLIC_URL) { - const publicUrl = new URL(env.PUBLIC_URL) - const nextUrlPublic = request.nextUrl.clone() - nextUrlPublic.host = publicUrl.host - nextUrlPublic.hostname = publicUrl.hostname - nextUrl = nextUrlPublic - } else { - nextUrl = request.nextUrl - } - + const nextUrlPublic = getPublicNextURL(request) const headers = new Headers(request.headers) headers.set("x-lang", lang) headers.set( @@ -31,7 +18,7 @@ export function getDefaultRequestHeaders(request: NextRequest) { request.nextUrl.pathname.replace(`/${lang}`, "").replace(`/webview`, "") ) ) - headers.set("x-url", removeTrailingSlash(nextUrl.href)) + headers.set("x-url", removeTrailingSlash(nextUrlPublic.href)) return headers } diff --git a/netlify.toml b/netlify.toml index 022eee2ee..850405109 100644 --- a/netlify.toml +++ b/netlify.toml @@ -30,6 +30,3 @@ package = "@netlify/plugin-nextjs" [images] remote_images = ["https://imagevault-stage.scandichotels.com.*", "https://imagevault.scandichotels.com.*"] - -[functions] -included_files = ["./.env"] diff --git a/package.json b/package.json index 669ab58da..0565ffa11 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "prebuild": "npm run update-dotenv && npm run lint && npm run test:unit", + "prebuild": "npm run lint && npm run test:unit", "build": "next build", "predev": "rm -rf .next", "dev": "PORT=3000 NEXT_PUBLIC_PORT=3000 next dev", @@ -20,7 +20,6 @@ "test:e2e:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/sponsoring \"cypress run --e2e\"", "test:setup": "npm run build && npm run start", "preinstall": "export $(cat .env.local | grep -v '^#' | xargs)", - "update-dotenv": "node update-dotenv.mjs", "test:unit": "jest", "test:unit:watch": "jest --watch" }, diff --git a/server/routers/user/utils.ts b/server/routers/user/utils.ts index 7196f389d..cde50270f 100644 --- a/server/routers/user/utils.ts +++ b/server/routers/user/utils.ts @@ -1,4 +1,5 @@ import { metrics } from "@opentelemetry/api" + import { Lang } from "@/constants/languages" import { env } from "@/env/server" import * as api from "@/lib/api" @@ -9,7 +10,9 @@ import type { FriendTransaction, Stay } from "./output" const meter = metrics.getMeter("trpc.user") const getProfileCounter = meter.createCounter("trpc.user.profile") -const getProfileSuccessCounter = meter.createCounter("trpc.user.profile-success") +const getProfileSuccessCounter = meter.createCounter( + "trpc.user.profile-success" +) const getProfileFailCounter = meter.createCounter("trpc.user.profile-fail") async function updateStaysBookingUrl( @@ -41,7 +44,7 @@ async function updateStaysBookingUrl( // Temporary Url, domain and lang support for current web const bookingUrl = new URL( "/hotelreservation/my-booking", - env.PUBLIC_URL ?? "" + env.PUBLIC_URL || "https://www.scandichotels.com" // fallback to production for ephemeral envs (like deploy previews) ) switch (lang) { case Lang.sv: @@ -83,10 +86,7 @@ async function updateStaysBookingUrl( } getProfileSuccessCounter.add(1) - console.info( - "api.user.profile updatebookingurl success", - JSON.stringify({}) - ) + console.info("api.user.profile updatebookingurl success", JSON.stringify({})) return data.map((d) => { const originalString = @@ -98,10 +98,7 @@ async function updateStaysBookingUrl( bookingUrl.searchParams.set("RefId", encryptedBookingValue) } else { bookingUrl.searchParams.set("lastName", apiJson.data.attributes.lastName) - bookingUrl.searchParams.set( - "bookingId", - d.attributes.confirmationNumber - ) + bookingUrl.searchParams.set("bookingId", d.attributes.confirmationNumber) } return { ...d, @@ -113,4 +110,4 @@ async function updateStaysBookingUrl( }) } -export { updateStaysBookingUrl } \ No newline at end of file +export { updateStaysBookingUrl } diff --git a/server/utils.ts b/server/utils.ts index 0179ddf93..0a727c118 100644 --- a/server/utils.ts +++ b/server/utils.ts @@ -1,6 +1,9 @@ import { z } from "zod" import { Lang } from "@/constants/languages" +import { env } from "@/env/server" + +import type { NextRequest } from "next/server" export const langInput = z.object({ lang: z.nativeEnum(Lang), @@ -33,3 +36,27 @@ export function toLang(lang: string): Lang | undefined { const lowerCaseLang = lang.toLowerCase() return Object.values(Lang).find((l) => l === lowerCaseLang) } + +export function getPublicURL(request: NextRequest) { + if (env.PUBLIC_URL) { + return env.PUBLIC_URL + } + + const host = request.nextUrl.host + const proto = request.nextUrl.protocol + return `${proto}//${host}` +} + +export function getPublicNextURL(request: NextRequest) { + if (env.PUBLIC_URL) { + const publicNextURL = request.nextUrl.clone() + // Akamai in front of Netlify for dedicated environments + // require us to rewrite the incoming host and hostname + // to match the public URL used to visit Akamai. + const url = new URL(env.PUBLIC_URL) + publicNextURL.host = url.host + publicNextURL.hostname = url.hostname + return publicNextURL + } + return request.nextUrl +} diff --git a/update-dotenv.mjs b/update-dotenv.mjs deleted file mode 100644 index 6f4521b78..000000000 --- a/update-dotenv.mjs +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Build time environment variables are not available in Netlify functions at - * runtime. The official workaround is to create an artifact and include that - * in the bundled functions so that the function can load/read it at runtime. - * In other words, during the build fill the .env file with the environment - * variables needed and then instruct Netlify to include the .env file together - * with the bundled function. - * - * This works but has two things to consider: - * - * 1. Any environment variable created in the Netlify UI will be considered - * defined. Even if the variable is set to "empty" in the Netlify UI it is - * still an empty string and therefore defined. - * - * 2. Next.js uses @next/env to automatically read the .env - * file into the process environment. @next/env does NOT override any - * defined variables, empty strings are also considered defined. So for - * @next/env to automatically pick up the .env file in the bundled functions - * we need to make sure that none of the variables in the .env file are - * defined in the Netlify UI. @next/env does not have any "override=true" - * option, like dotenv package. So rather than introduce dotenv and manually - * use it in *every* function, we can delegate to @next/env if we keep the - * environment variables in Netlify UI in check. - * - * We only run this on Netlify build. - * - * We define PUBLIC_URL and use that because we are behind Akamai reverse proxy. - * For the stable environments (test, stage, production) these are defined. For - * any other environment (branch deploys and deploy previews) we use the - * predefined Netlify environment variable DEPLOY_PRIME_URL. - * - * Both AUTH_URL and NEXTAUTH_URL is set to point to the PUBLIC_URL. - * We set both as a precaution as next-auth v5 is transitioning to AUTH_* but we - * have seen several occurences in the auth.js codebase that are not using both. - */ - -import fs from "node:fs" - -if (process.env.NETLIFY) { - const PUBLIC_URLS = { - production: "https://www.scandichotels.com", - stage: "https://stage.scandichotels.com", - test: "https://test2.scandichotels.com", - } - - let PUBLIC_URL - if (PUBLIC_URLS[process.env.CONTEXT]) { - PUBLIC_URL = PUBLIC_URLS[process.env.CONTEXT] - } else if (PUBLIC_URLS[process.env.BRANCH]) { - PUBLIC_URL = PUBLIC_URLS[process.env.BRANCH] - } else { - PUBLIC_URL = process.env.DEPLOY_PRIME_URL - } - - const AUTH_URL = `${PUBLIC_URL}/api/web/auth` - const NEXTAUTH_URL = AUTH_URL - - const replaceMap = { - AUTH_URL, - NEXTAUTH_URL, - PUBLIC_URL, - } - - let contents = fs.readFileSync("./.env", { encoding: "utf-8" }) - - for (const [key, value] of Object.entries(replaceMap)) { - contents = contents.replace(new RegExp(`${key}=.*`), `${key}="${value}"`) - } - - fs.writeFileSync("./.env", contents, { encoding: "utf-8" }) -}