Chore: Rebranding Gatehouse to Secuird (UI)

This commit is contained in:
2026-03-06 00:22:57 +05:45
parent 7be6415db1
commit 979b5a918e
27 changed files with 76 additions and 76 deletions

Before

Width:  |  Height:  |  Size: 661 B

After

Width:  |  Height:  |  Size: 661 B

@@ -1,21 +1,21 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface GatehouseLogoProps { interface SecuirdLogoProps {
size?: "sm" | "md" | "lg"; size?: "sm" | "md" | "lg";
variant?: "default" | "light"; variant?: "default" | "light";
className?: string; className?: string;
} }
/** /**
* Gatehouse Logo - Abstract gate/doorway mark * Secuird Logo - Abstract gate/doorway mark
* Represents controlled entry and policy enforcement * Represents controlled entry and policy enforcement
* Two vertical pillars forming a gateway with negative space * Two vertical pillars forming a gateway with negative space
*/ */
export function GatehouseLogo({ export function SecuirdLogo({
size = "md", size = "md",
variant = "default", variant = "default",
className className
}: GatehouseLogoProps) { }: SecuirdLogoProps) {
const sizeClasses = { const sizeClasses = {
sm: "w-8 h-8", sm: "w-8 h-8",
md: "w-9 h-9", md: "w-9 h-9",
+6 -6
View File
@@ -65,9 +65,9 @@ const isDev = import.meta.env.DEV;
const originalFetch = window.fetch; const originalFetch = window.fetch;
// Avoid patching multiple times during HMR // Avoid patching multiple times during HMR
const globalAny = window as unknown as { __gatehouseFetchPatched?: boolean }; const globalAny = window as unknown as { __secuirdFetchPatched?: boolean };
if (isDev && !globalAny.__gatehouseFetchPatched) { if (isDev && !globalAny.__secuirdFetchPatched) {
globalAny.__gatehouseFetchPatched = true; globalAny.__secuirdFetchPatched = true;
try { try {
window.fetch = async function (input, init) { window.fetch = async function (input, init) {
@@ -165,9 +165,9 @@ if (isDev && !globalAny.__gatehouseFetchPatched) {
}; };
} catch (patchError) { } catch (patchError) {
// Log any errors during fetch patching with full stack trace // Log any errors during fetch patching with full stack trace
console.error("[Gatehouse DevTools] Failed to patch fetch:", patchError); console.error("[Secuird DevTools] Failed to patch fetch:", patchError);
if (patchError instanceof Error) { if (patchError instanceof Error) {
console.error("[Gatehouse DevTools] Stack trace:", patchError.stack); console.error("[Secuird DevTools] Stack trace:", patchError.stack);
} }
} }
} }
@@ -220,7 +220,7 @@ export default function ApiDevTools() {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-4 py-2 border-b border-slate-700 bg-slate-800"> <div className="flex items-center justify-between px-4 py-2 border-b border-slate-700 bg-slate-800">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="font-semibold text-sm">Gatehouse API DevTools</span> <span className="font-semibold text-sm">Secuird API DevTools</span>
<Badge variant="outline" className="text-xs border-slate-600"> <Badge variant="outline" className="text-xs border-slate-600">
{logs.length} requests {logs.length} requests
</Badge> </Badge>
@@ -97,7 +97,7 @@ export default function MfaEnforcementLayout() {
<header className="h-14 border-b border-border bg-card flex items-center justify-between px-4 flex-shrink-0"> <header className="h-14 border-b border-border bg-card flex items-center justify-between px-4 flex-shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Shield className="w-5 h-5 text-primary" /> <Shield className="w-5 h-5 text-primary" />
<span className="font-semibold text-foreground">Gatehouse</span> <span className="font-semibold text-foreground">Secuird</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
+4 -4
View File
@@ -1,5 +1,5 @@
import { Outlet, Link } from "react-router-dom"; import { Outlet, Link } from "react-router-dom";
import { GatehouseLogo } from "@/components/branding/GatehouseLogo"; import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
export default function PublicLayout() { export default function PublicLayout() {
return ( return (
@@ -11,8 +11,8 @@ export default function PublicLayout() {
<header className="relative z-10 w-full py-6 px-4"> <header className="relative z-10 w-full py-6 px-4">
<div className="max-w-md mx-auto"> <div className="max-w-md mx-auto">
<Link to="/" className="flex items-center gap-2.5 justify-center"> <Link to="/" className="flex items-center gap-2.5 justify-center">
<GatehouseLogo size="md" /> <SecuirdLogo size="md" />
<span className="text-xl font-semibold text-foreground tracking-tight">Gatehouse</span> <span className="text-xl font-semibold text-foreground tracking-tight">Secuird</span>
</Link> </Link>
</div> </div>
</header> </header>
@@ -28,7 +28,7 @@ export default function PublicLayout() {
<footer className="relative z-10 py-6 px-4"> <footer className="relative z-10 py-6 px-4">
<div className="max-w-md mx-auto text-center"> <div className="max-w-md mx-auto text-center">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
© {new Date().getFullYear()} Gatehouse. Identity & Access. © {new Date().getFullYear()} Secuird. Identity & Access.
</p> </p>
</div> </div>
</footer> </footer>
+3 -3
View File
@@ -18,7 +18,7 @@ import {
Monitor, Monitor,
ShieldAlert, ShieldAlert,
} from "lucide-react"; } from "lucide-react";
import { GatehouseLogo } from "@/components/branding/GatehouseLogo"; import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
import { NavLink } from "@/components/NavLink"; import { NavLink } from "@/components/NavLink";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { import {
@@ -90,10 +90,10 @@ export function AppSidebar() {
{/* Logo */} {/* Logo */}
<SidebarHeader className="p-4 border-b border-sidebar-border"> <SidebarHeader className="p-4 border-b border-sidebar-border">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<GatehouseLogo size="sm" variant="light" /> <SecuirdLogo size="sm" variant="light" />
{!collapsed && ( {!collapsed && (
<span className="text-lg font-semibold text-sidebar-foreground tracking-tight"> <span className="text-lg font-semibold text-sidebar-foreground tracking-tight">
Gatehouse Secuird
</span> </span>
)} )}
</div> </div>
@@ -275,7 +275,7 @@ export function TotpEnrollmentWizard({
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Open your authenticator app and enter the 6-digit code shown for Gatehouse. Open your authenticator app and enter the 6-digit code shown for Secuird.
</p> </p>
<div className="flex justify-end gap-2 pt-2"> <div className="flex justify-end gap-2 pt-2">
+2 -2
View File
@@ -1,4 +1,4 @@
// Gatehouse Configuration // Secuird Configuration
// Environment-specific settings for the application // Environment-specific settings for the application
export const config = { export const config = {
@@ -9,7 +9,7 @@ export const config = {
// App metadata // App metadata
app: { app: {
name: "Gatehouse", name: "Secuird",
description: "Identity & Access Platform", description: "Identity & Access Platform",
}, },
+1 -1
View File
@@ -32,7 +32,7 @@ interface AuthContextType {
const AuthContext = createContext<AuthContextType | null>(null); const AuthContext = createContext<AuthContextType | null>(null);
// LocalStorage key for MFA compliance persistence // LocalStorage key for MFA compliance persistence
const MFA_COMPLIANCE_KEY = 'gatehouse_mfa_compliance'; const MFA_COMPLIANCE_KEY = 'secuird_mfa_compliance';
// Helper to persist MFA compliance to localStorage // Helper to persist MFA compliance to localStorage
function persistMfaCompliance(compliance: MfaComplianceSummary | null): void { function persistMfaCompliance(compliance: MfaComplianceSummary | null): void {
+1 -1
View File
@@ -4,7 +4,7 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* Gatehouse Design System - Enterprise Identity & Access Platform /* Secuird Design System - Enterprise Identity & Access Platform
Authoritative, infrastructure-grade aesthetic with slate/charcoal/muted blue palette Authoritative, infrastructure-grade aesthetic with slate/charcoal/muted blue palette
Colors are HSL for theming flexibility Colors are HSL for theming flexibility
*/ */
+3 -3
View File
@@ -1,4 +1,4 @@
// API Client for Gatehouse Backend // API Client for Secuird Backend
// Uses Bearer token authentication // Uses Bearer token authentication
import { config } from '@/config'; import { config } from '@/config';
@@ -259,8 +259,8 @@ class ApiError extends Error {
} }
// Token storage keys // Token storage keys
const TOKEN_KEY = 'gatehouse_token'; const TOKEN_KEY = 'secuird_token';
const TOKEN_EXPIRY_KEY = 'gatehouse_token_expiry'; const TOKEN_EXPIRY_KEY = 'secuird_token_expiry';
// Token management // Token management
export const tokenManager = { export const tokenManager = {
+2 -2
View File
@@ -3,7 +3,7 @@ import { useSearchParams, useNavigate } from "react-router-dom";
import { CheckCircle, XCircle, Loader2, Mail } from "lucide-react"; import { CheckCircle, XCircle, Loader2, Mail } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { GatehouseLogo } from "@/components/branding/GatehouseLogo"; import { SecuirdLogo } from "@/components/branding/SecuirdLogo";
import { api, ApiError } from "@/lib/api"; import { api, ApiError } from "@/lib/api";
type Status = "loading" | "success" | "error" | "missing"; type Status = "loading" | "success" | "error" | "missing";
@@ -42,7 +42,7 @@ export default function ActivatePage() {
<div className="w-full max-w-md space-y-6"> <div className="w-full max-w-md space-y-6">
{/* Logo */} {/* Logo */}
<div className="flex justify-center"> <div className="flex justify-center">
<GatehouseLogo size="md" /> <SecuirdLogo size="md" />
</div> </div>
<Card> <Card>
+2 -2
View File
@@ -61,7 +61,7 @@ export default function InviteAcceptPage() {
const result = await api.invites.accept(token, name || undefined, inviteData?.user_exists ? undefined : password); const result = await api.invites.accept(token, name || undefined, inviteData?.user_exists ? undefined : password);
if (result.token) { if (result.token) {
// Store the token manually since we're not using the normal login flow // Store the token manually since we're not using the normal login flow
localStorage.setItem("gatehouse_token", result.token); localStorage.setItem("secuird_token", result.token);
} }
navigate("/profile"); navigate("/profile");
} catch (err: unknown) { } catch (err: unknown) {
@@ -127,7 +127,7 @@ export default function InviteAcceptPage() {
<CheckCircle className="w-5 h-5 text-accent flex-shrink-0 mt-0.5" /> <CheckCircle className="w-5 h-5 text-accent flex-shrink-0 mt-0.5" />
<div className="text-sm"> <div className="text-sm">
<p className="font-medium text-foreground">Account found</p> <p className="font-medium text-foreground">Account found</p>
<p className="text-muted-foreground">You already have a Gatehouse account. Click below to join the organization.</p> <p className="text-muted-foreground">You already have a Secuird account. Click below to join the organization.</p>
</div> </div>
</div> </div>
) : ( ) : (
+14 -14
View File
@@ -27,8 +27,8 @@ import { OAuthProvider } from "@/lib/oauth";
type LoginStep = 'credentials' | 'totp' | 'webauthn' | 'passkey-email' | 'mfa-enrollment' | 'mfa'; type LoginStep = 'credentials' | 'totp' | 'webauthn' | 'passkey-email' | 'mfa-enrollment' | 'mfa';
const GATEHOUSE_API = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1'; const SECUIRD_API = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1';
const GATEHOUSE_OIDC = GATEHOUSE_API.replace(/\/api\/v1\/?$/, ''); const SECUIRD_OIDC = SECUIRD_API.replace(/\/api\/v1\/?$/, '');
/** /**
* Complete an OIDC authorization flow after the user has authenticated. * Complete an OIDC authorization flow after the user has authenticated.
@@ -36,7 +36,7 @@ const GATEHOUSE_OIDC = GATEHOUSE_API.replace(/\/api\/v1\/?$/, '');
* the auth code and returns the redirect URL for the calling application. * the auth code and returns the redirect URL for the calling application.
*/ */
async function completeOidcFlow(oidcSessionId: string, token: string): Promise<string> { async function completeOidcFlow(oidcSessionId: string, token: string): Promise<string> {
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/complete`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/complete`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ oidc_session_id: oidcSessionId, token }), body: JSON.stringify({ oidc_session_id: oidcSessionId, token }),
@@ -64,13 +64,13 @@ export default function LoginPage() {
const [mfaToken, setMfaToken] = useState<string | null>(null); const [mfaToken, setMfaToken] = useState<string | null>(null);
// OIDC bridge: if oidc_session_id is in the URL, we're acting as the // OIDC bridge: if oidc_session_id is in the URL, we're acting as the
// login UI for an OIDC authorization flow (e.g. SecuIRD → Gatehouse). // login UI for an OIDC authorization flow (e.g. SecuIRD → Secuird).
// After successful login, call /oidc/complete and redirect to the client app. // After successful login, call /oidc/complete and redirect to the client app.
const oidcSessionId = searchParams.get('oidc_session_id'); const oidcSessionId = searchParams.get('oidc_session_id');
const oidcError = searchParams.get('error'); const oidcError = searchParams.get('error');
// CLI bridge: if cli_token or cli_redirect is present the login was triggered // CLI bridge: if cli_token or cli_redirect is present the login was triggered
// by the Gatehouse CLI tool. After successful auth the token is delivered // by the Secuird CLI tool. After successful auth the token is delivered
// directly to the CLI's local callback server. // directly to the CLI's local callback server.
const cliToken = searchParams.get('cli_token'); const cliToken = searchParams.get('cli_token');
const cliRedirectParam = searchParams.get('cli_redirect'); const cliRedirectParam = searchParams.get('cli_redirect');
@@ -81,7 +81,7 @@ export default function LoginPage() {
useEffect(() => { useEffect(() => {
if (!cliToken || cliFetchedRef.current) return; if (!cliToken || cliFetchedRef.current) return;
cliFetchedRef.current = true; cliFetchedRef.current = true;
fetch(`${GATEHOUSE_API}/cli/redirect-url?token=${encodeURIComponent(cliToken)}`) fetch(`${SECUIRD_API}/cli/redirect-url?token=${encodeURIComponent(cliToken)}`)
.then((r) => r.json()) .then((r) => r.json())
.then((body) => { .then((body) => {
if (body?.data?.redirect_url) { if (body?.data?.redirect_url) {
@@ -165,7 +165,7 @@ export default function LoginPage() {
// MFA enrollment required - will be handled by ProtectedLayout // MFA enrollment required - will be handled by ProtectedLayout
// Navigation happens in AuthContext (MFA path always navigates) // Navigation happens in AuthContext (MFA path always navigates)
} else if (oidcSessionId) { } else if (oidcSessionId) {
// OIDC bridge: send token back to the Gatehouse backend to complete the flow // OIDC bridge: send token back to the Secuird backend to complete the flow
const token = tokenManager.getToken(); const token = tokenManager.getToken();
if (token) await finishOidcFlow(token); if (token) await finishOidcFlow(token);
} else if (cliRedirectUrl) { } else if (cliRedirectUrl) {
@@ -176,7 +176,7 @@ export default function LoginPage() {
// Normal login: navigation already handled by AuthContext (skipNavigate=false) // Normal login: navigation already handled by AuthContext (skipNavigate=false)
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] Login failed:", error); console.error("[Secuird] Login failed:", error);
} }
const message = error instanceof ApiError const message = error instanceof ApiError
@@ -246,7 +246,7 @@ export default function LoginPage() {
} }
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] MFA verification failed:", error); console.error("[Secuird] MFA verification failed:", error);
} }
const message = error instanceof ApiError const message = error instanceof ApiError
@@ -294,7 +294,7 @@ export default function LoginPage() {
// Normal login: navigation already handled by AuthContext (skipNavigate=false) // Normal login: navigation already handled by AuthContext (skipNavigate=false)
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] TOTP verification failed:", error); console.error("[Secuird] TOTP verification failed:", error);
} }
const message = error instanceof ApiError const message = error instanceof ApiError
@@ -363,7 +363,7 @@ export default function LoginPage() {
}); });
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] Passkey login failed:", error); console.error("[Secuird] Passkey login failed:", error);
} }
let message = "Failed to sign in with passkey"; let message = "Failed to sign in with passkey";
@@ -438,7 +438,7 @@ export default function LoginPage() {
} }
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] WebAuthn verification failed:", error); console.error("[Secuird] WebAuthn verification failed:", error);
} }
let message = "Failed to verify passkey"; let message = "Failed to verify passkey";
@@ -518,7 +518,7 @@ export default function LoginPage() {
} catch (error) { } catch (error) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] OAuth login failed:", error); console.error("[Secuird] OAuth login failed:", error);
} }
let message = `Failed to initiate ${provider} sign in`; let message = `Failed to initiate ${provider} sign in`;
@@ -939,7 +939,7 @@ export default function LoginPage() {
</h1> </h1>
<p className="text-muted-foreground mt-2"> <p className="text-muted-foreground mt-2">
{cliRedirectUrl {cliRedirectUrl
? "Sign in to grant the Gatehouse CLI access to your account" ? "Sign in to grant the Secuird CLI access to your account"
: oidcSessionId : oidcSessionId
? "An application is requesting access to your account" ? "An application is requesting access to your account"
: "Sign in to your account to continue"} : "Sign in to your account to continue"}
+6 -6
View File
@@ -9,11 +9,11 @@ import { useToast } from "@/hooks/use-toast";
type CallbackState = 'loading' | 'success' | 'error'; type CallbackState = 'loading' | 'success' | 'error';
const GATEHOUSE_API = (import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1') as string; const SECUIRD_API = (import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1') as string;
const GATEHOUSE_OIDC = GATEHOUSE_API.replace(/\/api\/v1\/?$/, ''); const SECUIRD_OIDC = SECUIRD_API.replace(/\/api\/v1\/?$/, '');
async function completeOidcFlow(oidcSessionId: string, token: string): Promise<string> { async function completeOidcFlow(oidcSessionId: string, token: string): Promise<string> {
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/complete`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/complete`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ oidc_session_id: oidcSessionId, token }), body: JSON.stringify({ oidc_session_id: oidcSessionId, token }),
@@ -24,7 +24,7 @@ async function completeOidcFlow(oidcSessionId: string, token: string): Promise<s
} }
/** /**
* OAuth callback page that handles the redirect from the Gatehouse backend * OAuth callback page that handles the redirect from the Secuird backend
* after a successful (or failed) OAuth provider authentication. * after a successful (or failed) OAuth provider authentication.
* *
* The backend exchanges the provider code for a session token and then * The backend exchanges the provider code for a session token and then
@@ -134,7 +134,7 @@ export default function OAuthCallbackPage() {
return; return;
} catch (oidcErr) { } catch (oidcErr) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] OIDC completion failed after OAuth:", oidcErr); console.error("[Secuird] OIDC completion failed after OAuth:", oidcErr);
} }
// Fall through to normal flow on failure — user is still logged in // Fall through to normal flow on failure — user is still logged in
} }
@@ -163,7 +163,7 @@ export default function OAuthCallbackPage() {
setStatus('error'); setStatus('error');
setError("Failed to load your profile. Please try signing in again."); setError("Failed to load your profile. Please try signing in again.");
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.error("[Gatehouse] OAuth callback refreshUser failed:", err); console.error("[Secuird] OAuth callback refreshUser failed:", err);
} }
} }
}; };
+4 -4
View File
@@ -6,8 +6,8 @@ import { Card } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { tokenManager } from "@/lib/api"; import { tokenManager } from "@/lib/api";
const GATEHOUSE_API = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1'; const SECUIRD_API = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5000/api/v1';
const GATEHOUSE_OIDC = GATEHOUSE_API.replace(/\/api\/v1\/?$/, ''); const SECUIRD_OIDC = SECUIRD_API.replace(/\/api\/v1\/?$/, '');
const SCOPE_META: Record<string, { icon: typeof Shield; label: string; description: string }> = { const SCOPE_META: Record<string, { icon: typeof Shield; label: string; description: string }> = {
openid: { icon: Shield, label: "OpenID", description: "Verify your identity" }, openid: { icon: Shield, label: "OpenID", description: "Verify your identity" },
@@ -41,7 +41,7 @@ export default function OIDCConsentPage() {
(async () => { (async () => {
try { try {
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/begin`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/begin`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ oidc_session_id: oidcSessionId }), body: JSON.stringify({ oidc_session_id: oidcSessionId }),
@@ -67,7 +67,7 @@ export default function OIDCConsentPage() {
navigate(`/login?oidc_session_id=${context.oidc_session_id}`); navigate(`/login?oidc_session_id=${context.oidc_session_id}`);
return; return;
} }
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/complete`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/complete`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ oidc_session_id: context.oidc_session_id, token }), body: JSON.stringify({ oidc_session_id: context.oidc_session_id, token }),
+5 -5
View File
@@ -1,7 +1,7 @@
/** /**
* OIDCLoginPage — Standalone OIDC proxy login UI * OIDCLoginPage — Standalone OIDC proxy login UI
* *
* Unified entry point for OIDC authorization flows via the Gatehouse OIDC bridge. * Unified entry point for OIDC authorization flows via the Secuird OIDC bridge.
* Handles: * Handles:
* 1. Unauthenticated users → shows an email/password login form * 1. Unauthenticated users → shows an email/password login form
* 2. Already-authenticated users → shows a consent/approval screen directly * 2. Already-authenticated users → shows a consent/approval screen directly
@@ -9,7 +9,7 @@
* Route: /oidc-login?oidc_session_id=<id> * Route: /oidc-login?oidc_session_id=<id>
* *
* Configure your oauth2-proxy / OIDC client's login_url to: * Configure your oauth2-proxy / OIDC client's login_url to:
* https://<gatehouse-ui>/oidc-login * https://<secuird-ui>/oidc-login
*/ */
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { useSearchParams, useNavigate } from "react-router-dom"; import { useSearchParams, useNavigate } from "react-router-dom";
@@ -37,7 +37,7 @@ import { useAuth } from "@/contexts/AuthContext";
import { ApiError, tokenManager } from "@/lib/api"; import { ApiError, tokenManager } from "@/lib/api";
// ── Configuration ───────────────────────────────────────────────────────────── // ── Configuration ─────────────────────────────────────────────────────────────
const GATEHOUSE_OIDC = (import.meta.env.VITE_API_BASE_URL ?? "http://localhost:5000/api/v1") const SECUIRD_OIDC = (import.meta.env.VITE_API_BASE_URL ?? "http://localhost:5000/api/v1")
.replace(/\/api\/v1\/?$/, ""); .replace(/\/api\/v1\/?$/, "");
// ── Scope display metadata ──────────────────────────────────────────────────── // ── Scope display metadata ────────────────────────────────────────────────────
@@ -62,7 +62,7 @@ type PageStep = "loading" | "login" | "consent" | "error";
// ── API helpers ─────────────────────────────────────────────────────────────── // ── API helpers ───────────────────────────────────────────────────────────────
async function fetchOIDCContext(oidcSessionId: string): Promise<OIDCContext> { async function fetchOIDCContext(oidcSessionId: string): Promise<OIDCContext> {
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/begin`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/begin`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ oidc_session_id: oidcSessionId }), body: JSON.stringify({ oidc_session_id: oidcSessionId }),
@@ -75,7 +75,7 @@ async function fetchOIDCContext(oidcSessionId: string): Promise<OIDCContext> {
} }
async function completeOIDCFlow(oidcSessionId: string, token: string): Promise<string> { async function completeOIDCFlow(oidcSessionId: string, token: string): Promise<string> {
const res = await fetch(`${GATEHOUSE_OIDC}/oidc/complete`, { const res = await fetch(`${SECUIRD_OIDC}/oidc/complete`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ oidc_session_id: oidcSessionId, token }), body: JSON.stringify({ oidc_session_id: oidcSessionId, token }),
+1 -1
View File
@@ -108,7 +108,7 @@ export default function RegisterPage() {
Create your account Create your account
</h1> </h1>
<p className="text-muted-foreground mt-2"> <p className="text-muted-foreground mt-2">
Get started with Gatehouse in seconds Get started with Secuird in seconds
</p> </p>
</div> </div>
+1 -1
View File
@@ -261,7 +261,7 @@ export default function CAsPage() {
<div> <div>
<h1 className="page-title">Certificate Authorities</h1> <h1 className="page-title">Certificate Authorities</h1>
<p className="page-description"> <p className="page-description">
Manage your organization's SSH CAs with <code>Gatehouse</code> Manage your organization's SSH CAs with <code>Secuird</code>
</p> </p>
</div> </div>
</div> </div>
+3 -3
View File
@@ -160,7 +160,7 @@ export default function OIDCClientsPage() {
<div className="page-header flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <div className="page-header flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div> <div>
<h1 className="page-title">OIDC Clients</h1> <h1 className="page-title">OIDC Clients</h1>
<p className="page-description">Applications that authenticate via Gatehouse</p> <p className="page-description">Applications that authenticate via Secuird</p>
</div> </div>
<Button onClick={() => setDialogMode("generic")}> <Button onClick={() => setDialogMode("generic")}>
<Plus className="w-4 h-4 mr-2" /> <Plus className="w-4 h-4 mr-2" />
@@ -235,7 +235,7 @@ export default function OIDCClientsPage() {
<Network className="w-10 h-10 text-muted-foreground/40" /> <Network className="w-10 h-10 text-muted-foreground/40" />
<div> <div>
<p className="font-medium text-muted-foreground">No OIDC clients yet</p> <p className="font-medium text-muted-foreground">No OIDC clients yet</p>
<p className="text-sm text-muted-foreground/70">Register an app to let it authenticate via Gatehouse</p> <p className="text-sm text-muted-foreground/70">Register an app to let it authenticate via Secuird</p>
</div> </div>
<div className="flex gap-2 flex-wrap justify-center"> <div className="flex gap-2 flex-wrap justify-center">
<Button variant="outline" onClick={() => setDialogMode("generic")}> <Button variant="outline" onClick={() => setDialogMode("generic")}>
@@ -320,7 +320,7 @@ export default function OIDCClientsPage() {
<DialogContent className="sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Add OIDC Client</DialogTitle> <DialogTitle>Add OIDC Client</DialogTitle>
<DialogDescription>Register an application to authenticate via Gatehouse</DialogDescription> <DialogDescription>Register an application to authenticate via Secuird</DialogDescription>
</DialogHeader> </DialogHeader>
<Tabs <Tabs
+5 -5
View File
@@ -47,7 +47,7 @@ export function CADetailCard({ ca, onEdit, onRotate, onDelete }: CADetailCardPro
const isSystem = !!ca.is_system; const isSystem = !!ca.is_system;
// ── User CA: server trusts this public key so it accepts user certs ────── // ── User CA: server trusts this public key so it accepts user certs ──────
const userCaServerSnippet = `# On each SSH server — trust Gatehouse-issued user certificates: const userCaServerSnippet = `# On each SSH server — trust Secuird-issued user certificates:
echo '${ca.public_key.trim()}' >> /etc/ssh/trusted_user_ca_keys echo '${ca.public_key.trim()}' >> /etc/ssh/trusted_user_ca_keys
# /etc/ssh/sshd_config (add once, then reload sshd): # /etc/ssh/sshd_config (add once, then reload sshd):
@@ -63,7 +63,7 @@ AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# ─── Server side (separate step) ──────────────────────────────────────────── # ─── Server side (separate step) ────────────────────────────────────────────
# 1. Collect the server's HOST public key: # 1. Collect the server's HOST public key:
# cat /etc/ssh/ssh_host_ed25519_key.pub # cat /etc/ssh/ssh_host_ed25519_key.pub
# 2. Submit it to Gatehouse → "Issue Host Certificate" to get a signed cert. # 2. Submit it to Secuird → "Issue Host Certificate" to get a signed cert.
# 3. Install the cert on the server: # 3. Install the cert on the server:
# /etc/ssh/sshd_config: # /etc/ssh/sshd_config:
# HostKey /etc/ssh/ssh_host_ed25519_key # HostKey /etc/ssh/ssh_host_ed25519_key
@@ -200,8 +200,8 @@ AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<Terminal className="w-3.5 h-3.5" /> <Terminal className="w-3.5 h-3.5" />
{isUser {isUser
? "Server setup — trust Gatehouse user certificates" ? "Server setup — trust Secuird user certificates"
: "Client setup — trust Gatehouse host certificates"} : "Client setup — trust Secuird host certificates"}
</span> </span>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="pb-3"> <AccordionContent className="pb-3">
@@ -209,7 +209,7 @@ AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
<div className="mb-2 rounded border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-950/40 px-2 py-1.5 text-xs text-amber-800 dark:text-amber-300"> <div className="mb-2 rounded border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-950/40 px-2 py-1.5 text-xs text-amber-800 dark:text-amber-300">
<strong>Two separate steps:</strong> (1) Put this CA public key in client{" "} <strong>Two separate steps:</strong> (1) Put this CA public key in client{" "}
<code className="font-mono">known_hosts</code>. (2) Issue a host certificate <code className="font-mono">known_hosts</code>. (2) Issue a host certificate
for each server via Gatehouse and install it as{" "} for each server via Secuird and install it as{" "}
<code className="font-mono">HostCertificate</code>. <code className="font-mono">HostCertificate</code>.
</div> </div>
)} )}
+1 -1
View File
@@ -127,7 +127,7 @@ export function CASection({
<p> <p>
Certificates are being signed by a CA key loaded from the server Certificates are being signed by a CA key loaded from the server
configuration, not managed through this UI. Generate a managed key below to configuration, not managed through this UI. Generate a managed key below to
take full control of certificate issuance from Gatehouse. take full control of certificate issuance from Secuird.
</p> </p>
</div> </div>
<Button <Button
+1 -1
View File
@@ -176,7 +176,7 @@ ssh-keygen -L -f /etc/ssh/ssh_host_ed25519_key-cert.pub`
</p> </p>
<p> <p>
<strong>Step 2 (here):</strong> For each server, collect its host public key, <strong>Step 2 (here):</strong> For each server, collect its host public key,
paste it below, and Gatehouse will sign it. Install the resulting certificate paste it below, and Secuird will sign it. Install the resulting certificate
as <code className="font-mono">HostCertificate</code> in{" "} as <code className="font-mono">HostCertificate</code> in{" "}
<code className="font-mono">sshd_config</code>. <code className="font-mono">sshd_config</code>.
</p> </p>
+1 -1
View File
@@ -172,7 +172,7 @@ export default function LinkedAccountsPage() {
<Alert className="mb-6"> <Alert className="mb-6">
<AlertCircle className="w-4 h-4" /> <AlertCircle className="w-4 h-4" />
<AlertDescription> <AlertDescription>
Linked accounts can only be used to sign in to an existing Gatehouse account. Linked accounts can only be used to sign in to an existing Secuird account.
They cannot be used to create new accounts. They cannot be used to create new accounts.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
+2 -2
View File
@@ -658,7 +658,7 @@ export default function SSHKeysPage() {
CA Public Key CA Public Key
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
Add this key to <code>TrustedUserCAKeys</code> on your servers so they accept certificates issued by Gatehouse. Add this key to <code>TrustedUserCAKeys</code> on your servers so they accept certificates issued by Secuird.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -821,7 +821,7 @@ TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys`}
{`echo '<challenge_text>' > /tmp/challenge.txt {`echo '<challenge_text>' > /tmp/challenge.txt
ssh-keygen -Y sign \\ ssh-keygen -Y sign \\
-f ~/.ssh/id_ed25519 \\ -f ~/.ssh/id_ed25519 \\
-n gatehouse \\ -n secuird \\
/tmp/challenge.txt /tmp/challenge.txt
cat /tmp/challenge.txt.sig | base64 -w0`} cat /tmp/challenge.txt.sig | base64 -w0`}
</pre> </pre>
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/NavLink.tsx","./src/components/auth/BannerAlert.tsx","./src/components/auth/ComplianceBanner.tsx","./src/components/auth/PasswordStrengthMeter.tsx","./src/components/branding/GatehouseLogo.tsx","./src/components/dev/ApiDevTools.tsx","./src/components/layouts/AuthenticatedLayout.tsx","./src/components/layouts/MfaEnforcementLayout.tsx","./src/components/layouts/ProtectedLayout.tsx","./src/components/layouts/PublicLayout.tsx","./src/components/navigation/AppSidebar.tsx","./src/components/navigation/TopBar.tsx","./src/components/security/AddPasskeyWizard.tsx","./src/components/security/TotpEnrollmentWizard.tsx","./src/components/security/TotpRemoveDialog.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/aspect-ratio.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/carousel.tsx","./src/components/ui/chart.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input-otp.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/menubar.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/use-toast.ts","./src/contexts/AuthContext.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/useOrganizations.ts","./src/lib/api.ts","./src/lib/encoding.ts","./src/lib/oauth.ts","./src/lib/utils.ts","./src/lib/webauthn.ts","./src/pages/Index.tsx","./src/pages/NotFound.tsx","./src/pages/auth/ForgotPasswordPage.tsx","./src/pages/auth/InviteAcceptPage.tsx","./src/pages/auth/LoginPage.tsx","./src/pages/auth/OAuthCallbackPage.tsx","./src/pages/auth/OIDCConsentPage.tsx","./src/pages/auth/OIDCErrorPage.tsx","./src/pages/auth/RegisterPage.tsx","./src/pages/auth/ResetPasswordPage.tsx","./src/pages/auth/VerifyEmailPage.tsx","./src/pages/org/CompliancePage.tsx","./src/pages/org/MembersPage.tsx","./src/pages/org/OIDCClientsPage.tsx","./src/pages/org/OrgAuditPage.tsx","./src/pages/org/OrgOverviewPage.tsx","./src/pages/org/PoliciesPage.tsx","./src/pages/user/ActivityPage.tsx","./src/pages/user/LinkedAccountsPage.tsx","./src/pages/user/ProfilePage.tsx","./src/pages/user/SecurityPage.tsx"],"errors":true,"version":"5.8.3"} {"root":["./src/App.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/NavLink.tsx","./src/components/auth/BannerAlert.tsx","./src/components/auth/ComplianceBanner.tsx","./src/components/auth/PasswordStrengthMeter.tsx","./src/components/branding/SecuirdLogo.tsx","./src/components/dev/ApiDevTools.tsx","./src/components/layouts/AuthenticatedLayout.tsx","./src/components/layouts/MfaEnforcementLayout.tsx","./src/components/layouts/ProtectedLayout.tsx","./src/components/layouts/PublicLayout.tsx","./src/components/navigation/AppSidebar.tsx","./src/components/navigation/TopBar.tsx","./src/components/security/AddPasskeyWizard.tsx","./src/components/security/TotpEnrollmentWizard.tsx","./src/components/security/TotpRemoveDialog.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/aspect-ratio.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/carousel.tsx","./src/components/ui/chart.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input-otp.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/menubar.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/use-toast.ts","./src/contexts/AuthContext.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/useOrganizations.ts","./src/lib/api.ts","./src/lib/encoding.ts","./src/lib/oauth.ts","./src/lib/utils.ts","./src/lib/webauthn.ts","./src/pages/Index.tsx","./src/pages/NotFound.tsx","./src/pages/auth/ForgotPasswordPage.tsx","./src/pages/auth/InviteAcceptPage.tsx","./src/pages/auth/LoginPage.tsx","./src/pages/auth/OAuthCallbackPage.tsx","./src/pages/auth/OIDCConsentPage.tsx","./src/pages/auth/OIDCErrorPage.tsx","./src/pages/auth/RegisterPage.tsx","./src/pages/auth/ResetPasswordPage.tsx","./src/pages/auth/VerifyEmailPage.tsx","./src/pages/org/CompliancePage.tsx","./src/pages/org/MembersPage.tsx","./src/pages/org/OIDCClientsPage.tsx","./src/pages/org/OrgAuditPage.tsx","./src/pages/org/OrgOverviewPage.tsx","./src/pages/org/PoliciesPage.tsx","./src/pages/user/ActivityPage.tsx","./src/pages/user/LinkedAccountsPage.tsx","./src/pages/user/ProfilePage.tsx","./src/pages/user/SecurityPage.tsx"],"errors":true,"version":"5.8.3"}
+1 -1
View File
@@ -8,7 +8,7 @@ export default defineConfig(({ mode }) => ({
server: { server: {
host: "::", host: "::",
port: 8080, port: 8080,
allowedHosts: process.env.VITE_ALLOWED_HOSTS?.split(",") || ["ui.webauthn.local","gatehouse-ui.hawkvelt.tech"], allowedHosts: process.env.VITE_ALLOWED_HOSTS?.split(",") || ["ui.webauthn.local","secuird-ui.hawkvelt.tech"],
}, },
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean), plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),
resolve: { resolve: {