From b17eb8e537b37f74ec2ad4ec1873386d2d57141b Mon Sep 17 00:00:00 2001
From: AsukaMari <2037177696@qq.com>
Date: Fri, 19 Jan 2024 17:48:30 +0800
Subject: [PATCH] fix: Some fixes for #12850 (#12862)

- refinement the error message when trueMail validation fails
- the settings of trueMail are not displayed after saving
- changing how `Active Email Validation` is saved
---
 CHANGELOG.md                                  |  1 +
 packages/backend/src/core/EmailService.ts     | 76 +++++++++++++------
 .../frontend/src/pages/admin/security.vue     | 18 +++--
 3 files changed, 63 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 948ee2b51f..4bb761bd96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
 ### General
 - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
 - Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
+- Feat: Add support for TrueMail
 
 ### Client
 - Feat: 新しいゲームを追加
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 7e812b4df2..8daee148eb 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -165,10 +165,17 @@ export class EmailService {
 			email: emailAddress,
 		});
 
+		if (exist !== 0) {
+			return {
+				available: false,
+				reason: 'used',
+			};
+		}
+
 		let validated: {
 			valid: boolean,
 			reason?: string | null,
-		};
+		} = { valid: true, reason: null };
 
 		if (meta.enableActiveEmailValidation) {
 			if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
@@ -185,27 +192,37 @@ export class EmailService {
 					validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
 				});
 			}
-		} else {
-			validated = { valid: true, reason: null };
+		}
+
+		if (!validated.valid) {
+			const formatReason: Record<string, 'format' | 'disposable' | 'mx' | 'smtp' | 'network' | 'blacklist' | undefined> = {
+				regex: 'format',
+				disposable: 'disposable',
+				mx: 'mx',
+				smtp: 'smtp',
+				network: 'network',
+				blacklist: 'blacklist',
+			};
+
+			return {
+				available: false,
+				reason: validated.reason ? formatReason[validated.reason] ?? null : null,
+			};
 		}
 
 		const emailDomain: string = emailAddress.split('@')[1];
 		const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
 
-		const available = exist === 0 && validated.valid && !isBanned;
+		if (isBanned) {
+			return {
+				available: false,
+				reason: 'banned',
+			};
+		}
 
 		return {
-			available,
-			reason: available ? null :
-			exist !== 0 ? 'used' :
-			isBanned ? 'banned' :
-			validated.reason === 'regex' ? 'format' :
-			validated.reason === 'disposable' ? 'disposable' :
-			validated.reason === 'mx' ? 'mx' :
-			validated.reason === 'smtp' ? 'smtp' :
-			validated.reason === 'network' ? 'network' :
-			validated.reason === 'blacklist' ? 'blacklist' :
-			null,
+			available: true,
+			reason: null,
 		};
 	}
 
@@ -222,7 +239,8 @@ export class EmailService {
 			},
 		});
 
-		const json = (await res.json()) as {
+		const json = (await res.json()) as Partial<{
+			message: string;
 			block: boolean;
 			catch_all: boolean;
 			deliverable_email: boolean;
@@ -237,8 +255,15 @@ export class EmailService {
 			mx_priority: { [key: string]: number };
 			privacy: boolean;
 			related_domains: string[];
-		};
+		}>;
 
+		/* api error: when there is only one `message` attribute in the returned result */
+		if (Object.keys(json).length === 1 && Reflect.has(json, 'message')) {
+			return {
+				valid: false,
+				reason: null,
+			};
+		}
 		if (json.email_address === undefined) {
 			return {
 				valid: false,
@@ -281,25 +306,26 @@ export class EmailService {
 				headers: {
 					'Content-Type': 'application/json',
 					Accept: 'application/json',
-					Authorization: truemailAuthKey
+					Authorization: truemailAuthKey,
 				},
 			});
-			
+
 			const json = (await res.json()) as {
 				email: string;
 				success: boolean;
-				errors?: { 
+				error?: string;
+				errors?: {
 					list_match?: string;
 					regex?: string;
 					mx?: string;
 					smtp?: string;
 				} | null;
 			};
-			
-			if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) {
+
+			if (json.email === undefined || json.errors?.regex) {
 				return {
-						valid: false,
-						reason: 'format',
+					valid: false,
+					reason: 'format',
 				};
 			}
 			if (json.errors?.smtp) {
@@ -320,7 +346,7 @@ export class EmailService {
 					reason: json.errors?.list_match as T || 'blacklist',
 				};
 			}
-			
+
 			return {
 				valid: true,
 				reason: null,
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index a691d8ea1e..d5d1258733 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -71,27 +71,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 					<div class="_gaps_m">
 						<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
-						<MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save">
+						<MkSwitch v-model="enableActiveEmailValidation">
 							<template #label>Enable</template>
 						</MkSwitch>
-						<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save">
+						<MkSwitch v-model="enableVerifymailApi">
 							<template #label>Use Verifymail.io API</template>
 						</MkSwitch>
-						<MkInput v-model="verifymailAuthKey" @update:modelValue="save">
+						<MkInput v-model="verifymailAuthKey">
 							<template #prefix><i class="ti ti-key"></i></template>
 							<template #label>Verifymail.io API Auth Key</template>
 						</MkInput>
-						<MkSwitch v-model="enableTruemailApi" @update:modelValue="save">
+						<MkSwitch v-model="enableTruemailApi">
 							<template #label>Use TrueMail API</template>
 						</MkSwitch>
-						<MkInput v-model="truemailInstance" @update:modelValue="save">
+						<MkInput v-model="truemailInstance">
 							<template #prefix><i class="ti ti-key"></i></template>
 							<template #label>TrueMail API Instance</template>
 						</MkInput>
-						<MkInput v-model="truemailAuthKey" @update:modelValue="save">
+						<MkInput v-model="truemailAuthKey">
 							<template #prefix><i class="ti ti-key"></i></template>
 							<template #label>TrueMail API Auth Key</template>
 						</MkInput>
+						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 					</div>
 				</MkFolder>
 
@@ -192,7 +193,10 @@ async function init() {
 	enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
 	enableVerifymailApi.value = meta.enableVerifymailApi;
 	verifymailAuthKey.value = meta.verifymailAuthKey;
-	bannedEmailDomains.value = meta.bannedEmailDomains.join('\n');
+	enableTruemailApi.value = meta.enableTruemailApi;
+	truemailInstance.value = meta.truemailInstance;
+	truemailAuthKey.value = meta.truemailAuthKey;
+	bannedEmailDomains.value = meta.bannedEmailDomains?.join('\n') || "";
 }
 
 function save() {
-- 
GitLab