Checkpoint: Réception automatique e-mail sécurisée : le planificateur est créé uniquement après un test IMAP concluant, les erreurs d’authentification sont affichées explicitement et ne déclenchent plus de boucle. Les administrateurs peuvent sélectionner, démarrer, arrêter et suivre le compte source déjà configuré sans afficher ni recopier de secrets. Tests de démarrage réussi/refusé, arrêt et ciblage de compte ajoutés ; 59 tests, TypeScript et build validés.
This commit is contained in:
@@ -34,6 +34,17 @@ export interface EmailImportConfig {
|
||||
|
||||
export type EmailImportTrigger = "manual" | "automatic";
|
||||
|
||||
export type EmailImportServiceStartResult = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type EmailConnectionTester = (
|
||||
config: EmailImportConfig,
|
||||
) => Promise<{ success: boolean; message: string }>;
|
||||
|
||||
type EmailCheckRunner = (config: EmailImportConfig) => Promise<void>;
|
||||
|
||||
type EmailImportSettingsSnapshot = {
|
||||
emailImportEnabled: number;
|
||||
emailImportAddress: string | null;
|
||||
@@ -566,21 +577,63 @@ export async function testImapConnection(config: EmailImportConfig): Promise<{ s
|
||||
}
|
||||
|
||||
/**
|
||||
* Start email import service for a user
|
||||
* Vérifie la boîte avant de créer la planification. Une authentification refusée
|
||||
* ne doit jamais produire une boucle de tentatives automatiques sans fin.
|
||||
*/
|
||||
export async function startEmailImportService(userId: number): Promise<boolean> {
|
||||
export async function startConfiguredEmailImportService(
|
||||
config: EmailImportConfig,
|
||||
frequencyMinutes: number,
|
||||
connectionTester: EmailConnectionTester = testImapConnection,
|
||||
emailCheckRunner: EmailCheckRunner = runEmailCheckExclusive,
|
||||
): Promise<EmailImportServiceStartResult> {
|
||||
const connection = await connectionTester(config);
|
||||
if (!connection.success) {
|
||||
console.error(
|
||||
`[EmailImport] Service non démarré pour user ${config.userId}: ${connection.message}`,
|
||||
);
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Stop existing service if running only after the new configuration has passed validation.
|
||||
stopEmailImportService(config.userId);
|
||||
|
||||
const frequencyMs = Math.max(1, frequencyMinutes || 30) * 60 * 1000;
|
||||
console.log(
|
||||
`[EmailImport] Starting email import service for user ${config.userId} with frequency ${frequencyMinutes || 30} minutes (auth: ${config.authMode || "basic"})`,
|
||||
);
|
||||
|
||||
// Run immediately on start. The preflight above confirms that the account accepts the credentials.
|
||||
emailCheckRunner(config).catch((error) => {
|
||||
console.error(`[EmailImport] Error checking emails for user ${config.userId}:`, error);
|
||||
});
|
||||
|
||||
const interval = setInterval(() => {
|
||||
emailCheckRunner(config).catch((error) => {
|
||||
console.error(`[EmailImport] Error checking emails for user ${config.userId}:`, error);
|
||||
});
|
||||
}, frequencyMs);
|
||||
|
||||
activeIntervals.set(config.userId, interval);
|
||||
console.log(`[EmailImport] Email import service started for user ${config.userId}`);
|
||||
return { success: true, message: "Service de réception e-mail démarré" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Start email import service for a user.
|
||||
*/
|
||||
export async function startEmailImportService(userId: number): Promise<EmailImportServiceStartResult> {
|
||||
try {
|
||||
// Get user's import settings
|
||||
const settings = await getImportSettingsByUser(userId);
|
||||
|
||||
if (!settings || settings.emailImportEnabled !== 1) {
|
||||
console.log(`[EmailImport] Email import not enabled for user ${userId}`);
|
||||
return false;
|
||||
return { success: false, message: "Activez et enregistrez la réception automatique avant de démarrer le service." };
|
||||
}
|
||||
|
||||
if (!settings.emailImportAddress || !settings.emailImportHost) {
|
||||
console.log(`[EmailImport] Email import configuration incomplete for user ${userId}`);
|
||||
return false;
|
||||
return { success: false, message: "Configuration IMAP incomplète : adresse e-mail ou serveur IMAP manquant." };
|
||||
}
|
||||
|
||||
const authMode = (settings as any).emailImportAuthMode as "basic" | "oauth2" || "basic";
|
||||
@@ -588,16 +641,13 @@ export async function startEmailImportService(userId: number): Promise<boolean>
|
||||
// Vérification selon le mode d'auth
|
||||
if (authMode === "basic" && !settings.emailImportPassword) {
|
||||
console.log(`[EmailImport] Email import password missing for user ${userId}`);
|
||||
return false;
|
||||
return { success: false, message: "Mot de passe IMAP manquant." };
|
||||
}
|
||||
if (authMode === "oauth2" && (!settings.azureTenantId || !settings.azureClientId || !settings.azureClientSecret)) {
|
||||
console.log(`[EmailImport] OAuth2 credentials incomplete for user ${userId}`);
|
||||
return false;
|
||||
return { success: false, message: "Configuration OAuth2 IMAP incomplète : renseignez les paramètres Microsoft Azure AD." };
|
||||
}
|
||||
|
||||
// Stop existing service if running
|
||||
stopEmailImportService(userId);
|
||||
|
||||
const config: EmailImportConfig = {
|
||||
userId,
|
||||
emailAddress: settings.emailImportAddress,
|
||||
@@ -611,31 +661,13 @@ export async function startEmailImportService(userId: number): Promise<boolean>
|
||||
azureClientSecret: settings.azureClientSecret || undefined,
|
||||
};
|
||||
|
||||
const frequencyMs = (settings.emailImportFrequency || 30) * 60 * 1000; // Convert minutes to milliseconds
|
||||
|
||||
console.log(
|
||||
`[EmailImport] Starting email import service for user ${userId} with frequency ${settings.emailImportFrequency} minutes (auth: ${authMode})`
|
||||
return startConfiguredEmailImportService(
|
||||
config,
|
||||
settings.emailImportFrequency || 30,
|
||||
);
|
||||
|
||||
// Run immediately on start
|
||||
runEmailCheckExclusive(config).catch((error) => {
|
||||
console.error(`[EmailImport] Error checking emails for user ${userId}:`, error);
|
||||
});
|
||||
|
||||
// Set up interval for periodic checks
|
||||
const interval = setInterval(() => {
|
||||
runEmailCheckExclusive(config).catch((error) => {
|
||||
console.error(`[EmailImport] Error checking emails for user ${userId}:`, error);
|
||||
});
|
||||
}, frequencyMs);
|
||||
|
||||
activeIntervals.set(userId, interval);
|
||||
console.log(`[EmailImport] Email import service started for user ${userId}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`[EmailImport] Error starting email import service for user ${userId}:`, error);
|
||||
return false;
|
||||
return { success: false, message: "Erreur technique lors du démarrage de la réception e-mail." };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,6 +690,33 @@ export function isEmailImportServiceRunning(userId: number): boolean {
|
||||
return activeIntervals.has(userId);
|
||||
}
|
||||
|
||||
type EmailImportAccountControllerDependencies = {
|
||||
start: (userId: number) => Promise<EmailImportServiceStartResult>;
|
||||
stop: (userId: number) => void;
|
||||
isRunning: (userId: number) => boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contrôles administratifs d'une boîte source. Les paramètres restent rattachés
|
||||
* à leur compte propriétaire ; seul le service planifié est administré ici.
|
||||
*/
|
||||
export function createEmailImportAccountController(
|
||||
dependencies: EmailImportAccountControllerDependencies = {
|
||||
start: startEmailImportService,
|
||||
stop: stopEmailImportService,
|
||||
isRunning: isEmailImportServiceRunning,
|
||||
},
|
||||
) {
|
||||
return {
|
||||
startAccount: (userId: number) => dependencies.start(userId),
|
||||
stopAccount: (userId: number): EmailImportServiceStartResult => {
|
||||
dependencies.stop(userId);
|
||||
return { success: true, message: "Service de réception e-mail arrêté" };
|
||||
},
|
||||
accountStatus: (userId: number) => ({ isRunning: dependencies.isRunning(userId) }),
|
||||
};
|
||||
}
|
||||
|
||||
async function triggerConfiguredEmailCheck(
|
||||
userId: number,
|
||||
requireAutomaticEnabled: boolean,
|
||||
|
||||
Reference in New Issue
Block a user