Conversation
…ent-payment-approver-role PM-3697 handle engagement payment approver role
| private readonly adminService: AdminService, | ||
| private readonly winningsRepo: WinningsRepository, | ||
| private readonly tcMembersService: TopcoderMembersService, | ||
| private readonly accessControlService: AccessControlService, |
There was a problem hiding this comment.
[security]
Consider verifying that accessControlService is used appropriately in the controller to ensure that the new role EngagementPaymentApprover is correctly integrated and that access control checks are performed where necessary.
| @Roles( | ||
| Role.PaymentAdmin, | ||
| Role.PaymentBaAdmin, | ||
| Role.EngagementPaymentApprover, |
There was a problem hiding this comment.
[correctness]
Ensure that the addition of Role.EngagementPaymentApprover to the @Roles decorator is backed by appropriate logic in the controller methods to handle this role's permissions correctly.
| const result = await this.winningsRepo.searchWinnings( | ||
| await this.adminService.applyBaAdminUserFilters( | ||
| const filters = | ||
| await this.accessControlService.applyFilters<WinningRequestDto>( |
There was a problem hiding this comment.
[❗❗ correctness]
The method applyFilters is being called with a generic type WinningRequestDto. Ensure that this method is designed to handle this type correctly, as incorrect handling could lead to runtime errors. Verify that the type constraints and transformations within applyFilters are appropriate for WinningRequestDto.
| await this.adminService.applyBaAdminUserFilters( | ||
| const filters = | ||
| await this.accessControlService.applyFilters<WinningRequestDto>( | ||
| user.id, |
There was a problem hiding this comment.
[❗❗ security]
The change from this.isBaAdmin(user) to user.roles suggests a shift in how roles are being handled. Ensure that user.roles contains the necessary information to apply the correct filters, and that this change does not inadvertently alter the logic or permissions.
| const result = await this.winningsRepo.searchWinnings( | ||
| await this.adminService.applyBaAdminUserFilters( | ||
| const filters = | ||
| await this.accessControlService.applyFilters<WinningRequestDto>( |
There was a problem hiding this comment.
[❗❗ correctness]
The method applyFilters is being used with a generic type WinningRequestDto. Ensure that applyFilters is designed to handle this specific type correctly, as incorrect handling could lead to unexpected behavior or errors.
| user.roles, | ||
| { | ||
| ...body, | ||
| limit: 999, |
There was a problem hiding this comment.
[maintainability]
The use of limit: 999 is hardcoded. Consider making this a configurable parameter or constant to improve maintainability and flexibility.
| async updateWinning( | ||
| @Body() body: WinningUpdateRequestDto, | ||
| @User() user: UserInfo, | ||
| @User() user: any, |
There was a problem hiding this comment.
[❗❗ correctness]
Changing the type of user from UserInfo to any can lead to potential runtime errors and makes the code less type-safe. Consider maintaining the UserInfo type or using a more specific type to ensure type safety and clarity.
| body, | ||
| user.id, | ||
| this.isBaAdmin(user), | ||
| user.roles, |
There was a problem hiding this comment.
[❗❗ correctness]
The change from this.isBaAdmin(user) to user.roles alters the method signature of updateWinnings. Ensure that updateWinnings can handle the user.roles array correctly and that this change does not introduce any unintended behavior. Consider whether the logic that previously relied on isBaAdmin needs to be replicated or adjusted elsewhere.
| if (this.isBaAdmin(user)) { | ||
| await this.adminService.verifyBaAdminAccessToWinning(winningId, user.id); | ||
| } | ||
| await this.adminService.verifyUserAccessToWinning( |
There was a problem hiding this comment.
[❗❗ security]
The method verifyUserAccessToWinning now takes user.roles as an additional parameter. Ensure that this method properly handles role-based access control and that the roles are validated correctly to prevent unauthorized access.
| if (this.isBaAdmin(user)) { | ||
| await this.adminService.verifyBaAdminAccessToWinning(winningId, user.id); | ||
| } | ||
| await this.adminService.verifyUserAccessToWinning( |
There was a problem hiding this comment.
[❗❗ security]
The method verifyUserAccessToWinning now takes user.roles as an additional parameter compared to the previous verifyBaAdminAccessToWinning. Ensure that this change in method signature is correctly handled in the adminService implementation and that it does not introduce any unintended access control issues.
| try { | ||
| await this.accessControlService.verifyAccess(winningsId, userId, roles); | ||
| } catch (err) { | ||
| throw new UnauthorizedException(err?.message ?? 'access denied'); |
There was a problem hiding this comment.
[security]
Catching a generic err and throwing an UnauthorizedException with err?.message could potentially expose sensitive error details. Consider logging the error internally and providing a generic message to the user.
| body: WinningUpdateRequestDto, | ||
| userId: string, | ||
| isBaAdmin?: boolean, | ||
| roles: string[] = [], |
There was a problem hiding this comment.
[correctness]
The parameter roles is initialized with an empty array by default. Ensure that the logic within the method properly handles cases where roles is empty, as this could affect authorization checks or role-based logic.
| if (isBaAdmin) { | ||
| await this.verifyBaAdminAccessToWinning(body.winningsId, userId); | ||
| } | ||
| await this.verifyUserAccessToWinning(body.winningsId, userId, roles); |
There was a problem hiding this comment.
[❗❗ security]
The function verifyUserAccessToWinning is called with roles as an argument, but the diff does not show the definition or implementation of this function. Ensure that roles is correctly handled within verifyUserAccessToWinning to avoid potential security issues related to access control.
| import { Injectable } from '@nestjs/common'; | ||
| import { TopcoderModule } from '../topcoder/topcoder.module'; | ||
|
|
||
| @Injectable() |
There was a problem hiding this comment.
[💡 maintainability]
The @Injectable() decorator is not necessary for the AccessControlRegistrar class since it is not being injected anywhere. Consider removing it to avoid confusion.
| EngagementPaymentApproverProvider, | ||
| AccessControlRegistrar, | ||
| ], | ||
| exports: [AccessControlService], |
There was a problem hiding this comment.
[design]
Consider exporting PaymentBaProvider and EngagementPaymentApproverProvider if they are intended to be used outside this module. This will improve the module's reusability.
| async applyFilters<T>( | ||
| userId: string, | ||
| roles: string[] = [], | ||
| req: any, |
There was a problem hiding this comment.
[maintainability]
Using any for the req parameter can lead to runtime errors and makes the code harder to understand. Consider defining a specific type or interface for req to improve type safety and maintainability.
| roles: string[] = [], | ||
| req: any, | ||
| ): Promise<T> { | ||
| let out = { ...req }; |
There was a problem hiding this comment.
[correctness]
The spread operator is used to create a shallow copy of req. If req contains nested objects, changes made by applyFilter could affect the original req. Consider using a deep copy if this is not the intended behavior.
| return out as T; | ||
| } | ||
|
|
||
| async verifyAccess(resourceId: string, userId: string, roles: string[] = []) { |
There was a problem hiding this comment.
[❗❗ correctness]
The verifyAccess method does not return any value or throw an error if access is denied. Consider implementing a mechanism to handle access denial, such as throwing an exception or returning a boolean.
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| async verifyAccessToResource(winningsId: string | string[], _userId: string) { |
There was a problem hiding this comment.
[💡 maintainability]
The parameter _userId is unused in the verifyAccessToResource method. Consider removing it if it's not needed, or use it if it serves a purpose in the logic.
| ); | ||
| if (unauthorized.length > 0) { | ||
| throw new Error( | ||
| `${Role.EngagementPaymentApprover} user is trying to access winning with category='${unauthorized.map(w => w.category).join(', ')}'`, |
There was a problem hiding this comment.
[design]
Throwing a generic Error may not provide enough context for error handling. Consider using a custom error class or a more specific error type to improve error handling and debugging.
| private readonly prisma: PrismaService, | ||
| ) {} | ||
|
|
||
| async applyFilter<T>(userId: string, req: any): Promise<T> { |
There was a problem hiding this comment.
[maintainability]
The req parameter is typed as any, which can lead to runtime errors and makes the function harder to understand. Consider defining a specific type or interface for req to improve type safety and maintainability.
| return { ...req, billingAccounts: bas } as T; | ||
| } | ||
|
|
||
| async verifyAccessToResource(winningsId: string, userId: string) { |
There was a problem hiding this comment.
[💡 readability]
The verifyAccessToResource method does not return a value when access is verified, which might be confusing. Consider explicitly returning a boolean or a specific result to indicate the outcome of the access verification.
|
|
||
| const unauthorized = paymentBAs.some((ba) => !allowedBAs.includes(`${ba}`)); | ||
| if (unauthorized) { | ||
| throw new Error( |
There was a problem hiding this comment.
[design]
Throwing a generic Error with a string message can make error handling less precise. Consider creating a custom error class to provide more context and improve error handling.
| @@ -0,0 +1,5 @@ | |||
| export interface RoleAccessProvider<Req = any> { | |||
There was a problem hiding this comment.
[correctness]
Using any as a default type for Req can lead to potential type safety issues. Consider using a more specific type or a generic constraint to ensure better type safety.
| @@ -0,0 +1,5 @@ | |||
| export interface RoleAccessProvider<Req = any> { | |||
| roleName: string; | |||
| applyFilter?(userId: string, req: Req): Promise<Req>; | |||
There was a problem hiding this comment.
[correctness]
The method applyFilter is optional, but its return type is a Promise<Req>. Ensure that the implementation handles cases where this method might not be defined, to avoid runtime errors.
| export interface RoleAccessProvider<Req = any> { | ||
| roleName: string; | ||
| applyFilter?(userId: string, req: Req): Promise<Req>; | ||
| verifyAccessToResource?(resourceId: string, userId: string): Promise<void>; |
There was a problem hiding this comment.
[correctness]
The method verifyAccessToResource is optional, but its return type is a Promise<void>. Ensure that the implementation handles cases where this method might not be defined, to avoid runtime errors.
https://topcoder.atlassian.net/browse/PM-3697