mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat(nestjs): resend the auth confirmation message
This commit is contained in:
@@ -12,6 +12,9 @@ export const events = {
|
|||||||
signUpConfirming: 'signUpConfirming',
|
signUpConfirming: 'signUpConfirming',
|
||||||
signUpConfirmed: 'signUpConfirmed',
|
signUpConfirmed: 'signUpConfirmed',
|
||||||
|
|
||||||
|
signUpConfirmResending: 'signUpConfirmResending',
|
||||||
|
signUpConfirmResended: 'signUpConfirmResended',
|
||||||
|
|
||||||
sendingResetPassword: 'onSendingResetPassword',
|
sendingResetPassword: 'onSendingResetPassword',
|
||||||
sendResetPassword: 'onSendResetPassword',
|
sendResetPassword: 'onSendResetPassword',
|
||||||
|
|
||||||
@@ -771,5 +774,4 @@ export const events = {
|
|||||||
onSalesByItemViewed: 'onSalesByItemViewed',
|
onSalesByItemViewed: 'onSalesByItemViewed',
|
||||||
onPurchasesByItemViewed: 'onPurchasesByItemViewed',
|
onPurchasesByItemViewed: 'onPurchasesByItemViewed',
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -144,11 +144,11 @@ import { CreditNotesApplyInvoiceModule } from '../CreditNotesApplyInvoice/Credit
|
|||||||
ScheduleModule.forRoot(),
|
ScheduleModule.forRoot(),
|
||||||
TenancyDatabaseModule,
|
TenancyDatabaseModule,
|
||||||
TenancyModelsModule,
|
TenancyModelsModule,
|
||||||
|
AuthModule,
|
||||||
TenancyModule,
|
TenancyModule,
|
||||||
ChromiumlyTenancyModule,
|
ChromiumlyTenancyModule,
|
||||||
TransformerModule,
|
TransformerModule,
|
||||||
MailModule,
|
MailModule,
|
||||||
AuthModule,
|
|
||||||
ItemsModule,
|
ItemsModule,
|
||||||
ItemCategoryModule,
|
ItemCategoryModule,
|
||||||
AccountsModule,
|
AccountsModule,
|
||||||
|
|||||||
@@ -72,3 +72,7 @@ export interface IAuthSignUpVerifiedEventPayload {
|
|||||||
verifyToken: string;
|
verifyToken: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISignUpConfigmResendedEventPayload {
|
||||||
|
user: SystemUser;
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { GetAuthMetaService } from './queries/GetAuthMeta.service';
|
|||||||
import { AuthedController } from './Authed.controller';
|
import { AuthedController } from './Authed.controller';
|
||||||
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
|
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
|
||||||
import { TenancyModule } from '../Tenancy/Tenancy.module';
|
import { TenancyModule } from '../Tenancy/Tenancy.module';
|
||||||
|
import { EnsureUserVerifiedGuard } from './guards/EnsureUserVerified.guard';
|
||||||
|
|
||||||
const models = [InjectSystemModel(PasswordReset)];
|
const models = [InjectSystemModel(PasswordReset)];
|
||||||
|
|
||||||
@@ -73,6 +74,10 @@ const models = [InjectSystemModel(PasswordReset)];
|
|||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
useClass: JwtAuthGuard,
|
useClass: JwtAuthGuard,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: EnsureUserVerifiedGuard,
|
||||||
|
},
|
||||||
AuthMailSubscriber,
|
AuthMailSubscriber,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AuthSignupDto } from './dtos/AuthSignup.dto';
|
|||||||
import { AuthSendResetPasswordService } from './commands/AuthSendResetPassword.service';
|
import { AuthSendResetPasswordService } from './commands/AuthSendResetPassword.service';
|
||||||
import { AuthResetPasswordService } from './commands/AuthResetPassword.service';
|
import { AuthResetPasswordService } from './commands/AuthResetPassword.service';
|
||||||
import { GetAuthMetaService } from './queries/GetAuthMeta.service';
|
import { GetAuthMetaService } from './queries/GetAuthMeta.service';
|
||||||
|
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthenticationApplication {
|
export class AuthenticationApplication {
|
||||||
@@ -19,6 +20,7 @@ export class AuthenticationApplication {
|
|||||||
private readonly authResetPasswordService: AuthResetPasswordService,
|
private readonly authResetPasswordService: AuthResetPasswordService,
|
||||||
private readonly authSendResetPasswordService: AuthSendResetPasswordService,
|
private readonly authSendResetPasswordService: AuthSendResetPasswordService,
|
||||||
private readonly authGetMeta: GetAuthMetaService,
|
private readonly authGetMeta: GetAuthMetaService,
|
||||||
|
private readonly getAuthedAccountService: GetAuthenticatedAccount,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,8 +55,8 @@ export class AuthenticationApplication {
|
|||||||
* @param {number} userId - System user id.
|
* @param {number} userId - System user id.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async signUpConfirmResend(userId: number) {
|
public async signUpConfirmResend() {
|
||||||
return this.authSignUpConfirmResendService.signUpConfirmResend(userId);
|
return this.authSignUpConfirmResendService.signUpConfirmResend();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,4 +85,11 @@ export class AuthenticationApplication {
|
|||||||
public async getAuthMeta() {
|
public async getAuthMeta() {
|
||||||
return this.authGetMeta.getAuthMeta();
|
return this.authGetMeta.getAuthMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the authenticated account meta.
|
||||||
|
*/
|
||||||
|
public getAuthedAccount() {
|
||||||
|
return this.getAuthedAccountService.getAccount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,47 @@
|
|||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
|
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
|
||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get, Post } from '@nestjs/common';
|
||||||
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
|
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
|
||||||
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
|
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
|
||||||
|
import { AuthenticationApplication } from './AuthApplication.sevice';
|
||||||
|
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||||
|
import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard';
|
||||||
|
|
||||||
@Controller('/auth')
|
@Controller('/auth')
|
||||||
@ApiTags('Auth')
|
@ApiTags('Auth')
|
||||||
@IgnoreTenantSeededRoute()
|
@IgnoreTenantSeededRoute()
|
||||||
@IgnoreTenantInitializedRoute()
|
@IgnoreTenantInitializedRoute()
|
||||||
|
@IgnoreUserVerifiedRoute()
|
||||||
export class AuthedController {
|
export class AuthedController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly getAuthedAccountService: GetAuthenticatedAccount,
|
private readonly getAuthedAccountService: GetAuthenticatedAccount,
|
||||||
|
private readonly authApp: AuthenticationApplication,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@Post('/signup/confirm/resend')
|
||||||
|
@ApiOperation({ summary: 'Resend the signup confirmation message' })
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
code: { type: 'number', example: 200 },
|
||||||
|
message: { type: 'string', example: 'resent successfully.' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
async resendSignupConfirm() {
|
||||||
|
await this.authApp.signUpConfirmResend();
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'The signup confirmation message has been resent successfully.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get('/account')
|
@Get('/account')
|
||||||
@ApiOperation({ summary: 'Retrieve the authenticated account' })
|
@ApiOperation({ summary: 'Retrieve the authenticated account' })
|
||||||
async getAuthedAcccount() {
|
async getAuthedAcccount() {
|
||||||
const data = await this.getAuthedAccountService.getAccount();
|
return this.getAuthedAccountService.getAccount();
|
||||||
|
|
||||||
return { data };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,6 @@ export class AuthSigninService {
|
|||||||
`Wrong password for user with email: ${email}`,
|
`Wrong password for user with email: ${email}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!user.verified) {
|
|
||||||
throw new UnauthorizedException(
|
|
||||||
`The user is not verified yet, check out your mail inbox.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,42 @@
|
|||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||||
|
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||||
|
import { ERRORS } from '../Auth.constants';
|
||||||
|
import { events } from '@/common/events/events';
|
||||||
|
import { ModelObject } from 'objection';
|
||||||
|
import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces';
|
||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class AuthSignupConfirmResendService {
|
export class AuthSignupConfirmResendService {
|
||||||
signUpConfirmResend(userId: number) {
|
constructor(
|
||||||
return;
|
private readonly eventPublisher: EventEmitter2,
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
|
||||||
|
@Inject(SystemUser.name)
|
||||||
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends the email confirmation of the given user.
|
||||||
|
* @param {number} userId - System User ID.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async signUpConfirmResend() {
|
||||||
|
const user = await this.tenancyContext.getSystemUser();
|
||||||
|
|
||||||
|
// Throw error if the user is already verified.
|
||||||
|
if (user.verified) {
|
||||||
|
throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED);
|
||||||
|
}
|
||||||
|
// Throw error if the verification token is not exist.
|
||||||
|
if (!user.verifyToken) {
|
||||||
|
throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED);
|
||||||
|
}
|
||||||
|
// Triggers `signUpConfirmResended` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.signUpConfirmResended, {
|
||||||
|
user,
|
||||||
|
} as ISignUpConfigmResendedEventPayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
SetMetadata,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { IS_PUBLIC_ROUTE } from '../Auth.constants';
|
||||||
|
|
||||||
|
export const IS_IGNORE_USER_VERIFIED = 'IS_IGNORE_USER_VERIFIED';
|
||||||
|
export const IgnoreUserVerifiedRoute = () =>
|
||||||
|
SetMetadata(IS_IGNORE_USER_VERIFIED, true);
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EnsureUserVerifiedGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private readonly tenancyContext: TenancyContext,
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the authenticated user if verified and throws exception if not.
|
||||||
|
* @param {ExecutionContext} context
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const isIgnoredUserVerified = this.reflector.getAllAndOverride<boolean>(
|
||||||
|
IS_IGNORE_USER_VERIFIED,
|
||||||
|
[context.getHandler(), context.getClass()],
|
||||||
|
);
|
||||||
|
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
||||||
|
IS_PUBLIC_ROUTE,
|
||||||
|
[context.getHandler(), context.getClass()],
|
||||||
|
);
|
||||||
|
// Skip the guard early if the route marked as public or ignored.
|
||||||
|
if (isPublic || isIgnoredUserVerified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const systemUser = await this.tenancyContext.getSystemUser();
|
||||||
|
|
||||||
|
if (!systemUser.verified) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
`The user is not verified yet, check out your mail inbox.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { OnEvent } from '@nestjs/event-emitter';
|
|||||||
import {
|
import {
|
||||||
IAuthSendedResetPassword,
|
IAuthSendedResetPassword,
|
||||||
IAuthSignedUpEventPayload,
|
IAuthSignedUpEventPayload,
|
||||||
|
ISignUpConfigmResendedEventPayload,
|
||||||
} from '../Auth.interfaces';
|
} from '../Auth.interfaces';
|
||||||
import { Queue } from 'bullmq';
|
import { Queue } from 'bullmq';
|
||||||
import { InjectQueue } from '@nestjs/bullmq';
|
import { InjectQueue } from '@nestjs/bullmq';
|
||||||
@@ -27,12 +28,15 @@ export class AuthMailSubscriber {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {IAuthSignedUpEventPayload} payload
|
* @param {IAuthSignedUpEventPayload | ISignUpConfigmResendedEventPayload} payload
|
||||||
*/
|
*/
|
||||||
@OnEvent(events.auth.signUp)
|
@OnEvent(events.auth.signUp)
|
||||||
async handleSignupSendVerificationMail(payload: IAuthSignedUpEventPayload) {
|
@OnEvent(events.auth.signUpConfirmResended)
|
||||||
|
async handleSignupSendVerificationMail(
|
||||||
|
payload: IAuthSignedUpEventPayload | ISignUpConfigmResendedEventPayload,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const job = await this.sendSignupVerificationMailQueue.add(
|
await this.sendSignupVerificationMailQueue.add(
|
||||||
SendSignupVerificationMailJob,
|
SendSignupVerificationMailJob,
|
||||||
{
|
{
|
||||||
email: payload.user.email,
|
email: payload.user.email,
|
||||||
@@ -43,7 +47,6 @@ export class AuthMailSubscriber {
|
|||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(job);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user