Initial commit with Email Import Wizard and Task Processor updates

This commit is contained in:
2026-05-04 08:02:11 +02:00
commit effdc5d59f
170 changed files with 67739 additions and 0 deletions
@@ -0,0 +1,36 @@
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { handleCallback, userManager } from './oidc';
import { consumeReturnUrl } from './sessionRedirect';
import { Spin } from 'antd';
export default function AuthCallback() {
const navigate = useNavigate();
const processed = useRef(false);
useEffect(() => {
if (processed.current) return;
processed.current = true;
// Behandelt Silent Renew im Iframe
if (window !== window.parent || window.opener) {
userManager.signinSilentCallback().catch(err => {
console.error('Silent renew callback error:', err);
});
return;
}
handleCallback()
.then(() => navigate(consumeReturnUrl(), { replace: true }))
.catch((err) => {
console.error('Auth Callback Fehler:', err);
navigate('/login', { replace: true });
});
}, [navigate]);
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Spin size="large" description="Anmeldung wird verarbeitet..." />
</div>
);
}
@@ -0,0 +1,96 @@
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
import { getUser, login, logout, userManager, type User } from './oidc';
import { triggerLoginRedirect } from './sessionRedirect';
import { Permission, mapGroupsToPermissions } from './permissions';
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
permissions: Permission[];
hasPermission: (permission: Permission) => boolean;
login: () => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [permissions, setPermissions] = useState<Permission[]>([]);
const [isLoading, setIsLoading] = useState(true);
const updatePermissions = (u: User | null) => {
if (!u) {
setPermissions([]);
return;
}
const groups = (u.profile as any).groups as string[] | undefined;
setPermissions(mapGroupsToPermissions(groups));
};
useEffect(() => {
getUser()
.then((u) => {
setUser(u);
updatePermissions(u);
})
.finally(() => setIsLoading(false));
const onUserLoaded = (u: User) => {
console.log('OIDC: User loaded/renewed');
setUser(u);
updatePermissions(u);
};
const onUserUnloaded = () => {
console.log('OIDC: User unloaded');
setUser(null);
updatePermissions(null);
};
const onSilentRenewError = (err: Error) => {
console.error('OIDC: Silent renew failed:', err);
if (/login_required|interaction_required|invalid_grant/.test(err.message ?? '')) {
setUser(null);
updatePermissions(null);
triggerLoginRedirect();
}
};
userManager.events.addUserLoaded(onUserLoaded);
userManager.events.addUserUnloaded(onUserUnloaded);
userManager.events.addSilentRenewError(onSilentRenewError);
return () => {
userManager.events.removeUserLoaded(onUserLoaded);
userManager.events.removeUserUnloaded(onUserUnloaded);
userManager.events.removeSilentRenewError(onSilentRenewError);
};
}, []);
const hasPermission = (permission: Permission) => {
if (permissions.includes(Permission.MANAGE_ALL)) return true;
return permissions.includes(permission);
};
return (
<AuthContext.Provider
value={{
user,
isAuthenticated: !!user && !user.expired,
isLoading,
permissions,
hasPermission,
login,
logout,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth(): AuthContextType {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
+43
View File
@@ -0,0 +1,43 @@
import { UserManager, WebStorageStateStore, type User } from 'oidc-client-ts';
import { getEnv } from '../utils/env';
const redirectUri = getEnv('VITE_OIDC_REDIRECT_URI')
|| `${window.location.origin}/auth/callback`;
const userManager = new UserManager({
authority: getEnv('VITE_OIDC_AUTHORITY'),
client_id: getEnv('VITE_OIDC_CLIENT_ID'),
redirect_uri: redirectUri,
post_logout_redirect_uri: window.location.origin,
response_type: 'code',
scope: 'openid profile email groups offline_access',
userStore: new WebStorageStateStore({ store: window.localStorage }),
automaticSilentRenew: true,
monitorSession: false, // Deaktiviert Session-Monitoring via Iframe (vermeidet Cookie-Probleme)
loadUserInfo: true,
accessTokenExpiringNotificationTimeInSeconds: 60, // Erneuert 60s vor Ablauf des 5min Tokens
});
export async function login(): Promise<void> {
await userManager.signinRedirect();
}
export async function handleCallback(): Promise<User> {
return userManager.signinRedirectCallback();
}
export async function logout(): Promise<void> {
await userManager.signoutRedirect();
}
export async function getUser(): Promise<User | null> {
return userManager.getUser();
}
export async function getAccessToken(): Promise<string | null> {
const user = await getUser();
return user?.access_token ?? null;
}
export { userManager };
export type { User };
@@ -0,0 +1,44 @@
export const Permission = {
MANAGE_ALL: 'MANAGE_ALL',
PROCESS_MANUALLY: 'PROCESS_MANUALLY',
VIEW_MAIL: 'VIEW_MAIL',
VIEW_INBOX: 'VIEW_INBOX',
VIEW_SCANNER: 'VIEW_SCANNER',
MANAGE_SETTINGS: 'MANAGE_SETTINGS',
} as const;
export type Permission = typeof Permission[keyof typeof Permission];
export function mapGroupsToPermissions(groups: string[] | undefined | null): Permission[] {
const permissions = new Set<Permission>();
if (!groups || !Array.isArray(groups)) {
return [];
}
// Superuser
if (groups.includes('PM_Admin')) {
permissions.add(Permission.MANAGE_ALL);
permissions.add(Permission.PROCESS_MANUALLY);
permissions.add(Permission.VIEW_MAIL);
permissions.add(Permission.VIEW_INBOX);
permissions.add(Permission.VIEW_SCANNER);
permissions.add(Permission.MANAGE_SETTINGS);
return Array.from(permissions);
}
if (groups.includes('PM_Belege')) {
permissions.add(Permission.PROCESS_MANUALLY);
}
if (groups.includes('PM_Maileingang')) {
permissions.add(Permission.VIEW_MAIL);
}
if (groups.includes('PM_Posteingang')) {
permissions.add(Permission.VIEW_INBOX);
}
if (groups.includes('PM_Scanner')) {
permissions.add(Permission.VIEW_SCANNER);
}
return Array.from(permissions);
}
@@ -0,0 +1,27 @@
import { userManager } from './oidc';
const KEY = 'pm.returnUrl';
let redirecting = false;
export function saveReturnUrl(path: string): void {
if (path === '/login' || path.startsWith('/auth/callback')) return;
sessionStorage.setItem(KEY, path);
}
export function consumeReturnUrl(): string {
const v = sessionStorage.getItem(KEY) ?? '/';
sessionStorage.removeItem(KEY);
return v;
}
export async function triggerLoginRedirect(): Promise<void> {
if (redirecting) return;
redirecting = true;
saveReturnUrl(window.location.pathname + window.location.search);
try {
await userManager.removeUser();
} catch (err) {
console.error('OIDC: removeUser before redirect failed', err);
}
await userManager.signinRedirect();
}