Re_Figma_Code/src/services/tanflowAuth.ts

202 lines
6.7 KiB
TypeScript

/**
* 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<string> {
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;
}