import { Alert, AlertDescription } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { EnvVarInput } from "@/components/ui/envVarInput"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { IS_ENTERPRISE } from "@/lib/constants/config"; import { getErrorMessage, useGetCoreConfigQuery, useUpdateCoreConfigMutation } from "@/lib/store"; import { AuthConfig, CoreConfig, DefaultCoreConfig } from "@/lib/types/config"; import { EnvVar } from "@/lib/types/schemas"; import { parseArrayFromText } from "@/lib/utils/array"; import { validateOrigins } from "@/lib/utils/validation"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { Link } from "@tanstack/react-router"; import { AlertTriangle, Info } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; export default function SecurityView() { const hasSettingsUpdateAccess = useRbac(RbacResource.Settings, RbacOperation.Update); const { data: bifrostConfig } = useGetCoreConfigQuery({ fromDB: true }); const config = bifrostConfig?.client_config; const [updateCoreConfig, { isLoading }] = useUpdateCoreConfigMutation(); const [localConfig, setLocalConfig] = useState(DefaultCoreConfig); const hideAuthDashboard = IS_ENTERPRISE; const [localValues, setLocalValues] = useState<{ allowed_origins: string; allowed_headers: string; required_headers: string; whitelisted_routes: string; }>({ allowed_origins: "", allowed_headers: "", required_headers: "", whitelisted_routes: "", }); const [authConfig, setAuthConfig] = useState({ admin_username: { value: "", env_var: "", from_env: false }, admin_password: { value: "", env_var: "", from_env: false }, is_enabled: false, disable_auth_on_inference: false, }); useEffect(() => { if (bifrostConfig && config) { setLocalConfig(config); setLocalValues({ allowed_origins: config?.allowed_origins?.join(", ") || "", allowed_headers: config?.allowed_headers?.join(", ") || "", required_headers: config?.required_headers?.join(", ") || "", whitelisted_routes: config?.whitelisted_routes?.join(", ") || "", }); } if (bifrostConfig?.auth_config) { setAuthConfig(bifrostConfig.auth_config); } }, [config, bifrostConfig]); const hasChanges = useMemo(() => { if (!config) return false; const localOrigins = localConfig.allowed_origins?.slice().sort().join(","); const serverOrigins = config.allowed_origins?.slice().sort().join(","); const originsChanged = localOrigins !== serverOrigins; const localHeaders = localConfig.allowed_headers?.slice().sort().join(","); const serverHeaders = config.allowed_headers?.slice().sort().join(","); const headersChanged = localHeaders !== serverHeaders; const usernameChanged = authConfig.admin_username?.value !== bifrostConfig?.auth_config?.admin_username?.value || authConfig.admin_username?.env_var !== bifrostConfig?.auth_config?.admin_username?.env_var || authConfig.admin_username?.from_env !== bifrostConfig?.auth_config?.admin_username?.from_env; const passwordChanged = authConfig.admin_password?.value !== bifrostConfig?.auth_config?.admin_password?.value || authConfig.admin_password?.env_var !== bifrostConfig?.auth_config?.admin_password?.env_var || authConfig.admin_password?.from_env !== bifrostConfig?.auth_config?.admin_password?.from_env; const authChanged = authConfig.is_enabled !== bifrostConfig?.auth_config?.is_enabled || usernameChanged || passwordChanged || authConfig.disable_auth_on_inference !== bifrostConfig?.auth_config?.disable_auth_on_inference; const localRequired = localConfig.required_headers?.slice().sort().join(","); const serverRequired = config.required_headers?.slice().sort().join(","); const requiredChanged = localRequired !== serverRequired; const localWhitelistedRoutes = localConfig.whitelisted_routes?.slice().sort().join(","); const serverWhitelistedRoutes = config.whitelisted_routes?.slice().sort().join(","); const whitelistedRoutesChanged = localWhitelistedRoutes !== serverWhitelistedRoutes; const enforceAuthOnInferenceChanged = localConfig.enforce_auth_on_inference !== config.enforce_auth_on_inference; const allowDirectKeysChanged = localConfig.allow_direct_keys !== config.allow_direct_keys; return ( originsChanged || headersChanged || requiredChanged || whitelistedRoutesChanged || authChanged || enforceAuthOnInferenceChanged || allowDirectKeysChanged ); }, [config, localConfig, authConfig, bifrostConfig]); const needsRestart = useMemo(() => { if (!config) return false; const localOrigins = localConfig.allowed_origins?.slice().sort().join(","); const serverOrigins = config.allowed_origins?.slice().sort().join(","); const originsChanged = localOrigins !== serverOrigins; const localHeaders = localConfig.allowed_headers?.slice().sort().join(","); const serverHeaders = config.allowed_headers?.slice().sort().join(","); const headersChanged = localHeaders !== serverHeaders; const enforceAuthOnInferenceChanged = localConfig.enforce_auth_on_inference !== config.enforce_auth_on_inference && IS_ENTERPRISE; return originsChanged || headersChanged || enforceAuthOnInferenceChanged; }, [config, localConfig]); const handleAllowedOriginsChange = useCallback((value: string) => { setLocalValues((prev) => ({ ...prev, allowed_origins: value })); setLocalConfig((prev) => ({ ...prev, allowed_origins: parseArrayFromText(value) })); }, []); const handleAllowedHeadersChange = useCallback((value: string) => { setLocalValues((prev) => ({ ...prev, allowed_headers: value })); setLocalConfig((prev) => ({ ...prev, allowed_headers: parseArrayFromText(value) })); }, []); const handleRequiredHeadersChange = useCallback((value: string) => { setLocalValues((prev) => ({ ...prev, required_headers: value })); setLocalConfig((prev) => ({ ...prev, required_headers: parseArrayFromText(value) })); }, []); const handleWhitelistedRoutesChange = useCallback((value: string) => { setLocalValues((prev) => ({ ...prev, whitelisted_routes: value })); setLocalConfig((prev) => ({ ...prev, whitelisted_routes: parseArrayFromText(value) })); }, []); const handleConfigChange = useCallback((field: keyof CoreConfig, value: boolean) => { setLocalConfig((prev) => ({ ...prev, [field]: value })); }, []); const handleAuthToggle = useCallback((checked: boolean) => { setAuthConfig((prev) => ({ ...prev, is_enabled: checked })); }, []); const handleDisableAuthOnInferenceToggle = useCallback((checked: boolean) => { setAuthConfig((prev) => ({ ...prev, disable_auth_on_inference: checked })); }, []); const handleAuthFieldChange = useCallback((field: "admin_username" | "admin_password", value: EnvVar) => { setAuthConfig((prev) => ({ ...prev, [field]: value })); }, []); const handleSave = useCallback(async () => { try { const validation = validateOrigins(localConfig.allowed_origins); if (!validation.isValid && localConfig.allowed_origins.length > 0) { toast.error( `Invalid origins: ${validation.invalidOrigins.join(", ")}. Origins must be valid URLs like https://example.com, wildcard patterns like https://*.example.com, or "*" to allow all origins`, ); return; } const hasUsername = authConfig.admin_username?.value || authConfig.admin_username?.env_var; const hasPassword = authConfig.admin_password?.value || authConfig.admin_password?.env_var; await updateCoreConfig({ ...bifrostConfig!, client_config: localConfig, auth_config: authConfig.is_enabled && hasUsername && hasPassword ? authConfig : { ...authConfig, is_enabled: false }, }).unwrap(); toast.success("Security settings updated successfully."); } catch (error) { toast.error(getErrorMessage(error)); } }, [bifrostConfig, localConfig, authConfig, updateCoreConfig]); return (

Security Settings

Configure security and access control settings.

{authConfig.is_enabled && !authConfig.disable_auth_on_inference && ( You will need to use Basic Auth for all your inference calls (including MCP tool execution). You can disable it below. Check{" "} API Keys )} {authConfig.is_enabled && authConfig.disable_auth_on_inference && ( Authentication is disabled for inference calls. Only dashboard, admin API and MCP tool execution calls require authentication. )} {/* Password Protect the Dashboard */} {!hideAuthDashboard && (

Set up authentication credentials to protect your Bifrost dashboard. Once configured, use the generated token for all admin API calls.

handleAuthFieldChange("admin_username", value)} />
handleAuthFieldChange("admin_password", value)} />

When enabled, inference API calls (chat completions, embeddings, etc.) will not require authentication. Dashboard and admin API calls will still require authentication.

)} {/* Enable Auth on Inference */}

{IS_ENTERPRISE ? "Require authentication (virtual key, API key, or user token) for all inference endpoints." : "Require a virtual key for all inference requests."}{" "} See{" "} documentation {" "} for details.

handleConfigChange("enforce_auth_on_inference", checked)} />
{/* Allowed Origins */} {needsRestart && } {/* Allow Direct API Keys */}

Allow API keys to be passed directly in request headers (Authorization, x-api-key, or x-goog-api-key). Bifrost will directly use the key.

handleConfigChange("allow_direct_keys", checked)} />

Comma-separated list of allowed origins for CORS and WebSocket connections. Localhost origins are always allowed. Each origin must be a complete URL with protocol (e.g., https://app.example.com, http://10.0.0.100:3000). Wildcards are supported for subdomains (e.g., https://*.example.com) or use "*" to allow all origins.