Skip to content

Conversation

@kkartunov
Copy link
Contributor

@kkartunov kkartunov commented Dec 18, 2025

);
const authUser: JwtUser = req['user'] as JwtUser;
const authUser: JwtUser =
(req['user'] as JwtUser) ?? ({ isMachine: false, roles: [] } as JwtUser);

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([]),

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' }]);

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(

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

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?: {

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', () => {

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(

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;

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;

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.

Copy link

Copilot AI left a 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.

Comment on lines +1933 to +1940
const shouldEnrichSubmitter =
submissions.length > 0 &&
(queryDto.challengeId
? true
: await this.canViewSubmitterIdentity(
authUser,
queryDto.challengeId,
));
Copy link

Copilot AI Dec 18, 2025

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.

Suggested change
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,
);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +92
async hasQueuedWorkflowRuns(submissionId: string): Promise<boolean> {
if (!submissionId) return false;

const existing = await this.prisma.aiWorkflowRun.findFirst({
where: {
submissionId,
},
});

return !!existing;
}
Copy link

Copilot AI Dec 18, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 2456 to 2461
if (!uid) {
return emptyContext;
return {
...emptyContext,
requesterUserId,
};
}
Copy link

Copilot AI Dec 18, 2025

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).

Copilot uses AI. Check for mistakes.
@kkartunov kkartunov merged commit cc58a2d into master Dec 18, 2025
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants