From 0a73973a7c6e6e95a5206bfc5388ff7f7a9ba8ed Mon Sep 17 00:00:00 2001 From: Nafu Satsuki Date: Sat, 18 Nov 2023 20:39:48 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=A1=E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89?= =?UTF-8?q?=E3=83=AC=E3=82=B9=E3=81=AE=E8=AA=8D=E8=A8=BC=E3=81=ABverifymai?= =?UTF-8?q?l.io=E3=82=92=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1700303245007-supportVerifyMailApi.js | 18 ++++ packages/backend/src/core/EmailService.ts | 92 +++++++++++++++++-- packages/backend/src/models/Meta.ts | 11 +++ .../server/api/endpoints/admin/update-meta.ts | 14 +++ .../frontend/src/pages/admin/security.vue | 13 +++ 5 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 packages/backend/migration/1700303245007-supportVerifyMailApi.js diff --git a/packages/backend/migration/1700303245007-supportVerifyMailApi.js b/packages/backend/migration/1700303245007-supportVerifyMailApi.js new file mode 100644 index 0000000000..3ac59ec37a --- /dev/null +++ b/packages/backend/migration/1700303245007-supportVerifyMailApi.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SupportVerifyMailApi1700303245007 { + name = 'SupportVerifyMailApi1700303245007' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "verifymailAuthKey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "enableVerifymailApi" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableVerifymailApi"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "verifymailAuthKey"`); + } +} diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index c9da3f77c0..8f28197ebc 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -13,6 +13,9 @@ import type Logger from '@/logger.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import {URLSearchParams} from "node:url"; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import {SubOutputFormat} from "deep-email-validator/dist/output/output.js"; @Injectable() export class EmailService { @@ -27,6 +30,7 @@ export class EmailService { private metaService: MetaService, private loggerService: LoggerService, + private httpRequestService: HttpRequestService, ) { this.logger = this.loggerService.getLogger('email'); } @@ -160,14 +164,25 @@ export class EmailService { email: emailAddress, }); - const validated = meta.enableActiveEmailValidation ? await validateEmail({ - email: emailAddress, - validateRegex: true, - validateMx: true, - validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので - validateDisposable: true, // 捨てアドかどうかチェック - validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので - }) : { valid: true, reason: null }; + const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null; + let validated; + + if (meta.enableActiveEmailValidation) { + if (verifymailApi) { + validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); + } else { + validated = meta.enableActiveEmailValidation ? await validateEmail({ + email: emailAddress, + validateRegex: true, + validateMx: true, + validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので + validateDisposable: true, // 捨てアドかどうかチェック + validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので + }) : { valid: true, reason: null }; + } + } else { + validated = { valid: true, reason: null }; + } const available = exist === 0 && validated.valid; @@ -182,4 +197,65 @@ export class EmailService { null, }; } + + private async verifyMail(emailAddress: string, verifymailAuthKey: string): Promise<{ + valid: boolean; + reason: 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | null; + }> { + const endpoint = 'https://verifymail.io/api/' + emailAddress + '?key=' + verifymailAuthKey; + const res = await this.httpRequestService.send(endpoint, { + method: 'GET', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json, */*', + }, + }); + + const json = (await res.json()) as { + block: boolean; + catch_all: boolean; + deliverable_email: boolean; + disposable: boolean; + domain: string; + email_address: string; + email_provider: string; + mx: boolean; + mx_fallback: boolean; + mx_host: string[]; + mx_ip: string[]; + mx_priority: { [key: string]: number }; + privacy: boolean; + related_domains: string[]; + }; + + if (json.email_address === undefined) { + return { + valid: false, + reason: 'format', + }; + } + if (json.deliverable_email !== undefined && !json.deliverable_email) { + return { + valid: false, + reason: 'smtp', + }; + } + if (json.disposable) { + return { + valid: false, + reason: 'disposable', + }; + } + if (json.mx !== undefined && !json.mx) { + return { + valid: false, + reason: 'mx', + }; + } + + return { + valid: true, + reason: null, + }; + } } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 14a72add1d..83e8962f5d 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -446,6 +446,17 @@ export class MiMeta { }) public enableActiveEmailValidation: boolean; + @Column('boolean', { + default: false, + }) + public enableVerifymailApi: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public verifymailAuthKey: string | null; + @Column('boolean', { default: true, }) diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index da3e5dd9ac..d6f9b2cd94 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -113,6 +113,8 @@ export const paramDef = { objectStorageS3ForcePathStyle: { type: 'boolean' }, enableIpLogging: { type: 'boolean' }, enableActiveEmailValidation: { type: 'boolean' }, + enableVerifymailApi: { type: 'boolean' }, + verifymailAuthKey: { type: 'string', nullable: true }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, @@ -454,6 +456,18 @@ export default class extends Endpoint { // eslint- set.enableActiveEmailValidation = ps.enableActiveEmailValidation; } + if (ps.enableVerifymailApi !== undefined) { + set.enableVerifymailApi = ps.enableVerifymailApi; + } + + if (ps.verifymailAuthKey !== undefined) { + if (ps.verifymailAuthKey === '') { + set.verifymailAuthKey = null; + } else { + set.verifymailAuthKey = ps.verifymailAuthKey; + } + } + if (ps.enableChartsForRemoteUser !== undefined) { set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser; } diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index a2594ee6c5..f7f76d910a 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -73,6 +73,13 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + @@ -132,6 +139,8 @@ let setSensitiveFlagAutomatically: boolean = $ref(false); let enableSensitiveMediaDetectionForVideos: boolean = $ref(false); let enableIpLogging: boolean = $ref(false); let enableActiveEmailValidation: boolean = $ref(false); +let enableVerifymailApi: boolean = $ref(false); +let verifymailAuthKey: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); @@ -150,6 +159,8 @@ async function init() { enableSensitiveMediaDetectionForVideos = meta.enableSensitiveMediaDetectionForVideos; enableIpLogging = meta.enableIpLogging; enableActiveEmailValidation = meta.enableActiveEmailValidation; + enableVerifymailApi = meta.enableVerifymailApi; + verifymailAuthKey = meta.verifymailAuthKey; } function save() { @@ -167,6 +178,8 @@ function save() { enableSensitiveMediaDetectionForVideos, enableIpLogging, enableActiveEmailValidation, + enableVerifymailApi, + verifymailAuthKey, }).then(() => { fetchInstance(); });