Merge pull request #78 from valueonag/feat/demo-system-readieness
dns switch poweron-center to poweron.swiss
This commit is contained in:
commit
3477126d9f
20 changed files with 68 additions and 609 deletions
2
.github/workflows/poweron_nyla_int.yml
vendored
2
.github/workflows/poweron_nyla_int.yml
vendored
|
|
@ -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: |
|
||||
|
|
|
|||
2
.github/workflows/poweron_nyla_main.yml
vendored
2
.github/workflows/poweron_nyla_main.yml
vendored
|
|
@ -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
6
.gitignore
vendored
|
|
@ -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
|
||||
31
README.md
31
README.md
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
6
config/env-poweron-nyla-dev.env
Normal file
6
config/env-poweron-nyla-dev.env
Normal 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
|
||||
6
config/env-poweron-nyla-int.env
Normal file
6
config/env-poweron-nyla-int.env
Normal 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
|
||||
6
config/env-poweron-nyla-prod.env
Normal file
6
config/env-poweron-nyla-prod.env
Normal 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
15
env.d.ts
vendored
|
|
@ -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
36
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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...');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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}</>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
};
|
||||
Loading…
Reference in a new issue