/** * Multi-Tenant Authentication Context * Handles authentication for different tenants and providers */ import React, { createContext, useContext, useReducer, useEffect } from 'react'; import api from '../services/api'; const MultiTenantAuthContext = createContext(); const authReducer = (state, action) => { switch (action.type) { case 'SET_LOADING': return { ...state, loading: action.payload }; case 'SET_TENANT': return { ...state, tenant: action.payload }; case 'SET_AUTH_CONFIG': return { ...state, authConfig: action.payload }; case 'LOGIN_START': return { ...state, loading: true, error: null }; case 'LOGIN_SUCCESS': return { ...state, user: action.payload.user, token: action.payload.token, tenant: action.payload.tenant, isAuthenticated: true, loading: false, error: null }; case 'LOGIN_FAILURE': return { ...state, loading: false, error: action.payload, isAuthenticated: false }; case 'LOGOUT': return { ...state, user: null, token: null, isAuthenticated: false, loading: false }; case 'SET_ERROR': return { ...state, error: action.payload, loading: false }; default: return state; } }; const initialState = { user: null, token: localStorage.getItem('token'), tenant: null, authConfig: null, isAuthenticated: false, loading: true, error: null }; export const MultiTenantAuthProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, initialState); // Determine tenant from URL or storage const determineTenant = () => { // Method 1: Subdomain const hostname = window.location.hostname; const subdomain = hostname.split('.')[0]; if (subdomain && subdomain !== 'www' && subdomain !== 'localhost' && !hostname.includes('127.0.0.1')) { return subdomain; } // Method 2: URL parameter const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('tenant')) { return urlParams.get('tenant'); } // Method 3: Local storage const storedTenant = localStorage.getItem('tenant'); if (storedTenant) { return storedTenant; } // Default tenant return 'default'; }; // Initialize authentication useEffect(() => { const initAuth = async () => { try { const tenantId = determineTenant(); dispatch({ type: 'SET_TENANT', payload: tenantId }); localStorage.setItem('tenant', tenantId); // Get tenant auth configuration const authConfigResponse = await api.get(`/auth/config/${tenantId}`); dispatch({ type: 'SET_AUTH_CONFIG', payload: authConfigResponse.data.data }); // Check if user is already authenticated const token = localStorage.getItem('token'); if (token) { // Validate token with current tenant context const profileResponse = await api.get('/users/profile'); dispatch({ type: 'LOGIN_SUCCESS', payload: { user: profileResponse.data.data, token: token, tenant: tenantId } }); } else { dispatch({ type: 'SET_LOADING', payload: false }); } } catch (error) { console.error('Auth initialization error:', error); localStorage.removeItem('token'); dispatch({ type: 'SET_LOADING', payload: false }); } }; initAuth(); }, []); // Handle URL token (from SSO redirects) useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const urlToken = urlParams.get('token'); const urlTenant = urlParams.get('tenant'); if (urlToken && urlTenant) { localStorage.setItem('token', urlToken); localStorage.setItem('tenant', urlTenant); // Clean URL window.history.replaceState({}, document.title, window.location.pathname); // Reload to reinitialize with new token window.location.reload(); } }, []); // Login function const login = async (credentials) => { try { dispatch({ type: 'LOGIN_START' }); const tenantId = state.tenant || determineTenant(); // For local authentication, use existing login endpoint if (state.authConfig?.provider === 'local' || state.authConfig?.provider === 'ldap') { const response = await api.post('/auth/login', { ...credentials, tenant: tenantId }); const { user, token } = response.data.data; localStorage.setItem('token', token); localStorage.setItem('tenant', tenantId); dispatch({ type: 'LOGIN_SUCCESS', payload: { user, token, tenant: tenantId } }); return { success: true }; } else { // For SSO providers, redirect to SSO endpoint const ssoUrl = getSSOLoginUrl(state.authConfig, tenantId); window.location.href = ssoUrl; return { success: true, redirect: true }; } } catch (error) { const errorMessage = error.response?.data?.message || 'Login failed'; dispatch({ type: 'LOGIN_FAILURE', payload: errorMessage }); return { success: false, error: errorMessage }; } }; // SSO Login const loginWithSSO = () => { const tenantId = state.tenant || determineTenant(); const ssoUrl = getSSOLoginUrl(state.authConfig, tenantId); window.location.href = ssoUrl; }; // Get SSO login URL const getSSOLoginUrl = (authConfig, tenantId) => { const returnUrl = encodeURIComponent(window.location.pathname + window.location.search); switch (authConfig?.provider) { case 'saml': return `/auth/saml/${tenantId}/login?returnUrl=${returnUrl}`; case 'oauth': return `/auth/oauth/${tenantId}/login?returnUrl=${returnUrl}`; default: return `/login?tenant=${tenantId}`; } }; // Logout const logout = async () => { try { const tenantId = state.tenant; // Call logout endpoint const response = await api.post('/auth/logout'); // Clear local storage localStorage.removeItem('token'); localStorage.removeItem('tenant'); dispatch({ type: 'LOGOUT' }); // Handle provider-specific logout if (response.data.logout_url) { window.location.href = response.data.logout_url; } else { window.location.href = `/login?tenant=${tenantId}`; } } catch (error) { console.error('Logout error:', error); // Force logout even if API call fails localStorage.removeItem('token'); localStorage.removeItem('tenant'); dispatch({ type: 'LOGOUT' }); window.location.href = '/login'; } }; // Switch tenant const switchTenant = (newTenantId) => { localStorage.setItem('tenant', newTenantId); localStorage.removeItem('token'); // Clear token when switching tenants window.location.href = `/login?tenant=${newTenantId}`; }; const value = { ...state, login, logout, loginWithSSO, switchTenant, getSSOLoginUrl }; return ( {children} ); }; export const useMultiTenantAuth = () => { const context = useContext(MultiTenantAuthContext); if (!context) { throw new Error('useMultiTenantAuth must be used within a MultiTenantAuthProvider'); } return context; }; export default MultiTenantAuthContext;