From 4beed9d46423c15521e2123f366214fbd3a741aa Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Thu, 13 Nov 2025 03:24:47 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20apply=20auth=20branding=20to=20reso?= =?UTF-8?q?urce=20auth=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginPage/upsertLoginPageBranding.ts | 4 +- src/app/auth/resource/[resourceGuid]/page.tsx | 23 +++++- src/components/AuthPageBrandingForm.tsx | 10 +-- src/components/BrandingLogo.tsx | 12 +-- src/components/ResourceAuthPortal.tsx | 75 ++++++++++++++++--- 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/server/private/routers/loginPage/upsertLoginPageBranding.ts b/server/private/routers/loginPage/upsertLoginPageBranding.ts index e553d14d..fc212538 100644 --- a/server/private/routers/loginPage/upsertLoginPageBranding.ts +++ b/server/private/routers/loginPage/upsertLoginPageBranding.ts @@ -38,8 +38,8 @@ const paramsSchema = z const bodySchema = z .object({ logoUrl: z.string().url(), - logoWidth: z.number().min(1), - logoHeight: z.number().min(1), + logoWidth: z.coerce.number().min(1), + logoHeight: z.coerce.number().min(1), title: z.string(), subtitle: z.string().optional(), resourceTitle: z.string(), diff --git a/src/app/auth/resource/[resourceGuid]/page.tsx b/src/app/auth/resource/[resourceGuid]/page.tsx index d51f2210..eeb01eaa 100644 --- a/src/app/auth/resource/[resourceGuid]/page.tsx +++ b/src/app/auth/resource/[resourceGuid]/page.tsx @@ -19,7 +19,10 @@ import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types"; import AutoLoginHandler from "@app/components/AutoLoginHandler"; import { build } from "@server/build"; import { headers } from "next/headers"; -import { GetLoginPageResponse } from "@server/routers/loginPage/types"; +import { + GetLoginPageBrandingResponse, + GetLoginPageResponse +} from "@server/routers/loginPage/types"; import { GetOrgTierResponse } from "@server/routers/billing/types"; import { TierId } from "@server/lib/billing/tiers"; import { CheckOrgUserAccessResponse } from "@server/routers/org"; @@ -261,6 +264,23 @@ export default async function ResourceAuthPage(props: { } } + let loginPageBranding: Omit< + GetLoginPageBrandingResponse, + "loginPageBrandingId" + > | null = null; + try { + const res = await internal.get< + AxiosResponse + >( + `/org/${authInfo.orgId}/login-page-branding`, + await authCookieHeader() + ); + if (res.status === 200) { + const { loginPageBrandingId, ...rest } = res.data.data; + loginPageBranding = rest; + } + } catch (error) {} + return ( <> {userIsUnauthorized && isSSOOnly ? ( @@ -283,6 +303,7 @@ export default async function ResourceAuthPage(props: { redirect={redirectUrl} idps={loginIdps} orgId={build === "saas" ? authInfo.orgId : undefined} + branding={loginPageBranding} /> )} diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index 39cac35a..6d013816 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -69,8 +69,8 @@ const AuthPageFormSchema = z.object({ message: "Invalid logo URL, must be a valid image URL" } ), - logoWidth: z.number().min(1), - logoHeight: z.number().min(1), + logoWidth: z.coerce.number().min(1), + logoHeight: z.coerce.number().min(1), title: z.string(), subtitle: z.string().optional(), resourceTitle: z.string(), @@ -102,8 +102,8 @@ export default function AuthPageBrandingForm({ resolver: zodResolver(AuthPageFormSchema), defaultValues: { logoUrl: branding?.logoUrl ?? "", - logoWidth: branding?.logoWidth ?? 500, - logoHeight: branding?.logoHeight ?? 500, + logoWidth: branding?.logoWidth ?? 100, + logoHeight: branding?.logoHeight ?? 100, title: branding?.title ?? `Log in to {{orgName}}`, subtitle: branding?.subtitle ?? `Log in to {{orgName}}`, resourceTitle: @@ -240,7 +240,7 @@ export default function AuthPageBrandingForm({ ( diff --git a/src/components/BrandingLogo.tsx b/src/components/BrandingLogo.tsx index 540b8e0e..86a49496 100644 --- a/src/components/BrandingLogo.tsx +++ b/src/components/BrandingLogo.tsx @@ -7,6 +7,7 @@ import Image from "next/image"; import { useEffect, useState } from "react"; type BrandingLogoProps = { + logoPath?: string; width: number; height: number; }; @@ -38,16 +39,17 @@ export default function BrandingLogo(props: BrandingLogoProps) { if (isUnlocked() && env.branding.logo?.darkPath) { return env.branding.logo.darkPath; } - return "/logo/word_mark_white.png"; + return "/logo/word_mark_white.png"; } - const path = getPath(); - setPath(path); - }, [theme, env]); + setPath(props.logoPath ?? getPath()); + }, [theme, env, props.logoPath]); + + const Component = props.logoPath ? "img" : Image; return ( path && ( - Logo getNumMethods()); const [passwordError, setPasswordError] = useState(null); const [pincodeError, setPincodeError] = useState(null); @@ -309,13 +318,37 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { } } - function getTitle() { + function replacePlaceholder( + stringWithPlaceholder: string, + data: Record + ) { + let newString = stringWithPlaceholder; + + const keys = Object.keys(data); + + for (const key of keys) { + newString = newString.replace( + new RegExp(`{{${key}}}`, "gm"), + data[key] + ); + } + + return newString; + } + + function getTitle(resourceName: string) { if ( isUnlocked() && build !== "oss" && - env.branding.resourceAuthPage?.titleText + (!!env.branding.resourceAuthPage?.titleText || + !!props.branding?.resourceTitle) ) { - return env.branding.resourceAuthPage.titleText; + if (props.branding?.resourceTitle) { + return replacePlaceholder(props.branding?.resourceTitle, { + resourceName + }); + } + return env.branding.resourceAuthPage?.titleText; } return t("authenticationRequired"); } @@ -324,10 +357,16 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { if ( isUnlocked() && build !== "oss" && - env.branding.resourceAuthPage?.subtitleText + (env.branding.resourceAuthPage?.subtitleText || + props.branding?.resourceSubtitle) ) { - return env.branding.resourceAuthPage.subtitleText - .split("{{resourceName}}") + if (props.branding?.resourceSubtitle) { + return replacePlaceholder(props.branding?.resourceSubtitle, { + resourceName + }); + } + return env.branding.resourceAuthPage?.subtitleText + ?.split("{{resourceName}}") .join(resourceName); } return numMethods > 1 @@ -335,8 +374,16 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { : t("authenticationRequest", { name: resourceName }); } - const logoWidth = isUnlocked() ? env.branding.logo?.authPage?.width || 100 : 100; - const logoHeight = isUnlocked() ? env.branding.logo?.authPage?.height || 100 : 100; + const logoWidth = isUnlocked() + ? (props.branding?.logoWidth ?? + env.branding.logo?.authPage?.width ?? + 100) + : 100; + const logoHeight = isUnlocked() + ? (props.branding?.logoHeight ?? + env.branding.logo?.authPage?.height ?? + 100) + : 100; return (
@@ -377,15 +424,19 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { {isUnlocked() && build !== "oss" && - env.branding?.resourceAuthPage?.showLogo && ( + (env.branding?.resourceAuthPage?.showLogo || + props.branding) && (
)} - {getTitle()} + + {getTitle(props.resource.name)} + {getSubtitle(props.resource.name)}