/** * QuadrantX Dashboard - Authentication Module * * Handles Cognito authentication using the hosted UI. */ class AuthManager { constructor(config) { this.config = config; this.tokens = null; this.user = null; } /** * Initialize authentication state */ async init() { // Check for callback with auth code const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { await this.handleCallback(code); // Clean URL window.history.replaceState({}, document.title, window.location.pathname); } // Check for stored tokens this.loadTokens(); if (this.tokens && this.tokens.id_token) { // Validate token if (this.isTokenExpired(this.tokens.id_token)) { // Try refresh if (this.tokens.refresh_token) { await this.refreshTokens(); } else { this.clearTokens(); } } if (this.tokens) { this.user = this.parseIdToken(this.tokens.id_token); } } return this.isAuthenticated(); } /** * Check if user is authenticated */ isAuthenticated() { return this.tokens !== null && this.tokens.id_token !== null; } /** * Check if user is approved */ isApproved() { // Cognito custom attributes are prefixed with 'custom:' return this.user && (this.user['custom:approved'] === 'true' || this.user.approved === 'true'); } /** * Get allowed categories */ getAllowedCategories() { if (!this.user) return []; try { const categories = this.user['custom:allowed_categories'] || this.user.allowed_categories || '[]'; // Handle "ALL" as a special case if (categories === 'ALL') return 'ALL'; return JSON.parse(categories); } catch { return []; } } /** * Get user's role */ getRole() { return this.user ? (this.user['custom:role'] || this.user.role) : null; } /** * Get user's email */ getEmail() { return this.user ? this.user.email : null; } /** * Get access token for API calls */ getAccessToken() { return this.tokens ? this.tokens.access_token : null; } /** * Get ID token for API calls */ getIdToken() { return this.tokens ? this.tokens.id_token : null; } /** * Redirect to Cognito login page */ login() { const loginUrl = this.buildAuthUrl('login'); window.location.href = loginUrl; } /** * Redirect to Cognito signup page */ signup() { const signupUrl = this.buildAuthUrl('signup'); window.location.href = signupUrl; } /** * Logout user */ logout() { this.clearTokens(); const logoutUrl = `${this.config.COGNITO_DOMAIN}/logout?` + `client_id=${this.config.COGNITO_CLIENT_ID}&` + `logout_uri=${encodeURIComponent(this.config.REDIRECT_URI)}`; window.location.href = logoutUrl; } /** * Build Cognito hosted UI URL */ buildAuthUrl(action = 'login') { const endpoint = action === 'signup' ? 'signup' : 'login'; return `${this.config.COGNITO_DOMAIN}/${endpoint}?` + `client_id=${this.config.COGNITO_CLIENT_ID}&` + `response_type=code&` + `scope=email+openid+profile&` + `redirect_uri=${encodeURIComponent(this.config.REDIRECT_URI)}`; } /** * Handle OAuth callback with authorization code */ async handleCallback(code) { try { const tokenEndpoint = `${this.config.COGNITO_DOMAIN}/oauth2/token`; const body = new URLSearchParams({ grant_type: 'authorization_code', client_id: this.config.COGNITO_CLIENT_ID, code: code, redirect_uri: this.config.REDIRECT_URI }); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() }); if (!response.ok) { throw new Error('Token exchange failed'); } const tokens = await response.json(); this.saveTokens(tokens); this.user = this.parseIdToken(tokens.id_token); } catch (error) { console.error('Auth callback error:', error); this.clearTokens(); } } /** * Refresh tokens using refresh token */ async refreshTokens() { if (!this.tokens || !this.tokens.refresh_token) { return false; } try { const tokenEndpoint = `${this.config.COGNITO_DOMAIN}/oauth2/token`; const body = new URLSearchParams({ grant_type: 'refresh_token', client_id: this.config.COGNITO_CLIENT_ID, refresh_token: this.tokens.refresh_token }); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() }); if (!response.ok) { throw new Error('Token refresh failed'); } const newTokens = await response.json(); // Preserve refresh token if not returned if (!newTokens.refresh_token) { newTokens.refresh_token = this.tokens.refresh_token; } this.saveTokens(newTokens); this.user = this.parseIdToken(newTokens.id_token); return true; } catch (error) { console.error('Token refresh error:', error); this.clearTokens(); return false; } } /** * Parse JWT ID token to extract claims */ parseIdToken(idToken) { try { const parts = idToken.split('.'); if (parts.length !== 3) return null; const payload = JSON.parse(atob(parts[1])); return payload; } catch (error) { console.error('Token parse error:', error); return null; } } /** * Check if token is expired */ isTokenExpired(token) { try { const payload = this.parseIdToken(token); if (!payload || !payload.exp) return true; // Add 60 second buffer return Date.now() >= (payload.exp * 1000) - 60000; } catch { return true; } } /** * Save tokens to localStorage */ saveTokens(tokens) { this.tokens = tokens; localStorage.setItem('quadrantx_tokens', JSON.stringify(tokens)); } /** * Load tokens from localStorage */ loadTokens() { try { const stored = localStorage.getItem('quadrantx_tokens'); if (stored) { this.tokens = JSON.parse(stored); } } catch (error) { console.error('Token load error:', error); this.tokens = null; } } /** * Clear all stored tokens */ clearTokens() { this.tokens = null; this.user = null; localStorage.removeItem('quadrantx_tokens'); } } // Export for use in app window.AuthManager = AuthManager;