feat(sso): Azure AD / Entra ID SAML 2.0 integration

- sso_service: fix process_callback for Azure AD claim URIs (email, role)
  - Default role_attr to full Azure role claim URI
  - Fallback email resolution via Azure email claim URI + NameID
  - Username defaults to full email (prevents collision with local accounts)
  - User lookup also tries email field for existing local accounts
  - Logs warning when unknown role received from IdP

- frontend/api/sso.ts: new API module with getSsoStatus, getSsoConfig, updateSsoConfig

- LoginPage: redesigned for SSO-first flow
  - Shows Azure SSO button as primary when SSO enabled+configured
  - Local login collapsed under "Emergency admin access" section
  - Falls back to normal local login form when SSO is disabled

- SystemPage: new SsoConfigSection component (guided 5-step wizard)
  - Step 1: Copy SP Entity ID and ACS URL for IT team + metadata XML download
  - Step 2: Azure App Roles reference table (6 roles with exact values)
  - Step 3: Tenant ID field auto-fills idp_entity_id and idp_sso_url
  - Step 4: X.509 certificate paste field
  - Step 5: Attribute mapping pre-filled with Azure AD claim URIs
  - Enable/disable toggle + save
This commit is contained in:
kitos
2026-06-08 13:48:36 +02:00
parent 0c9f3051b4
commit a7725ba519
4 changed files with 701 additions and 63 deletions
+66
View File
@@ -0,0 +1,66 @@
import client from "./client";
export interface SsoStatus {
enabled: boolean;
provider_name: string | null;
configured: boolean;
login_url: string | null;
}
export interface SsoConfig {
id: string;
is_enabled: boolean;
provider_name: string | null;
sp_entity_id: string | null;
sp_acs_url: string | null;
sp_slo_url: string | null;
sp_certificate: string | null;
idp_entity_id: string | null;
idp_sso_url: string | null;
idp_slo_url: string | null;
idp_certificate: string | null;
attr_email: string | null;
attr_username: string | null;
attr_role: string | null;
default_role: string | null;
auto_provision: boolean;
created_at: string | null;
updated_at: string | null;
}
export interface SsoConfigUpdate {
is_enabled: boolean;
provider_name?: string | null;
sp_entity_id?: string | null;
sp_acs_url?: string | null;
sp_slo_url?: string | null;
sp_certificate?: string | null;
sp_private_key?: string | null;
idp_entity_id?: string | null;
idp_sso_url?: string | null;
idp_slo_url?: string | null;
idp_certificate?: string | null;
attr_email?: string | null;
attr_username?: string | null;
attr_role?: string | null;
default_role?: string | null;
auto_provision?: boolean;
}
/** Public — used by LoginPage to decide which login to show. */
export async function getSsoStatus(): Promise<SsoStatus> {
const { data } = await client.get<SsoStatus>("/sso/status");
return data;
}
/** Admin-only — full config. Returns 404 if never configured. */
export async function getSsoConfig(): Promise<SsoConfig> {
const { data } = await client.get<SsoConfig>("/sso/config");
return data;
}
/** Admin-only — create or replace SSO config. */
export async function updateSsoConfig(payload: SsoConfigUpdate): Promise<SsoConfig> {
const { data } = await client.put<SsoConfig>("/sso/config", payload);
return data;
}