import axios from 'axios'; // Get tenant ID from env var or use a default const tenantId = process.env.MICROSOFT_TENANT_ID || 'common'; // Use 'organizations' or actual tenant ID // Microsoft OAuth URLs with configurable tenant const MICROSOFT_AUTHORIZE_URL = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; const MICROSOFT_TOKEN_URL = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; // Client configuration from environment variables const clientId = process.env.MICROSOFT_CLIENT_ID; const clientSecret = process.env.MICROSOFT_CLIENT_SECRET; const redirectUri = process.env.MICROSOFT_REDIRECT_URI; // Log configuration for debugging console.log('Microsoft OAuth Configuration:', { tenantId, authorizeUrl: MICROSOFT_AUTHORIZE_URL, tokenUrl: MICROSOFT_TOKEN_URL, clientIdFirstChars: clientId ? clientId.substring(0, 5) + '...' : 'undefined', redirectUri }); // Required scopes for IMAP and SMTP access const REQUIRED_SCOPES = [ 'offline_access', 'https://outlook.office.com/IMAP.AccessAsUser.All', 'https://outlook.office.com/SMTP.Send' ].join(' '); /** * Generates the authorization URL for Microsoft OAuth */ export function getMicrosoftAuthUrl(state: string): string { const params = new URLSearchParams({ client_id: clientId!, response_type: 'code', redirect_uri: redirectUri!, scope: REQUIRED_SCOPES, state, response_mode: 'query' }); return `${MICROSOFT_AUTHORIZE_URL}?${params.toString()}`; } /** * Exchange authorization code for tokens */ export async function exchangeCodeForTokens(code: string): Promise<{ access_token: string; refresh_token: string; expires_in: number; }> { const params = new URLSearchParams({ client_id: clientId!, client_secret: clientSecret!, code, redirect_uri: redirectUri!, grant_type: 'authorization_code' }); try { console.log(`Exchanging code for tokens. URL: ${MICROSOFT_TOKEN_URL}`); const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log('Token exchange successful!'); return { access_token: response.data.access_token, refresh_token: response.data.refresh_token, expires_in: response.data.expires_in }; } catch (error: any) { console.error('Error exchanging code for tokens:', error); // Enhanced error logging if (error.response) { console.error('Response data:', error.response.data); console.error('Response status:', error.response.status); console.error('Response headers:', error.response.headers); // Extract the error message from Microsoft's response format const errorData = error.response.data; if (errorData && errorData.error_description) { throw new Error(`Token exchange failed: ${errorData.error_description}`); } } throw new Error('Failed to exchange authorization code for tokens'); } } /** * Refresh an access token using a refresh token */ export async function refreshAccessToken(refreshToken: string): Promise<{ access_token: string; refresh_token?: string; expires_in: number; }> { const params = new URLSearchParams({ client_id: clientId!, client_secret: clientSecret!, refresh_token: refreshToken, grant_type: 'refresh_token', scope: REQUIRED_SCOPES }); try { console.log(`Refreshing access token. URL: ${MICROSOFT_TOKEN_URL}`); const response = await axios.post(MICROSOFT_TOKEN_URL, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); console.log('Token refresh successful!'); return { access_token: response.data.access_token, refresh_token: response.data.refresh_token, expires_in: response.data.expires_in }; } catch (error: any) { console.error('Error refreshing token:', error); // Enhanced error logging if (error.response) { console.error('Response data:', error.response.data); console.error('Response status:', error.response.status); console.error('Response headers:', error.response.headers); // Extract the error message from Microsoft's response format const errorData = error.response.data; if (errorData && errorData.error_description) { throw new Error(`Token refresh failed: ${errorData.error_description}`); } } throw new Error('Failed to refresh access token'); } } /** * Create special XOAUTH2 string for IMAP authentication */ export function createXOAuth2Token(email: string, accessToken: string): string { // This creates the XOAUTH2 token in the required format for ImapFlow // Format: user=\x01auth=Bearer \x01\x01 const auth = `user=${email}\x01auth=Bearer ${accessToken}\x01\x01`; const base64Auth = Buffer.from(auth).toString('base64'); console.log('Generated XOAUTH2 token (length):', base64Auth.length); return base64Auth; }