diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 80cc8d8..b9f4b71 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Settings, @@ -19,8 +19,26 @@ import { Edit2, X, Link2, + KeyRound, + Copy, + Building2, + Server, + Database, + HardDrive, + Clock, + PackageOpen, + Download, + Upload, + ShieldCheck, } from "lucide-react"; import { useAuth } from "../context/AuthContext"; +import client from "../api/client"; +import { + getSsoConfig, + updateSsoConfig, + type SsoConfig, + type SsoConfigUpdate, +} from "../api/sso"; import { getEmailConfig, updateEmailConfig, @@ -1308,11 +1326,484 @@ function JiraConfigSection() { ); } +// --------------------------------------------------------------------------- +// SSO / Azure AD Config Section (admin only) +// --------------------------------------------------------------------------- + +const AZURE_ROLES = [ + { value: "admin", label: "Aegis Admin", desc: "Full platform access including system settings" }, + { value: "red_lead", label: "Aegis Red Lead", desc: "Red team lead — manage tests, campaigns and templates" }, + { value: "blue_lead", label: "Aegis Blue Lead", desc: "Blue team lead — validate tests and manage coverage" }, + { value: "red_tech", label: "Aegis Red Tech", desc: "Red team technician — execute tests" }, + { value: "blue_tech", label: "Aegis Blue Tech", desc: "Blue team technician — review detections" }, + { value: "viewer", label: "Aegis Viewer", desc: "Read-only access to dashboards and reports" }, +]; + +function CopyFieldSSO({ value, label }: { value: string; label: string }) { + const [copied, setCopied] = useState(false); + const copy = () => { + navigator.clipboard.writeText(value).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + return ( +
+ +
+ + {value || not set} + + +
+
+ ); +} + +function SsoConfigSection() { + const qc = useQueryClient(); + const origin = typeof window !== "undefined" ? window.location.origin : ""; + const defaultSpEntityId = `${origin}/api/v1/sso/metadata`; + const defaultSpAcsUrl = `${origin}/api/v1/sso/callback`; + + const { data: existingConfig, isLoading: configLoading } = useQuery({ + queryKey: ["sso-config"], + queryFn: async () => { + try { return await getSsoConfig(); } catch { return null; } + }, + }); + + const [tenantId, setTenantId] = useState(""); + const [form, setForm] = useState({ + is_enabled: false, + provider_name: "Azure AD / Entra ID", + sp_entity_id: defaultSpEntityId, + sp_acs_url: defaultSpAcsUrl, + idp_entity_id: "", + idp_sso_url: "", + idp_certificate: "", + attr_email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + attr_username: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + attr_role: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", + default_role: "viewer", + auto_provision: true, + }); + const [formLoaded, setFormLoaded] = useState(false); + const [saveSuccess, setSaveSuccess] = useState(false); + + if (existingConfig && !formLoaded) { + setForm({ + is_enabled: existingConfig.is_enabled, + provider_name: existingConfig.provider_name ?? "Azure AD / Entra ID", + sp_entity_id: existingConfig.sp_entity_id ?? defaultSpEntityId, + sp_acs_url: existingConfig.sp_acs_url ?? defaultSpAcsUrl, + idp_entity_id: existingConfig.idp_entity_id ?? "", + idp_sso_url: existingConfig.idp_sso_url ?? "", + idp_certificate: existingConfig.idp_certificate ?? "", + attr_email: existingConfig.attr_email ?? "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + attr_username: existingConfig.attr_username ?? "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + attr_role: existingConfig.attr_role ?? "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", + default_role: existingConfig.default_role ?? "viewer", + auto_provision: existingConfig.auto_provision ?? true, + }); + if (existingConfig.idp_sso_url) { + const m = existingConfig.idp_sso_url.match(/microsoftonline\.com\/([^/]+)\//); + if (m) setTenantId(m[1]); + } + setFormLoaded(true); + } + + const handleTenantChange = (val: string) => { + setTenantId(val); + if (val.trim()) { + setForm((f) => ({ + ...f, + idp_entity_id: `https://sts.windows.net/${val.trim()}/`, + idp_sso_url: `https://login.microsoftonline.com/${val.trim()}/saml2`, + })); + } + }; + + const saveMutation = useMutation({ + mutationFn: (payload: SsoConfigUpdate) => updateSsoConfig(payload), + onSuccess: () => { + setSaveSuccess(true); + setTimeout(() => setSaveSuccess(false), 4000); + qc.invalidateQueries({ queryKey: ["sso-config"] }); + qc.invalidateQueries({ queryKey: ["sso-status"] }); + }, + }); + + const handleSave = (e: React.FormEvent) => { e.preventDefault(); saveMutation.mutate(form); }; + const isConfigured = !!(form.idp_entity_id && form.idp_sso_url && form.idp_certificate); + + return ( +
+
+
+
+ +
+
+

Azure AD / Entra ID SSO

+

+ Delegate authentication to Azure Active Directory via SAML 2.0. + Users sign in with corporate credentials; roles assigned automatically via Azure App Roles. +

+
+
+ + {form.is_enabled && isConfigured + ? <> Active + : <> {isConfigured ? "Disabled" : "Not configured"}} + +
+ + {configLoading ? ( +
+ ) : ( +
+ {/* Step 1 */} +
+
+ 1 +

Give these values to your IT team

+
+ +
+ + {/* Step 2 */} +
+
+ 2 +

Create App Roles in Azure

+
+
+ + + + + + + + + + {AZURE_ROLES.map((r) => ( + + + + + + ))} + +
Display NameValue (exact)Description
{r.label}{r.value}{r.desc}
+
+
+ + {/* Step 3 */} +
+
+ 3 +

Enter Azure tenant details

+
+
+
+ + handleTenantChange(e.target.value)} + placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 placeholder-gray-500 focus:border-indigo-500 focus:outline-none font-mono" /> +

Azure AD → Overview → Tenant ID

+
+
+ + setForm({ ...form, idp_entity_id: e.target.value })} + placeholder="https://sts.windows.net/{tenant-id}/" + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 placeholder-gray-500 focus:border-indigo-500 focus:outline-none font-mono" /> +
+
+ + setForm({ ...form, idp_sso_url: e.target.value })} + placeholder="https://login.microsoftonline.com/{tenant-id}/saml2" + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 placeholder-gray-500 focus:border-indigo-500 focus:outline-none font-mono" /> +
+
+ + setForm({ ...form, provider_name: e.target.value })} + placeholder="Azure AD / Entra ID" + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 placeholder-gray-500 focus:border-indigo-500 focus:outline-none" /> +
+
+
+ + {/* Step 4 */} +
+
+ 4 +

Paste IdP certificate

+
+