/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/ /** * Auth Service * @copyright 2021 Manaknightdigital Inc. * @link https://manaknightdigital.com * @license Proprietary Software licensing * @author Ryan Wong * */ const { Op } = require('sequelize') const passwordService = require('./PasswordService') const mailService = require('./MailService') const generateCode = require('../utils/generateCode') const db = require('../models') const errors = { EMAIL_ADDRESS_NOT_FOUND: 'EMAIL_ADDRESS_NOT_FOUND', EMAIL_ADDRESS_ALREADY_EXIST: 'EMAIL_ADDRESS_ALREADY_EXIST', PASSWORD_NOT_MATCH: 'PASSWORD_NOT_MATCH', INVALID_EMAIL_CONFIRMATION_CODE: 'INVALID_EMAIL_CONFIRMATION_CODE', INVALID_EMAIL_OR_PASSWORD: 'INVALID_EMAIL_OR_PASSWORD', ACCOUNT_ALREADY_VERIFIED: 'ACCOUNT_ALREADY_VERIFIED', CODE_DOES_NOT_EXIST: 'CODE_DOES_NOT_EXIST', } module.exports = { /** * Register new user with email and password * @name authService.register * @param {String} email user new email address * @param {String} password user new password * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token * @example * const payload = await authService.register(req.body.email, req.body.password) */ register: async function (email, password, role_id, code, user_details = {}) { let User let Credential try { const isEmailAddressExist = await db.credential.getByFields({ email, }) ;`` if (isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_ALREADY_EXIST) const codeExists = await db.code.getByFields({ code, is_used: { [Op.or]: [0, null] }, user_id: { [Op.or]: [0, null] }, }) if (!codeExists) throw new Error(errors.CODE_DOES_NOT_EXIST) const hashedPassword = await passwordService.hash(password) User = await db.user.insert( { ...user_details, role_id }, { returnAllFields: true } ) Credential = await db.credential.insert( { email: email, password: hashedPassword, user_id: User.id, type: 'n', verify: 0, role_id, status: 1, }, { returnAllFields: true } ) await db.code.edit( { user_id: User.id, is_used: 1, status: 1 }, codeExists.id ) return { credential: Credential.id, user: User } } catch (error) { if (Credential) { await db.credential.realDelete(Credential.id) } if (User) { await db.user.realDelete(User.id) } throw new Error(error.message) } }, /** * Login user with email and password * @name authService.login * @param {String} email user email address * @param {String} password user password * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token * @example * const payload = await authService.login(req.body.email, req.body.password) */ login: async function (email, password, role_id) { const isEmailAddressExist = await db.credential.getByFields({ email, status: 1, role_id, type: 'n', }) if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND) const { password: hashedPassword, id, user_id } = isEmailAddressExist const user = await db.user.getByFields({ id: user_id, status: 1, }) if (!user) { throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND) } const isPasswordMatch = await passwordService.compareHash( password, hashedPassword ) if (!isPasswordMatch) throw new Error(errors.INVALID_EMAIL_OR_PASSWORD) return { credential: id, user } }, /** * Send email and save to database * @name authService.forgotPassword * @param {String} email user email address * @return {Promise.} * @example * await authService.forgotPassword(req.body.email) */ forgotPassword: async function (email) { try { const isEmailAddressExist = await db.credential.getByFields({ where: { email: email, }, }) if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND) const { user_id } = isEmailAddressExist const getUser = await db.user.getByPk(user_id) const verificationCode = generateCode(6) mailService.initialize({ hostname: process.env.EMAIL_SMTP_SMTP_HOST, port: process.env.EMAIL_SMTP_SMTP_PORT, username: process.env.EMAIL_SMTP_SMTP_USER, password: EMAIL_SMTP_SMTP_PASS, from: process.env.MAIL_FROM, to: email, }) const mailTemplate = await mailService.template('reset-password') const injectedMailTemplate = mailService.inject( { body: mailTemplate.body, subject: mailTemplate.subject, }, { username: `${getUser.first_name} ${getUser.last_name}`, verification_code: verificationCode, } ) await mailService.send(injectedMailTemplate) await db.token.insert({ token: verificationCode, user_id }) } catch (error) { throw new Error(error) } }, /** * Verify forgot password confirmation code * @name authService.verifyForgotPassword * @param {code} code confirmation code * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token * @example * const payload = await authService.verifyForgotPassword(req.body.code) */ verifyForgotPassword: async function (code) { try { const Token = await db.token.findOne({ where: { token: code, }, }) const Credential = await db.credential.getByFields({ user_id: Token.user_id, }) return { credential: Credential.id, user: Credential.user_id } } catch (error) { throw new Error(error) } }, /** * Reset password * @name authService.resetPassword * @param {String} password user new password * @param {String} credential_id user credential id * @example * await authService.resetPassword(req.body.password, credential_id) */ resetPassword: async function (password, credential_id) { try { const hashedPassword = await passwordService.hash(password) await db.credential.edit( { password: hashedPassword, }, credential_id ) } catch (error) { throw new Error(error) } }, /** * Email confirmation * @name authService.emailConfirmation * @param {String} email user email address * @example * await authService.emailConfirmation(email) */ emailConfirmation: async function (email) { try { const isEmailAddressExist = await db.credential.getByFields({ where: { email: email, }, }) if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND) const { user_id } = isEmailAddressExist const getUser = await db.user.getByPk(user_id) const confirmationCode = generateCode(6) mailService.initialize({ hostname: process.env.EMAIL_SMTP_SMTP_HOST, port: process.env.EMAIL_SMTP_SMTP_PORT, username: process.env.EMAIL_SMTP_SMTP_USER, password: EMAIL_SMTP_SMTP_PASS, from: process.env.MAIL_FROM, to: email, }) const mailTemplate = await mailService.template('email-confirmation') const injectedMailTemplate = mailService.inject( { body: mailTemplate.body, subject: mailTemplate.subject, }, { username: `${getUser.first_name} ${getUser.last_name}`, confirmation_code: confirmationCode, } ) await mailService.send(injectedMailTemplate) await db.token.insert({ token: confirmationCode, user_id, type: 6 }) } catch (error) { throw new Error(error) } }, /** * Verify Email address * @name authService.emailVerify * @param {String} token email confirmation code * @param {string} user_id user id * @example * await authService.emailVerify(email, user_id) */ emailVerify: async function (token, user_id) { try { const isTokenExist = await db.token.getByFields({ where: { user_id, token, type: 6, }, }) if (!isTokenExist) throw new Error(errors.INVALID_EMAIL_CONFIRMATION_CODE) await db.token.realDelete(isTokenExist.id) } catch (error) { throw new Error(error) } }, /** * check if user need to change password before logging in * @name authService.forcePasswordChange * @param {string} user_id user id */ forcePasswordChange: async function (user_id) { try { const { profile_id } = await db.user.getByPk(user_id) const { force_password_change } = await db.profile.getByPk(profile_id) if (force_password_change) return true else return false } catch (error) { throw new Error(error) } }, verifyAccount: async ({ email, roleId }) => { const credential = await db.credential.getByFields({ email, status: 1, role_id: roleId, }) if (!credential) { throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND) } if ( credential.type !== 'n' || (credential.type === 'n' && credential.verify) ) { throw new Error(errors.ACCOUNT_ALREADY_VERIFIED) } mailService.initialize({ hostname: process.env.EMAIL_SMTP_SMTP_HOST, port: process.env.EMAIL_SMTP_SMTP_PORT, username: process.env.EMAIL_SMTP_SMTP_USER, password: process.env.EMAIL_SMTP_SMTP_PASS, from: process.env.MAIL_FROM, to: email, }) const accountConfirmationMailTemplate = await mailService.template( 'verify-account' ) const token = Buffer.from(JSON.stringify(credential.id))?.toString('base64') const BASE_URL = process.env.BASE_URL const link = (BASE_URL?.endsWith('/') ? BASE_URL?.slice(0, -1) : BASE_URL) + '/member/verify-account/' + token const accountConfirmationMailTemplateFinal = mailService.inject( { body: accountConfirmationMailTemplate.html, subject: accountConfirmationMailTemplate.subject, }, { email, link, } ) await mailService.send(accountConfirmationMailTemplateFinal) }, }