/** * Office 365 OAuth2 IMAP Authentication Helper * * Microsoft a désactivé l'authentification basique (Basic Auth) pour Exchange Online * depuis octobre 2022. Ce module implémente l'authentification OAuth2 via les * credentials Azure AD (Client Credentials Flow) pour obtenir un token d'accès * permettant de s'authentifier en IMAP via le mécanisme XOAUTH2. * * Prérequis côté Azure AD : * - Application enregistrée avec les permissions : IMAP.AccessAsUser.All (ou IMAP.AccessAsApp) * - Ou utiliser le flow "client_credentials" avec IMAP.AccessAsApp * * Référence : https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth */ interface OAuth2TokenResponse { access_token: string; token_type: string; expires_in: number; scope?: string; error?: string; error_description?: string; } /** * Obtient un token OAuth2 pour IMAP via le flow Client Credentials * (utilisé pour les applications de service sans interaction utilisateur) * * @param tenantId - Azure AD Tenant ID * @param clientId - Azure AD Application (Client) ID * @param clientSecret - Azure AD Client Secret * @returns Access token string */ export async function getOffice365ImapToken( tenantId: string, clientId: string, clientSecret: string ): Promise { const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const params = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, scope: "https://outlook.office365.com/.default", grant_type: "client_credentials", }); const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: params.toString(), }); const data = (await response.json()) as OAuth2TokenResponse; if (!response.ok || data.error) { throw new Error( `OAuth2 token error: ${data.error || response.status} - ${ data.error_description || response.statusText }` ); } if (!data.access_token) { throw new Error("OAuth2: no access_token in response"); } return data.access_token; } /** * Génère la chaîne XOAUTH2 encodée en base64 pour l'authentification IMAP * Format : base64("user=" + email + "\x01auth=Bearer " + token + "\x01\x01") * * @param email - Adresse email de la boîte à surveiller * @param accessToken - Token OAuth2 obtenu via getOffice365ImapToken * @returns Chaîne XOAUTH2 encodée en base64 */ export function buildXOAuth2String(email: string, accessToken: string): string { const xoauth2 = `user=${email}\x01auth=Bearer ${accessToken}\x01\x01`; return Buffer.from(xoauth2).toString("base64"); } /** * Teste la connexion IMAP OAuth2 en obtenant un token et en vérifiant * que les credentials Azure AD sont valides. * * @param tenantId - Azure AD Tenant ID * @param clientId - Azure AD Application (Client) ID * @param clientSecret - Azure AD Client Secret * @param emailAddress - Adresse email à tester * @returns { success: boolean; message: string } */ export async function testOffice365ImapOAuth2( tenantId: string, clientId: string, clientSecret: string, emailAddress: string ): Promise<{ success: boolean; message: string; token?: string }> { try { if (!tenantId || !clientId || !clientSecret) { return { success: false, message: "Credentials Azure AD incomplets (Tenant ID, Client ID ou Client Secret manquant)", }; } const token = await getOffice365ImapToken(tenantId, clientId, clientSecret); return { success: true, message: `Token OAuth2 obtenu avec succès pour ${emailAddress}. L'authentification IMAP OAuth2 est configurée.`, token, }; } catch (error: any) { const msg = error.message || String(error); // Messages d'erreur plus clairs pour les cas courants if (msg.includes("AADSTS700016") || msg.includes("application was not found")) { return { success: false, message: `Client ID invalide ou application non trouvée dans le tenant Azure AD. Vérifiez le Client ID.`, }; } if (msg.includes("AADSTS7000215") || msg.includes("Invalid client secret")) { return { success: false, message: `Client Secret invalide ou expiré. Générez un nouveau secret dans Azure AD.`, }; } if (msg.includes("AADSTS90002") || msg.includes("Tenant") || msg.includes("tenant")) { return { success: false, message: `Tenant ID invalide. Vérifiez l'ID du tenant Azure AD.`, }; } if (msg.includes("AADSTS65001") || msg.includes("consent")) { return { success: false, message: `Permissions non accordées. L'application Azure AD nécessite le consentement admin pour IMAP.AccessAsApp.`, }; } return { success: false, message: `Erreur OAuth2 : ${msg}`, }; } }