diff --git a/server/config/config.js b/server/config/config.js index a3efaf32d..6f409e1b4 100644 --- a/server/config/config.js +++ b/server/config/config.js @@ -74,4 +74,6 @@ module.exports = { api_key: 'b0JDZW56RnV6aEthb0RGPXVEcUI' }, jwtSecret: 'b0JDZW56RnV6aEthb0RGPXVEcUI', + contactUsMail: 'support@bigcapital.ly', + baseURL: 'https://bigcapital.ly', }; diff --git a/server/src/http/controllers/Authentication.ts b/server/src/http/controllers/Authentication.ts index 9b0d1537c..83f74d632 100644 --- a/server/src/http/controllers/Authentication.ts +++ b/server/src/http/controllers/Authentication.ts @@ -76,14 +76,15 @@ export default class AuthenticationController extends BaseController{ */ get resetPasswordSchema(): ValidationChain[] { return [ - check('password').exists().isLength({ min: 5 }).custom((value, { req }) => { - if (value !== req.body.confirm_password) { - throw new Error("Passwords don't match"); - } else { - return value; - } - }), - ] + check('password').exists().isLength({ min: 5 }) + .custom((value, { req }) => { + if (value !== req.body.confirm_password) { + throw new Error("Passwords don't match"); + } else { + return value; + } + }), + ]; } /** @@ -111,7 +112,7 @@ export default class AuthenticationController extends BaseController{ return res.status(200).send({ token, user }); } catch (error) { if (error instanceof ServiceError) { - if (error.errorType === 'invalid_details') { + if (['invalid_details', 'invalid_password'].indexOf(error.errorType) !== -1) { return res.boom.badRequest(null, { errors: [{ type: 'INVALID_DETAILS', code: 100 }], }); @@ -201,8 +202,6 @@ export default class AuthenticationController extends BaseController{ type: 'RESET_PASSWORD_SUCCESS', }) } catch(error) { - console.log(error); - if (error instanceof ServiceError) { if (error.errorType === 'token_invalid') { return res.boom.badRequest(null, { diff --git a/server/src/jobs/ResetPasswordMail.ts b/server/src/jobs/ResetPasswordMail.ts index baf7076a4..0cc99523c 100644 --- a/server/src/jobs/ResetPasswordMail.ts +++ b/server/src/jobs/ResetPasswordMail.ts @@ -2,8 +2,17 @@ import { Container, Inject } from 'typedi'; import AuthenticationService from '@/services/Authentication'; export default class WelcomeEmailJob { - @Inject() - authService: AuthenticationService; + /** + * Constructor method. + * @param {Agenda} agenda + */ + constructor(agenda) { + agenda.define( + 'reset-password-mail', + { priority: 'high' }, + this.handler.bind(this), + ); + } /** * Handle send welcome mail job. @@ -11,17 +20,18 @@ export default class WelcomeEmailJob { * @param {Function} done */ public async handler(job, done: Function): Promise { - const { email, organizationName, firstName } = job.attrs.data; + const { user, token } = job.attrs.data; const Logger = Container.get('logger'); + const authService = Container.get(AuthenticationService); - Logger.info(`Send reset password mail - started: ${job.attrs.data}`); + Logger.info(`[send_reset_password] started: ${job.attrs.data}`); try { - await this.authService.mailMessages.sendResetPasswordMessage(); - Logger.info(`Send reset password mail - finished: ${job.attrs.data}`); + await authService.mailMessages.sendResetPasswordMessage(user, token); + Logger.info(`[send_reset_password] finished: ${job.attrs.data}`); done() } catch (error) { - Logger.info(`Send reset password mail - error: ${job.attrs.data}, error: ${error}`); + Logger.info(`[send_reset_password] error: ${job.attrs.data}, error: ${error}`); done(error); } } diff --git a/server/src/jobs/welcomeEmail.ts b/server/src/jobs/welcomeEmail.ts index c257f6cae..8c741c82f 100644 --- a/server/src/jobs/welcomeEmail.ts +++ b/server/src/jobs/welcomeEmail.ts @@ -2,8 +2,18 @@ import { Container, Inject } from 'typedi'; import AuthenticationService from '@/services/Authentication'; export default class WelcomeEmailJob { - @Inject() - authService: AuthenticationService; + /** + * Constructor method. + * @param {Agenda} agenda - + */ + constructor(agenda) { + // Welcome mail and SMS message. + agenda.define( + 'welcome-email', + { priority: 'high' }, + this.handler.bind(this), + ); + } /** * Handle send welcome mail job. @@ -11,17 +21,18 @@ export default class WelcomeEmailJob { * @param {Function} done */ public async handler(job, done: Function): Promise { - const { email, organizationName, firstName } = job.attrs.data; + const { organizationName, user } = job.attrs.data; const Logger = Container.get('logger'); + const authService = Container.get(AuthenticationService); + + Logger.info(`[welcome_mail] send welcome mail message - started: ${job.attrs.data}`); - Logger.info(`Send welcome mail message - started: ${job.attrs.data}`); - try { - await this.authService.mailMessages.sendWelcomeMessage(); - Logger.info(`Send welcome mail message - finished: ${job.attrs.data}`); - done() + await authService.mailMessages.sendWelcomeMessage(user, organizationName); + Logger.info(`[welcome_mail] send welcome mail message - finished: ${job.attrs.data}`); + done(); } catch (error) { - Logger.info(`Send welcome mail message - error: ${job.attrs.data}, error: ${error}`); + Logger.info(`[welcome_mail] send welcome mail message - error: ${job.attrs.data}, error: ${error}`); done(error); } } diff --git a/server/src/loaders/jobs.ts b/server/src/loaders/jobs.ts index 4909dae85..c1095af2f 100644 --- a/server/src/loaders/jobs.ts +++ b/server/src/loaders/jobs.ts @@ -13,23 +13,15 @@ import SendMailNotificationTrialEnd from '@/jobs/MailNotificationTrialEnd'; import UserInviteMailJob from '@/jobs/UserInviteMail'; export default ({ agenda }: { agenda: Agenda }) => { - // Welcome mail and SMS message. - agenda.define( - 'welcome-email', - { priority: 'high' }, - new WelcomeEmailJob().handler, - ); + new WelcomeEmailJob(agenda); + new ResetPasswordMailJob(agenda); + agenda.define( 'welcome-sms', { priority: 'high' }, new WelcomeSMSJob().handler ); - // Reset password mail. - agenda.define( - 'reset-password-mail', - { priority: 'high' }, - new ResetPasswordMailJob().handler, - ); + // User invite mail. agenda.define( 'user-invite-mail', diff --git a/server/src/services/Authentication/AuthenticationMailMessages.ts b/server/src/services/Authentication/AuthenticationMailMessages.ts index f9461a1fd..e74585971 100644 --- a/server/src/services/Authentication/AuthenticationMailMessages.ts +++ b/server/src/services/Authentication/AuthenticationMailMessages.ts @@ -1,35 +1,78 @@ -import { Service } from "typedi"; +import fs from 'fs'; +import { Service, Container } from "typedi"; +import Mustache from 'mustache'; +import path from 'path'; +import { ISystemUser } from '@/interfaces'; +import config from '@/../config/config'; +import { ISystemUser } from 'src/interfaces'; @Service() export default class AuthenticationMailMesssages { - - sendWelcomeMessage() { - const Logger = Container.get('logger'); + /** + * Sends welcome message. + * @param {ISystemUser} user - The system user. + * @param {string} organizationName - + * @return {Promise} + */ + sendWelcomeMessage(user: ISystemUser, organizationName: string): Promise { const Mail = Container.get('mail'); - + const filePath = path.join(global.rootPath, 'views/mail/Welcome.html'); const template = fs.readFileSync(filePath, 'utf8'); const rendered = Mustache.render(template, { - email, organizationName, firstName, + email: user.email, + firstName: user.firstName, + organizationName, }); const mailOptions = { - to: email, + to: user.email, from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`, subject: 'Welcome to Bigcapital', html: rendered, }; - Mail.sendMail(mailOptions, (error) => { - if (error) { - Logger.error('Failed send welcome mail', { error, form }); - done(error); - return; - } - Logger.info('User has been sent welcome email successfuly.', { form }); - done(); - }); + return new Promise((resolve, reject) => { + Mail.sendMail(mailOptions, (error) => { + if (error) { + resolve(error); + return; + } + reject(); + }); + }); } - sendResetPasswordMessage() { + /** + * Sends reset password message. + * + * @param {ISystemUser} user - The system user. + * @param {string} token - Reset password token. + * @return {Promise} + */ + sendResetPasswordMessage(user: ISystemUser, token: string): Promise { + const Mail = Container.get('mail'); + const filePath = path.join(global.rootPath, 'views/mail/ResetPassword.html'); + const template = fs.readFileSync(filePath, 'utf8'); + const rendered = Mustache.render(template, { + resetPasswordUrl: `${config.baseURL}/reset/${token}`, + first_name: user.firstName, + last_name: user.lastName, + contact_us_email: config.contactUsMail, + }); + const mailOptions = { + to: user.email, + from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`, + subject: 'Bigcapital - Password Reset', + html: rendered, + }; + return new Promise((resolve, reject) => { + Mail.sendMail(mailOptions, (error) => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); } } \ No newline at end of file diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index c14f36d02..b391b58c5 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -65,7 +65,7 @@ export default class AuthenticationService { this.logger.info('[login] check password validation.'); if (!user.verifyPassword(password)) { - throw new ServiceError('password_invalid'); + throw new ServiceError('invalid_password'); } if (!user.active) { @@ -133,8 +133,10 @@ export default class AuthenticationService { tenant_id: tenant.id, }); - this.eventDispatcher.dispatch(events.auth.register, { registerDTO }); - + // Triggers `onRegister` event. + this.eventDispatcher.dispatch(events.auth.register, { + registerDTO, user: registeredUser + }); return registeredUser; } @@ -194,9 +196,12 @@ export default class AuthenticationService { await this.validateEmailExistance(email); // Delete all stored tokens of reset password that associate to the give email. + this.logger.info('[send_reset_password] trying to delete all tokens by email.'); await PasswordReset.query().where('email', email).delete(); const token = uniqid(); + + this.logger.info('[send_reset_password] insert the generated token.'); const passwordReset = await PasswordReset.query().insert({ email, token }); const user = await SystemUser.query().findOne('email', email); diff --git a/server/src/services/Subscription/MailMessages.ts b/server/src/services/Subscription/MailMessages.ts index 572fc84d2..4c50a5243 100644 --- a/server/src/services/Subscription/MailMessages.ts +++ b/server/src/services/Subscription/MailMessages.ts @@ -2,7 +2,11 @@ import { Service } from "typedi"; @Service() export default class SubscriptionMailMessages { - + /** + * + * @param phoneNumber + * @param remainingDays + */ public async sendRemainingSubscriptionPeriod(phoneNumber: string, remainingDays: number) { const message: string = ` Your remaining subscription is ${remainingDays} days, @@ -11,6 +15,11 @@ export default class SubscriptionMailMessages { this.smsClient.sendMessage(phoneNumber, message); } + /** + * + * @param phoneNumber + * @param remainingDays + */ public async sendRemainingTrialPeriod(phoneNumber: string, remainingDays: number) { const message: string = ` Your remaining free trial is ${remainingDays} days, diff --git a/server/src/services/Subscription/Subscription.ts b/server/src/services/Subscription/Subscription.ts index c63861a25..8949667d6 100644 --- a/server/src/services/Subscription/Subscription.ts +++ b/server/src/services/Subscription/Subscription.ts @@ -1,3 +1,4 @@ +import { Inject } from 'typedi'; import { Tenant, Plan } from '@/system/models'; import { IPaymentContext } from '@/interfaces'; import { NotAllowedChangeSubscriptionPlan } from '@/exceptions'; @@ -5,6 +6,9 @@ import { NotAllowedChangeSubscriptionPlan } from '@/exceptions'; export default class Subscription { paymentContext: IPaymentContext|null; + @Inject('logger') + logger: any; + /** * Constructor method. * @param {IPaymentContext} diff --git a/server/src/subscribers/authentication.ts b/server/src/subscribers/authentication.ts index a2e2da00f..26b0d468e 100644 --- a/server/src/subscribers/authentication.ts +++ b/server/src/subscribers/authentication.ts @@ -3,7 +3,6 @@ import { pick } from 'lodash'; import { EventSubscriber, On } from 'event-dispatch'; import events from '@/subscribers/events'; - @EventSubscriber() export class AuthenticationSubscriber { @@ -14,13 +13,14 @@ export class AuthenticationSubscriber { @On(events.auth.register) public onRegister(payload) { - const { registerDTO } = payload; + const { registerDTO, user } = payload; const agenda = Container.get('agenda'); // Send welcome mail to the user. agenda.now('welcome-email', { - ...pick(registerDTO, ['email', 'organizationName', 'firstName']), + ...pick(registerDTO, ['organizationName']), + user, }); } @@ -32,7 +32,6 @@ export class AuthenticationSubscriber { @On(events.auth.sendResetPassword) public onSendResetPassword (payload) { const { user, token } = payload; - const agenda = Container.get('agenda'); // Send reset password mail. diff --git a/server/src/subscribers/organization.ts b/server/src/subscribers/organization.ts new file mode 100644 index 000000000..670505ede --- /dev/null +++ b/server/src/subscribers/organization.ts @@ -0,0 +1,16 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from "event-dispatch"; +import events from '@/subscribers/events'; + +@EventSubscriber() +export class OrganizationSubscriber { + + @On(events.organization.build) + public onBuild(payload) { + const agenda = Container.get('agenda'); + + agenda.now('welcome-sms', { + email, organizationName, firstName, + }); + } +} \ No newline at end of file diff --git a/server/views/mail/ResetPassword.html b/server/views/mail/ResetPassword.html index 1bdc23ffc..fb4c6bd25 100644 --- a/server/views/mail/ResetPassword.html +++ b/server/views/mail/ResetPassword.html @@ -184,8 +184,8 @@ } .btn-primary a { - background-color: #2d95fd; - border-color: #2d95fd; + background-color: #1968F0; + border-color: #1968F0; color: #ffffff; } @@ -374,8 +374,8 @@

Reset Your Password

-

Hi {{ first_name }} {{ last_name }},

-

Click On The link blow to reset your password.

+

Hi {{ first_name }} {{ last_name }},

+

Click on the link blow to reset your password.

@@ -384,7 +384,7 @@ - +
Reset Your password Reset Your password
diff --git a/server/views/mail/Welcome.html b/server/views/mail/Welcome.html index bde1211fe..2f610316a 100644 --- a/server/views/mail/Welcome.html +++ b/server/views/mail/Welcome.html @@ -335,8 +335,6 @@ border-color: #004dd0 !important; } } - - [data-icon="bigcapital"] path { fill: #004dd0; }