M21: add optional organisation identity

This commit is contained in:
NuklearRabbit
2026-08-10 15:35:25 +02:00
parent 509cb95110
commit c3f1cfc699
38 changed files with 563 additions and 110 deletions
+2
View File
@@ -11,6 +11,8 @@ export interface SystemStatus {
environment: string;
demo_mode: boolean;
knowledge_provider: string;
oidc_enabled: boolean;
oidc_provider_name: string | null;
}
export interface Vehicle {
+10 -2
View File
@@ -6,6 +6,8 @@ interface AuthState {
user: CurrentUser | null;
loading: boolean;
demoMode: boolean;
oidcEnabled: boolean;
oidcProviderName: string | null;
loginAs: (role: Role) => Promise<void>;
loginWithPassword: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
@@ -35,11 +37,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<CurrentUser | null>(() => readCachedUser());
const [loading, setLoading] = useState(true);
const [demoMode, setDemoMode] = useState(true);
const [oidcEnabled, setOidcEnabled] = useState(false);
const [oidcProviderName, setOidcProviderName] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
api.get<SystemStatus>("/api/v1/system/status")
.then((system) => setDemoMode(system.demo_mode))
.then((system) => {
setDemoMode(system.demo_mode);
setOidcEnabled(system.oidc_enabled);
setOidcProviderName(system.oidc_provider_name);
})
.catch(() => setDemoMode(true))
.finally(() => api
.get<CurrentUser>("/api/v1/auth/session")
@@ -119,7 +127,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, []);
return (
<AuthContext.Provider value={{ user, loading, demoMode, loginAs, loginWithPassword, logout }}>{children}</AuthContext.Provider>
<AuthContext.Provider value={{ user, loading, demoMode, oidcEnabled, oidcProviderName, loginAs, loginWithPassword, logout }}>{children}</AuthContext.Provider>
);
}
+3 -1
View File
@@ -23,5 +23,7 @@
"emailLabel": "Email address",
"passwordLabel": "Password",
"signIn": "Sign in",
"passwordLoginFailed": "Sign-in failed. Check your credentials."
"passwordLoginFailed": "Sign-in failed. Check your credentials.",
"or": "or",
"signInWithOrganization": "Sign in with {{provider}}"
}
+3 -1
View File
@@ -23,5 +23,7 @@
"emailLabel": "Adresse e-mail",
"passwordLabel": "Mot de passe",
"signIn": "Se connecter",
"passwordLoginFailed": "Connexion impossible. Vérifiez vos identifiants."
"passwordLoginFailed": "Connexion impossible. Vérifiez vos identifiants.",
"or": "ou",
"signInWithOrganization": "Se connecter avec {{provider}}"
}
+3 -1
View File
@@ -23,5 +23,7 @@
"emailLabel": "E-mailadres",
"passwordLabel": "Wachtwoord",
"signIn": "Aanmelden",
"passwordLoginFailed": "Aanmelden mislukt. Controleer je gegevens."
"passwordLoginFailed": "Aanmelden mislukt. Controleer je gegevens.",
"or": "of",
"signInWithOrganization": "Aanmelden met {{provider}}"
}
+8 -1
View File
@@ -10,7 +10,7 @@ import { PRODUCT_NAME } from "../product";
export function Login() {
const { t } = useTranslation(["auth", "common"]);
const { loginAs, loginWithPassword, loading, demoMode } = useAuth();
const { loginAs, loginWithPassword, loading, demoMode, oidcEnabled, oidcProviderName } = useAuth();
const { manifest } = useDemoManifest();
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
@@ -100,6 +100,13 @@ export function Login() {
<label>{t("passwordLabel")}<input type="password" autoComplete="current-password" value={password} onChange={(event) => setPassword(event.target.value)} minLength={8} required /></label>
<button type="submit" className="button button-primary" disabled={loading}>{t("signIn")}</button>
</form>}
{oidcEnabled && <>
<div className="login-divider"><span>{t("or")}</span></div>
<a className="button login-oidc" href="/api/v1/auth/oidc/login">
<Icon name="shield" />
{t("signInWithOrganization", { provider: oidcProviderName })}
</a>
</>}
<div className="access-note"><Icon name="shield" /><p><strong>{t("safeByDesignTitle")}</strong><span>{t("safeByDesignDetail")}</span></p></div>
</div>
</section>
+4
View File
@@ -113,6 +113,10 @@ a:hover { color: var(--teal); }
.sidebar-language { display: none; padding: 0 20px 14px; }
.login-panel-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.login-panel-top .page-eyebrow { margin: 0; }
.login-divider { display: flex; align-items: center; gap: 12px; margin: 18px 0; color: var(--muted); font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; }
.login-divider::before, .login-divider::after { content: ""; height: 1px; flex: 1; background: var(--line); }
.login-oidc { width: 100%; min-height: 44px; justify-content: center; gap: 8px; background: white; border: 1px solid var(--line-strong); }
.login-oidc svg { width: 17px; }
.demo-badge { position: relative; }
.demo-badge-trigger { display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; color: #48566a; background: #eaf0f5; border: 1px solid #d7e0e8; border-radius: 999px; font-size: .68rem; font-weight: 700; letter-spacing: .02em; cursor: pointer; }
.demo-badge-trigger:hover { background: #dfe8ef; }