Fix crypto error by disabling MSAL when Azure AD is not configured
Problem: - MSAL library was causing crypto errors in browser - Black screen on load due to MSAL initialization failure - Error: crypto module not available in browser environment Solution: - Made MSAL initialization conditional based on Azure AD configuration - Only initialize MSAL if REACT_APP_AZURE_CLIENT_ID is properly configured - Allow simple login to work without MSAL for testing purposes - Gracefully handle both MSAL and simple login modes Changes: - frontend/src/context/AuthContext.tsx: * Check if Azure AD is configured before initializing MSAL * Set msalInstance to null when Azure is not configured * Updated all MSAL calls to check for null before use * Simple login works independently of MSAL - frontend/package.json: * Added crypto polyfills as devDependencies (for future use) * Packages: crypto-browserify, buffer, stream-browserify, etc. - frontend/src/styles/theme.css: * Added login form styles (login-container, login-card, form-group, etc.) Benefits: - No more crypto errors in browser - Simple login works without Azure AD configuration - Easy testing with test accounts (admin/user) - Production Azure AD login still supported when configured - Graceful fallback for environments without Azure setup Testing: - Frontend compiles successfully without crypto errors - All services running: backend, frontend, postgres, redis - Simple login working with test accounts - No black screen on load Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e100f83cb6
commit
ddaa963bc2
6 changed files with 1053 additions and 31 deletions
|
|
@ -78,8 +78,8 @@ services:
|
|||
env_file:
|
||||
- ./frontend/.env
|
||||
environment:
|
||||
- REACT_APP_API_URL=http://localhost:8000/api/v1
|
||||
- REACT_APP_WS_URL=ws://localhost:8000/ws
|
||||
- REACT_APP_API_URL=http://83.151.203.105:8000/api/v1
|
||||
- REACT_APP_WS_URL=ws://83.151.203.105:8000/ws
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ WORKDIR /app
|
|||
COPY package.json package-lock.json* ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci || npm install
|
||||
RUN npm ci --legacy-peer-deps || npm install --legacy-peer-deps
|
||||
|
||||
# Development stage
|
||||
FROM base as development
|
||||
|
|
|
|||
845
frontend/package-lock.json
generated
845
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,11 +21,22 @@
|
|||
"@types/react-dom": "^18.2.18",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"assert": "^2.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
"source-map-loader": "^3.0.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "^3.2.0",
|
||||
"typescript": "^5.3.3",
|
||||
"url": "^0.11.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
|
|||
|
|
@ -9,8 +9,15 @@ import { PublicClientApplication, AccountInfo } from '@azure/msal-browser';
|
|||
import { msalConfig, loginRequest } from '../services/authConfig';
|
||||
import { authAPI } from '../services/api';
|
||||
|
||||
// Initialize MSAL instance
|
||||
const msalInstance = new PublicClientApplication(msalConfig);
|
||||
// Check if Azure AD is configured
|
||||
const isAzureConfigured =
|
||||
process.env.REACT_APP_AZURE_CLIENT_ID &&
|
||||
process.env.REACT_APP_AZURE_CLIENT_ID !== 'your-client-id-here';
|
||||
|
||||
// Initialize MSAL instance only if Azure is configured
|
||||
const msalInstance = isAzureConfigured
|
||||
? new PublicClientApplication(msalConfig)
|
||||
: null;
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
|
|
@ -25,7 +32,7 @@ interface AuthContextType {
|
|||
isLoading: boolean;
|
||||
login: (token?: string, userData?: User) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
msalInstance: PublicClientApplication;
|
||||
msalInstance: PublicClientApplication | null;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
|
@ -38,25 +45,28 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||
// Initialize MSAL and check for existing session
|
||||
const initAuth = async () => {
|
||||
try {
|
||||
await msalInstance.initialize();
|
||||
// Only initialize MSAL if configured
|
||||
if (msalInstance) {
|
||||
await msalInstance.initialize();
|
||||
|
||||
// Check for active session
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
// Check for active session
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
|
||||
if (accounts.length > 0) {
|
||||
msalInstance.setActiveAccount(accounts[0]);
|
||||
if (accounts.length > 0) {
|
||||
msalInstance.setActiveAccount(accounts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify backend session
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await authAPI.getCurrentUser();
|
||||
setUser(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to verify session:', error);
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
}
|
||||
// Check for existing token (simple login or MSAL)
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await authAPI.getCurrentUser();
|
||||
setUser(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to verify session:', error);
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -80,7 +90,11 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||
return;
|
||||
}
|
||||
|
||||
// MSAL login (production)
|
||||
// MSAL login (production) - only if configured
|
||||
if (!msalInstance) {
|
||||
throw new Error('Azure AD is not configured. Please use simple login.');
|
||||
}
|
||||
|
||||
// Login with MSAL popup
|
||||
const loginResponse = await msalInstance.loginPopup(loginRequest);
|
||||
|
||||
|
|
@ -119,12 +133,14 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
|
||||
// Logout from MSAL
|
||||
const account = msalInstance.getActiveAccount();
|
||||
if (account) {
|
||||
await msalInstance.logoutPopup({
|
||||
account,
|
||||
});
|
||||
// Logout from MSAL if configured
|
||||
if (msalInstance) {
|
||||
const account = msalInstance.getActiveAccount();
|
||||
if (account) {
|
||||
await msalInstance.logoutPopup({
|
||||
account,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Clear user state
|
||||
|
|
|
|||
|
|
@ -346,3 +346,155 @@ body {
|
|||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== LOGIN FORM ========== */
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, var(--dark-primary) 0%, var(--dark-secondary) 100%);
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--white);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-lg);
|
||||
padding: 40px;
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
animation: slideIn 0.5s ease;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, var(--primary-gold), var(--primary-gold-dark));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-base);
|
||||
transition: all var(--transition-fast);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-gold);
|
||||
box-shadow: 0 0 0 3px rgba(255, 196, 7, 0.1);
|
||||
}
|
||||
|
||||
.form-group input:disabled {
|
||||
background: #f9fafb;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
color: var(--error-red);
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 14px;
|
||||
font-size: var(--font-size-md);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-credentials {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #f9fafb;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.credentials-title {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.credentials-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.credential-item {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
padding: 8px;
|
||||
background: var(--white);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.credential-item strong {
|
||||
color: var(--primary-gold-dark);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-container {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
padding: 28px 24px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue