dns switch poweron-center to poweron.swiss

This commit is contained in:
ValueOn AG 2026-05-08 11:49:24 +02:00
parent 5c8c80872a
commit f5a5793309
20 changed files with 68 additions and 609 deletions

View file

@ -27,7 +27,7 @@ jobs:
- name: Copy integration environment file
run: |
cp config/.env.int .env
cp config/env-poweron-nyla-int.env .env
- name: Install dependencies
run: |

View file

@ -27,7 +27,7 @@ jobs:
- name: Copy production environment file
run: |
cp config/.env.prod .env
cp config/env-poweron-nyla-prod.env .env
- name: Install dependencies
run: |

6
.gitignore vendored
View file

@ -30,7 +30,5 @@ dist-ssr
.cursorignore
# Keep environment template files in config/
!config/.env.dev
!config/.env.int
!config/.env.prod
# Keep environment files in config/ (naming: env-<workflow>.env)
!config/env-*.env

View file

@ -5,9 +5,9 @@
```mermaid
graph TB
%% Environment Files
ENV_DEV[".env.dev<br/>Development"]
ENV_PROD[".env.prod<br/>Production"]
ENV_INT[".env.int<br/>Integration"]
ENV_DEV["env-poweron-nyla-dev.env<br/>Development"]
ENV_PROD["env-poweron-nyla-prod.env<br/>Production"]
ENV_INT["env-poweron-nyla-int.env<br/>Integration"]
%% Configuration System
CONFIG_TS["config.ts<br/>TypeScript Config<br/>(React Frontend)"]
@ -114,30 +114,25 @@ The app uses a **dual configuration system** to handle environment variables acr
- **Used by:** Express servers and build scripts
### Environment Files
- **`config/.env.dev`** - Development environment variables
- **Why:** Separate config for local development with debug settings
- **How:** Copied to root `.env` by `npm run dev` command
- **Contains:** Local API URLs, debug flags, dev-specific settings
- **`config/.env.prod`** - Production environment variables
- **Why:** Production-specific settings (live API URLs, optimized settings)
- **How:** Copied to root `.env` by GitHub Actions workflow
- **Contains:** Production API URLs, security settings, performance configs
Naming convention: `env-<workflow-name>.env` — matches the GitHub Actions workflow that uses it.
- **`config/.env.int`** - Integration environment variables
- **Why:** Testing environment that mirrors production but with test data
- **How:** Copied to root `.env` by integration deployment workflow
- **Contains:** Staging API URLs, test user credentials, integration settings
- **`config/env-poweron-nyla-dev.env`** — Local development (localhost gateway)
- **`config/env-poweron-nyla-int.env`** — Integration (used by `poweron_nyla_int` workflow)
- **`config/env-poweron-nyla-prod.env`** — Production (used by `poweron_nyla_main` workflow)
Each env is copied to root `.env` at build time (by CI or manually for local dev).
### Usage
```bash
# Development (loads .env.dev)
# Local development — copy env then start Vite
cp config/env-poweron-nyla-dev.env .env
npm run dev
# Production build (loads .env.prod)
# Production build (CI copies env-poweron-nyla-prod.env → .env)
npm run build:prod
# Integration build (loads .env.int)
# Integration build (CI copies env-poweron-nyla-int.env → .env)
npm run build:int
```

View file

@ -1,34 +0,0 @@
# Development Environment Configuration
# Frontend Nyla - Development
# API Configuration
VITE_API_BASE_URL="http://localhost:8000/"
VITE_API_TIMEOUT=10000
# Microsoft Entra ID Configuration
VITE_MICROSOFT_CLIENT_ID=24cd6c8a-b592-4905-a5ba-d5fa9f911154
VITE_MICROSOFT_TENANT_ID=6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_CLIENT_SECRET=2iw8Q~jwqG1iacxHopBt5pstu6R45UC1gIQabcbD
VITE_ENTRA_AUTHORITY=https://login.microsoftonline.com/6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_REDIRECT_PATH=/auth/callback/
VITE_ENTRA_REDIRECT_URI=http://localhost:8000/api/msft/auth/callback/
# Application Configuration
VITE_APP_NAME=PowerOn Nyla dev
VITE_APP_VERSION=0.0.0
VITE_APP_ENVIRONMENT=development
# Debug Configuration
VITE_DEBUG=true
VITE_LOG_LEVEL=debug
VITE_ENABLE_CONSOLE_LOGS=true
# Feature Flags
VITE_ENABLE_ANALYTICS=false
VITE_ENABLE_ERROR_REPORTING=false
VITE_ENABLE_PERFORMANCE_MONITORING=false
# Development Server
VITE_DEV_SERVER_PORT=5176
VITE_DEV_SERVER_HOST=localhost
VITE_DEV_SERVER_HTTPS=false

View file

@ -1,33 +0,0 @@
# Integration/Test Environment Configuration
# Frontend Nyla - Integration
# API Configuration
VITE_API_BASE_URL=https://gateway-int.poweron-center.net
VITE_API_TIMEOUT=12000
# Microsoft Entra ID Configuration
VITE_MICROSOFT_CLIENT_ID=24cd6c8a-b592-4905-a5ba-d5fa9f911154
VITE_MICROSOFT_TENANT_ID=6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_CLIENT_SECRET=2iw8Q~jwqG1iacxHopBt5pstu6R45UC1gIQabcbD
VITE_ENTRA_AUTHORITY=https://login.microsoftonline.com/6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_REDIRECT_PATH=/auth/callback/
VITE_ENTRA_REDIRECT_URI=https://gateway-int.poweron-center.net/api/msft/auth/callback/
# Application Configuration
VITE_APP_NAME=Poweron Nyla int
VITE_APP_VERSION=0.0.0
VITE_APP_ENVIRONMENT=integration
# Debug Configuration
VITE_DEBUG=true
VITE_LOG_LEVEL=info
VITE_ENABLE_CONSOLE_LOGS=true
# Feature Flags
VITE_ENABLE_ANALYTICS=true
VITE_ENABLE_ERROR_REPORTING=true
VITE_ENABLE_PERFORMANCE_MONITORING=true
# Test Configuration
VITE_ENABLE_MOCK_DATA=false
VITE_ENABLE_TEST_MODE=true

View file

@ -1,33 +0,0 @@
# Production Environment Configuration
# Frontend Nyla - Production
# API Configuration
VITE_API_BASE_URL=https://gateway-prod.poweron-center.net
VITE_API_TIMEOUT=15000
# Microsoft Entra ID Configuration
VITE_MICROSOFT_CLIENT_ID=24cd6c8a-b592-4905-a5ba-d5fa9f911154
VITE_MICROSOFT_TENANT_ID=6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_CLIENT_SECRET=2iw8Q~jwqG1iacxHopBt5pstu6R45UC1gIQabcbD
VITE_ENTRA_AUTHORITY=https://login.microsoftonline.com/6a51aaeb-2467-4186-9504-2a05aedc591f
VITE_ENTRA_REDIRECT_PATH=/auth/callback/
VITE_ENTRA_REDIRECT_URI=https://gateway-prod.poweron-center.net/api/msft/auth/callback/
# Application Configuration
VITE_APP_NAME=PowerOn Nyla
VITE_APP_VERSION=0.0.0
VITE_APP_ENVIRONMENT=production
# Debug Configuration
VITE_DEBUG=false
VITE_LOG_LEVEL=error
VITE_ENABLE_CONSOLE_LOGS=false
# Feature Flags
VITE_ENABLE_ANALYTICS=true
VITE_ENABLE_ERROR_REPORTING=true
VITE_ENABLE_PERFORMANCE_MONITORING=true
# Security Configuration
VITE_ENABLE_HTTPS=true
VITE_ENABLE_CSP=true

View file

@ -0,0 +1,6 @@
# Environment: poweron-nyla-dev (local development)
# Consumed by: Vite build (title) + SPA runtime (getApiBaseUrl / getAppName)
# Auth and secrets live on the gateway — never in frontend env.
VITE_API_BASE_URL="http://localhost:8000/"
VITE_APP_NAME=PowerOn Nyla dev

View file

@ -0,0 +1,6 @@
# Environment: poweron-nyla-int (integration)
# Consumed by: Vite build (title) + SPA runtime (getApiBaseUrl / getAppName)
# Auth and secrets live on the gateway — never in frontend env.
VITE_API_BASE_URL=https://gateway-int.poweron.swiss
VITE_APP_NAME=Poweron Nyla int

View file

@ -0,0 +1,6 @@
# Environment: poweron-nyla-prod (production)
# Consumed by: Vite build (title) + SPA runtime (getApiBaseUrl / getAppName)
# Auth and secrets live on the gateway — never in frontend env.
VITE_API_BASE_URL=https://gateway-prod.poweron.swiss
VITE_APP_NAME=PowerOn Nyla

15
env.d.ts vendored
View file

@ -1,15 +1,6 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_MICROSOFT_CLIENT_ID: string
readonly VITE_MICROSOFT_TENANT_ID: string
readonly VITE_ENTRA_CLIENT_SECRET: string
readonly VITE_ENTRA_AUTHORITY: string
readonly VITE_ENTRA_REDIRECT_PATH: string
readonly VITE_ENTRA_REDIRECT_URI: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
readonly VITE_API_BASE_URL?: string
readonly VITE_APP_NAME?: string
}

36
package-lock.json generated
View file

@ -8,8 +8,6 @@
"name": "frontend_nyla_new",
"version": "0.0.0",
"dependencies": {
"@azure/msal-browser": "^4.12.0",
"@azure/msal-react": "^3.0.12",
"@monaco-editor/react": "^4.7.0",
"@types/leaflet": "^1.9.21",
"@xstate/react": "^5.0.0",
@ -101,40 +99,6 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/@azure/msal-browser": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.16.0.tgz",
"integrity": "sha512-yF8gqyq7tVnYftnrWaNaxWpqhGQXoXpDfwBtL7UCGlIbDMQ1PUJF/T2xCL6NyDNHoO70qp1xU8GjjYTyNIefkw==",
"license": "MIT",
"dependencies": {
"@azure/msal-common": "15.9.0"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@azure/msal-common": {
"version": "15.9.0",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.9.0.tgz",
"integrity": "sha512-lbz/D+C9ixUG3hiZzBLjU79a0+5ZXCorjel3mwXluisKNH0/rOS/ajm8yi4yI9RP5Uc70CAcs9Ipd0051Oh/kA==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@azure/msal-react": {
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-3.0.16.tgz",
"integrity": "sha512-fIFc3z9UrHoOCG4rApNWMRr83DnQlo+CHfLSPNBQa4rndIkr+XYBpdYDqlzqtmikRf3A+CYNVOQ+lQX6jM0zdw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@azure/msal-browser": "^4.16.0",
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",

View file

@ -18,8 +18,6 @@
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@azure/msal-browser": "^4.12.0",
"@azure/msal-react": "^3.0.12",
"@monaco-editor/react": "^4.7.0",
"@types/leaflet": "^1.9.21",
"@xstate/react": "^5.0.0",

View file

@ -25,7 +25,6 @@ import Reset from './pages/Reset';
import { InvitePage } from './pages/InvitePage';
// Providers
import { AuthProvider } from './providers/auth/AuthProvider';
import { ProtectedRoute } from './providers/auth/ProtectedRoute';
import { LanguageProvider } from './providers/language/LanguageContext';
import { ToastProvider } from './contexts/ToastContext';
@ -71,7 +70,6 @@ function App() {
return (
<LanguageProvider>
<AuthProvider>
<ToastProvider>
<VoiceCatalogProvider>
<WorkflowSelectionProvider>
@ -240,7 +238,6 @@ function App() {
</WorkflowSelectionProvider>
</VoiceCatalogProvider>
</ToastProvider>
</AuthProvider>
</LanguageProvider>
);
}

View file

@ -1,15 +1,12 @@
import { useState } from 'react';
import { useMsal } from '@azure/msal-react';
import api from '../api';
import { useApiRequest } from './useApi';
import { getApiBaseUrl } from '../../config/config';
import { setUserDataCache, clearUserDataCache, type CachedUserData } from '../utils/userCache';
import {
loginApi,
fetchCurrentUserApi,
registerApi,
registerWithMsalApi,
checkUsernameAvailabilityApi,
logoutApi,
requestPasswordResetApi,
@ -18,7 +15,6 @@ import {
type RegisterResponse,
type UsernameAvailabilityResponse,
type RegisterData,
type MsalRegisterData,
type PasswordResetRequestResponse,
type PasswordResetResponse
} from '../api/authApi';
@ -408,48 +404,6 @@ export function useGoogleAuth() {
};
}
// Microsoft Registration
export function useMsalRegister() {
const { instance, accounts } = useMsal();
const { request, isLoading, error } = useApiRequest<MsalRegisterData, any>();
const registerWithMsal = async (): Promise<RegisterResponse> => {
try {
if (!accounts || accounts.length === 0) {
// If not signed in with Microsoft, sign in first
await instance.loginPopup({
scopes: ['user.read']
});
}
// Get the current account
const currentAccount = instance.getAllAccounts()[0];
if (!currentAccount) {
throw new Error('No Microsoft account found');
}
// Prepare user data from Microsoft account
const userData: MsalRegisterData = {
username: currentAccount.username,
email: currentAccount.username,
fullName: currentAccount.name || currentAccount.username,
language: 'de'
};
// Register the user through our backend
return await registerWithMsalApi(request, userData);
} catch (error: any) {
throw error;
}
};
return {
registerWithMsal,
error,
isLoading
};
}
// Username availability check
export function useUsernameAvailability() {
const [isChecking, setIsChecking] = useState(false);
@ -568,145 +522,32 @@ export function useLogout() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const _clearLocalState = () => {
clearUserDataCache();
localStorage.removeItem('authToken');
sessionStorage.clear();
document.cookie.split(";").forEach((c) => {
const name = c.split("=")[0].trim();
if (name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
});
};
const logout = async (): Promise<void> => {
setIsLoading(true);
setError(null);
try {
// Call logout endpoint to clear JWT tokens on server
await logoutApi();
// CRITICAL: Wait for browser to process Set-Cookie headers from logout response
// This gives the browser time to clear httpOnly cookies before redirect
// Give browser time to process Set-Cookie headers from logout response
await new Promise(resolve => setTimeout(resolve, 1000));
// Clear user data cache from sessionStorage
clearUserDataCache();
// Clear auth authority from sessionStorage
sessionStorage.removeItem('auth_authority');
// Clear MSAL cache tokens from localStorage
// MSAL stores tokens with keys starting with 'msal.'
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (
key.startsWith('msal.') ||
key === 'auth_token' ||
key === 'refresh_token' ||
key.includes('token') ||
key.includes('auth') ||
key.includes('msal')
)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
localStorage.removeItem(key);
});
// Clear ALL MSAL cache data (including account keys, token keys, version)
const msalKeysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('msal.')) {
msalKeysToRemove.push(key);
}
}
msalKeysToRemove.forEach(key => {
localStorage.removeItem(key);
});
// Clear sessionStorage as well (CSRF tokens, etc.)
sessionStorage.clear();
// Clear cookies as backup (in case backend doesn't clear them properly)
// Note: This only works for cookies that are accessible to JavaScript
const cookies = document.cookie.split(";");
cookies.forEach(function(c) {
const cookieName = c.split("=")[0].trim();
if (cookieName === 'auth_token' || cookieName === 'refresh_token' || cookieName.includes('token') || cookieName.includes('msal')) {
document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
});
// Redirect to login page
window.location.href = '/login?logout=true';
} catch (error: any) {
let errorMessage = 'Logout failed';
if (error.response) {
errorMessage = error.response.data?.detail || errorMessage;
}
setError(errorMessage);
// Even if logout fails on server, clear local data and redirect
clearUserDataCache();
sessionStorage.removeItem('auth_authority');
// Clear MSAL cache tokens from localStorage
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (
key.startsWith('msal.') ||
key === 'auth_token' ||
key === 'refresh_token' ||
key.includes('token') ||
key.includes('auth') ||
key.includes('msal')
)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
localStorage.removeItem(key);
});
// Clear ALL MSAL cache data (including account keys, token keys, version)
const msalKeysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('msal.')) {
msalKeysToRemove.push(key);
}
}
msalKeysToRemove.forEach(key => {
localStorage.removeItem(key);
});
// Clear sessionStorage as well
sessionStorage.clear();
// Clear cookies as backup (in case backend doesn't clear them properly)
document.cookie.split(";").forEach(function(c) {
const cookieName = c.split("=")[0].trim();
if (cookieName === 'auth_token' || cookieName === 'refresh_token' || cookieName.includes('token') || cookieName.includes('msal')) {
document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
});
window.location.href = '/login?logout=true';
} catch (err: any) {
setError(err.response?.data?.detail || 'Logout failed');
} finally {
_clearLocalState();
setIsLoading(false);
window.location.href = '/login?logout=true';
}
};

View file

@ -172,36 +172,14 @@ export function useCurrentUser() {
// Clear auth authority from sessionStorage
sessionStorage.removeItem('auth_authority');
// Optional: clear MSAL browser cache only (PowerOn JWT lives in httpOnly cookies + backend).
// Do not call msal.logoutRedirect — that signs the user out of Microsoft globally.
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key && key.startsWith('msal.')) {
localStorage.removeItem(key);
}
}
localStorage.removeItem('authToken');
// Clear cookies as backup (in case backend doesn't clear them properly)
// Note: This only works for cookies that are accessible to JavaScript
console.log('🍪 Checking cookies for cleanup...');
console.log('🍪 All cookies:', document.cookie);
const cookies = document.cookie.split(";");
console.log('🍪 Cookie count:', cookies.length);
cookies.forEach(function(c) {
const cookieName = c.split("=")[0].trim();
console.log('🍪 Checking cookie:', cookieName);
if (cookieName === 'auth_token' || cookieName === 'refresh_token' || cookieName.includes('token') || cookieName.includes('msal')) {
console.log('🗑️ Clearing cookie:', cookieName);
document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
document.cookie.split(";").forEach((c) => {
const name = c.split("=")[0].trim();
if (name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
});
console.log('🍪 Cookies after cleanup attempt:', document.cookie);
console.log('✅ Cleanup completed');
// Redirect to login or home page
console.log('🔄 Redirecting to login page...');

View file

@ -3,7 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
import { FaEnvelopeOpenText } from 'react-icons/fa';
import styles from './Register.module.css';
import { useRegister, useMsalRegister, useUsernameAvailability } from '../hooks/useAuthentication';
import { useRegister, useUsernameAvailability } from '../hooks/useAuthentication';
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
import { PENDING_INVITATION_KEY } from './InvitePage';
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
@ -21,7 +21,6 @@ function Register() {
const navigate = useNavigate();
const location = useLocation();
const { register, error: registerError, isLoading } = useRegister();
const { error: msalError } = useMsalRegister();
const { checkAvailability, isChecking, error: availabilityError } = useUsernameAvailability();
const invitationUsername = (location.state as any)?.invitationUsername || '';
const invitationEmail = (location.state as any)?.invitationEmail || '';
@ -118,7 +117,6 @@ function Register() {
const _getErrorMessage = () => {
if (validationError) return validationError;
if (registerError) return typeof registerError === 'string' ? registerError : t('Registrierung fehlgeschlagen');
if (msalError) return typeof msalError === 'string' ? msalError : t('Microsoft-Registrierung fehlgeschlagen');
if (availabilityError) return typeof availabilityError === 'string' ? availabilityError : t('Benutzernamen-Prüfung fehlgeschlagen');
return null;
};

View file

@ -1,99 +0,0 @@
import {
AuthenticationResult,
EventType,
PublicClientApplication
} from "@azure/msal-browser";
import { msalConfig } from "./authConfig";
import { MsalProvider } from "@azure/msal-react";
import { ReactNode, useEffect, useState } from "react";
import { useLanguage } from "../language/LanguageContext";
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider = ({ children }: AuthProviderProps) => {
const { t } = useLanguage();
const [msalInstance, setMsalInstance] = useState<PublicClientApplication | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
const msalApp = new PublicClientApplication(msalConfig);
const initializeMsal = async () => {
try {
// Set event handlers first, so we catch all events
msalApp.addEventCallback((event) => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
const payload = event?.payload as AuthenticationResult;
if (payload?.account) {
msalApp.setActiveAccount(payload.account);
console.log("MSAL login successful");
// Store authentication authority for backend communication
if (payload.account?.environment) {
sessionStorage.setItem('auth_authority', payload.account.environment);
}
console.log('✅ MSAL login successful - tokens will be set in httpOnly cookies by backend');
}
} else if (event.eventType === EventType.LOGIN_FAILURE) {
console.error("MSAL login failed:", event.error);
}
});
// Initialize MSAL
await msalApp.initialize();
msalApp.enableAccountStorageEvents();
// Handle any redirect response
const response = await msalApp.handleRedirectPromise();
if (response) {
// If we have a response, we've completed a redirect flow
console.log("MSAL redirect completed successfully");
if (response.account) {
msalApp.setActiveAccount(response.account);
// Store authentication authority
if (response.account.environment) {
sessionStorage.setItem('auth_authority', response.account.environment);
}
console.log('✅ MSAL redirect completed - tokens will be set in httpOnly cookies by backend');
}
}
// Check for accounts
const accounts = msalApp.getAllAccounts();
if (accounts.length > 0) {
msalApp.setActiveAccount(accounts[0]);
// Store authentication authority for existing accounts
if (accounts[0].environment) {
sessionStorage.setItem('auth_authority', accounts[0].environment);
}
console.log('✅ MSAL account found - tokens will be set in httpOnly cookies by backend');
}
setMsalInstance(msalApp);
setIsInitialized(true);
} catch (err) {
console.error("MSAL initialization failed", err);
}
};
initializeMsal();
}, []);
if (!isInitialized || !msalInstance) {
return <div>{t('Authentifizierung wird geladen…')}</div>;
}
return <MsalProvider instance={msalInstance}>{children}</MsalProvider>;
};
export function useAuthProvider() {
return { AuthProvider };
}

View file

@ -1,4 +1,3 @@
import { useMsal } from "@azure/msal-react";
import { Navigate, useLocation } from "react-router-dom";
import { ReactNode, useEffect, useState } from "react";
import { useLanguage } from "../language/LanguageContext";
@ -13,110 +12,36 @@ export const ProtectedRoute = ({
redirectPath = "/login"
}: ProtectedRouteProps) => {
const { t } = useLanguage();
const { accounts } = useMsal();
const location = useLocation();
const [isChecking, setIsChecking] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
const checkAuthentication = async () => {
try {
// Check for MSAL authentication
const hasMsalAccount = accounts.length > 0;
// Check for backend authentication via API call
let hasBackendAuth = false;
try {
// Check for authentication authority (httpOnly cookies are handled automatically)
const authAuthority = sessionStorage.getItem('auth_authority');
console.log('🔍 Checking auth authority:', authAuthority);
if (authAuthority) {
hasBackendAuth = true;
console.log('✅ Authenticated with backend (httpOnly cookies), authority:', authAuthority);
} else {
hasBackendAuth = false;
console.log('❌ No authentication authority found');
}
} catch (error) {
console.log('❌ Backend authentication failed:', error);
hasBackendAuth = false;
}
const authAuthority = sessionStorage.getItem('auth_authority');
setIsAuthenticated(!!authAuthority);
setIsChecking(false);
}, []);
// User is authenticated if either method is valid
const isAuth = hasMsalAccount || hasBackendAuth;
setIsAuthenticated(isAuth);
console.log('🔐 Authentication status:', {
hasMsalAccount,
hasBackendAuth,
isAuthenticated: isAuth,
authAuthority: sessionStorage.getItem('auth_authority')
});
if (hasBackendAuth) {
console.log('✅ Authenticated with backend cookies');
} else if (hasMsalAccount) {
console.log('✅ Authenticated with MSAL');
} else {
console.log('❌ No valid authentication found');
}
} catch (error) {
console.error('❌ Error checking authentication:', error);
setIsAuthenticated(false);
} finally {
setIsChecking(false);
}
};
// Small delay to ensure MSAL is initialized and localStorage is updated
const timer = setTimeout(() => {
checkAuthentication();
}, 200);
return () => clearTimeout(timer);
}, [accounts]);
// Re-check authentication when component mounts or accounts change
// This handles cases where auth_authority is set after initial mount
useEffect(() => {
if (!isChecking) {
// Double-check authentication state periodically when not initially loading
const recheckTimer = setTimeout(() => {
const authAuthority = sessionStorage.getItem('auth_authority');
const hasMsalAccount = accounts.length > 0;
const hasBackendAuth = !!authAuthority;
const isAuth = hasMsalAccount || hasBackendAuth;
// Only update if authentication state actually changed
const isAuth = !!authAuthority;
if (isAuth !== isAuthenticated) {
console.log('🔄 Authentication state changed, updating...', {
previous: isAuthenticated,
current: isAuth,
authAuthority,
hasMsalAccount,
hasBackendAuth
});
setIsAuthenticated(isAuth);
}
}, 300);
return () => clearTimeout(recheckTimer);
}
}, [isChecking, isAuthenticated, accounts]);
}, [isChecking, isAuthenticated]);
// If still checking, show loading
if (isChecking) {
return <div>{t('Authentifizierung wird geprüft…')}</div>;
}
// Check if user is authenticated through either method
if (!isAuthenticated) {
console.log("No valid authentication found, redirecting to login");
return <Navigate to={redirectPath} state={{ from: location }} replace />;
}
console.log("User is authenticated, rendering protected content");
return <>{children}</>;
};
};

View file

@ -1,45 +0,0 @@
import { LogLevel } from '@azure/msal-browser';
export const msalConfig = {
auth: {
clientId: '24cd6c8a-b592-4905-a5ba-d5fa9f911154',
authority: 'https://login.microsoftonline.com/6a51aaeb-2467-4186-9504-2a05aedc591f/',
redirectUri: '/',
postLogoutRedirectUri: '/',
navigateToLoginRequestUrl: false,
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level: any, message: any, containsPii: any) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
return;
}
},
},
},
};
export const loginRequest = {
scopes: ["openid", "profile", "email", "api://24cd6c8a-b592-4905-a5ba-d5fa9f911154/user_impersonation"],
};