メールアドレスの認証にverifymail.ioを使えるようにする。
This commit is contained in:
		
							parent
							
								
									83ea0395f6
								
							
						
					
					
						commit
						0a73973a7c
					
				|  | @ -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"`); | ||||
|     } | ||||
| } | ||||
|  | @ -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, | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
| 	}) | ||||
|  |  | |||
|  | @ -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<typeof meta, typeof paramDef> { // 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; | ||||
| 			} | ||||
|  |  | |||
|  | @ -73,6 +73,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 						<MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save"> | ||||
| 							<template #label>Enable</template> | ||||
| 						</MkSwitch> | ||||
| 						<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save"> | ||||
| 							<template #label>Use Verifymail API</template> | ||||
| 						</MkSwitch> | ||||
| 						<MkInput v-model="verifymailAuthKey" @update:modelValue="save"> | ||||
| 							<template #prefix><i class="ti ti-key"></i></template> | ||||
| 							<template #label>Verifymail API Auth Key</template> | ||||
| 						</MkInput> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
|  | @ -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(); | ||||
| 	}); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue