feat: add admin route guard for the authentication

This commit is contained in:
mahyargdz
2025-07-15 15:12:27 +03:30
parent a37317e22c
commit 9e0923c675
10 changed files with 57 additions and 17 deletions
@@ -1,8 +1,9 @@
import { UseGuards, applyDecorators } from "@nestjs/common"; import { UseGuards, applyDecorators } from "@nestjs/common";
import { ApiBearerAuth } from "@nestjs/swagger"; import { ApiBearerAuth } from "@nestjs/swagger";
import { AdminRouteGuard } from "../../modules/auth/guards/admin.guard";
import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard"; import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard";
export function AuthGuards() { export function AuthGuards() {
return applyDecorators(UseGuards(JwtAuthGuard), ApiBearerAuth("authorization")); return applyDecorators(UseGuards(JwtAuthGuard, AdminRouteGuard), ApiBearerAuth("authorization"));
} }
+2 -1
View File
@@ -4,6 +4,7 @@ import { FastifyRequest } from "fastify";
import { ADMIN_ROUTE } from "../../../common/decorators/admin.decorator"; import { ADMIN_ROUTE } from "../../../common/decorators/admin.decorator";
import { AuthMessage } from "../../../common/enums/message.enum"; import { AuthMessage } from "../../../common/enums/message.enum";
import { RoleEnum } from "../../users/enums/role.enum";
@Injectable() @Injectable()
export class AdminRouteGuard implements CanActivate { export class AdminRouteGuard implements CanActivate {
@@ -18,7 +19,7 @@ export class AdminRouteGuard implements CanActivate {
if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS);
if (!user.role) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); if (user.isAdmin === undefined || user.role === RoleEnum.USER) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS);
return true; return true;
} }
+1
View File
@@ -21,6 +21,7 @@ export class JwtAuthGuard extends AuthGuard([CONSOLE_JWT_STRATEGY_NAME, LOCAL_JW
return super.canActivate(context); return super.canActivate(context);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handleRequest(err: unknown, user: any, _info: unknown) { handleRequest(err: unknown, user: any, _info: unknown) {
if (err || !user) throw new UnauthorizedException(AuthMessage.TOKEN_EXPIRED_OR_INVALID); if (err || !user) throw new UnauthorizedException(AuthMessage.TOKEN_EXPIRED_OR_INVALID);
return user; return user;
@@ -6,6 +6,7 @@ import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
import { PurchaseQuotaDto } from "./DTO/purchase-quota.dto"; import { PurchaseQuotaDto } from "./DTO/purchase-quota.dto";
import { UpdateBusinessSettingsDto } from "./DTO/update-business-settings.dto"; import { UpdateBusinessSettingsDto } from "./DTO/update-business-settings.dto";
import { BusinessesService } from "./services/businesses.service"; import { BusinessesService } from "./services/businesses.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator"; import { BusinessDec } from "../../common/decorators/business.decorator";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
@@ -22,6 +23,7 @@ export class BusinessesController {
} }
@Patch("settings") @Patch("settings")
@AdminRoute()
@UseInterceptors(BusinessInterceptor) @UseInterceptors(BusinessInterceptor)
@ApiOperation({ summary: "Update business settings" }) @ApiOperation({ summary: "Update business settings" })
@AuthGuards() @AuthGuards()
@@ -30,6 +32,7 @@ export class BusinessesController {
} }
@Get("settings") @Get("settings")
@AdminRoute()
@UseInterceptors(BusinessInterceptor) @UseInterceptors(BusinessInterceptor)
@ApiOperation({ summary: "Get business settings" }) @ApiOperation({ summary: "Get business settings" })
@AuthGuards() @AuthGuards()
@@ -38,6 +41,7 @@ export class BusinessesController {
} }
@Get("quota") @Get("quota")
@AdminRoute()
@UseInterceptors(BusinessInterceptor) @UseInterceptors(BusinessInterceptor)
@ApiOperation({ summary: "Get business quota information" }) @ApiOperation({ summary: "Get business quota information" })
@ApiResponse({ status: 200, description: "Business quota information retrieved successfully", type: BusinessQuotaResponseDto }) @ApiResponse({ status: 200, description: "Business quota information retrieved successfully", type: BusinessQuotaResponseDto })
@@ -47,6 +51,7 @@ export class BusinessesController {
} }
@Post("quota/purchase") @Post("quota/purchase")
@AdminRoute()
@UseInterceptors(BusinessInterceptor) @UseInterceptors(BusinessInterceptor)
@ApiOperation({ summary: "Purchase additional quota for business" }) @ApiOperation({ summary: "Purchase additional quota for business" })
@ApiResponse({ status: 201, description: "Quota purchase initiated successfully" }) @ApiResponse({ status: 201, description: "Quota purchase initiated successfully" })
@@ -4,6 +4,7 @@ import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
import { CreateDomainDto } from "./DTO/create-domain.dto"; import { CreateDomainDto } from "./DTO/create-domain.dto";
import { UpdateDomainDto } from "./DTO/update-domain.dto"; import { UpdateDomainDto } from "./DTO/update-domain.dto";
import { DomainsService } from "./services/domains.service"; import { DomainsService } from "./services/domains.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator"; import { BusinessDec } from "../../common/decorators/business.decorator";
import { PaginationDto } from "../../common/DTO/pagination.dto"; import { PaginationDto } from "../../common/DTO/pagination.dto";
@@ -18,6 +19,7 @@ export class DomainsController {
constructor(private readonly domainsService: DomainsService) {} constructor(private readonly domainsService: DomainsService) {}
@Post() @Post()
@AdminRoute()
@ApiOperation({ summary: "Create a new domain and get setup instructions" }) @ApiOperation({ summary: "Create a new domain and get setup instructions" })
@ApiResponse({ status: 201, description: "Domain created with setup instructions" }) @ApiResponse({ status: 201, description: "Domain created with setup instructions" })
createDomain(@Body() createDomainDto: CreateDomainDto, @BusinessDec("id") businessId: string) { createDomain(@Body() createDomainDto: CreateDomainDto, @BusinessDec("id") businessId: string) {
@@ -25,6 +27,7 @@ export class DomainsController {
} }
@Get() @Get()
@AdminRoute()
@ApiOperation({ summary: "Get domain for business" }) @ApiOperation({ summary: "Get domain for business" })
@ApiResponse({ status: 200, description: "Domains retrieved successfully" }) @ApiResponse({ status: 200, description: "Domains retrieved successfully" })
getBusinessDomain(@BusinessDec("id") businessId: string, @Query() _query: PaginationDto) { getBusinessDomain(@BusinessDec("id") businessId: string, @Query() _query: PaginationDto) {
@@ -32,6 +35,7 @@ export class DomainsController {
} }
@Patch(":id") @Patch(":id")
@AdminRoute()
@ApiOperation({ summary: "Update domain" }) @ApiOperation({ summary: "Update domain" })
@ApiResponse({ status: 200, description: "Domain updated successfully" }) @ApiResponse({ status: 200, description: "Domain updated successfully" })
updateDomain(@Param() paramDto: ParamDto, @Body() updateDomainDto: UpdateDomainDto, @BusinessDec("id") businessId: string) { updateDomain(@Param() paramDto: ParamDto, @Body() updateDomainDto: UpdateDomainDto, @BusinessDec("id") businessId: string) {
@@ -39,6 +43,7 @@ export class DomainsController {
} }
@Delete(":id") @Delete(":id")
@AdminRoute()
@ApiOperation({ summary: "Delete domain" }) @ApiOperation({ summary: "Delete domain" })
@ApiResponse({ status: 200, description: "Domain deleted successfully" }) @ApiResponse({ status: 200, description: "Domain deleted successfully" })
deleteDomain(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) { deleteDomain(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
@@ -46,6 +51,7 @@ export class DomainsController {
} }
@Get("setup") @Get("setup")
@AdminRoute()
@ApiOperation({ summary: "Get domain setup instructions" }) @ApiOperation({ summary: "Get domain setup instructions" })
@ApiResponse({ status: 200, description: "Domain setup instructions retrieved" }) @ApiResponse({ status: 200, description: "Domain setup instructions retrieved" })
getDomainSetupInstructions(@BusinessDec("id") businessId: string) { getDomainSetupInstructions(@BusinessDec("id") businessId: string) {
@@ -53,6 +59,7 @@ export class DomainsController {
} }
@Get("dns-records") @Get("dns-records")
@AdminRoute()
@ApiOperation({ summary: "Get DNS records for domain" }) @ApiOperation({ summary: "Get DNS records for domain" })
@ApiResponse({ status: 200, description: "DNS records retrieved successfully" }) @ApiResponse({ status: 200, description: "DNS records retrieved successfully" })
getDomainDnsRecords(@BusinessDec("id") businessId: string) { getDomainDnsRecords(@BusinessDec("id") businessId: string) {
@@ -60,6 +67,7 @@ export class DomainsController {
} }
@Get("verify-dns") @Get("verify-dns")
@AdminRoute()
@ApiOperation({ summary: "Check DNS records, verify domain configuration and return verification status" }) @ApiOperation({ summary: "Check DNS records, verify domain configuration and return verification status" })
@ApiResponse({ status: 200, description: "DNS records verified and status returned" }) @ApiResponse({ status: 200, description: "DNS records verified and status returned" })
verifyDnsRecords(@BusinessDec("id") businessId: string) { verifyDnsRecords(@BusinessDec("id") businessId: string) {
@@ -3,6 +3,7 @@ import { ApiOperation, ApiParam, ApiQuery, ApiResponse } from "@nestjs/swagger";
import { MailboxQueryDto } from "./DTO/mailbox.dto"; import { MailboxQueryDto } from "./DTO/mailbox.dto";
import { MailboxService } from "./services/mailbox.service"; import { MailboxService } from "./services/mailbox.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { UserDec } from "../../common/decorators/user.decorator"; import { UserDec } from "../../common/decorators/user.decorator";
import { CreateMailboxDto, UpdateMailboxDto } from "../mail-server/DTO"; import { CreateMailboxDto, UpdateMailboxDto } from "../mail-server/DTO";
@@ -21,6 +22,7 @@ export class MailboxController {
} }
@Get("mailboxes") @Get("mailboxes")
@AdminRoute()
@ApiOperation({ summary: "List mailboxes for a user" }) @ApiOperation({ summary: "List mailboxes for a user" })
@ApiParam({ name: "userId", description: "User ID" }) @ApiParam({ name: "userId", description: "User ID" })
@ApiQuery({ name: "counters", required: false, description: "Include message counters" }) @ApiQuery({ name: "counters", required: false, description: "Include message counters" })
@@ -31,6 +33,7 @@ export class MailboxController {
} }
@Get("mailboxes/:mailboxId") @Get("mailboxes/:mailboxId")
@AdminRoute()
@ApiOperation({ summary: "Get mailbox details" }) @ApiOperation({ summary: "Get mailbox details" })
@ApiParam({ name: "userId", description: "User ID" }) @ApiParam({ name: "userId", description: "User ID" })
@ApiParam({ name: "mailboxId", description: "Mailbox ID" }) @ApiParam({ name: "mailboxId", description: "Mailbox ID" })
@@ -41,6 +44,7 @@ export class MailboxController {
} }
@Post("mailboxes") @Post("mailboxes")
@AdminRoute()
@ApiOperation({ summary: "Create a new mailbox" }) @ApiOperation({ summary: "Create a new mailbox" })
@ApiParam({ name: "userId", description: "User ID" }) @ApiParam({ name: "userId", description: "User ID" })
@ApiResponse({ status: 201, description: "Mailbox created successfully" }) @ApiResponse({ status: 201, description: "Mailbox created successfully" })
@@ -50,6 +54,7 @@ export class MailboxController {
} }
@Patch("mailboxes/:mailboxId") @Patch("mailboxes/:mailboxId")
@AdminRoute()
@ApiOperation({ summary: "Update mailbox" }) @ApiOperation({ summary: "Update mailbox" })
@ApiParam({ name: "userId", description: "User ID" }) @ApiParam({ name: "userId", description: "User ID" })
@ApiParam({ name: "mailboxId", description: "Mailbox ID" }) @ApiParam({ name: "mailboxId", description: "Mailbox ID" })
@@ -60,6 +65,7 @@ export class MailboxController {
} }
@Delete("mailboxes/:mailboxId") @Delete("mailboxes/:mailboxId")
@AdminRoute()
@ApiOperation({ summary: "Delete a mailbox" }) @ApiOperation({ summary: "Delete a mailbox" })
@ApiParam({ name: "userId", description: "User ID" }) @ApiParam({ name: "userId", description: "User ID" })
@ApiParam({ name: "mailboxId", description: "Mailbox ID" }) @ApiParam({ name: "mailboxId", description: "Mailbox ID" })
@@ -5,6 +5,7 @@ import { ApiOperation, ApiResponse } from "@nestjs/swagger";
import { CreateEmailSignatureDto } from "./DTO/create-email-signature.dto"; import { CreateEmailSignatureDto } from "./DTO/create-email-signature.dto";
import { UpdateEmailSignatureDto } from "./DTO/update-email-signature.dto"; import { UpdateEmailSignatureDto } from "./DTO/update-email-signature.dto";
import { EmailSignaturesService } from "./services/email-signatures.service"; import { EmailSignaturesService } from "./services/email-signatures.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator"; import { BusinessDec } from "../../common/decorators/business.decorator";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
@@ -17,6 +18,7 @@ export class EmailSignaturesController {
constructor(private readonly emailSignaturesService: EmailSignaturesService) {} constructor(private readonly emailSignaturesService: EmailSignaturesService) {}
@Post() @Post()
@AdminRoute()
@ApiOperation({ summary: "Create a new email signature" }) @ApiOperation({ summary: "Create a new email signature" })
@ApiResponse({ status: 201, description: "Email signature created successfully" }) @ApiResponse({ status: 201, description: "Email signature created successfully" })
@ApiResponse({ status: 400, description: "Bad request" }) @ApiResponse({ status: 400, description: "Bad request" })
@@ -25,6 +27,7 @@ export class EmailSignaturesController {
} }
@Get() @Get()
@AdminRoute()
@ApiOperation({ summary: "Get a specific email signature" }) @ApiOperation({ summary: "Get a specific email signature" })
@ApiResponse({ status: 200, description: "Email signature retrieved successfully" }) @ApiResponse({ status: 200, description: "Email signature retrieved successfully" })
@ApiResponse({ status: 404, description: "Email signature not found" }) @ApiResponse({ status: 404, description: "Email signature not found" })
@@ -33,6 +36,7 @@ export class EmailSignaturesController {
} }
@Patch() @Patch()
@AdminRoute()
@ApiOperation({ summary: "Update an email signature" }) @ApiOperation({ summary: "Update an email signature" })
@ApiResponse({ status: 200, description: "Email signature updated successfully" }) @ApiResponse({ status: 200, description: "Email signature updated successfully" })
@ApiResponse({ status: 404, description: "Email signature not found" }) @ApiResponse({ status: 404, description: "Email signature not found" })
@@ -41,6 +45,7 @@ export class EmailSignaturesController {
} }
@Delete(":id") @Delete(":id")
@AdminRoute()
@ApiOperation({ summary: "Delete an email signature" }) @ApiOperation({ summary: "Delete an email signature" })
@ApiResponse({ status: 200, description: "Email signature deleted successfully" }) @ApiResponse({ status: 200, description: "Email signature deleted successfully" })
@ApiResponse({ status: 404, description: "Email signature not found" }) @ApiResponse({ status: 404, description: "Email signature not found" })
@@ -28,5 +28,20 @@ export class TemplateRepository extends EntityRepository<Template> {
return template; return template;
} }
async findTemplateByIdWithoutRawHtmlAndStructure(id: string, businessId: string) {
const template = await this.findOne(
{ id, business: { id: businessId }, deletedAt: null },
{ exclude: ["rawHtml", "structure"], populate: ["business"] },
);
return template;
}
//**************************************************** */
async findTemplateByIdWithoutRawHtml(id: string, businessId: string) {
const template = await this.findOne({ id, business: { id: businessId }, deletedAt: null }, { exclude: ["rawHtml"], populate: ["business"] });
return template;
}
//**************************************************** */ //**************************************************** */
} }
@@ -53,11 +53,10 @@ export class TemplatesService {
} }
//**************************************************** */ //**************************************************** */
async findTemplateById(id: string, businessId: string) {
const template = await this.templateRepository.findTemplateById(id, businessId);
async findTemplateByIdForAdmin(id: string, businessId: string) {
const template = await this.templateRepository.findTemplateByIdWithoutRawHtml(id, businessId);
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND); if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
return { return {
template, template,
}; };
@@ -85,7 +84,7 @@ export class TemplatesService {
//**************************************************** */ //**************************************************** */
async deleteTemplate(id: string, businessId: string) { async deleteTemplate(id: string, businessId: string) {
const template = await this.templateRepository.findTemplateById(id, businessId); const template = await this.templateRepository.findTemplateByIdWithoutRawHtmlAndStructure(id, businessId);
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND); if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
@@ -99,20 +98,12 @@ export class TemplatesService {
//**************************************************** */ //**************************************************** */
async getTemplatesByBusinessId(businessId: string) { async getTemplatesByBusinessId(businessId: string) {
return this.templateRepository.find( return this.templateRepository.find({ business: { id: businessId }, deletedAt: null }, { populate: ["business"] });
{
business: { id: businessId },
deletedAt: null,
},
{
populate: ["business"],
},
);
} }
//**************************************************** */ //**************************************************** */
async setSelectedTemplate(id: string, businessId: string) { async setSelectedTemplate(id: string, businessId: string) {
const template = await this.templateRepository.findTemplateById(id, businessId); const template = await this.templateRepository.findTemplateByIdWithoutRawHtmlAndStructure(id, businessId);
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND); if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
@@ -6,6 +6,7 @@ import { TemplateParamDto } from "./DTO/template-param.dto";
import { TemplateQueryDto } from "./DTO/template-query.dto"; import { TemplateQueryDto } from "./DTO/template-query.dto";
import { UpdateTemplateDto } from "./DTO/update-template.dto"; import { UpdateTemplateDto } from "./DTO/update-template.dto";
import { TemplatesService } from "./services/templates.service"; import { TemplatesService } from "./services/templates.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator"; import { BusinessDec } from "../../common/decorators/business.decorator";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
@@ -18,6 +19,7 @@ export class TemplatesController {
constructor(private readonly templatesService: TemplatesService) {} constructor(private readonly templatesService: TemplatesService) {}
@Post() @Post()
@AdminRoute()
@ApiOperation({ summary: "Create a new template" }) @ApiOperation({ summary: "Create a new template" })
@ApiResponse({ status: 201, description: "Template created successfully" }) @ApiResponse({ status: 201, description: "Template created successfully" })
@ApiResponse({ status: 400, description: "Bad request - invalid data" }) @ApiResponse({ status: 400, description: "Bad request - invalid data" })
@@ -27,6 +29,7 @@ export class TemplatesController {
} }
@Get() @Get()
@AdminRoute()
@ApiOperation({ summary: "Get all templates for business" }) @ApiOperation({ summary: "Get all templates for business" })
@ApiResponse({ status: 200, description: "Templates retrieved successfully" }) @ApiResponse({ status: 200, description: "Templates retrieved successfully" })
findAllTemplates(@Query() queryDto: TemplateQueryDto, @BusinessDec("id") businessId: string) { findAllTemplates(@Query() queryDto: TemplateQueryDto, @BusinessDec("id") businessId: string) {
@@ -34,14 +37,16 @@ export class TemplatesController {
} }
@Get(":id") @Get(":id")
@AdminRoute()
@ApiOperation({ summary: "Get template by ID" }) @ApiOperation({ summary: "Get template by ID" })
@ApiResponse({ status: 200, description: "Template retrieved successfully" }) @ApiResponse({ status: 200, description: "Template retrieved successfully" })
@ApiResponse({ status: 404, description: "Template not found" }) @ApiResponse({ status: 404, description: "Template not found" })
findTemplateById(@Param() params: TemplateParamDto, @BusinessDec("id") businessId: string) { findTemplateById(@Param() params: TemplateParamDto, @BusinessDec("id") businessId: string) {
return this.templatesService.findTemplateById(params.id, businessId); return this.templatesService.findTemplateByIdForAdmin(params.id, businessId);
} }
@Patch(":id") @Patch(":id")
@AdminRoute()
@ApiOperation({ summary: "Update template by ID" }) @ApiOperation({ summary: "Update template by ID" })
@ApiResponse({ status: 200, description: "Template updated successfully" }) @ApiResponse({ status: 200, description: "Template updated successfully" })
@ApiResponse({ status: 404, description: "Template not found" }) @ApiResponse({ status: 404, description: "Template not found" })
@@ -50,6 +55,7 @@ export class TemplatesController {
} }
@Delete(":id") @Delete(":id")
@AdminRoute()
@ApiOperation({ summary: "Delete template by ID" }) @ApiOperation({ summary: "Delete template by ID" })
@ApiResponse({ status: 200, description: "Template deleted successfully" }) @ApiResponse({ status: 200, description: "Template deleted successfully" })
@ApiResponse({ status: 404, description: "Template not found" }) @ApiResponse({ status: 404, description: "Template not found" })
@@ -58,6 +64,7 @@ export class TemplatesController {
} }
@Patch(":id/set-selected") @Patch(":id/set-selected")
@AdminRoute()
@ApiOperation({ summary: "Set template as selected (unselects all other templates for the business)" }) @ApiOperation({ summary: "Set template as selected (unselects all other templates for the business)" })
@ApiResponse({ status: 200, description: "Template set as selected successfully" }) @ApiResponse({ status: 200, description: "Template set as selected successfully" })
@ApiResponse({ status: 404, description: "Template not found" }) @ApiResponse({ status: 404, description: "Template not found" })