Skip to content

PM-1437 Feat/auth #4

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

Merged
merged 5 commits into from
Jul 10, 2025
Merged

PM-1437 Feat/auth #4

merged 5 commits into from
Jul 10, 2025

Conversation

kkartunov
Copy link
Contributor

Adds auth layer to the MCP server: https://topcoder.atlassian.net/browse/PM-1437

@kkartunov kkartunov changed the title Feat/auth PM-1437 Feat/auth Jul 10, 2025
export const decodeAuthToken = async (
authHeader: string,
): Promise<boolean | jwt.JwtPayload | string> => {
const [type, idToken] = authHeader?.split(' ') ?? [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high
correctness
The use of optional chaining (authHeader?.split(' ')) may lead to a runtime error if authHeader is undefined. Consider adding a check for authHeader before attempting to split it.

let decoded: jwt.JwtPayload | string;
try {
const signingKey = await getSigningKey(idToken);
decoded = jwt.verify(idToken, signingKey);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
maintainability
The jwt.verify function can throw an error if the token is expired or invalid. While the error is caught, consider handling specific error types to provide more informative logging or responses.

const signingKey = await getSigningKey(idToken);
decoded = jwt.verify(idToken, signingKey);
} catch (error) {
logger.error('Error verifying JWT', error);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high
security
Logging the error object directly may expose sensitive information. Consider sanitizing the error message before logging.

export const checkM2MScope =
(...requiredM2mScopes: M2mScope[]) =>
async (req: Request) => {
const decodedAuth = await decodeAuthToken(req.headers.authorization ?? '');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The use of req.headers.authorization ?? '' assumes that an empty string is a valid input for decodeAuthToken. Consider handling cases where the authorization header is missing more explicitly, potentially returning an error or a specific response.

async (req: Request) => {
const decodedAuth = await decodeAuthToken(req.headers.authorization ?? '');

const authorizedScopes = ((decodedAuth as JwtPayload).scope ?? '').split(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The scope claim is split by spaces, which assumes that scopes are space-separated. Ensure this assumption aligns with the JWT format used in your application, as different formats might use different separators.

export const checkHasUserRole =
(...requiredUserRoles: Role[]) =>
async (req: Request) => {
const decodedAuth = await decodeAuthToken(req.headers.authorization ?? '');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high
correctness
Consider handling the case where decodeAuthToken returns null or an unexpected structure to avoid runtime errors. This could be done by adding a check after decoding the token.

const decodedAuth = await decodeAuthToken(req.headers.authorization ?? '');

const decodedUserRoles = Object.keys(decodedAuth).reduce((roles, key) => {
if (key.match(/claims\/roles$/gi)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The use of Object.keys(decodedAuth) and reduce may lead to unexpected behavior if decodedAuth is not an object or if the keys do not match the expected pattern. Consider validating the structure of decodedAuth before processing.

return roles;
}, []);

if (!requiredUserRoles.some((role) => decodedUserRoles.includes(role))) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
performance
The use of some with includes could be optimized for performance by using a set for decodedUserRoles if the list is large, as includes has O(n) complexity.

import { Injectable, Inject } from '@nestjs/common';
import { Tool } from '@rekog/mcp-nest';
import { Tool } from '@tc/mcp-nest';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high
correctness
The import path for Tool has changed from @rekog/mcp-nest to @tc/mcp-nest. Ensure that this change is intentional and that the new module provides the same functionality as the previous one.

import { REQUEST } from '@nestjs/core';
import { QUERY_CHALLENGES_TOOL_PARAMETERS } from './queryChallenges.parameters';
import { TopcoderChallengesService } from 'src/shared/topcoder/challenges.service';
import { Logger } from 'src/shared/global';
import { QUERY_CHALLENGES_TOOL_OUTPUT_SCHEMA } from './queryChallenges.output';
import {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high
security
Consider verifying that the newly imported authentication guards (authGuard, checkHasUserRole, checkM2MScope) are correctly integrated and used within the tool. This is crucial for ensuring the security layer is properly enforced.

},
})
async queryChallenges(params) {
private async _queryChallenges(params) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
design
Renaming queryChallenges to _queryChallenges suggests it is intended to be private. Ensure that this change aligns with the intended design and usage within the application, as it may affect how this method is accessed or overridden.


@Tool({
name: 'query-tc-challenges-private',
description:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The description for query-tc-challenges-private mentions public challenges, which seems inconsistent with the tool name suggesting private challenges. Ensure the description accurately reflects the functionality.


@Tool({
name: 'query-tc-challenges-protected',
description:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The description for query-tc-challenges-protected mentions public challenges, which may not align with the intended functionality for protected challenges. Verify that the description matches the tool's purpose.

}

@Tool({
name: 'query-tc-challenges-m2m',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
correctness
The description for query-tc-challenges-m2m mentions public challenges, which might not be accurate for machine-to-machine scope. Confirm that the description is appropriate for the intended use case.

}

@Tool({
name: 'query-tc-challenges-public',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
security
Consider adding an authentication guard to query-tc-challenges-public to ensure consistent security practices across all query methods.

@@ -24,10 +19,7 @@ export class Logger extends NestLogger {
}

private formatMessages(messages: any[]): string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium
maintainability
The removal of the requestIdPrefix could impact the ability to trace logs back to specific requests. If this was intentional, consider documenting how request tracing will be handled elsewhere.

@kkartunov kkartunov merged commit cd5f685 into dev Jul 10, 2025
2 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.

2 participants