Initial commit with Email Import Wizard and Task Processor updates
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user