diff --git a/packages/server/src/modules/Accounts/Accounts.controller.ts b/packages/server/src/modules/Accounts/Accounts.controller.ts index 462165550..0a58bce0f 100644 --- a/packages/server/src/modules/Accounts/Accounts.controller.ts +++ b/packages/server/src/modules/Accounts/Accounts.controller.ts @@ -9,6 +9,7 @@ import { ParseIntPipe, Put, HttpCode, + UseGuards, } from '@nestjs/common'; import { AccountsApplication } from './AccountsApplication.service'; import { CreateAccountDTO } from './CreateAccount.dto'; @@ -32,6 +33,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { AccountAction } from './Accounts.types'; @Controller('accounts') @ApiTags('Accounts') @@ -40,11 +46,13 @@ import { @ApiExtraModels(GetAccountTransactionResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class AccountsController { constructor(private readonly accountsApplication: AccountsApplication) { } @Post('validate-bulk-delete') @HttpCode(200) + @RequirePermission(AccountAction.DELETE, AbilitySubject.Account) @ApiOperation({ summary: 'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.', @@ -67,6 +75,7 @@ export class AccountsController { @Post('bulk-delete') @HttpCode(200) + @RequirePermission(AccountAction.DELETE, AbilitySubject.Account) @ApiOperation({ summary: 'Deletes multiple accounts in bulk.' }) @ApiResponse({ status: 200, @@ -81,6 +90,7 @@ export class AccountsController { } @Post() + @RequirePermission(AccountAction.CREATE, AbilitySubject.Account) @ApiOperation({ summary: 'Create an account' }) @ApiResponse({ status: 200, @@ -91,6 +101,7 @@ export class AccountsController { } @Put(':id') + @RequirePermission(AccountAction.EDIT, AbilitySubject.Account) @ApiOperation({ summary: 'Edit the given account.' }) @ApiResponse({ status: 200, @@ -111,6 +122,7 @@ export class AccountsController { } @Delete(':id') + @RequirePermission(AccountAction.DELETE, AbilitySubject.Account) @ApiOperation({ summary: 'Delete the given account.' }) @ApiResponse({ status: 200, @@ -129,6 +141,7 @@ export class AccountsController { @Post(':id/activate') @HttpCode(200) + @RequirePermission(AccountAction.EDIT, AbilitySubject.Account) @ApiOperation({ summary: 'Activate the given account.' }) @ApiResponse({ status: 200, @@ -147,6 +160,7 @@ export class AccountsController { @Post(':id/inactivate') @HttpCode(200) + @RequirePermission(AccountAction.EDIT, AbilitySubject.Account) @ApiOperation({ summary: 'Inactivate the given account.' }) @ApiResponse({ status: 200, @@ -164,6 +178,7 @@ export class AccountsController { } @Get('types') + @RequirePermission(AccountAction.VIEW, AbilitySubject.Account) @ApiOperation({ summary: 'Retrieves the account types.' }) @ApiResponse({ status: 200, @@ -180,6 +195,7 @@ export class AccountsController { } @Get('transactions') + @RequirePermission(AccountAction.VIEW, AbilitySubject.Account) @ApiOperation({ summary: 'Retrieves the account transactions.' }) @ApiResponse({ status: 200, @@ -198,6 +214,7 @@ export class AccountsController { } @Get(':id') + @RequirePermission(AccountAction.VIEW, AbilitySubject.Account) @ApiOperation({ summary: 'Retrieves the account details.' }) @ApiResponse({ status: 200, @@ -216,6 +233,7 @@ export class AccountsController { } @Get() + @RequirePermission(AccountAction.VIEW, AbilitySubject.Account) @ApiOperation({ summary: 'Retrieves the accounts.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/BillPayments/BillPayments.controller.ts b/packages/server/src/modules/BillPayments/BillPayments.controller.ts index 36d9c5785..80a442f3c 100644 --- a/packages/server/src/modules/BillPayments/BillPayments.controller.ts +++ b/packages/server/src/modules/BillPayments/BillPayments.controller.ts @@ -7,6 +7,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { BillPaymentsApplication } from './BillPaymentsApplication.service'; import { @@ -26,12 +27,18 @@ import { BillPaymentsPages } from './commands/BillPaymentsPages.service'; import { BillPaymentResponseDto } from './dtos/BillPaymentResponse.dto'; import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { IPaymentMadeAction } from './types/BillPayments.types'; @Controller('bill-payments') @ApiTags('Bill Payments') @ApiExtraModels(BillPaymentResponseDto) @ApiExtraModels(PaginatedResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class BillPaymentsController { constructor( private billPaymentsApplication: BillPaymentsApplication, @@ -39,12 +46,14 @@ export class BillPaymentsController { ) {} @Post() + @RequirePermission(IPaymentMadeAction.Create, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Create a new bill payment.' }) public createBillPayment(@Body() billPaymentDTO: CreateBillPaymentDto) { return this.billPaymentsApplication.createBillPayment(billPaymentDTO); } @Delete(':billPaymentId') + @RequirePermission(IPaymentMadeAction.Delete, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Delete the given bill payment.' }) @ApiParam({ name: 'billPaymentId', @@ -59,6 +68,7 @@ export class BillPaymentsController { } @Put(':billPaymentId') + @RequirePermission(IPaymentMadeAction.Edit, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Edit the given bill payment.' }) @ApiParam({ name: 'billPaymentId', @@ -77,6 +87,7 @@ export class BillPaymentsController { } @Get('/new-page/entries') + @RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Retrieves the payable entries of the new page once vendor be selected.', @@ -95,6 +106,7 @@ export class BillPaymentsController { } @Get(':billPaymentId/bills') + @RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Retrieves the bills of the given bill payment.' }) @ApiParam({ name: 'billPaymentId', @@ -107,6 +119,7 @@ export class BillPaymentsController { } @Get('/:billPaymentId/edit-page') + @RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Retrieves the edit page of the given bill payment.', }) @@ -126,6 +139,7 @@ export class BillPaymentsController { } @Get(':billPaymentId') + @RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Retrieves the bill payment details.' }) @ApiResponse({ status: 200, @@ -145,6 +159,7 @@ export class BillPaymentsController { } @Get() + @RequirePermission(IPaymentMadeAction.View, AbilitySubject.PaymentMade) @ApiOperation({ summary: 'Retrieves the bill payments list.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/Bills/Bills.controller.ts b/packages/server/src/modules/Bills/Bills.controller.ts index e81f97393..26253be7e 100644 --- a/packages/server/src/modules/Bills/Bills.controller.ts +++ b/packages/server/src/modules/Bills/Bills.controller.ts @@ -17,6 +17,7 @@ import { Get, Query, HttpCode, + UseGuards, } from '@nestjs/common'; import { BillsApplication } from './Bills.application'; import { IBillsFilter } from './Bills.types'; @@ -28,6 +29,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { BillAction } from './Bills.types'; @Controller('bills') @ApiTags('Bills') @@ -35,10 +41,12 @@ import { @ApiExtraModels(PaginatedResponseDto) @ApiCommonHeaders() @ApiExtraModels(ValidateBulkDeleteResponseDto) +@UseGuards(AuthorizationGuard, PermissionGuard) export class BillsController { constructor(private billsApplication: BillsApplication) { } @Post('validate-bulk-delete') + @RequirePermission(BillAction.Delete, AbilitySubject.Bill) @ApiOperation({ summary: 'Validate which bills can be deleted and return the results.', }) @@ -58,6 +66,7 @@ export class BillsController { } @Post('bulk-delete') + @RequirePermission(BillAction.Delete, AbilitySubject.Bill) @ApiOperation({ summary: 'Deletes multiple bills.' }) @HttpCode(200) @ApiResponse({ @@ -73,12 +82,14 @@ export class BillsController { } @Post() + @RequirePermission(BillAction.Create, AbilitySubject.Bill) @ApiOperation({ summary: 'Create a new bill.' }) createBill(@Body() billDTO: CreateBillDto) { return this.billsApplication.createBill(billDTO); } @Put(':id') + @RequirePermission(BillAction.Edit, AbilitySubject.Bill) @ApiOperation({ summary: 'Edit the given bill.' }) @ApiParam({ name: 'id', @@ -91,6 +102,7 @@ export class BillsController { } @Delete(':id') + @RequirePermission(BillAction.Delete, AbilitySubject.Bill) @ApiOperation({ summary: 'Delete the given bill.' }) @ApiParam({ name: 'id', @@ -103,6 +115,7 @@ export class BillsController { } @Get() + @RequirePermission(BillAction.View, AbilitySubject.Bill) @ApiOperation({ summary: 'Retrieves the bills.' }) @ApiResponse({ status: 200, @@ -132,6 +145,7 @@ export class BillsController { } @Get(':id/payment-transactions') + @RequirePermission(BillAction.View, AbilitySubject.Bill) @ApiOperation({ summary: 'Retrieve the specific bill associated payment transactions.', }) @@ -146,6 +160,7 @@ export class BillsController { } @Get(':id') + @RequirePermission(BillAction.View, AbilitySubject.Bill) @ApiOperation({ summary: 'Retrieves the bill details.' }) @ApiResponse({ status: 200, @@ -165,6 +180,7 @@ export class BillsController { } @Patch(':id/open') + @RequirePermission(BillAction.Edit, AbilitySubject.Bill) @ApiOperation({ summary: 'Open the given bill.' }) @ApiParam({ name: 'id', @@ -177,6 +193,7 @@ export class BillsController { } @Get('due') + @RequirePermission(BillAction.View, AbilitySubject.Bill) @ApiOperation({ summary: 'Retrieves the due bills.' }) getDueBills(@Body('vendorId') vendorId?: number) { return this.billsApplication.getDueBills(vendorId); diff --git a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts index eec6c4e28..649a714da 100644 --- a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts +++ b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts @@ -18,6 +18,7 @@ import { Put, Query, Res, + UseGuards, } from '@nestjs/common'; import { CreditNoteApplication } from './CreditNoteApplication.service'; import { ICreditNotesQueryDTO } from './types/CreditNotes.types'; @@ -30,6 +31,11 @@ import { ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; import { AcceptType } from '@/constants/accept-type'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { CreditNoteAction } from './types/CreditNotes.types'; @Controller('credit-notes') @ApiTags('Credit Notes') @@ -37,6 +43,7 @@ import { AcceptType } from '@/constants/accept-type'; @ApiExtraModels(PaginatedResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class CreditNotesController { /** * @param {CreditNoteApplication} creditNoteApplication - The credit note application service. @@ -44,6 +51,7 @@ export class CreditNotesController { constructor(private creditNoteApplication: CreditNoteApplication) { } @Post() + @RequirePermission(CreditNoteAction.Create, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Create a new credit note' }) @ApiResponse({ status: 201, description: 'Credit note successfully created' }) @ApiResponse({ status: 400, description: 'Invalid input data' }) @@ -52,6 +60,7 @@ export class CreditNotesController { } @Get('state') + @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Get credit note state' }) @ApiResponse({ status: 200, description: 'Returns the credit note state' }) getCreditNoteState() { @@ -59,6 +68,7 @@ export class CreditNotesController { } @Get(':id') + @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Get a specific credit note by ID' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiResponse({ @@ -92,6 +102,7 @@ export class CreditNotesController { } @Get() + @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Get all credit notes' }) @ApiResponse({ status: 200, @@ -115,6 +126,7 @@ export class CreditNotesController { } @Put(':id') + @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Update a credit note' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiResponse({ status: 200, description: 'Credit note successfully updated' }) @@ -131,6 +143,7 @@ export class CreditNotesController { } @Put(':id/open') + @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Open a credit note' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiResponse({ status: 200, description: 'Credit note successfully opened' }) @@ -140,6 +153,7 @@ export class CreditNotesController { } @Post('validate-bulk-delete') + @RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Validates which credit notes can be deleted and returns the results.', @@ -161,6 +175,7 @@ export class CreditNotesController { } @Post('bulk-delete') + @RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Deletes multiple credit notes.' }) @ApiResponse({ status: 200, @@ -173,6 +188,7 @@ export class CreditNotesController { } @Delete(':id') + @RequirePermission(CreditNoteAction.Delete, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Delete a credit note' }) @ApiParam({ name: 'id', description: 'Credit note ID', type: 'number' }) @ApiResponse({ status: 200, description: 'Credit note successfully deleted' }) diff --git a/packages/server/src/modules/Customers/Customers.controller.ts b/packages/server/src/modules/Customers/Customers.controller.ts index 910fda307..fc70ec603 100644 --- a/packages/server/src/modules/Customers/Customers.controller.ts +++ b/packages/server/src/modules/Customers/Customers.controller.ts @@ -7,6 +7,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { CustomersApplication } from './CustomersApplication.service'; import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto'; @@ -26,15 +27,22 @@ import { ValidateBulkDeleteCustomersResponseDto, } from './dtos/BulkDeleteCustomers.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { CustomerAction } from './types/Customers.types'; @Controller('customers') @ApiTags('Customers') @ApiExtraModels(CustomerResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class CustomersController { constructor(private customersApplication: CustomersApplication) { } @Get(':id') + @RequirePermission(CustomerAction.View, AbilitySubject.Customer) @ApiOperation({ summary: 'Retrieves the customer details.' }) @ApiResponse({ status: 200, @@ -46,6 +54,7 @@ export class CustomersController { } @Get() + @RequirePermission(CustomerAction.View, AbilitySubject.Customer) @ApiOperation({ summary: 'Retrieves the customers paginated list.' }) @ApiResponse({ status: 200, @@ -60,6 +69,7 @@ export class CustomersController { } @Post() + @RequirePermission(CustomerAction.Create, AbilitySubject.Customer) @ApiOperation({ summary: 'Create a new customer.' }) @ApiResponse({ status: 201, @@ -71,6 +81,7 @@ export class CustomersController { } @Put(':id') + @RequirePermission(CustomerAction.Edit, AbilitySubject.Customer) @ApiOperation({ summary: 'Edit the given customer.' }) @ApiResponse({ status: 200, @@ -85,6 +96,7 @@ export class CustomersController { } @Delete(':id') + @RequirePermission(CustomerAction.Delete, AbilitySubject.Customer) @ApiOperation({ summary: 'Delete the given customer.' }) @ApiResponse({ status: 200, @@ -95,6 +107,7 @@ export class CustomersController { } @Put(':id/opening-balance') + @RequirePermission(CustomerAction.Edit, AbilitySubject.Customer) @ApiOperation({ summary: 'Edit the opening balance of the given customer.' }) @ApiResponse({ status: 200, @@ -112,6 +125,7 @@ export class CustomersController { } @Post('validate-bulk-delete') + @RequirePermission(CustomerAction.Delete, AbilitySubject.Customer) @ApiOperation({ summary: 'Validates which customers can be deleted and returns counts of deletable and non-deletable customers.', @@ -131,6 +145,7 @@ export class CustomersController { } @Post('bulk-delete') + @RequirePermission(CustomerAction.Delete, AbilitySubject.Customer) @ApiOperation({ summary: 'Deletes multiple customers in bulk.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/Expenses/Expenses.controller.ts b/packages/server/src/modules/Expenses/Expenses.controller.ts index 2f80404a2..571a9c9e8 100644 --- a/packages/server/src/modules/Expenses/Expenses.controller.ts +++ b/packages/server/src/modules/Expenses/Expenses.controller.ts @@ -7,6 +7,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { ExpensesApplication } from './ExpensesApplication.service'; import { IExpensesFilter } from './Expenses.types'; @@ -25,6 +26,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ExpenseAction } from './Expenses.types'; @Controller('expenses') @ApiTags('Expenses') @@ -34,10 +40,12 @@ import { ValidateBulkDeleteResponseDto, ) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class ExpensesController { constructor(private readonly expensesApplication: ExpensesApplication) { } @Post('validate-bulk-delete') + @RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense) @ApiOperation({ summary: 'Validate which expenses can be deleted and return the results.', }) @@ -58,6 +66,7 @@ export class ExpensesController { } @Post('bulk-delete') + @RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense) @ApiOperation({ summary: 'Deletes multiple expenses.' }) @ApiResponse({ status: 200, @@ -76,6 +85,7 @@ export class ExpensesController { * @param {IExpenseCreateDTO} expenseDTO */ @Post() + @RequirePermission(ExpenseAction.Create, AbilitySubject.Expense) @ApiOperation({ summary: 'Create a new expense transaction.' }) public createExpense(@Body() expenseDTO: CreateExpenseDto) { return this.expensesApplication.createExpense(expenseDTO); @@ -87,6 +97,7 @@ export class ExpensesController { * @param {IExpenseEditDTO} expenseDTO */ @Put(':id') + @RequirePermission(ExpenseAction.Edit, AbilitySubject.Expense) @ApiOperation({ summary: 'Edit the given expense transaction.' }) public editExpense( @Param('id') expenseId: number, @@ -100,6 +111,7 @@ export class ExpensesController { * @param {number} expenseId */ @Delete(':id') + @RequirePermission(ExpenseAction.Delete, AbilitySubject.Expense) @ApiOperation({ summary: 'Delete the given expense transaction.' }) public deleteExpense(@Param('id') expenseId: number) { return this.expensesApplication.deleteExpense(expenseId); @@ -110,6 +122,7 @@ export class ExpensesController { * @param {number} expenseId */ @Post(':id/publish') + @RequirePermission(ExpenseAction.Edit, AbilitySubject.Expense) @ApiOperation({ summary: 'Publish the given expense transaction.' }) public publishExpense(@Param('id') expenseId: number) { return this.expensesApplication.publishExpense(expenseId); @@ -119,6 +132,7 @@ export class ExpensesController { * Get the expense transaction details. */ @Get() + @RequirePermission(ExpenseAction.View, AbilitySubject.Expense) @ApiOperation({ summary: 'Get the expense transactions.' }) @ApiResponse({ status: 200, @@ -146,6 +160,7 @@ export class ExpensesController { * @param {number} expenseId */ @Get(':id') + @RequirePermission(ExpenseAction.View, AbilitySubject.Expense) @ApiOperation({ summary: 'Get the expense transaction details.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts index 18c3c2441..e552f1a90 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts @@ -1,5 +1,5 @@ import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { APAgingSummaryApplication } from './APAgingSummaryApplication'; import { AcceptType } from '@/constants/accept-type'; import { @@ -11,14 +11,21 @@ import { import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; import { APAgingSummaryResponseExample } from './APAgingSummary.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('reports/payable-aging-summary') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class APAgingSummaryController { constructor(private readonly APAgingSummaryApp: APAgingSummaryApplication) { } @Get() + @RequirePermission(ReportsAction.READ_AP_AGING_SUMMARY, AbilitySubject.Report) @ApiOperation({ summary: 'Get payable aging summary' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts index ec5dc24c4..46bfaf439 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts @@ -1,5 +1,4 @@ -import { Controller, Get, Headers } from '@nestjs/common'; -import { Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { ARAgingSummaryApplication } from './ARAgingSummaryApplication'; import { AcceptType } from '@/constants/accept-type'; import { Response } from 'express'; @@ -12,14 +11,21 @@ import { import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; import { ARAgingSummaryResponseExample } from './ARAgingSummary.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('reports/receivable-aging-summary') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class ARAgingSummaryController { constructor(private readonly ARAgingSummaryApp: ARAgingSummaryApplication) {} @Get() + @RequirePermission(ReportsAction.READ_AR_AGING_SUMMARY, AbilitySubject.Report) @ApiOperation({ summary: 'Get receivable aging summary' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts b/packages/server/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts index 41775b737..7d80f00c3 100644 --- a/packages/server/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/BalanceSheet/BalanceSheet.controller.ts @@ -1,5 +1,5 @@ import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { AcceptType } from '@/constants/accept-type'; import { BalanceSheetApplication } from './BalanceSheetApplication'; import { @@ -11,10 +11,16 @@ import { import { BalanceSheetQueryDto } from './BalanceSheet.dto'; import { BalanceSheetResponseExample } from './BalanceSheet.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('/reports/balance-sheet') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class BalanceSheetStatementController { constructor(private readonly balanceSheetApp: BalanceSheetApplication) {} @@ -25,6 +31,7 @@ export class BalanceSheetStatementController { * @param {string} acceptHeader - Accept header. */ @Get('') + @RequirePermission(ReportsAction.READ_BALANCE_SHEET, AbilitySubject.Report) @ApiOperation({ summary: 'Get balance sheet statement' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts b/packages/server/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts index 870c5e4dd..f963e17bb 100644 --- a/packages/server/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/CashFlowStatement/Cashflow.controller.ts @@ -1,5 +1,5 @@ import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { AcceptType } from '@/constants/accept-type'; import { CashflowSheetApplication } from './CashflowSheetApplication'; import { @@ -11,14 +11,21 @@ import { import { CashFlowStatementQueryDto } from './CashFlowStatementQuery.dto'; import { CashflowStatementResponseExample } from './CashflowStatement.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('reports/cashflow-statement') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class CashflowController { constructor(private readonly cashflowSheetApp: CashflowSheetApplication) { } @Get() + @RequirePermission(ReportsAction.READ_CASHFLOW, AbilitySubject.Report) @ApiResponse({ status: 200, description: 'Cashflow statement report', diff --git a/packages/server/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts b/packages/server/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts index 7a65dc77f..7cdf63f21 100644 --- a/packages/server/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/GeneralLedger/GeneralLedger.controller.ts @@ -5,22 +5,29 @@ import { ApiTags, } from '@nestjs/swagger'; import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { GeneralLedgerApplication } from './GeneralLedgerApplication'; import { AcceptType } from '@/constants/accept-type'; import { GeneralLedgerQueryDto } from './GeneralLedgerQuery.dto'; import { GeneralLedgerResponseExample } from './GeneralLedger.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('/reports/general-ledger') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class GeneralLedgerController { constructor( private readonly generalLedgerApplication: GeneralLedgerApplication, ) {} @Get() + @RequirePermission(ReportsAction.READ_GENERAL_LEDGET, AbilitySubject.Report) @ApiResponse({ status: 200, description: 'General ledger report', diff --git a/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.controller.ts b/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.controller.ts index a33184f88..415d13133 100644 --- a/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/JournalSheet/JournalSheet.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { Response } from 'express'; import { AcceptType } from '@/constants/accept-type'; import { JournalSheetApplication } from './JournalSheetApplication'; @@ -11,14 +11,21 @@ import { import { JournalSheetQueryDto } from './JournalSheetQuery.dto'; import { JournalSheetResponseExample } from './JournalSheet.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('/reports/journal') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class JournalSheetController { constructor(private readonly journalSheetApp: JournalSheetApplication) {} @Get() + @RequirePermission(ReportsAction.READ_JOURNAL, AbilitySubject.Report) @ApiResponse({ status: 200, description: 'Journal report', diff --git a/packages/server/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts b/packages/server/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts index 38895cd96..70cc31561 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ProfitLossSheet/ProfitLossSheet.controller.ts @@ -1,5 +1,5 @@ import { Response } from 'express'; -import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; +import { Controller, Get, Headers, Query, Res, UseGuards } from '@nestjs/common'; import { ProfitLossSheetApplication } from './ProfitLossSheetApplication'; import { AcceptType } from '@/constants/accept-type'; import { @@ -11,10 +11,16 @@ import { import { ProfitLossSheetQueryDto } from './ProfitLossSheetQuery.dto'; import { ProfitLossSheetResponseExample } from './ProfitLossSheet.swagger'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ReportsAction } from '../../types/Report.types'; @Controller('/reports/profit-loss-sheet') @ApiTags('Reports') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class ProfitLossSheetController { constructor( private readonly profitLossSheetApp: ProfitLossSheetApplication, @@ -27,6 +33,7 @@ export class ProfitLossSheetController { * @param {string} acceptHeader */ @Get('/') + @RequirePermission(ReportsAction.READ_PROFIT_LOSS, AbilitySubject.Report) @ApiResponse({ status: 200, description: 'Profit & loss statement', diff --git a/packages/server/src/modules/InventoryAdjutments/InventoryAdjustments.controller.ts b/packages/server/src/modules/InventoryAdjutments/InventoryAdjustments.controller.ts index 00e4c8e5b..6fbaa5c36 100644 --- a/packages/server/src/modules/InventoryAdjutments/InventoryAdjustments.controller.ts +++ b/packages/server/src/modules/InventoryAdjutments/InventoryAdjustments.controller.ts @@ -14,6 +14,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsApplication.service'; import { IInventoryAdjustmentsFilter } from './types/InventoryAdjustments.types'; @@ -21,17 +22,24 @@ import { InventoryAdjustment } from './models/InventoryAdjustment'; import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto'; import { InventoryAdjustmentResponseDto } from './dtos/InventoryAdjustmentResponse.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { InventoryAdjustmentAction } from './types/InventoryAdjustments.types'; @Controller('inventory-adjustments') @ApiTags('Inventory Adjustments') @ApiExtraModels(InventoryAdjustmentResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class InventoryAdjustmentsController { constructor( private readonly inventoryAdjustmentsApplicationService: InventoryAdjustmentsApplicationService, ) {} @Post('quick') + @RequirePermission(InventoryAdjustmentAction.CREATE, AbilitySubject.InventoryAdjustment) @ApiOperation({ summary: 'Create a quick inventory adjustment.' }) @ApiResponse({ status: 200, @@ -46,6 +54,7 @@ export class InventoryAdjustmentsController { } @Delete(':id') + @RequirePermission(InventoryAdjustmentAction.DELETE, AbilitySubject.InventoryAdjustment) @ApiOperation({ summary: 'Delete the given inventory adjustment.' }) @ApiResponse({ status: 200, @@ -60,6 +69,7 @@ export class InventoryAdjustmentsController { } @Get() + @RequirePermission(InventoryAdjustmentAction.VIEW, AbilitySubject.InventoryAdjustment) @ApiOperation({ summary: 'Retrieves the inventory adjustments.' }) @ApiResponse({ status: 200, @@ -78,6 +88,7 @@ export class InventoryAdjustmentsController { } @Get(':id') + @RequirePermission(InventoryAdjustmentAction.VIEW, AbilitySubject.InventoryAdjustment) @ApiOperation({ summary: 'Retrieves the inventory adjustment details.' }) @ApiResponse({ status: 200, @@ -94,6 +105,7 @@ export class InventoryAdjustmentsController { } @Put(':id/publish') + @RequirePermission(InventoryAdjustmentAction.EDIT, AbilitySubject.InventoryAdjustment) @ApiOperation({ summary: 'Publish the given inventory adjustment.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/Items/Item.controller.ts b/packages/server/src/modules/Items/Item.controller.ts index 5f174f766..1c49978a9 100644 --- a/packages/server/src/modules/Items/Item.controller.ts +++ b/packages/server/src/modules/Items/Item.controller.ts @@ -9,6 +9,7 @@ import { Put, Query, HttpCode, + UseGuards, } from '@nestjs/common'; import { TenantController } from '../Tenancy/Tenant.controller'; import { ItemsApplicationService } from './ItemsApplication.service'; @@ -35,6 +36,11 @@ import { ValidateBulkDeleteItemsResponseDto, } from './dtos/BulkDeleteItems.dto'; import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ItemAction } from '@/interfaces/Item'; @Controller('/items') @ApiTags('Items') @@ -48,12 +54,14 @@ import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto'; @ApiExtraModels(ValidateBulkDeleteItemsResponseDto) @ApiExtraModels(ItemApiErrorResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class ItemsController extends TenantController { constructor(private readonly itemsApplication: ItemsApplicationService) { super(); } @Get() + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Retrieves the item list.' }) @ApiResponse({ status: 200, @@ -144,6 +152,7 @@ export class ItemsController extends TenantController { * @returns The updated item id. */ @Put(':id') + @RequirePermission(ItemAction.EDIT, AbilitySubject.Item) @ApiOperation({ summary: 'Edit the given item (product or service).' }) @ApiResponse({ status: 200, @@ -174,6 +183,7 @@ export class ItemsController extends TenantController { @Post('validate-bulk-delete') @HttpCode(200) + @RequirePermission(ItemAction.DELETE, AbilitySubject.Item) @ApiOperation({ summary: 'Validates which items can be deleted and returns counts of deletable and non-deletable items.', @@ -194,6 +204,7 @@ export class ItemsController extends TenantController { @Post('bulk-delete') @HttpCode(200) + @RequirePermission(ItemAction.DELETE, AbilitySubject.Item) @ApiOperation({ summary: 'Deletes multiple items in bulk.' }) @ApiResponse({ status: 200, @@ -208,6 +219,7 @@ export class ItemsController extends TenantController { } @Post() + @RequirePermission(ItemAction.CREATE, AbilitySubject.Item) @ApiOperation({ summary: 'Create a new item (product or service).' }) @ApiResponse({ status: 200, @@ -230,6 +242,7 @@ export class ItemsController extends TenantController { } @Delete(':id') + @RequirePermission(ItemAction.DELETE, AbilitySubject.Item) @ApiOperation({ summary: 'Delete the given item (product or service).' }) @ApiResponse({ status: 200, @@ -255,6 +268,7 @@ export class ItemsController extends TenantController { } @Patch(':id/inactivate') + @RequirePermission(ItemAction.EDIT, AbilitySubject.Item) @ApiOperation({ summary: 'Inactivate the given item (product or service).' }) @ApiResponse({ status: 200, @@ -273,6 +287,7 @@ export class ItemsController extends TenantController { } @Patch(':id/activate') + @RequirePermission(ItemAction.EDIT, AbilitySubject.Item) @ApiOperation({ summary: 'Activate the given item (product or service).' }) @ApiResponse({ status: 200, @@ -291,6 +306,7 @@ export class ItemsController extends TenantController { } @Get(':id') + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Get the given item (product or service).' }) @ApiResponse({ status: 200, @@ -312,6 +328,7 @@ export class ItemsController extends TenantController { } @Get(':id/invoices') + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Retrieves the item associated invoices transactions.', }) @@ -337,6 +354,7 @@ export class ItemsController extends TenantController { } @Get(':id/bills') + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Retrieves the item associated bills transactions.', }) @@ -362,6 +380,7 @@ export class ItemsController extends TenantController { } @Get(':id/estimates') + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Retrieves the item associated estimates transactions.', }) @@ -387,6 +406,7 @@ export class ItemsController extends TenantController { } @Get(':id/receipts') + @RequirePermission(ItemAction.VIEW, AbilitySubject.Item) @ApiOperation({ summary: 'Retrieves the item associated receipts transactions.', }) diff --git a/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts b/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts index 239ebadf7..3352cffaa 100644 --- a/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts +++ b/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts @@ -8,6 +8,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { @@ -29,16 +30,23 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { ManualJournalAction } from './types/ManualJournals.types'; @Controller('manual-journals') @ApiTags('Manual Journals') @ApiExtraModels(ManualJournalResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class ManualJournalsController { constructor(private manualJournalsApplication: ManualJournalsApplication) { } @Post('validate-bulk-delete') + @RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Validate which manual journals can be deleted and return the results.', @@ -60,6 +68,7 @@ export class ManualJournalsController { } @Post('bulk-delete') + @RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Deletes multiple manual journals.' }) @ApiResponse({ status: 200, @@ -75,6 +84,7 @@ export class ManualJournalsController { } @Post() + @RequirePermission(ManualJournalAction.Create, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Create a new manual journal.' }) @ApiResponse({ status: 201, @@ -86,6 +96,7 @@ export class ManualJournalsController { } @Put(':id') + @RequirePermission(ManualJournalAction.Edit, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Edit the given manual journal.' }) @ApiResponse({ status: 200, @@ -110,6 +121,7 @@ export class ManualJournalsController { } @Delete(':id') + @RequirePermission(ManualJournalAction.Delete, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Delete the given manual journal.' }) @ApiResponse({ status: 200, @@ -127,6 +139,7 @@ export class ManualJournalsController { } @Patch(':id/publish') + @RequirePermission(ManualJournalAction.Edit, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Publish the given manual journal.' }) @ApiResponse({ status: 200, @@ -147,6 +160,7 @@ export class ManualJournalsController { } @Get(':id') + @RequirePermission(ManualJournalAction.View, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Retrieves the manual journal details.' }) @ApiResponse({ status: 200, @@ -167,6 +181,7 @@ export class ManualJournalsController { } @Get() + @RequirePermission(ManualJournalAction.View, AbilitySubject.ManualJournal) @ApiOperation({ summary: 'Retrieves the manual journals paginated list.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts b/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts index cac29ebef..8800409f9 100644 --- a/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts +++ b/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts @@ -19,6 +19,7 @@ import { Put, Query, Res, + UseGuards, } from '@nestjs/common'; import { PaymentReceivesApplication } from './PaymentReceived.application'; import { @@ -38,6 +39,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { PaymentReceiveAction } from './types/PaymentReceived.types'; @Controller('payments-received') @ApiTags('Payments Received') @@ -46,6 +52,7 @@ import { @ApiExtraModels(PaymentReceivedStateResponseDto) @ApiExtraModels(ValidateBulkDeleteResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class PaymentReceivesController { constructor(private paymentReceivesApplication: PaymentReceivesApplication) { } @@ -94,6 +101,7 @@ export class PaymentReceivesController { } @Post() + @RequirePermission(PaymentReceiveAction.Create, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Create a new payment received.' }) public createPaymentReceived( @Body() paymentReceiveDTO: CreatePaymentReceivedDto, @@ -104,6 +112,7 @@ export class PaymentReceivesController { } @Put(':id') + @RequirePermission(PaymentReceiveAction.Edit, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Edit the given payment received.' }) public editPaymentReceive( @Param('id', ParseIntPipe) paymentReceiveId: number, @@ -116,6 +125,7 @@ export class PaymentReceivesController { } @Delete(':id') + @RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Delete the given payment received.' }) public deletePaymentReceive( @Param('id', ParseIntPipe) paymentReceiveId: number, @@ -126,6 +136,7 @@ export class PaymentReceivesController { } @Get() + @RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Retrieves the payment received list.' }) @ApiResponse({ status: 200, @@ -151,6 +162,7 @@ export class PaymentReceivesController { } @Post('validate-bulk-delete') + @RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Validates which payments received can be deleted and returns the results.', @@ -172,6 +184,7 @@ export class PaymentReceivesController { } @Post('bulk-delete') + @RequirePermission(PaymentReceiveAction.Delete, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Deletes multiple payments received.' }) @ApiResponse({ status: 200, @@ -187,6 +200,7 @@ export class PaymentReceivesController { } @Get('state') + @RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Retrieves the payment received state.' }) @ApiResponse({ status: 200, @@ -200,6 +214,7 @@ export class PaymentReceivesController { } @Get(':id/invoices') + @RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Retrieves the payment received invoices.' }) @ApiResponse({ status: 200, @@ -215,6 +230,7 @@ export class PaymentReceivesController { } @Get(':id') + @RequirePermission(PaymentReceiveAction.View, AbilitySubject.PaymentReceive) @ApiOperation({ summary: 'Retrieves the payment received details.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/Roles/Permission.guard.ts b/packages/server/src/modules/Roles/Permission.guard.ts new file mode 100644 index 000000000..51aa72833 --- /dev/null +++ b/packages/server/src/modules/Roles/Permission.guard.ts @@ -0,0 +1,67 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; +import { REQUIRED_PERMISSION_KEY, RequiredPermission } from './RequirePermission.decorator'; +import { AbilitySubject } from './Roles.types'; + +/** + * Guard that checks if the user has the required permission to access a route. + * Uses CASL ability instance attached to the request by AuthorizationGuard. + */ +@Injectable() +export class PermissionGuard implements CanActivate { + constructor(private readonly reflector: Reflector) {} + + /** + * Checks if the user has the required permission to access the route. + * @param context - The execution context + * @returns A boolean indicating if the user can access the route + * @throws ForbiddenException if the user doesn't have the required permission + */ + canActivate(context: ExecutionContext): boolean { + const requiredPermission = this.reflector.getAllAndOverride( + REQUIRED_PERMISSION_KEY, + [context.getHandler(), context.getClass()], + ); + + // If no permission is required, allow access + if (!requiredPermission) { + return true; + } + + const request = context.switchToHttp().getRequest(); + const ability = (request as any).ability; + + // If no ability instance is attached to the request, deny access + if (!ability) { + throw new ForbiddenException('Ability instance not found. Ensure AuthorizationGuard is applied.'); + } + + const { ability: action, subject } = requiredPermission; + + // Check if the user has the required permission using CASL ability + const hasPermission = ability.can(action, subject); + + if (!hasPermission) { + throw new ForbiddenException( + `You do not have permission to ${action} ${subject}`, + ); + } + + return true; + } + + /** + * Helper method to check if a subject value is a valid AbilitySubject. + * @param subject - The subject value to check + * @returns True if the subject is a valid AbilitySubject enum value + */ + private isValidSubject(subject: string): subject is AbilitySubject { + return Object.values(AbilitySubject).includes(subject as AbilitySubject); + } +} diff --git a/packages/server/src/modules/Roles/RequirePermission.decorator.ts b/packages/server/src/modules/Roles/RequirePermission.decorator.ts new file mode 100644 index 000000000..40330c48c --- /dev/null +++ b/packages/server/src/modules/Roles/RequirePermission.decorator.ts @@ -0,0 +1,29 @@ +import { SetMetadata } from '@nestjs/common'; +import { AbilitySubject } from './Roles.types'; + +export const REQUIRED_PERMISSION_KEY = 'requiredPermission'; + +export interface RequiredPermission { + ability: string; + subject: AbilitySubject | string; +} + +/** + * Decorator to specify required ability and subject for a route handler or controller. + * @param ability - The ability/action required (e.g., 'Create', 'View', 'Edit', 'Delete') + * @param subject - The subject/entity the ability applies to (e.g., AbilitySubject.Item, AbilitySubject.SaleInvoice) + * @example + * ```typescript + * @RequirePermission('Create', AbilitySubject.Item) + * @Post() + * async createItem(@Body() dto: CreateItemDto) { ... } + * + * @RequirePermission('View', AbilitySubject.SaleInvoice) + * @Get(':id') + * async getInvoice(@Param('id') id: number) { ... } + * ``` + */ +export const RequirePermission = ( + ability: string, + subject: AbilitySubject | string, +) => SetMetadata(REQUIRED_PERMISSION_KEY, { ability, subject }); diff --git a/packages/server/src/modules/Roles/Roles.module.ts b/packages/server/src/modules/Roles/Roles.module.ts index 4e00ba21e..489501a12 100644 --- a/packages/server/src/modules/Roles/Roles.module.ts +++ b/packages/server/src/modules/Roles/Roles.module.ts @@ -10,6 +10,8 @@ import { RolePermission } from './models/RolePermission.model'; import { RolesController } from './Roles.controller'; import { RolesApplication } from './Roles.application'; import { RolePermissionsSchema } from './queries/RolePermissionsSchema'; +import { AuthorizationGuard } from './Authorization.guard'; +import { PermissionGuard } from './Permission.guard'; const models = [ RegisterTenancyModel(Role), @@ -25,9 +27,11 @@ const models = [ GetRoleService, GetRolesService, RolesApplication, - RolePermissionsSchema + RolePermissionsSchema, + AuthorizationGuard, + PermissionGuard, ], controllers: [RolesController], - exports: [...models], + exports: [...models, AuthorizationGuard, PermissionGuard], }) export class RolesModule {} diff --git a/packages/server/src/modules/Roles/dtos/Role.dto.ts b/packages/server/src/modules/Roles/dtos/Role.dto.ts index 02b04c876..3ef300553 100644 --- a/packages/server/src/modules/Roles/dtos/Role.dto.ts +++ b/packages/server/src/modules/Roles/dtos/Role.dto.ts @@ -1,3 +1,4 @@ +import { IsOptional } from '@/common/decorators/Validators'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { @@ -41,7 +42,7 @@ export class CommandRolePermissionDto { export class CreateRolePermissionDto extends CommandRolePermissionDto { } export class EditRolePermissionDto extends CommandRolePermissionDto { @IsNumber() - @IsNotEmpty() + @IsOptional() @ApiProperty({ example: 1, description: 'The permission ID', @@ -59,7 +60,6 @@ class CommandRoleDto { roleName: string; @IsString() - @IsNotEmpty() @ApiProperty({ example: 'Administrator', description: 'The description of the role', @@ -71,9 +71,9 @@ export class CreateRoleDto extends CommandRoleDto { @IsArray() @ArrayMinSize(1) @ValidateNested({ each: true }) - @Type(() => CommandRolePermissionDto) + @Type(() => CreateRolePermissionDto) @ApiProperty({ - type: [CommandRolePermissionDto], + type: [CreateRolePermissionDto], description: 'The permissions of the role', }) permissions: Array; diff --git a/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts b/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts index fe11ea3a6..9e53d856d 100644 --- a/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts +++ b/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts @@ -19,6 +19,7 @@ import { Put, Query, Res, + UseGuards, } from '@nestjs/common'; import { SaleEstimatesApplication } from './SaleEstimates.application'; import { @@ -40,6 +41,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { SaleEstimateAction } from './types/SaleEstimates.types'; @Controller('sale-estimates') @ApiTags('Sale Estimates') @@ -48,8 +54,10 @@ import { @ApiExtraModels(SaleEstiamteStateResponseDto) @ApiCommonHeaders() @ApiExtraModels(ValidateBulkDeleteResponseDto) +@UseGuards(AuthorizationGuard, PermissionGuard) export class SaleEstimatesController { @Post('validate-bulk-delete') + @RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Validates which sale estimates can be deleted and returns the results.', @@ -71,6 +79,7 @@ export class SaleEstimatesController { } @Post('bulk-delete') + @RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Deletes multiple sale estimates.' }) @ApiResponse({ status: 200, @@ -93,6 +102,7 @@ export class SaleEstimatesController { ) { } @Post() + @RequirePermission(SaleEstimateAction.Create, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Create a new sale estimate.' }) @ApiResponse({ status: 200, @@ -105,6 +115,7 @@ export class SaleEstimatesController { } @Put(':id') + @RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Edit the given sale estimate.' }) @ApiResponse({ status: 200, @@ -131,6 +142,7 @@ export class SaleEstimatesController { } @Delete(':id') + @RequirePermission(SaleEstimateAction.Delete, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Delete the given sale estimate.' }) @ApiResponse({ status: 200, @@ -153,6 +165,7 @@ export class SaleEstimatesController { } @Get('state') + @RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Retrieves the sale estimate state.' }) @ApiResponse({ status: 200, @@ -166,6 +179,7 @@ export class SaleEstimatesController { } @Get() + @RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Retrieves the sale estimates.' }) @ApiResponse({ status: 200, @@ -189,6 +203,7 @@ export class SaleEstimatesController { } @Post(':id/deliver') + @RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Deliver the given sale estimate.' }) @ApiResponse({ status: 200, @@ -207,6 +222,7 @@ export class SaleEstimatesController { } @Put(':id/approve') + @RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Approve the given sale estimate.' }) @ApiParam({ name: 'id', @@ -221,6 +237,7 @@ export class SaleEstimatesController { } @Put(':id/reject') + @RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Reject the given sale estimate.' }) @ApiParam({ name: 'id', @@ -235,6 +252,7 @@ export class SaleEstimatesController { } @Post(':id/notify-sms') + @RequirePermission(SaleEstimateAction.NotifyBySms, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Notify the given sale estimate by SMS.' }) @ApiParam({ name: 'id', @@ -251,6 +269,7 @@ export class SaleEstimatesController { } @Get(':id/sms-details') + @RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Retrieves the sale estimate SMS details.' }) public getSaleEstimateSmsDetails( @Param('id', ParseIntPipe) saleEstimateId: number, @@ -262,6 +281,7 @@ export class SaleEstimatesController { @Post(':id/mail') @HttpCode(200) + @RequirePermission(SaleEstimateAction.Edit, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Send the given sale estimate by mail.' }) @ApiParam({ name: 'id', @@ -280,6 +300,7 @@ export class SaleEstimatesController { } @Get(':id/mail') + @RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Retrieves the sale estimate mail state.' }) @ApiParam({ name: 'id', @@ -296,6 +317,7 @@ export class SaleEstimatesController { } @Get(':id') + @RequirePermission(SaleEstimateAction.View, AbilitySubject.SaleEstimate) @ApiOperation({ summary: 'Retrieves the sale estimate details.', }) diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts index 5c85d097b..3850c1f07 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts @@ -12,6 +12,7 @@ import { Put, Query, Res, + UseGuards, } from '@nestjs/common'; import { ISaleInvoiceWriteoffDTO, @@ -43,6 +44,11 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { SaleInvoiceAction } from './SaleInvoice.types'; @Controller('sale-invoices') @ApiTags('Sale Invoices') @@ -52,10 +58,12 @@ import { @ApiExtraModels(GenerateSaleInvoiceSharableLinkResponseDto) @ApiCommonHeaders() @ApiExtraModels(ValidateBulkDeleteResponseDto) +@UseGuards(AuthorizationGuard, PermissionGuard) export class SaleInvoicesController { constructor(private saleInvoiceApplication: SaleInvoiceApplication) { } @Post('validate-bulk-delete') + @RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Validates which sale invoices can be deleted and returns the results.', @@ -77,6 +85,7 @@ export class SaleInvoicesController { } @Post('bulk-delete') + @RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Deletes multiple sale invoices.' }) @ApiResponse({ status: 200, @@ -90,6 +99,7 @@ export class SaleInvoicesController { } @Post() + @RequirePermission(SaleInvoiceAction.Create, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Create a new sale invoice.' }) @ApiResponse({ status: 201, @@ -121,6 +131,7 @@ export class SaleInvoicesController { } @Put(':id') + @RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Edit the given sale invoice.' }) @ApiResponse({ status: 200, @@ -141,6 +152,7 @@ export class SaleInvoicesController { } @Delete(':id') + @RequirePermission(SaleInvoiceAction.Delete, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Delete the given sale invoice.' }) @ApiResponse({ status: 200, @@ -158,6 +170,7 @@ export class SaleInvoicesController { } @Get('receivable') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the receivable sale invoices.' }) @ApiResponse({ status: 200, @@ -176,6 +189,7 @@ export class SaleInvoicesController { } @Get('state') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoice state.' }) @ApiResponse({ status: 200, @@ -190,6 +204,7 @@ export class SaleInvoicesController { } @Get(':id') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoice details.' }) @ApiResponse({ status: 200, @@ -228,6 +243,7 @@ export class SaleInvoicesController { } @Get() + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoices.' }) @ApiResponse({ status: 200, @@ -251,6 +267,7 @@ export class SaleInvoicesController { } @Put(':id/deliver') + @RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Deliver the given sale invoice.' }) @ApiResponse({ status: 200, @@ -269,6 +286,7 @@ export class SaleInvoicesController { } @Post(':id/writeoff') + @RequirePermission(SaleInvoiceAction.Writeoff, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Write off the given sale invoice.' }) @HttpCode(200) @ApiResponse({ @@ -290,6 +308,7 @@ export class SaleInvoicesController { } @Post(':id/cancel-writeoff') + @RequirePermission(SaleInvoiceAction.Writeoff, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Cancel the written off sale invoice.' }) @ApiResponse({ status: 200, @@ -309,6 +328,7 @@ export class SaleInvoicesController { } @Get(':id/payments') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoice payments.' }) @ApiResponse({ status: 404, description: 'The sale invoice not found.' }) @ApiParam({ @@ -322,6 +342,7 @@ export class SaleInvoicesController { } @Get(':id/html') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoice HTML.' }) @ApiResponse({ status: 404, description: 'The sale invoice not found.' }) @ApiParam({ @@ -335,6 +356,7 @@ export class SaleInvoicesController { } @Get(':id/mail') + @RequirePermission(SaleInvoiceAction.View, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Retrieves the sale invoice mail state.' }) @ApiResponse({ status: 200, @@ -354,6 +376,7 @@ export class SaleInvoicesController { } @Post(':id/generate-link') + @RequirePermission(SaleInvoiceAction.Edit, AbilitySubject.SaleInvoice) @ApiOperation({ summary: 'Generate sharable sale invoice link (private or public)', }) diff --git a/packages/server/src/modules/Settings/Settings.controller.ts b/packages/server/src/modules/Settings/Settings.controller.ts index 1477510b6..6a226060e 100644 --- a/packages/server/src/modules/Settings/Settings.controller.ts +++ b/packages/server/src/modules/Settings/Settings.controller.ts @@ -1,16 +1,22 @@ import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Body, Controller, Get, Put } from '@nestjs/common'; +import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common'; import { SettingsApplicationService } from './SettingsApplication.service'; -import { ISettingsDTO } from './Settings.types'; +import { ISettingsDTO, PreferencesAction } from './Settings.types'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; @Controller('settings') @ApiTags('Settings') +@UseGuards(AuthorizationGuard, PermissionGuard) export class SettingsController { constructor( private readonly settingsApplicationService: SettingsApplicationService, ) {} @Put() + @RequirePermission(PreferencesAction.Mutate, AbilitySubject.Preferences) @ApiOperation({ summary: 'Save the given settings.' }) async saveSettings(@Body() settingsDTO: ISettingsDTO) { return this.settingsApplicationService.saveSettings(settingsDTO); diff --git a/packages/server/src/modules/TaxRates/TaxRate.controller.ts b/packages/server/src/modules/TaxRates/TaxRate.controller.ts index 43a2cf63a..0edaff458 100644 --- a/packages/server/src/modules/TaxRates/TaxRate.controller.ts +++ b/packages/server/src/modules/TaxRates/TaxRate.controller.ts @@ -6,6 +6,7 @@ import { Param, Post, Put, + UseGuards, } from '@nestjs/common'; import { TaxRatesApplication } from './TaxRate.application'; import { @@ -18,15 +19,22 @@ import { import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto'; import { TaxRateResponseDto } from './dtos/TaxRateResponse.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { TaxRateAction } from './TaxRates.types'; @Controller('tax-rates') @ApiTags('Tax Rates') @ApiExtraModels(TaxRateResponseDto) @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class TaxRatesController { constructor(private readonly taxRatesApplication: TaxRatesApplication) { } @Post() + @RequirePermission(TaxRateAction.CREATE, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Create a new tax rate.' }) @ApiResponse({ status: 201, @@ -38,6 +46,7 @@ export class TaxRatesController { } @Put(':id') + @RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Edit the given tax rate.' }) @ApiResponse({ status: 200, @@ -54,6 +63,7 @@ export class TaxRatesController { } @Delete(':id') + @RequirePermission(TaxRateAction.DELETE, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Delete the given tax rate.' }) @ApiResponse({ status: 200, @@ -67,6 +77,7 @@ export class TaxRatesController { } @Get(':id') + @RequirePermission(TaxRateAction.VIEW, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Retrieves the tax rate details.' }) @ApiResponse({ status: 200, @@ -80,6 +91,7 @@ export class TaxRatesController { } @Get() + @RequirePermission(TaxRateAction.VIEW, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Retrieves the tax rates.' }) @ApiResponse({ status: 200, @@ -101,6 +113,7 @@ export class TaxRatesController { } @Put(':id/activate') + @RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Activate the given tax rate.' }) @ApiResponse({ status: 200, @@ -114,6 +127,7 @@ export class TaxRatesController { } @Put(':id/inactivate') + @RequirePermission(TaxRateAction.EDIT, AbilitySubject.TaxRate) @ApiOperation({ summary: 'Inactivate the given tax rate.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/UsersModule/Users.controller.ts b/packages/server/src/modules/UsersModule/Users.controller.ts index 301202de0..5e7218f71 100644 --- a/packages/server/src/modules/UsersModule/Users.controller.ts +++ b/packages/server/src/modules/UsersModule/Users.controller.ts @@ -22,7 +22,7 @@ export class UsersController { /** * Edit details of the given user. */ - @Post(':id') + @Put(':id') @ApiOperation({ summary: 'Edit details of the given user.' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/UsersModule/commands/InactivateUser.service.ts b/packages/server/src/modules/UsersModule/commands/InactivateUser.service.ts index 173f0e822..4e9758d0f 100644 --- a/packages/server/src/modules/UsersModule/commands/InactivateUser.service.ts +++ b/packages/server/src/modules/UsersModule/commands/InactivateUser.service.ts @@ -43,11 +43,11 @@ export class InactivateUserService { // Throw serivce error if the user is already inactivated. this.throwErrorIfUserInactive(tenantUser); - // Marks the tenant user as active. + // Marks the tenant user as inactive. await this.tenantUserModel() .query() .findById(userId) - .update({ active: true }); + .update({ active: false }); // Triggers `onTenantUserActivated` event. await this.eventEmitter.emitAsync(events.tenantUser.onInactivated, { diff --git a/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts b/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts index a98bcf9b7..0bf41cf3e 100644 --- a/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts +++ b/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts @@ -7,6 +7,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { IVendorCreditsQueryDTO } from './types/VendorCredit.types'; @@ -26,17 +27,24 @@ import { BulkDeleteDto, ValidateBulkDeleteResponseDto, } from '@/common/dtos/BulkDelete.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { VendorCreditAction } from './types/VendorCredit.types'; @Controller('vendor-credits') @ApiTags('Vendor Credits') @ApiCommonHeaders() @ApiExtraModels(ValidateBulkDeleteResponseDto) +@UseGuards(AuthorizationGuard, PermissionGuard) export class VendorCreditsController { constructor( private readonly vendorCreditsApplication: VendorCreditsApplicationService, ) { } @Post('validate-bulk-delete') + @RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Validates which vendor credits can be deleted and returns the results.', @@ -58,6 +66,7 @@ export class VendorCreditsController { } @Post('bulk-delete') + @RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Deletes multiple vendor credits.' }) @ApiResponse({ status: 200, @@ -73,24 +82,28 @@ export class VendorCreditsController { } @Post() + @RequirePermission(VendorCreditAction.Create, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Create a new vendor credit.' }) async createVendorCredit(@Body() dto: CreateVendorCreditDto) { return this.vendorCreditsApplication.createVendorCredit(dto); } @Put(':id/open') + @RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Open the given vendor credit.' }) async openVendorCredit(@Param('id') vendorCreditId: number) { return this.vendorCreditsApplication.openVendorCredit(vendorCreditId); } @Get() + @RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Retrieves the vendor credits.' }) async getVendorCredits(@Query() filterDTO: IVendorCreditsQueryDTO) { return this.vendorCreditsApplication.getVendorCredits(filterDTO); } @Put(':id') + @RequirePermission(VendorCreditAction.Edit, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Edit the given vendor credit.' }) async editVendorCredit( @Param('id') vendorCreditId: number, @@ -100,12 +113,14 @@ export class VendorCreditsController { } @Delete(':id') + @RequirePermission(VendorCreditAction.Delete, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Delete the given vendor credit.' }) async deleteVendorCredit(@Param('id') vendorCreditId: number) { return this.vendorCreditsApplication.deleteVendorCredit(vendorCreditId); } @Get(':id') + @RequirePermission(VendorCreditAction.View, AbilitySubject.VendorCredit) @ApiOperation({ summary: 'Retrieves the vendor credit details.' }) async getVendorCredit(@Param('id') vendorCreditId: number) { return this.vendorCreditsApplication.getVendorCredit(vendorCreditId); diff --git a/packages/server/src/modules/Vendors/Vendors.controller.ts b/packages/server/src/modules/Vendors/Vendors.controller.ts index f2d212b8b..e66642db6 100644 --- a/packages/server/src/modules/Vendors/Vendors.controller.ts +++ b/packages/server/src/modules/Vendors/Vendors.controller.ts @@ -7,6 +7,7 @@ import { Post, Put, Query, + UseGuards, } from '@nestjs/common'; import { VendorsApplication } from './VendorsApplication.service'; import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto'; @@ -24,44 +25,56 @@ import { BulkDeleteVendorsDto, ValidateBulkDeleteVendorsResponseDto, } from './dtos/BulkDeleteVendors.dto'; +import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator'; +import { PermissionGuard } from '@/modules/Roles/Permission.guard'; +import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; +import { AbilitySubject } from '@/modules/Roles/Roles.types'; +import { VendorAction } from '../Customers/types/Customers.types'; @Controller('vendors') @ApiTags('Vendors') @ApiCommonHeaders() +@UseGuards(AuthorizationGuard, PermissionGuard) export class VendorsController { constructor(private vendorsApplication: VendorsApplication) {} @Get() + @RequirePermission(VendorAction.View, AbilitySubject.Vendor) @ApiOperation({ summary: 'Retrieves the vendors.' }) getVendors(@Query() filterDTO: GetVendorsQueryDto) { return this.vendorsApplication.getVendors(filterDTO); } @Get(':id') + @RequirePermission(VendorAction.View, AbilitySubject.Vendor) @ApiOperation({ summary: 'Retrieves the vendor details.' }) getVendor(@Param('id') vendorId: number) { return this.vendorsApplication.getVendor(vendorId); } @Post() + @RequirePermission(VendorAction.Create, AbilitySubject.Vendor) @ApiOperation({ summary: 'Create a new vendor.' }) createVendor(@Body() vendorDTO: CreateVendorDto) { return this.vendorsApplication.createVendor(vendorDTO); } @Put(':id') + @RequirePermission(VendorAction.Edit, AbilitySubject.Vendor) @ApiOperation({ summary: 'Edit the given vendor.' }) editVendor(@Param('id') vendorId: number, @Body() vendorDTO: EditVendorDto) { return this.vendorsApplication.editVendor(vendorId, vendorDTO); } @Delete(':id') + @RequirePermission(VendorAction.Delete, AbilitySubject.Vendor) @ApiOperation({ summary: 'Delete the given vendor.' }) deleteVendor(@Param('id') vendorId: number) { return this.vendorsApplication.deleteVendor(vendorId); } @Put(':id/opening-balance') + @RequirePermission(VendorAction.Edit, AbilitySubject.Vendor) @ApiOperation({ summary: 'Edit the given vendor opening balance.' }) editOpeningBalance( @Param('id') vendorId: number, @@ -74,6 +87,7 @@ export class VendorsController { } @Post('validate-bulk-delete') + @RequirePermission(VendorAction.Delete, AbilitySubject.Vendor) @ApiOperation({ summary: 'Validates which vendors can be deleted and returns counts of deletable and non-deletable vendors.', @@ -93,6 +107,7 @@ export class VendorsController { } @Post('bulk-delete') + @RequirePermission(VendorAction.Delete, AbilitySubject.Vendor) @ApiOperation({ summary: 'Deletes multiple vendors in bulk.' }) @ApiResponse({ status: 200, diff --git a/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx b/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx index 70e895801..5e882bf0e 100644 --- a/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx +++ b/packages/webapp/src/containers/GlobalErrors/GlobalErrors.tsx @@ -57,7 +57,7 @@ function GlobalErrors({ if (globalErrors.access_denied) { toastKeySomethingWrong = AppToaster.show( { - message: intl.get('global_error.you_dont_have_permissions'), + message: globalErrors.access_denied.message || intl.get('global_error.you_dont_have_permissions'), intent: Intent.DANGER, onDismiss: () => { globalErrorsSet({ access_denied: false }); diff --git a/packages/webapp/src/hooks/useRequest.tsx b/packages/webapp/src/hooks/useRequest.tsx index fd7f087ac..f337458cb 100644 --- a/packages/webapp/src/hooks/useRequest.tsx +++ b/packages/webapp/src/hooks/useRequest.tsx @@ -58,7 +58,7 @@ export default function useApiRequest() { setLogout(); } if (status === 403) { - setGlobalErrors({ access_denied: true }); + setGlobalErrors({ access_denied: { message: data.message } }); } if (status === 429) { setGlobalErrors({ too_many_requests: true });