import { PlatEnum } from 'enums/PlatEnum' import logger from 'logger/logger' import { Account } from 'modules/Account' import { CodeRecord, CodeStatus, CodeType, DEFAULT_CODE, DEFAULT_EXPIRE_TIME } from 'modules/CodeRecord' import { DEFAULT_LOGIN_MAIL_HTML, DEFAULT_LOGIN_MAIL_SUBJECT, DEFAULT_REGIST_HTML, DEFAULT_REGIST_SUBJECT, DEFAULT_RESET_HTML, DEFAULT_RESET_SUBJECT, DEFAULT_VERIFY_MAIL_HTML, DEFAULT_VERIFY_MAIL_SUBJECT, EmailSvr, } from 'service/email.svr' import { sha1, uuid } from 'zutils/utils/security.util' import { BaseController, role, ROLE_ANON, router, ZError } from 'zutils' import { SyncLocker } from 'common/SyncLocker' export const isEmail = (email: string) => { const reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/ return reg.test(email) } class MailController extends BaseController { /** * 通过邮件, 密码形式的登录 */ @role(ROLE_ANON) @router('post /wallet/login/email') async loginWithEmail(req, res) { logger.db('login', req) let { email, password } = req.params if (!email || !password) { throw new ZError(10, 'params mismatch') } // check Account exists let record = await Account.findByEmail(email) if (!record) { throw new ZError(11, 'account not exists') } if (record.locked) { throw new ZError(12, 'account locked') } if (!record.verifyPassword(password)) { throw new ZError(13, 'password error') } const token = await res.jwtSign({ id: record.id, openid: record.openId, version: record.accountVersion || 0, plat: PlatEnum.EMAIL, }) return { token: token } } /** * 注册email账号 */ @role(ROLE_ANON) @router('post /email/regist') async registMailAccount(req, res) { logger.db('regist', req) let { email, code, password } = req.params if (!email || !code || !password) { throw new ZError(10, 'params mismatch') } let account = await Account.findByEmail(email) if (account) { throw new ZError(11, 'account exists') } let record = await CodeRecord.findByEmail(email, CodeType.REGIST) if (!record) { throw new ZError(12, 'code not exists') } if (record.status !== CodeStatus.PENDING) { throw new ZError(13, 'code expired') } if (record.code !== code) { throw new ZError(14, 'code error') } account = new Account({ plat: PlatEnum.EMAIL, email, openId: uuid() }) account.updatePassword(password) account.emailReal = email account.emailVerified = true const { api_platform } = req.headers if (api_platform) { account.platform = api_platform } await account.save() record.status = CodeStatus.SUCCESS await record.save() const ztoken = await res.jwtSign({ id: account.id, openid: account.openId, plat: PlatEnum.EMAIL }) return { token: ztoken } } /** * 通过email重置密码 */ @role(ROLE_ANON) @router('post /email/reset_password') async resetMailPassword(req, res) { logger.db('reset_pass', req) let { email, code, password } = req.params if (!email || !code || !password) { throw new ZError(10, 'params mismatch') } let account = await Account.findByEmail(email) if (!account) { throw new ZError(11, 'account not exists') } let record = await CodeRecord.findByEmail(email, CodeType.RESET) if (!record) { throw new ZError(12, 'code not exists') } if (record.status !== CodeStatus.PENDING) { throw new ZError(13, 'code expired') } if (record.code !== code) { throw new ZError(14, 'code error') } account.updatePassword(password) await account.save() record.status = CodeStatus.SUCCESS await record.save() return {} } /** * 发送验证码 */ @role(ROLE_ANON) @router('post /email/send_code') async sendVerifyCode(req, res) { logger.db('send_mail_code', req) let { email, type } = req.params if (!email || !type) { throw new ZError(10, 'params mismatch') } if (!isEmail(email)) { throw new ZError(12, 'email error') } type = parseInt(type) if (type !== CodeType.REGIST && type !== CodeType.RESET && type !== CodeType.VERIFY && type !== CodeType.LOGIN) { throw new ZError(13, 'type error') } const lockKey = sha1(`${email}_${type}`) await new SyncLocker().checkLock(req, lockKey, 55000) if (type === CodeType.REGIST) { let account = await Account.findByEmail(email) if (account) { throw new ZError(11, 'account exists') } } let record = await CodeRecord.findByEmail(email, type) if (!record) { record = new CodeRecord({ email, type, code: DEFAULT_CODE }) await record.save() } let html, subject switch (type) { case CodeType.REGIST: html = DEFAULT_REGIST_HTML subject = DEFAULT_REGIST_SUBJECT break case CodeType.RESET: html = DEFAULT_RESET_HTML subject = DEFAULT_RESET_SUBJECT break case CodeType.VERIFY: html = DEFAULT_VERIFY_MAIL_HTML subject = DEFAULT_VERIFY_MAIL_SUBJECT case CodeType.LOGIN: html = DEFAULT_LOGIN_MAIL_HTML subject = DEFAULT_LOGIN_MAIL_SUBJECT } if (!html || !subject) { throw new ZError(15, 'type error') } subject = record.code + ' ' + subject html = html.replace('{{ocde}}', record.code) html = html.replace('{{time}}', new Date().format('yyyy-MM-dd hh:mm:ss')) let msgData = { to: email, html, subject, } setImmediate(async () => { try { let { errcode, errmsg, data } = await new EmailSvr().sendMail(msgData) if (errcode) { logger.info(`error send mail:: email: ${email}, type: ${type}, errcode: ${errcode}, errmsg: ${errmsg}`) record.status = CodeStatus.FAIL } else { logger.info(`success send mail:: email: ${email}, type: ${type}, messageId: ${data.messageId}`) record.mailSend = true record.emailId = data.messageId record.expiredAt = Date.now() + DEFAULT_EXPIRE_TIME } await record.save() } catch (err) { logger.info(`error send mail:: email: ${email}, type: ${type}`) logger.error(err) record.status = CodeStatus.FAIL await record.save() throw new ZError(14, 'send mail error') } }) return {} } /** * 检查email是否已经被注册 */ @role(ROLE_ANON) @router('post /email/check') async checkMailExists(req, res) { let { email } = req.params if (!email) { throw new ZError(10, 'params mismatch') } let account = await Account.findByEmail(email) return { exists: !!account } } }