Checkpoint: OAuth2 IMAP pour Office 365 : ajout du mode d'authentification OAuth2 pour l'import email (Azure AD Client Credentials), sélecteur basic/oauth2 dans l'UI, bouton "Tester la connexion IMAP", procédure testEmailConnection dans le router. Migration DB emailImportAuthMode appliquée.
This commit is contained in:
152
server/office365OAuth.ts
Normal file
152
server/office365OAuth.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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<string> {
|
||||
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}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user