UNPKG

@snehal96/unimail

Version:

Unified email fetching & document extraction layer for modern web apps

134 lines (133 loc) 5.46 kB
import { ConfidentialClientApplication } from '@azure/msal-node'; /** * Microsoft-specific OAuth implementation for Outlook/Microsoft 365 */ export class OutlookOAuthProvider { /** * Initialize the Microsoft OAuth flow and generate an authorization URL */ async initializeOAuthFlow(options) { const msalApp = this.createMsalApp(options); // Generate a unique state value for security const state = options.state || Math.random().toString(36).substring(2); // List of Microsoft Graph API permission scopes for mail access const scopes = options.scopes || [ 'Mail.Read', 'offline_access', // Required for refresh tokens 'openid', 'profile', 'User.Read' ]; // Create authorization URL const authCodeUrlParameters = { scopes, redirectUri: options.redirectUri, prompt: options.prompt || 'consent', state }; try { const authUrl = await msalApp.getAuthCodeUrl(authCodeUrlParameters); return { authUrl, state }; } catch (error) { throw new Error(`Failed to initialize OAuth flow: ${error.message}`); } } /** * Handle the OAuth callback and exchange the code for tokens */ async handleCallback(code, options) { const msalApp = this.createMsalApp(options); try { const tokenRequest = { code, scopes: options.scopes, redirectUri: options.redirectUri, }; const response = await msalApp.acquireTokenByCode(tokenRequest); if (!response || !response.accessToken) { throw new Error('No access token returned from Microsoft OAuth flow'); } // Cast to our interface to handle types properly const authResponse = response; // Calculate expiry time const expiresAt = authResponse.expiresOn ? authResponse.expiresOn.getTime() : (Date.now() + 3600 * 1000); return { accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken || '', expiresAt, tokenType: authResponse.tokenType || 'Bearer', scope: authResponse.scopes?.join(' '), idToken: authResponse.idToken || '', }; } catch (error) { throw new Error(`Failed to exchange authorization code: ${error.message}`); } } /** * Refresh an access token using a refresh token */ async refreshToken(refreshToken, options) { const msalApp = this.createMsalApp(options); try { const tokenRequest = { refreshToken, scopes: options.scopes, }; const response = await msalApp.acquireTokenByRefreshToken(tokenRequest); if (!response || !response.accessToken) { throw new Error('No access token returned when refreshing token'); } // Cast to our interface to handle types properly const authResponse = response; // Calculate expiry time const expiresAt = authResponse.expiresOn ? authResponse.expiresOn.getTime() : (Date.now() + 3600 * 1000); return { accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken || refreshToken, // Keep existing refresh token if new one not provided expiresAt, tokenType: authResponse.tokenType || 'Bearer', scope: authResponse.scopes?.join(' '), idToken: authResponse.idToken || '', }; } catch (error) { throw new Error(`Failed to refresh token: ${error.message}`); } } /** * Revoke a token * Note: Microsoft identity platform doesn't have a specific token revocation endpoint like Google * Best practice is to clear the token from storage and implement a short token lifetime */ async revokeToken(token, options) { // Microsoft Graph doesn't provide an API to revoke tokens directly // The token must be removed from storage and will expire based on its lifetime // If a real revocation is needed, you could implement additional logic to: // 1. Force the user to re-authenticate next time // 2. Remove refresh tokens from your storage // 3. Consider integration with Azure AD API to revoke tokens for your application console.warn('Token revocation for Microsoft OAuth is not fully supported'); return true; // Return success as we've done what we can } /** * Create a MSAL ConfidentialClientApplication with the provided options * @private */ createMsalApp(options) { const msalConfig = { auth: { clientId: options.clientId, clientSecret: options.clientSecret, // Add tenant ID if available, otherwise use common endpoint for multi-tenant apps authority: `https://login.microsoftonline.com/${options.tenantId || 'common'}` } }; return new ConfidentialClientApplication(msalConfig); } }