diff --git a/server/db/pg/schema/privateSchema.ts b/server/db/pg/schema/privateSchema.ts index 3900f46a..0512af22 100644 --- a/server/db/pg/schema/privateSchema.ts +++ b/server/db/pg/schema/privateSchema.ts @@ -214,7 +214,7 @@ export const loginPageOrg = pgTable("loginPageOrg", { export const loginPageBranding = pgTable("loginPageBranding", { loginPageBrandingId: serial("loginPageBrandingId").primaryKey(), - logoUrl: text("logoUrl").notNull(), + logoUrl: text("logoUrl"), logoWidth: integer("logoWidth").notNull(), logoHeight: integer("logoHeight").notNull(), primaryColor: text("primaryColor"), diff --git a/server/db/sqlite/schema/privateSchema.ts b/server/db/sqlite/schema/privateSchema.ts index 32aa543e..2661ccdd 100644 --- a/server/db/sqlite/schema/privateSchema.ts +++ b/server/db/sqlite/schema/privateSchema.ts @@ -206,7 +206,7 @@ export const loginPageBranding = sqliteTable("loginPageBranding", { loginPageBrandingId: integer("loginPageBrandingId").primaryKey({ autoIncrement: true }), - logoUrl: text("logoUrl").notNull(), + logoUrl: text("logoUrl"), logoWidth: integer("logoWidth").notNull(), logoHeight: integer("logoHeight").notNull(), primaryColor: text("primaryColor"), diff --git a/server/private/routers/loginPage/upsertLoginPageBranding.ts b/server/private/routers/loginPage/upsertLoginPageBranding.ts index 4e2b666b..146f0721 100644 --- a/server/private/routers/loginPage/upsertLoginPageBranding.ts +++ b/server/private/routers/loginPage/upsertLoginPageBranding.ts @@ -35,7 +35,29 @@ const paramsSchema = z.strictObject({ }); const bodySchema = z.strictObject({ - logoUrl: z.url(), + logoUrl: z + .union([ + z.string().length(0), + z.url().refine( + async (url) => { + try { + const response = await fetch(url); + return ( + response.status === 200 && + ( + response.headers.get("content-type") ?? "" + ).startsWith("image/") + ); + } catch (error) { + return false; + } + }, + { + error: "Invalid logo URL, must be a valid image URL" + } + ) + ]) + .optional(), logoWidth: z.coerce.number().min(1), logoHeight: z.coerce.number().min(1), resourceTitle: z.string(), @@ -95,6 +117,10 @@ export async function upsertLoginPageBranding( typeof loginPageBranding >; + if ((updateData.logoUrl ?? "").trim().length === 0) { + updateData.logoUrl = undefined; + } + if ( build !== "saas" && !config.getRawPrivateConfig().flags.use_org_only_idp diff --git a/src/app/[orgId]/settings/general/auth-page/page.tsx b/src/app/[orgId]/settings/general/auth-page/page.tsx index 0944c9f7..73c54827 100644 --- a/src/app/[orgId]/settings/general/auth-page/page.tsx +++ b/src/app/[orgId]/settings/general/auth-page/page.tsx @@ -11,6 +11,7 @@ import { GetLoginPageResponse } from "@server/routers/loginPage/types"; import { AxiosResponse } from "axios"; +import { redirect } from "next/navigation"; export interface AuthPageProps { params: Promise<{ orgId: string }>; @@ -18,6 +19,12 @@ export interface AuthPageProps { export default async function AuthPage(props: AuthPageProps) { const orgId = (await props.params).orgId; + + // custom auth branding is only available in enterprise and saas + if (build === "oss") { + redirect(`/${orgId}/settings/general/`); + } + let subscriptionStatus: GetOrgTierResponse | null = null; try { const subRes = await getCachedSubscription(orgId); diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index 119a39cb..e89c9855 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -42,24 +42,27 @@ export type AuthPageCustomizationProps = { }; const AuthPageFormSchema = z.object({ - logoUrl: z.url().refine( - async (url) => { - try { - const response = await fetch(url); - return ( - response.status === 200 && - (response.headers.get("content-type") ?? "").startsWith( - "image/" - ) - ); - } catch (error) { - return false; + logoUrl: z.union([ + z.string().length(0), + z.url().refine( + async (url) => { + try { + const response = await fetch(url); + return ( + response.status === 200 && + (response.headers.get("content-type") ?? "").startsWith( + "image/" + ) + ); + } catch (error) { + return false; + } + }, + { + error: "Invalid logo URL, must be a valid image URL" } - }, - { - error: "Invalid logo URL, must be a valid image URL" - } - ), + ) + ]), logoWidth: z.coerce.number().min(1), logoHeight: z.coerce.number().min(1), orgTitle: z.string().optional(), @@ -90,7 +93,6 @@ export default function AuthPageBrandingForm({ deleteBranding, null ); - const [setIsDeleteModalOpen] = useState(false); const t = useTranslations(); diff --git a/src/components/BrandingLogo.tsx b/src/components/BrandingLogo.tsx index 139d76b4..f6152f15 100644 --- a/src/components/BrandingLogo.tsx +++ b/src/components/BrandingLogo.tsx @@ -7,7 +7,7 @@ import Image from "next/image"; import { useEffect, useState } from "react"; type BrandingLogoProps = { - logoPath?: string; + logoPath?: string | null; width: number; height: number; }; diff --git a/src/components/ResourceAuthPortal.tsx b/src/components/ResourceAuthPortal.tsx index 133d9a6c..0020330c 100644 --- a/src/components/ResourceAuthPortal.tsx +++ b/src/components/ResourceAuthPortal.tsx @@ -88,7 +88,7 @@ type ResourceAuthPortalProps = { idps?: LoginFormIDP[]; orgId?: string; branding?: { - logoUrl: string; + logoUrl?: string | null; logoWidth: number; logoHeight: number; primaryColor: string | null;