/** * Tanflow Authentication Service * Handles OAuth flow with Tanflow IAM Suite */ import { TokenManager } from '../utils/tokenManager'; import axios from 'axios'; const TANFLOW_BASE_URL = import.meta.env.VITE_TANFLOW_BASE_URL || ''; const TANFLOW_CLIENT_ID = import.meta.env.VITE_TANFLOW_CLIENT_ID || ''; const TANFLOW_REDIRECT_URI = `${window.location.origin}/login/callback`; /** * Initiate Tanflow SSO login * Redirects user to Tanflow authorization endpoint */ export function initiateTanflowLogin(): void { // Check if we're coming from a logout - if so, add prompt=login to force re-authentication const urlParams = new URLSearchParams(window.location.search); const isAfterLogout = urlParams.has('logout') || urlParams.has('tanflow_logged_out'); // Clear any previous logout flags before starting new login if (isAfterLogout) { sessionStorage.removeItem('tanflow_logged_out'); sessionStorage.removeItem('__logout_in_progress__'); sessionStorage.removeItem('__force_logout__'); console.log('🚪 Cleared logout flags before initiating Tanflow login'); } const state = Math.random().toString(36).substring(7); // Store provider type and state to identify Tanflow callback sessionStorage.setItem('auth_provider', 'tanflow'); sessionStorage.setItem('tanflow_auth_state', state); let authUrl = `${TANFLOW_BASE_URL}/protocol/openid-connect/auth?` + `client_id=${TANFLOW_CLIENT_ID}&` + `response_type=code&` + `scope=openid&` + `redirect_uri=${encodeURIComponent(TANFLOW_REDIRECT_URI)}&` + `state=${state}`; // Add prompt=login if coming from logout to force re-authentication // This ensures Tanflow requires login even if a session still exists if (isAfterLogout) { authUrl += `&prompt=login`; console.log('🚪 Adding prompt=login to force re-authentication after logout'); } console.log('🚪 Initiating Tanflow login', { isAfterLogout, hasPrompt: isAfterLogout }); window.location.href = authUrl; } /** * Exchange authorization code for tokens via backend * Backend handles the token exchange securely with client secret */ export async function exchangeTanflowCodeForTokens( code: string, state: string ): Promise<{ accessToken: string; refreshToken: string; idToken: string; user: any; }> { const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; try { const response = await axios.post( `${API_BASE_URL}/auth/tanflow/token-exchange`, { code, redirectUri: TANFLOW_REDIRECT_URI, state, }, { withCredentials: true, headers: { 'Content-Type': 'application/json', }, } ); const data = response.data?.data || response.data; // Store tokens if (data.accessToken) { TokenManager.setAccessToken(data.accessToken); } if (data.refreshToken) { TokenManager.setRefreshToken(data.refreshToken); } if (data.idToken) { TokenManager.setIdToken(data.idToken); } if (data.user) { TokenManager.setUserData(data.user); } return data; } catch (error: any) { console.error('❌ Tanflow token exchange failed:', { message: error.message, response: error.response?.data, status: error.response?.status, }); throw error; } } /** * Refresh access token using refresh token */ export async function refreshTanflowToken(): Promise { const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; const refreshToken = TokenManager.getRefreshToken(); if (!refreshToken) { throw new Error('No refresh token available'); } try { const response = await axios.post( `${API_BASE_URL}/auth/tanflow/refresh`, { refreshToken }, { withCredentials: true, headers: { 'Content-Type': 'application/json', }, } ); const data = response.data?.data || response.data; const accessToken = data.accessToken; if (accessToken) { TokenManager.setAccessToken(accessToken); return accessToken; } throw new Error('Failed to refresh token'); } catch (error: any) { console.error('❌ Tanflow token refresh failed:', error); throw error; } } /** * Logout from Tanflow * Uses id_token for logout and redirects back to app * Note: This should be called AFTER backend logout API is called and tokens are cleared * DO NOT clear tokens here - they should already be cleared by AuthContext */ export function tanflowLogout(idToken: string): void { if (!idToken) { console.warn('🚪 No id_token available for Tanflow logout, redirecting to home'); // Fallback: redirect to home with logout flags (similar to OKTA approach) const homeUrl = `${window.location.origin}/?tanflow_logged_out=true&logout=${Date.now()}`; window.location.replace(homeUrl); return; } // Build Tanflow logout URL with redirect back to login callback // IMPORTANT: Use the base redirect URI (without query params) to match registered URIs // Tanflow requires exact match with registered "Valid Post Logout Redirect URIs" // The same URI used for login should be registered for logout // Using the base URI ensures it matches what's registered in Tanflow client config const postLogoutRedirectUri = TANFLOW_REDIRECT_URI; // Use base URI without query params // Construct logout URL - ensure all parameters are properly encoded // Keycloak/Tanflow expects: client_id, id_token_hint, post_logout_redirect_uri const logoutUrl = new URL(`${TANFLOW_BASE_URL}/protocol/openid-connect/logout`); logoutUrl.searchParams.set('client_id', TANFLOW_CLIENT_ID); logoutUrl.searchParams.set('id_token_hint', idToken); logoutUrl.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri); const finalLogoutUrl = logoutUrl.toString(); console.log('🚪 Tanflow logout initiated', { hasIdToken: !!idToken, idTokenPrefix: idToken ? idToken.substring(0, 20) + '...' : 'none', postLogoutRedirectUri, logoutUrlBase: `${TANFLOW_BASE_URL}/protocol/openid-connect/logout`, finalLogoutUrl: finalLogoutUrl.replace(idToken.substring(0, 20), '***'), }); // DO NOT clear auth_provider here - we need it to detect Tanflow callback // The logout flags should already be set by AuthContext // Just ensure they're there sessionStorage.setItem('__logout_in_progress__', 'true'); sessionStorage.setItem('__force_logout__', 'true'); // Don't set tanflow_logged_out here - it will be set when Tanflow redirects back // Redirect to Tanflow logout endpoint // Tanflow will clear the session and redirect back to post_logout_redirect_uri // The redirect will include tanflow_logged_out=true in the query params console.log('🚪 Redirecting to Tanflow logout endpoint...'); window.location.href = finalLogoutUrl; }