import { ConfidentialClientApplication } from "@azure/msal-node"; // ─── Azure AD Authentication ────────────────────────────────────────────────── let msalClient: ConfidentialClientApplication | null = null; /** * Vérifie que les 3 variables d'environnement Azure AD sont présentes */ export function isAzureAdConfigured(): boolean { return !!( process.env.AZURE_AD_TENANT_ID && process.env.AZURE_AD_CLIENT_ID && process.env.AZURE_AD_CLIENT_SECRET ); } /** * Instancie le client MSAL (lazy, singleton) */ function getMsalClient(): ConfidentialClientApplication { if (!isAzureAdConfigured()) { throw new Error("Azure AD is not configured"); } if (!msalClient) { msalClient = new ConfidentialClientApplication({ auth: { clientId: process.env.AZURE_AD_CLIENT_ID!, authority: `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}`, clientSecret: process.env.AZURE_AD_CLIENT_SECRET!, }, }); } return msalClient; } /** * Retourne l'URL de redirection Azure AD pour l'utilisateur */ export async function getAzureAuthUrl(): Promise { const client = getMsalClient(); const redirectUri = process.env.AZURE_AD_REDIRECT_URI || "http://localhost:3000/api/auth/azure/callback"; return client.getAuthCodeUrl({ scopes: ["user.read"], redirectUri, }); } /** * Échange le code OAuth contre un token et retourne les infos utilisateur. * azureAdId = homeAccountId = "{objectId}.{tenantId}" (~73 caractères) */ export async function handleAzureCallback(code: string) { const client = getMsalClient(); const redirectUri = process.env.AZURE_AD_REDIRECT_URI || "http://localhost:3000/api/auth/azure/callback"; const response = await client.acquireTokenByCode({ code, scopes: ["user.read"], redirectUri, }); if (!response || !response.account) { throw new Error("Failed to acquire token from Azure AD"); } return { azureAdId: response.account.homeAccountId, // "{objectId}.{tenantId}" email: response.account.username, // UPN (ex: user@domain.com) name: response.account.name || response.account.username, }; }