From b54eff648e591d08fcbb3ff7532cf63cbb9d7436 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 19 Jun 2026 03:56:26 +0530 Subject: [PATCH] feat: implement runtime authentication handling - Added a new proxy function to manage runtime authentication types and set cookies accordingly. - Introduced runtime authentication configuration to dynamically adjust UI based on the selected auth type. - Updated global styles to hide specific authentication buttons based on the current auth type. - Refactored sign-in button and hero section components to utilize the new runtime authentication logic. - Created a new utility file for runtime authentication configuration and initialization script. --- surfsense_web/app/globals.css | 5 ++ surfsense_web/app/layout.tsx | 15 +++++- .../components/auth/sign-in-button.tsx | 46 ++++++++-------- .../components/homepage/hero-section.tsx | 28 +++++----- surfsense_web/lib/runtime-auth-config.ts | 52 +++++++++++++++++++ surfsense_web/proxy.ts | 24 +++++++++ 6 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 surfsense_web/lib/runtime-auth-config.ts create mode 100644 surfsense_web/proxy.ts diff --git a/surfsense_web/app/globals.css b/surfsense_web/app/globals.css index 3cdb34bff..4a29edfa6 100644 --- a/surfsense_web/app/globals.css +++ b/surfsense_web/app/globals.css @@ -58,6 +58,11 @@ --highlight: oklch(0.852 0.199 91.936); } +html[data-surfsense-auth-type="GOOGLE"] .runtime-auth-local, +html[data-surfsense-auth-type="LOCAL"] .runtime-auth-google { + display: none; +} + .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx index 1e9c9eebe..46182f40e 100644 --- a/surfsense_web/app/layout.tsx +++ b/surfsense_web/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata, Viewport } from "next"; import "./globals.css"; import { RootProvider } from "fumadocs-ui/provider/next"; import { Roboto } from "next/font/google"; +import Script from "next/script"; import { AnnouncementToastProvider } from "@/components/announcements/AnnouncementToastProvider"; import { DesktopUpdateToast } from "@/components/desktop/desktop-update-toast"; import { GlobalLoadingProvider } from "@/components/providers/GlobalLoadingProvider"; @@ -16,8 +17,13 @@ import { import { ThemeProvider } from "@/components/theme/theme-provider"; import { Toaster } from "@/components/ui/sonner"; import { LocaleProvider } from "@/contexts/LocaleContext"; +import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config"; import { PlatformProvider } from "@/contexts/platform-context"; import { ReactQueryClientProvider } from "@/lib/query-client/query-client.provider"; +import { + getRuntimeAuthInitScript, + resolveRuntimeAuthUiMode, +} from "@/lib/runtime-auth-config"; import { cn } from "@/lib/utils"; const roboto = Roboto({ @@ -131,8 +137,15 @@ export default function RootLayout({ // Language can be switched dynamically through LanguageSwitcher component // Locale state is managed by LocaleContext and persisted in localStorage return ( - + + diff --git a/surfsense_web/components/auth/sign-in-button.tsx b/surfsense_web/components/auth/sign-in-button.tsx index 581e37603..d0a563a54 100644 --- a/surfsense_web/components/auth/sign-in-button.tsx +++ b/surfsense_web/components/auth/sign-in-button.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { useState } from "react"; import { Button } from "@/components/ui/button"; -import { BUILD_TIME_AUTH_TYPE, buildBackendUrl } from "@/lib/env-config"; +import { buildBackendUrl } from "@/lib/env-config"; import { trackLoginAttempt } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; @@ -46,7 +46,6 @@ interface SignInButtonProps { } export const SignInButton = ({ variant = "desktop" }: SignInButtonProps) => { - const isGoogleAuth = BUILD_TIME_AUTH_TYPE === "GOOGLE"; const [isRedirecting, setIsRedirecting] = useState(false); const handleGoogleLogin = () => { @@ -56,44 +55,45 @@ export const SignInButton = ({ variant = "desktop" }: SignInButtonProps) => { window.location.href = buildBackendUrl("/auth/google/authorize-redirect"); }; - const getClassName = () => { + const getGoogleClassName = () => { if (variant === "desktop") { - return isGoogleAuth - ? "hidden rounded-full border border-white bg-white px-5 py-2 text-sm font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] md:flex dark:border-white" - : "hidden rounded-full bg-black px-8 py-2 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] md:block dark:bg-white dark:text-black"; + return "hidden rounded-full border border-white bg-white px-5 py-2 text-sm font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] md:flex dark:border-white"; } if (variant === "compact") { - return isGoogleAuth - ? "rounded-full border border-white bg-white px-4 py-1.5 text-sm font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] dark:border-white" - : "rounded-full bg-black px-6 py-1.5 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black"; + return "rounded-full border border-white bg-white px-4 py-1.5 text-sm font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] dark:border-white"; } // mobile - return isGoogleAuth - ? "w-full rounded-lg border border-white bg-white px-8 py-2.5 font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] dark:border-white touch-manipulation" - : "w-full rounded-lg bg-black px-8 py-2 font-medium text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black text-center touch-manipulation"; + return "w-full rounded-lg border border-white bg-white px-8 py-2.5 font-medium text-[#1f1f1f] shadow-sm hover:bg-zinc-100 hover:text-[#1f1f1f] dark:border-white touch-manipulation"; }; - if (isGoogleAuth) { - return ( + const getLocalClassName = () => { + if (variant === "desktop") { + return "hidden rounded-full bg-black px-8 py-2 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] md:block dark:bg-white dark:text-black"; + } + if (variant === "compact") { + return "rounded-full bg-black px-6 py-1.5 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black"; + } + return "w-full rounded-lg bg-black px-8 py-2 font-medium text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black text-center touch-manipulation"; + }; + + return ( + <> - ); - } - - return ( - - Sign In - + + Sign In + + ); }; diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index 0f3bfe1aa..c9430f098 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -37,7 +37,7 @@ import { getAssetLabel, usePrimaryDownload, } from "@/lib/desktop-download-utils"; -import { BUILD_TIME_AUTH_TYPE, buildBackendUrl } from "@/lib/env-config"; +import { buildBackendUrl } from "@/lib/env-config"; import { trackLoginAttempt } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; @@ -314,7 +314,6 @@ export function HeroSection() { } function GetStartedButton() { - const isGoogleAuth = BUILD_TIME_AUTH_TYPE === "GOOGLE"; const [isRedirecting, setIsRedirecting] = useState(false); const handleGoogleLogin = () => { @@ -324,29 +323,26 @@ function GetStartedButton() { window.location.href = buildBackendUrl("/auth/google/authorize-redirect"); }; - if (isGoogleAuth) { - return ( + return ( + <> - ); - } - - return ( - + + ); } diff --git a/surfsense_web/lib/runtime-auth-config.ts b/surfsense_web/lib/runtime-auth-config.ts new file mode 100644 index 000000000..9e8d1921d --- /dev/null +++ b/surfsense_web/lib/runtime-auth-config.ts @@ -0,0 +1,52 @@ +export const RUNTIME_AUTH_TYPE_COOKIE_NAME = "surfsense_auth_type"; + +export type RuntimeAuthUiMode = "GOOGLE" | "LOCAL"; + +export function resolveRuntimeAuthUiMode( + value: string | null | undefined, + fallback: string | null | undefined = "GOOGLE" +): RuntimeAuthUiMode { + const candidate = value?.trim().toUpperCase(); + if (candidate === "GOOGLE") return "GOOGLE"; + if (candidate === "LOCAL") return "LOCAL"; + + const fallbackCandidate = fallback?.trim().toUpperCase(); + return fallbackCandidate === "GOOGLE" ? "GOOGLE" : "LOCAL"; +} + +export function getRuntimeAuthInitScript(fallbackAuthType: string): string { + const fallback = resolveRuntimeAuthUiMode(fallbackAuthType); + const cookieName = JSON.stringify(RUNTIME_AUTH_TYPE_COOKIE_NAME); + const fallbackValue = JSON.stringify(fallback); + + return ` +(function() { + try { + var cookieName = ${cookieName}; + var fallback = ${fallbackValue}; + var prefix = cookieName + "="; + var rawValue = fallback; + var cookies = document.cookie ? document.cookie.split(";") : []; + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + if (cookie.indexOf(prefix) === 0) { + rawValue = decodeURIComponent(cookie.slice(prefix.length)); + break; + } + } + var normalized = String(rawValue || fallback).toUpperCase() === "GOOGLE" ? "GOOGLE" : "LOCAL"; + window.__SURFSENSE_AUTH_TYPE__ = normalized; + document.documentElement.setAttribute("data-surfsense-auth-type", normalized); + } catch (_) { + window.__SURFSENSE_AUTH_TYPE__ = ${fallbackValue}; + document.documentElement.setAttribute("data-surfsense-auth-type", ${fallbackValue}); + } +})(); +`; +} + +declare global { + interface Window { + __SURFSENSE_AUTH_TYPE__?: RuntimeAuthUiMode; + } +} diff --git a/surfsense_web/proxy.ts b/surfsense_web/proxy.ts new file mode 100644 index 000000000..b53ce68a7 --- /dev/null +++ b/surfsense_web/proxy.ts @@ -0,0 +1,24 @@ +import { NextResponse, type NextRequest } from "next/server"; +import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config"; +import { + RUNTIME_AUTH_TYPE_COOKIE_NAME, + resolveRuntimeAuthUiMode, +} from "@/lib/runtime-auth-config"; + +export function proxy(request: NextRequest) { + const response = NextResponse.next(); + const authType = resolveRuntimeAuthUiMode(process.env.AUTH_TYPE, BUILD_TIME_AUTH_TYPE); + + response.cookies.set(RUNTIME_AUTH_TYPE_COOKIE_NAME, authType, { + path: "/", + maxAge: 60 * 60 * 24 * 365, + sameSite: "lax", + secure: request.nextUrl.protocol === "https:", + }); + + return response; +} + +export const config = { + matcher: ["/((?!api|auth|_next/static|_next/image|favicon.ico|.*\\..*).*)"], +};