import { refreshAndSaveTokens } from "../db/mail"; import { decrypt } from "./crypto"; /** * MailConnection-Shape: nur die Felder die für Auth-Resolution nötig sind. * Beide Scan-Endpoints bekommen das volle Prisma-Objekt — dieses Interface * dient als explizites Subset damit der Helper nicht vom vollen Typ abhängt. */ export interface MailConnectionAuthFields { id: string; email: string; authMethod: string; passwordEncrypted: string; oauthAccessToken?: string | null; oauthTokenExpiry?: Date | null; } export type ImapAuth = | { user: string; accessToken: string } | { user: string; pass: string }; /** * Gibt das korrekte `auth`-Objekt für ImapFlow zurück. * * - oauth2_microsoft: Access-Token decrypten, bei Ablauf via MS-Endpoint refreshen. * Nutzt refreshAndSaveTokens() aus db/mail (Race-Condition-sicher, Prisma-basiert). * - Alle anderen authMethods (app_password, default): passwordEncrypted decrypten. * * Wirft wenn: * - App-Password leer oder decrypt fehlschlägt * - OAuth-Token fehlt und kein Refresh möglich * - refreshAndSaveTokens() wirft (revoked refresh_token, MS-Endpoint-Fehler) * * @param connection MailConnection-Felder (Subset) * @param clientId MS Azure App Registration Client-ID (nur für OAuth-Pfad) */ export async function resolveImapAuth( connection: MailConnectionAuthFields, clientId: string, ): Promise { if (connection.authMethod === "oauth2_microsoft") { // Token-Expiry-Check: 5-Minuten-Puffer damit der Scan nicht // mitten in einem großen Mailbox-Durchlauf mit abgelaufenem Token stirbt. const fiveMinFromNow = Date.now() + 5 * 60 * 1000; const isExpiredOrMissing = !connection.oauthTokenExpiry || connection.oauthTokenExpiry.getTime() < fiveMinFromNow; let accessToken: string; if (isExpiredOrMissing) { // Wirft wenn Refresh-Token fehlt oder MS-Endpoint antwortet mit Fehler. // Caller (scan.post / scan-internal.post) soll per try/catch continue-n. accessToken = await refreshAndSaveTokens(connection.id, clientId); } else { if (!connection.oauthAccessToken) { throw new Error( `oauth2_microsoft connection ${connection.id} has no oauthAccessToken stored`, ); } accessToken = decrypt(connection.oauthAccessToken); } return { user: connection.email, accessToken }; } // App-Password-Pfad (gmail, icloud, gmx, yahoo, custom) if (!connection.passwordEncrypted) { throw new Error( `Connection ${connection.id} has no passwordEncrypted (authMethod=${connection.authMethod})`, ); } const pass = decrypt(connection.passwordEncrypted); return { user: connection.email, pass }; }