mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
refactor: auth module to nestjs
This commit is contained in:
@@ -1,25 +1,74 @@
|
||||
import { Body, Controller, Post, Request } from '@nestjs/common';
|
||||
import { Body, Controller, Param, Post, Request } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger';
|
||||
import { PublicRoute } from './Jwt.guard';
|
||||
import { AuthenticationApplication } from './AuthApplication.sevice';
|
||||
import { AuthSignupDto } from './dtos/AuthSignup.dto';
|
||||
import { AuthSigninDto } from './dtos/AuthSignin.dto';
|
||||
|
||||
@ApiTags('Auth')
|
||||
@Controller('/auth')
|
||||
@PublicRoute()
|
||||
export class AuthController {
|
||||
constructor(private readonly authApp: AuthenticationApplication) {}
|
||||
|
||||
@Post('/signin')
|
||||
@ApiOperation({ summary: 'Sign in a user' })
|
||||
@ApiBody({ type: AuthSigninDto })
|
||||
signin(@Request() req: Request, @Body() signinDto: AuthSigninDto) {
|
||||
return this.authApp.signIn(signinDto);
|
||||
}
|
||||
|
||||
@Post('/signup')
|
||||
@ApiOperation({ summary: 'Sign up a new user' })
|
||||
@ApiBody({ type: AuthSignupDto })
|
||||
signup(@Request() req: Request, @Body() signupDto: AuthSignupDto) {
|
||||
this.authApp.signUp(signupDto);
|
||||
return this.authApp.signUp(signupDto);
|
||||
}
|
||||
|
||||
@Post('/signup/confirm')
|
||||
@ApiOperation({ summary: 'Confirm user signup' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', example: 'user@example.com' },
|
||||
token: { type: 'string', example: 'confirmation-token' },
|
||||
},
|
||||
},
|
||||
})
|
||||
signupConfirm(@Body('email') email: string, @Body('token') token: string) {
|
||||
return this.authApp.signUpConfirm(email, token);
|
||||
}
|
||||
|
||||
@Post('/send_reset_password')
|
||||
@ApiOperation({ summary: 'Send reset password email' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', example: 'user@example.com' },
|
||||
},
|
||||
},
|
||||
})
|
||||
sendResetPassword(@Body('email') email: string) {
|
||||
return this.authApp.sendResetPassword(email);
|
||||
}
|
||||
|
||||
@Post('/reset_password/:token')
|
||||
@ApiOperation({ summary: 'Reset password using token' })
|
||||
@ApiParam({ name: 'token', description: 'Reset password token' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
password: { type: 'string', example: 'new-password' },
|
||||
},
|
||||
},
|
||||
})
|
||||
resetPassword(
|
||||
@Param('token') token: string,
|
||||
@Body('password') password: string,
|
||||
) {
|
||||
return this.authApp.resetPassword(token, password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@ import { AuthSignupConfirmResendService } from './commands/AuthSignupConfirmRese
|
||||
import { AuthSignupConfirmService } from './commands/AuthSignupConfirm.service';
|
||||
import { AuthSignupService } from './commands/AuthSignup.service';
|
||||
import { AuthSigninService } from './commands/AuthSignin.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { PasswordReset } from './models/PasswordReset';
|
||||
import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module';
|
||||
import { AuthenticationMailMesssages } from './AuthMailMessages.esrvice';
|
||||
|
||||
const models = [RegisterTenancyModel(PasswordReset)];
|
||||
|
||||
@Module({
|
||||
controllers: [AuthController],
|
||||
@@ -18,7 +24,10 @@ import { AuthSigninService } from './commands/AuthSignin.service';
|
||||
secret: 'asdfasdfasdf',
|
||||
signOptions: { expiresIn: '60s' },
|
||||
}),
|
||||
TenantDBManagerModule,
|
||||
...models,
|
||||
],
|
||||
exports: [...models],
|
||||
providers: [
|
||||
AuthService,
|
||||
JwtStrategy,
|
||||
@@ -29,6 +38,7 @@ import { AuthSigninService } from './commands/AuthSignin.service';
|
||||
AuthSignupConfirmService,
|
||||
AuthSignupService,
|
||||
AuthSigninService,
|
||||
AuthenticationMailMesssages,
|
||||
],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SystemUser } from '../System/models/SystemUser';
|
||||
import { ModelObject } from 'objection';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Mail } from '../Mail/Mail';
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationMailMesssages {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
/**
|
||||
* Sends reset password message.
|
||||
* @param {ISystemUser} user - The system user.
|
||||
* @param {string} token - Reset password token.
|
||||
* @returns {Mail}
|
||||
*/
|
||||
sendResetPasswordMessage(user: ModelObject<SystemUser>, token: string) {
|
||||
const baseURL = this.configService.get('baseURL');
|
||||
|
||||
return new Mail()
|
||||
.setSubject('Bigcapital - Password Reset')
|
||||
.setView('mail/ResetPassword.html')
|
||||
.setTo(user.email)
|
||||
.setAttachments([
|
||||
{
|
||||
filename: 'bigcapital.png',
|
||||
path: `${global.__views_dir}/images/bigcapital.png`,
|
||||
cid: 'bigcapital_logo',
|
||||
},
|
||||
])
|
||||
.setData({
|
||||
resetPasswordUrl: `${baseURL}/auth/reset_password/${token}`,
|
||||
first_name: user.firstName,
|
||||
last_name: user.lastName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends signup verification mail.
|
||||
* @param {string} email - Email address
|
||||
* @param {string} fullName - User name.
|
||||
* @param {string} token - Verification token.
|
||||
* @returns {Mail}
|
||||
*/
|
||||
sendSignupVerificationMail(email: string, fullName: string, token: string) {
|
||||
const baseURL = this.configService.get('baseURL');
|
||||
const verifyUrl = `${baseURL}/auth/email_confirmation?token=${token}&email=${email}`;
|
||||
|
||||
return new Mail()
|
||||
.setSubject('Bigcapital - Verify your email')
|
||||
.setView('mail/SignupVerifyEmail.html')
|
||||
.setTo(email)
|
||||
.setAttachments([
|
||||
{
|
||||
filename: 'bigcapital.png',
|
||||
path: `${global.__views_dir}/images/bigcapital.png`,
|
||||
cid: 'bigcapital_logo',
|
||||
},
|
||||
])
|
||||
.setData({ verifyUrl, fullName });
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,88 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
import { PasswordReset } from '../models/PasswordReset';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { ERRORS } from '../Auth.constants';
|
||||
import { hashPassword } from '../Auth.utils';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IAuthResetedPasswordEventPayload } from '../Auth.interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class AuthResetPasswordService {
|
||||
resetPassword(token: string, password: string): Promise<{ message: string }> {
|
||||
return new Promise((resolve) => {
|
||||
resolve({ message: 'Reset password link sent to your email' });
|
||||
});
|
||||
/**
|
||||
* @param {ConfigService} configService - Config service.
|
||||
* @param {EventEmitter2} eventEmitter - Event emitter.
|
||||
* @param {typeof SystemUser} systemUserModel
|
||||
* @param {typeof PasswordReset} passwordResetModel - Reset password model.
|
||||
*/
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
|
||||
@Inject(PasswordReset.name)
|
||||
private readonly passwordResetModel: typeof PasswordReset,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Resets a user password from given token.
|
||||
* @param {string} token - Password reset token.
|
||||
* @param {string} password - New Password.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async resetPassword(token: string, password: string): Promise<void> {
|
||||
// Finds the password reset token.
|
||||
const tokenModel = await this.passwordResetModel
|
||||
.query()
|
||||
.findOne('token', token);
|
||||
|
||||
// In case the password reset token not found throw token invalid error..
|
||||
if (!tokenModel) {
|
||||
throw new ServiceError(ERRORS.TOKEN_INVALID);
|
||||
}
|
||||
const resetPasswordSeconds = this.configService.get('resetPasswordSeconds');
|
||||
|
||||
// Different between tokne creation datetime and current time.
|
||||
if (moment().diff(tokenModel.createdAt, 'seconds') > resetPasswordSeconds) {
|
||||
// Deletes the expired token by expired token email.
|
||||
await this.deletePasswordResetToken(tokenModel.email);
|
||||
throw new ServiceError(ERRORS.TOKEN_EXPIRED);
|
||||
}
|
||||
const user = await this.systemUserModel
|
||||
.query()
|
||||
.findOne({ email: tokenModel.email });
|
||||
|
||||
if (!user) {
|
||||
throw new ServiceError(ERRORS.USER_NOT_FOUND);
|
||||
}
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
await this.systemUserModel
|
||||
.query()
|
||||
.findById(user.id)
|
||||
.update({ password: hashedPassword });
|
||||
|
||||
// Deletes the used token.
|
||||
await this.deletePasswordResetToken(tokenModel.email);
|
||||
|
||||
// Triggers `onResetPassword` event.
|
||||
await this.eventEmitter.emitAsync(events.auth.resetPassword, {
|
||||
user,
|
||||
token,
|
||||
password,
|
||||
} as IAuthResetedPasswordEventPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the password reset token by the given email.
|
||||
* @param {string} email
|
||||
* @returns {Promise}
|
||||
*/
|
||||
private async deletePasswordResetToken(email: string) {
|
||||
return PasswordReset.query().where('email', email).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,70 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as uniqid from 'uniqid';
|
||||
import {
|
||||
IAuthSendedResetPassword,
|
||||
IAuthSendingResetPassword,
|
||||
} from '../Auth.interfaces';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { PasswordReset } from '../models/PasswordReset';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
import { events } from '@/common/events/events';
|
||||
|
||||
@Injectable()
|
||||
export class AuthSendResetPasswordService {
|
||||
sendResetPassword(email: string): Promise<{ message: string }> {
|
||||
return new Promise((resolve) => {
|
||||
resolve({ message: 'Reset password link sent to your email' });
|
||||
/**
|
||||
* @param {EventEmitter2} eventPublisher - Event emitter.
|
||||
* @param {typeof PasswordReset} resetPasswordModel - Password reset model.
|
||||
* @param {typeof SystemUser} systemUserModel - System user model.
|
||||
*/
|
||||
constructor(
|
||||
private readonly eventPublisher: EventEmitter2,
|
||||
|
||||
@Inject(PasswordReset.name)
|
||||
private readonly resetPasswordModel: typeof PasswordReset,
|
||||
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sends the given email reset password email.
|
||||
* @param {string} email - Email address.
|
||||
*/
|
||||
async sendResetPassword(email: string): Promise<void> {
|
||||
const user = await this.systemUserModel
|
||||
.query()
|
||||
.findOne({ email })
|
||||
.throwIfNotFound();
|
||||
|
||||
const token: string = uniqid();
|
||||
|
||||
// Triggers sending reset password event.
|
||||
await this.eventPublisher.emitAsync(events.auth.sendingResetPassword, {
|
||||
user,
|
||||
token,
|
||||
} as IAuthSendingResetPassword);
|
||||
|
||||
// Delete all stored tokens of reset password that associate to the give email.
|
||||
this.deletePasswordResetToken(email);
|
||||
|
||||
// Creates a new password reset row with unique token.
|
||||
const passwordReset = await this.resetPasswordModel.query().insert({
|
||||
email,
|
||||
token,
|
||||
});
|
||||
// Triggers sent reset password event.
|
||||
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
|
||||
user,
|
||||
token,
|
||||
} as IAuthSendedResetPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the password reset token by the given email.
|
||||
* @param {string} email
|
||||
* @returns {Promise}
|
||||
*/
|
||||
private async deletePasswordResetToken(email: string) {
|
||||
return this.resetPasswordModel.query().where('email', email).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
import * as crypto from 'crypto';
|
||||
import * as moment from 'moment';
|
||||
import { events } from '@/common/events/events';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { SystemModel } from '@/modules/System/models/SystemModel';
|
||||
|
||||
export class PasswordReset extends SystemModel {
|
||||
readonly email: string;
|
||||
readonly token: string;
|
||||
|
||||
readonly createdAt: Date;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'password_resets';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
}
|
||||
@@ -103,8 +103,8 @@ export class PlanSubscription extends mixin(SystemModel) {
|
||||
* Relations mappings.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Tenant = require('system/models/Tenant');
|
||||
const Plan = require('system/models/Subscriptions/Plan');
|
||||
const { TenantModel } = require('../../System/models/TenantModel');
|
||||
const { Plan } = require('./Plan');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -112,7 +112,7 @@ export class PlanSubscription extends mixin(SystemModel) {
|
||||
*/
|
||||
tenant: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Tenant.default,
|
||||
modelClass: TenantModel,
|
||||
join: {
|
||||
from: 'subscription_plan_subscriptions.tenantId',
|
||||
to: 'tenants.id',
|
||||
@@ -124,7 +124,7 @@ export class PlanSubscription extends mixin(SystemModel) {
|
||||
*/
|
||||
plan: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Plan.default,
|
||||
modelClass: Plan,
|
||||
join: {
|
||||
from: 'subscription_plan_subscriptions.planId',
|
||||
to: 'subscription_plans.id',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { Global, Inject, Injectable } from '@nestjs/common';
|
||||
import uniqid from 'uniqid';
|
||||
import * as uniqid from 'uniqid';
|
||||
import { TenantRepository as TenantBaseRepository } from '@/common/repository/TenantRepository';
|
||||
import { SystemKnexConnection } from '../SystemDB/SystemDB.constants';
|
||||
import { TenantModel } from '../models/TenantModel';
|
||||
|
||||
Reference in New Issue
Block a user