-
Notifications
You must be signed in to change notification settings - Fork 8
[PROD] - PS489 #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PROD] - PS489 #187
Conversation
… including reviews)
…rkflows have already been queued. if so, prevent further queueing
…queueing-ai-workflow-runs PS-489 - prevent double queueing ai workflow runs
| ); | ||
| const authUser: JwtUser = req['user'] as JwtUser; | ||
| const authUser: JwtUser = | ||
| (req['user'] as JwtUser) ?? ({ isMachine: false, roles: [] } as JwtUser); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[correctness]
Using the nullish coalescing operator (??) to provide a default value for authUser is a good approach. However, ensure that this default value is appropriate for all cases where req['user'] might be null or undefined. If there are specific roles or machine states that should be handled differently, consider refining this default value.
| reviewType: { | ||
| findMany: jest.fn().mockResolvedValue([]), | ||
| }, | ||
| $queryRaw: jest.fn().mockResolvedValue([]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ security]
The use of $queryRaw with jest.fn().mockResolvedValue([]) indicates raw SQL queries are being mocked. Ensure that the raw SQL queries are safe from SQL injection vulnerabilities, especially if any user input is involved.
| prismaMock.submission.findFirst.mockResolvedValue({ | ||
| id: 'submission-new', | ||
| }); | ||
| prismaMock.$queryRaw.mockResolvedValue([{ id: 'submission-new' }]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ security]
The use of $queryRaw.mockResolvedValue([{ id: 'submission-new' }]) suggests that raw SQL queries are being executed. Verify that these queries are parameterized to prevent SQL injection attacks.
| }, | ||
| ]); | ||
|
|
||
| const result = await listService.listSubmission( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ security]
The test case exposes submitter identity but strips reviews for anonymous challenge queries is handling sensitive data. Ensure that the logic for stripping reviews and URLs is correctly implemented in the actual service to prevent accidental data exposure.
| export const ReviewApplicationRoleOpportunityTypeMap: Record< | ||
| ReviewApplicationRole, | ||
| ReviewOpportunityType | ||
| PrismaReviewOpportunityType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ correctness]
The change from ReviewOpportunityType to PrismaReviewOpportunityType in the ReviewApplicationRoleOpportunityTypeMap may introduce a mismatch if the enum values in PrismaReviewOpportunityType do not align with those in ReviewOpportunityType. Ensure that the values are compatible or provide a mapping if necessary.
| method: string; | ||
| query: Record<string, unknown>; | ||
| user: { | ||
| user?: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ correctness]
Making the user property optional in TestRequest could lead to runtime errors if the code assumes user is always present. Ensure that all usages of request.user handle the case where user is undefined.
| expect(() => guard.canActivate(context)).toThrow(ForbiddenException); | ||
| }); | ||
|
|
||
| it('allows anonymous access when challengeId is provided', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ security]
The test case allows anonymous access when challengeId is provided introduces a scenario where anonymous access is allowed based on the presence of challengeId. Ensure that this behavior is intentional and does not introduce a security vulnerability by allowing unauthorized access.
| return true; | ||
| } | ||
|
|
||
| private allowAnonymousSubmissionListByChallenge( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[maintainability]
The allowAnonymousSubmissionListByChallenge method duplicates logic from allowSubmissionListByChallenge. Consider refactoring to avoid code duplication and improve maintainability.
| return false; | ||
| } | ||
|
|
||
| const challengeId = request?.query?.challengeId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[❗❗ security]
The challengeId is extracted from request.query without validation. Ensure that challengeId is sanitized to prevent injection attacks.
| } | ||
|
|
||
| async hasQueuedWorkflowRuns(submissionId: string): Promise<boolean> { | ||
| if (!submissionId) return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[correctness]
The check for !submissionId should be more explicit. Consider using typeof submissionId !== 'string' || submissionId.trim() === '' to ensure that submissionId is a non-empty string. This will prevent potential issues with falsy values like 0 or false being passed as valid submission IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements anonymous access to submission listings when filtered by challenge ID and adds duplicate workflow run queueing prevention. The changes support PS-489 (per the JIRA ticket reference).
Key changes:
- Adds anonymous GET access to submissions list endpoint when challengeId is provided
- Implements duplicate workflow run prevention by checking for existing runs before queueing
- Exposes submitter identity (handle, rating) to anonymous users viewing challenge submissions while protecting review details and URLs
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared/modules/global/workflow-queue.handler.ts | Adds hasQueuedWorkflowRuns method to check for existing workflow runs before queueing new ones |
| src/shared/modules/global/submission-scan-complete.orchestrator.ts | Uses the new duplicate check to prevent queueing workflow runs multiple times for the same submission |
| src/shared/guards/tokenRoles.guard.ts | Adds allowAnonymousSubmissionListByChallenge method to permit unauthenticated requests to list submissions when challengeId is provided |
| src/shared/guards/tokenRoles.guard.spec.ts | Adds test coverage for anonymous access with challengeId and updates user type to be optional |
| src/dto/reviewApplication.dto.ts | Changes type import to use Prisma-generated type for better type safety and removes now-unnecessary eslint disable comment |
| src/api/submission/submission.service.ts | Adds support for anonymous users by defaulting authUser, always enriching submitter identity for challenge queries, handling anonymous users in visibility context, and stripping reviews/URLs for anonymous access |
| src/api/submission/submission.service.spec.ts | Adds test case for anonymous challenge queries and updates mock setup to support the new behavior |
| src/api/submission/submission.controller.ts | Provides a default authUser object when request user is undefined to enable anonymous access |
| src/api/review-application/reviewApplication.service.ts | Removes obsolete eslint-disable comment now that type is properly aligned |
| pnpm-lock.yaml | Updates various dependencies to their latest patch/minor versions |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const shouldEnrichSubmitter = | ||
| submissions.length > 0 && | ||
| (queryDto.challengeId | ||
| ? true | ||
| : await this.canViewSubmitterIdentity( | ||
| authUser, | ||
| queryDto.challengeId, | ||
| )); |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditional logic for enriching submitter identity has a bug. When queryDto.challengeId is provided, the code always enriches submitter data (true), bypassing the canViewSubmitterIdentity check entirely. This means anonymous users querying by challengeId will have submissions enriched with submitter handles and ratings, which may be intentional for this feature. However, the fallback path passes queryDto.challengeId to canViewSubmitterIdentity, which will always be undefined in that branch (since if it were defined, the ternary would have taken the true path). This likely should pass undefined explicitly or restructure the logic to be clearer.
| const shouldEnrichSubmitter = | |
| submissions.length > 0 && | |
| (queryDto.challengeId | |
| ? true | |
| : await this.canViewSubmitterIdentity( | |
| authUser, | |
| queryDto.challengeId, | |
| )); | |
| let shouldEnrichSubmitter = false; | |
| if (submissions.length > 0) { | |
| if (queryDto.challengeId) { | |
| // For challenge listings, always enrich submitter data | |
| shouldEnrichSubmitter = true; | |
| } else { | |
| shouldEnrichSubmitter = await this.canViewSubmitterIdentity( | |
| authUser, | |
| undefined, | |
| ); | |
| } | |
| } |
| async hasQueuedWorkflowRuns(submissionId: string): Promise<boolean> { | ||
| if (!submissionId) return false; | ||
|
|
||
| const existing = await this.prisma.aiWorkflowRun.findFirst({ | ||
| where: { | ||
| submissionId, | ||
| }, | ||
| }); | ||
|
|
||
| return !!existing; | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hasQueuedWorkflowRuns method only checks if any workflow run exists for the submission but doesn't verify the status. Based on the context where it's used to prevent duplicate queueing, this check should probably verify that there are workflow runs with status 'INIT', 'QUEUED', or 'DISPATCHED' rather than just any workflow run. A completed or failed workflow run shouldn't prevent queueing new runs.
| if (!uid) { | ||
| return emptyContext; | ||
| return { | ||
| ...emptyContext, | ||
| requesterUserId, | ||
| }; | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unreachable code detected. The condition if (!uid) at line 2456 will never be true because it's checked after a previous condition at line 2430 that handles the case where !isPrivilegedRequester && !requesterUserId. Since uid is assigned from requesterUserId at line 2454, and privileged requesters return early at line 2447-2452, the only way to reach line 2456 is if requesterUserId has a value (making uid truthy).
https://topcoder.atlassian.net/browse/PS-489
https://topcoder.atlassian.net/browse/PM-3173