feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
Backend Image / build-and-push (push) Successful in 2m28s
Frontend Image / build-and-push (push) Successful in 2m10s

- 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:
Nick Szaunig committed 2026-08-31 18:56:08 +02:00
1 parent 0a52b29c57
commit 8432313fa9
83 files changed
+2684 -289

No files matched your search

+33
View File
@@ -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' });
+17 -1
View File
@@ -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>