From 87f23f582c423982a3c531cbcce63a6b6164f6fa Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Sat, 15 Nov 2025 06:08:02 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8apply=20branding=20to=20org=20auth=20p?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loginPage/loadLoginPageBranding.ts | 89 +++---------------- server/routers/loginPage/types.ts | 1 + src/app/auth/(private)/org/page.tsx | 54 ++++++++--- src/components/ResourceAuthPortal.tsx | 21 +---- src/lib/replacePlaceholder.ts | 17 ++++ 5 files changed, 76 insertions(+), 106 deletions(-) create mode 100644 src/lib/replacePlaceholder.ts diff --git a/server/private/routers/loginPage/loadLoginPageBranding.ts b/server/private/routers/loginPage/loadLoginPageBranding.ts index 94632639..823f75a6 100644 --- a/server/private/routers/loginPage/loadLoginPageBranding.ts +++ b/server/private/routers/loginPage/loadLoginPageBranding.ts @@ -13,16 +13,8 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { - db, - idpOrg, - loginPage, - loginPageBranding, - loginPageBrandingOrg, - loginPageOrg, - resources -} from "@server/db"; -import { eq, and, type InferSelectModel } from "drizzle-orm"; +import { db, loginPageBranding, loginPageBrandingOrg, orgs } from "@server/db"; +import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -31,37 +23,15 @@ import { fromError } from "zod-validation-error"; import type { LoadLoginPageBrandingResponse } from "@server/routers/loginPage/types"; const querySchema = z.object({ - resourceId: z.coerce.number().int().positive().optional(), - idpId: z.coerce.number().int().positive().optional(), - orgId: z.string().min(1).optional(), - fullDomain: z.string().min(1).optional() + orgId: z.string().min(1) }); -async function query(orgId?: string, fullDomain?: string) { - let orgLink: InferSelectModel | null = null; - if (orgId !== undefined) { - [orgLink] = await db - .select() - .from(loginPageBrandingOrg) - .where(eq(loginPageBrandingOrg.orgId, orgId)); - } else if (fullDomain) { - const [res] = await db - .select() - .from(loginPage) - .where(eq(loginPage.fullDomain, fullDomain)) - .innerJoin( - loginPageOrg, - eq(loginPage.loginPageId, loginPageOrg.loginPageId) - ) - .innerJoin( - loginPageBrandingOrg, - eq(loginPageBrandingOrg.orgId, loginPageOrg.orgId) - ) - .limit(1); - - orgLink = res.loginPageBrandingOrg; - } - +async function query(orgId: string) { + const [orgLink] = await db + .select() + .from(loginPageBrandingOrg) + .where(eq(loginPageBrandingOrg.orgId, orgId)) + .innerJoin(orgs, eq(loginPageBrandingOrg.orgId, orgs.orgId)); if (!orgLink) { return null; } @@ -73,14 +43,15 @@ async function query(orgId?: string, fullDomain?: string) { and( eq( loginPageBranding.loginPageBrandingId, - orgLink.loginPageBrandingId + orgLink.loginPageBrandingOrg.loginPageBrandingId ) ) ) .limit(1); return { ...res, - orgId: orgLink.orgId + orgId: orgLink.orgs.orgId, + orgName: orgLink.orgs.name }; } @@ -100,41 +71,9 @@ export async function loadLoginPageBranding( ); } - const { resourceId, idpId, fullDomain } = parsedQuery.data; + const { orgId } = parsedQuery.data; - let orgId: string | undefined = undefined; - if (resourceId) { - const [resource] = await db - .select() - .from(resources) - .where(eq(resources.resourceId, resourceId)) - .limit(1); - - if (!resource) { - return next( - createHttpError(HttpCode.NOT_FOUND, "Resource not found") - ); - } - - orgId = resource.orgId; - } else if (idpId) { - const [idpOrgLink] = await db - .select() - .from(idpOrg) - .where(eq(idpOrg.idpId, idpId)); - - if (!idpOrgLink) { - return next( - createHttpError(HttpCode.NOT_FOUND, "IdP not found") - ); - } - - orgId = idpOrgLink.orgId; - } else if (parsedQuery.data.orgId) { - orgId = parsedQuery.data.orgId; - } - - const branding = await query(orgId, fullDomain); + const branding = await query(orgId); if (!branding) { return next( diff --git a/server/routers/loginPage/types.ts b/server/routers/loginPage/types.ts index 6ef9ca81..8a253d07 100644 --- a/server/routers/loginPage/types.ts +++ b/server/routers/loginPage/types.ts @@ -12,6 +12,7 @@ export type LoadLoginPageResponse = LoginPage & { orgId: string }; export type LoadLoginPageBrandingResponse = LoginPageBranding & { orgId: string; + orgName: string; }; export type GetLoginPageBrandingResponse = LoginPageBranding; diff --git a/src/app/auth/(private)/org/page.tsx b/src/app/auth/(private)/org/page.tsx index 2ac24e5a..afd9d0c4 100644 --- a/src/app/auth/(private)/org/page.tsx +++ b/src/app/auth/(private)/org/page.tsx @@ -9,7 +9,10 @@ import { LoginFormIDP } from "@app/components/LoginForm"; import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types"; import { build } from "@server/build"; import { headers } from "next/headers"; -import { LoadLoginPageResponse } from "@server/routers/loginPage/types"; +import { + LoadLoginPageBrandingResponse, + LoadLoginPageResponse +} from "@server/routers/loginPage/types"; import IdpLoginButtons from "@app/components/private/IdpLoginButtons"; import { Card, @@ -26,6 +29,7 @@ import ValidateSessionTransferToken from "@app/components/private/ValidateSessio import { GetOrgTierResponse } from "@server/routers/billing/types"; import { TierId } from "@server/lib/billing/tiers"; import { getCachedSubscription } from "@app/lib/api/getCachedSubscription"; +import { replacePlaceholder } from "@app/lib/replacePlaceholder"; export const dynamic = "force-dynamic"; @@ -33,7 +37,6 @@ export default async function OrgAuthPage(props: { params: Promise<{}>; searchParams: Promise<{ token?: string }>; }) { - const params = await props.params; const searchParams = await props.searchParams; const env = pullEnv(); @@ -122,12 +125,10 @@ export default async function OrgAuthPage(props: { let loginIdps: LoginFormIDP[] = []; if (build === "saas") { - const idpsRes = await cache( - async () => - await priv.get>( - `/org/${loginPage!.orgId}/idp` - ) - )(); + const idpsRes = await priv.get>( + `/org/${loginPage.orgId}/idp` + ); + loginIdps = idpsRes.data.data.idps.map((idp) => ({ idpId: idp.idpId, name: idp.name, @@ -135,6 +136,16 @@ export default async function OrgAuthPage(props: { })) as LoginFormIDP[]; } + let branding: LoadLoginPageBrandingResponse | null = null; + try { + const res = await priv.get< + AxiosResponse + >(`/login-page-branding?orgId=${loginPage.orgId}`); + if (res.status === 200) { + branding = res.data.data; + } + } catch (error) {} + return (
@@ -152,11 +163,30 @@ export default async function OrgAuthPage(props: {
- {t("orgAuthSignInTitle")} + {branding?.logoUrl && ( +
+ +
+ )} + + {branding?.orgTitle + ? replacePlaceholder(branding.orgTitle, { + orgName: branding.orgName + }) + : t("orgAuthSignInTitle")} + - {loginIdps.length > 0 - ? t("orgAuthChooseIdpDescription") - : ""} + {branding?.orgSubtitle + ? replacePlaceholder(branding.orgSubtitle, { + orgName: branding.orgName + }) + : loginIdps.length > 0 + ? t("orgAuthChooseIdpDescription") + : ""}
diff --git a/src/components/ResourceAuthPortal.tsx b/src/components/ResourceAuthPortal.tsx index 66be584b..aad61e25 100644 --- a/src/components/ResourceAuthPortal.tsx +++ b/src/components/ResourceAuthPortal.tsx @@ -48,6 +48,7 @@ import { useTranslations } from "next-intl"; import { build } from "@server/build"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types"; +import { replacePlaceholder } from "@app/lib/replacePlaceholder"; const pinSchema = z.object({ pin: z @@ -329,24 +330,6 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { } } - 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 ( build !== "oss" && @@ -399,7 +382,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { return (
diff --git a/src/lib/replacePlaceholder.ts b/src/lib/replacePlaceholder.ts new file mode 100644 index 00000000..598056e3 --- /dev/null +++ b/src/lib/replacePlaceholder.ts @@ -0,0 +1,17 @@ +export 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; +}