feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
- Profilseite mit MFA-Einrichtung, Passwort/E-Mail/Name aendern, Konto deaktivieren - Bearbeiten-Buttons fuer Bienenstaende/Voelker/Koeniginnen/Aufgaben-Datensaetze - Restliche englische Enum-Werte durch deutsche Labels ersetzt (SelectValue-Fix) - Sidebar-Eintraege fuer Ernten/Schleuderungen ergaenzt - Automatisierungen von 3 auf 6 Regeln erweitert (Schwarmstimmung, kritischer Gesundheitsstatus, Lagerbestand) - Responsive Grid-Layout fuer Volk-/Koeniginnen-/Honigchargen-/Bienenstand-Detailseiten - Etikettenvorlagen mit Platzhaltern inkl. QR-Code (verweist auf Herkunftsseite) und PDF-Druck - Entra-ID-SSO (Authorization Code + PKCE, kein Auto-Provisioning, Secrets nur ueber Environment-Variablen)
This commit is contained in:
1 parent
0a52b29c57
commit
8432313fa9
83 files changed
+2684
-289
No files matched your search
@@ -72,6 +72,39 @@ export function refreshRequest(): Promise<AccessTokenResponse | null> {
|
||||
return inFlightRefresh;
|
||||
}
|
||||
|
||||
export async function ssoStatusRequest(): Promise<boolean> {
|
||||
const apiUrl = await getApiUrl();
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/auth/sso/status`);
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
const data = (await response.json()) as { enabled: boolean };
|
||||
return data.enabled;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ssoLoginUrl(): Promise<string> {
|
||||
const apiUrl = await getApiUrl();
|
||||
return `${apiUrl}/auth/sso/login`;
|
||||
}
|
||||
|
||||
// The one-time completion code lives only in an httpOnly cookie the callback
|
||||
// set (never in the URL or a request body) - the browser sends it automatically.
|
||||
export async function ssoCompleteRequest(): Promise<AccessTokenResponse> {
|
||||
const apiUrl = await getApiUrl();
|
||||
const response = await fetch(`${apiUrl}/auth/sso/complete`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('SSO-Anmeldung fehlgeschlagen. Bitte erneut versuchen.');
|
||||
}
|
||||
return response.json() as Promise<AccessTokenResponse>;
|
||||
}
|
||||
|
||||
export async function logoutRequest(): Promise<void> {
|
||||
const apiUrl = await getApiUrl();
|
||||
await fetch(`${apiUrl}/auth/logout`, { method: 'POST', credentials: 'include' });
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
loginRequest,
|
||||
logoutRequest,
|
||||
refreshRequest,
|
||||
ssoCompleteRequest,
|
||||
verifyMfaRequest,
|
||||
} from './api';
|
||||
import { getAccessToken, setAccessToken as storeAccessToken } from './token-store';
|
||||
@@ -29,6 +30,7 @@ interface AuthContextValue {
|
||||
isLoading: boolean;
|
||||
login: (email: string, password: string) => Promise<LoginOutcome>;
|
||||
verifyMfa: (mfaToken: string, code: string) => Promise<void>;
|
||||
completeSso: () => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
hasPermission: (permission: string) => boolean;
|
||||
refreshUser: () => Promise<void>;
|
||||
@@ -76,6 +78,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setUser(await applyAccessToken(result.accessToken));
|
||||
}, []);
|
||||
|
||||
const completeSso = useCallback(async () => {
|
||||
const result = await ssoCompleteRequest();
|
||||
setUser(await applyAccessToken(result.accessToken));
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
await logoutRequest();
|
||||
storeAccessToken(null);
|
||||
@@ -100,7 +107,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ user, isLoading, login, verifyMfa, logout, hasPermission, refreshUser }}
|
||||
value={{
|
||||
user,
|
||||
isLoading,
|
||||
login,
|
||||
verifyMfa,
|
||||
completeSso,
|
||||
logout,
|
||||
hasPermission,
|
||||
refreshUser,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
|
||||
Reference in new issue
Block a user