Skip to content

Feat/auth #3

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

Closed
wants to merge 8 commits into from
Closed

Feat/auth #3

wants to merge 8 commits into from

Conversation

kkartunov
Copy link
Contributor

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

version: 2.1
defaults: &defaults
docker:
- image: cimg/python:3.13.5-browsers

Choose a reason for hiding this comment

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

high
correctness
The Docker image cimg/python:3.13.5-browsers does not exist. Consider using a valid version or checking for typos in the version number.

install_deploysuite: &install_deploysuite
name: Installation of install_deploysuite.
command: |
git clone --branch v1.4.17 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript

Choose a reason for hiding this comment

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

medium
maintainability
Cloning a specific branch from a public repository without pinning to a commit hash can lead to unexpected changes if the branch is updated. Consider using a commit hash to ensure consistency.

- deploy:
name: Running MasterScript.
command: |
./awsconfiguration.sh $DEPLOY_ENV

Choose a reason for hiding this comment

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

high
security
The script awsconfiguration.sh is executed with a variable $DEPLOY_ENV that is not validated within this context. Ensure that $DEPLOY_ENV is sanitized to prevent potential security issues.

source awsenvconf
./psvar-processor.sh -t appenv -p /config/${APPNAME}/deployvar
source deployvar_env
./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -j /config/${APPNAME}/appvar -i ${APPNAME} -p FARGATE

Choose a reason for hiding this comment

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

high
security
The master_deploy.sh script is executed with several environment variables. Ensure these variables are properly validated and sanitized to prevent injection vulnerabilities.

docker buildx build --no-cache=true -t ${APPNAME}:latest .

Choose a reason for hiding this comment

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

high
correctness
There is a typo in the variable substitution syntax. The closing bracket should be removed. It should be ${APPNAME}:latest instead of ${APPNAME}}:latest.

specifier: ^1.6.2
version: 1.6.2(@modelcontextprotocol/[email protected])(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected])
'@tc/mcp-nest':
specifier: topcoder-platform/MCP-Nest.git

Choose a reason for hiding this comment

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

medium
maintainability
Switching from a versioned package to a Git repository URL for @tc/mcp-nest could lead to potential issues with version consistency and reproducibility. Consider specifying a version tag or commit hash to ensure stability and avoid unexpected changes in dependencies.

@@ -842,8 +842,9 @@ packages:
resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}

'@rekog/[email protected]':
resolution: {integrity: sha512-swcu99a/woQ5T1f4E/YJct1w/xzszH83bhzEuZwXh+cYRnQUJKhaJd5Aey/XyOtt/D9lQTKSdnoKdpija8kuWA==}
'@rekog/mcp-nest@https://codeload.github.com/topcoder-platform/MCP-Nest/tar.gz/8950083c999f7636fed9f62b26330748aed9ed30':

Choose a reason for hiding this comment

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

medium
maintainability
Switching from a versioned package to a tarball URL may affect reproducibility and dependency management. Consider using a versioned package to ensure consistent builds and easier updates.

@@ -4386,7 +4387,7 @@ snapshots:

'@pkgr/[email protected]': {}

'@rekog/mcp-nest@1.6.2(@modelcontextprotocol/[email protected])(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected])':
'@rekog/mcp-nest@https://codeload.github.com/topcoder-platform/MCP-Nest/tar.gz/8950083c999f7636fed9f62b26330748aed9ed30(@modelcontextprotocol/[email protected])(@nestjs/[email protected]([email protected])([email protected])([email protected])([email protected]))(@nestjs/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected])':

Choose a reason for hiding this comment

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

medium
maintainability
The change from a versioned package to a direct GitHub tarball URL could impact reproducibility and maintainability. Consider using a specific version tag or commit hash to ensure consistent builds.

@@ -19,7 +19,7 @@ export class HealthCheckController {

@Public()
@Version([VERSION_NEUTRAL, '1'])
@Get('/healthcheck')
@Get('/health')

Choose a reason for hiding this comment

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

medium
maintainability
The endpoint path has been changed from /healthcheck to /health. Ensure that any clients or documentation referencing the old path are updated accordingly to prevent potential disruptions.

@@ -1,14 +1,11 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { McpModule } from '@rekog/mcp-nest';
import { McpModule } 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 change from @rekog/mcp-nest to @tc/mcp-nest should be verified to ensure that the new module path is correct and that all dependencies are properly updated. Incorrect module paths can lead to runtime errors.

import { CreateRequestStoreMiddleware } from './core/request/createRequestStore.middleware';
import { AuthGuard, RolesGuard } from './core/auth/guards';
import { APP_GUARD } from '@nestjs/core';
import { nanoid } from 'nanoid';

Choose a reason for hiding this comment

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

medium
security
The change from randomUUID to nanoid should be reviewed to ensure that nanoid provides the necessary uniqueness and security properties required by the application. If randomUUID was chosen for its cryptographic properties, ensure nanoid is a suitable replacement.

@@ -17,30 +14,18 @@ import { APP_GUARD } from '@nestjs/core';
version: '1.0.0',
streamableHttp: {
enableJsonResponse: false,
sessionIdGenerator: () => randomUUID(),
sessionIdGenerator: () => nanoid(),

Choose a reason for hiding this comment

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

high
security
Switching from randomUUID() to nanoid() for session ID generation may have implications on the uniqueness and security of the IDs. Ensure nanoid() provides sufficient entropy and uniqueness for your use case.

})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// consumer.apply(TokenValidatorMiddleware).forRoutes('*');
// consumer.apply(CreateRequestStoreMiddleware).forRoutes('*');
consumer.apply(TokenValidatorMiddleware).forRoutes('*');

Choose a reason for hiding this comment

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

medium
correctness
Re-enabling TokenValidatorMiddleware without CreateRequestStoreMiddleware might lead to issues if the latter is required for proper request handling. Verify if CreateRequestStoreMiddleware is necessary for the application's functionality.


@IsString()
@IsOptional()
API_BASE = '/v6/mcp';

Choose a reason for hiding this comment

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

medium
maintainability
Consider using a constant or configuration file for the default value of API_BASE to enhance maintainability and avoid hardcoding paths directly in the class.

@@ -1,5 +1,8 @@
export enum Role {
Admin = 'administrator',

Choose a reason for hiding this comment

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

medium
maintainability
Consider using a consistent naming convention for enum values. The Admin value uses a different naming style compared to User. This could lead to confusion and maintenance challenges in the future.

* @returns A promise that resolves to `true` if the authorization token is valid, otherwise `false`.
*/
export const authGuard = async (req: Request) => {
if (!(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
The use of await without a try-catch block could lead to unhandled promise rejections if decodeAuthToken throws an error. Consider wrapping this in a try-catch to handle potential errors gracefully.

* @returns A promise that resolves to `true` if the authorization token is valid, otherwise `false`.
*/
export const authGuard = async (req: Request) => {
if (!(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 fallback. If decodeAuthToken does not handle empty strings appropriately, this could lead to unexpected behavior. Ensure that decodeAuthToken can handle this case or provide a more suitable default value.

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.

low
readability
Consider using a more descriptive variable name than idToken to avoid confusion with ID tokens, as this variable represents a JWT access token.

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 errors for various reasons, including token expiration and invalid signature. Consider handling specific error types to provide more informative logging and potentially different handling strategies.

@@ -1,2 +1,3 @@
export * from './auth.guard';
export * from './roles.guard';
export * from './m2m-scope.guard';

Choose a reason for hiding this comment

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

high
correctness
Ensure that the new m2m-scope.guard module is correctly implemented and tested, as it replaces the previous roles.guard. This change could impact the authorization logic if not properly handled.

@@ -1,2 +1,3 @@
export * from './auth.guard';
export * from './roles.guard';
export * from './m2m-scope.guard';
export * from './role.guard';

Choose a reason for hiding this comment

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

high
security
Verify that the new role.guard module is correctly integrated and does not introduce any regressions or security issues. Ensure it is thoroughly tested.

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.

high
correctness
Consider handling the case where decodeAuthToken might throw an error, such as when the token is invalid or malformed. This will prevent unhandled promise rejections and improve the robustness of the function.

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 property is assumed to be a string. Ensure that decodedAuth is always a JwtPayload and that scope is a string to avoid runtime errors. Consider adding type checks or assertions.

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
The decodeAuthToken function is called with req.headers.authorization ?? ''. If the authorization header is missing, this will pass an empty string to decodeAuthToken. Ensure that decodeAuthToken can handle an empty string appropriately, or add a check to handle missing authorization headers before calling this function.

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

const decodedUserRoles = Object.keys(decodedAuth).reduce((roles, key) => {

Choose a reason for hiding this comment

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

medium
correctness
The reduce function is used to accumulate roles, but it returns the first matching claims/roles value found. If there are multiple claims with roles, only the last one will be used. Consider whether this behavior is intended or if you need to accumulate roles from multiple claims.

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
correctness
The some method is used to check if any required role is present in decodedUserRoles. If decodedUserRoles is empty due to a missing or malformed token, this will always return false. Ensure that the function handles cases where decodedUserRoles is unexpectedly empty.

@@ -4,10 +4,15 @@ import { Logger } from 'src/shared/global';
import { ENV_CONFIG } from './config';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'],

Choose a reason for hiding this comment

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

low
maintainability
Consider configuring the logger to include 'debug' level logs if detailed information is needed during development or troubleshooting.


const logger = new Logger('bootstrap()');

// Global prefix for all routes
app.setGlobalPrefix(ENV_CONFIG.API_BASE);

Choose a reason for hiding this comment

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

high
security
Ensure that ENV_CONFIG.API_BASE is correctly defined and sanitized to prevent potential routing issues or security vulnerabilities.

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
Ensure that the import path @tc/mcp-nest is correct and intended, as it differs from the previous @rekog/mcp-nest. This change could affect module resolution if the package structure has not been updated accordingly.

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 into the tool's logic. Without proper integration, the intended authentication layer may not function as expected.

},
})
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
maintainability
Renaming queryChallenges to _queryChallenges suggests it's intended to be private, but TypeScript does not enforce privacy for methods prefixed with an underscore. Consider using the private keyword for actual privacy enforcement.


@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
readability
The description field for query-tc-challenges-private mentions 'public Topcoder challenges', which seems incorrect given the method name implies private challenges. Ensure the description accurately reflects the functionality to avoid confusion.

}

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

Choose a reason for hiding this comment

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

medium
readability
The description field for query-tc-challenges-protected mentions 'public Topcoder challenges', which seems incorrect given the method name implies protected challenges. Ensure the description accurately reflects the functionality to avoid confusion.

}

@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
readability
The description field for query-tc-challenges-m2m mentions 'public Topcoder challenges', which seems incorrect given the method name implies M2M challenges. Ensure the description accurately reflects the functionality to avoid confusion.

? [`{${this.store.requestId}}`]
: [];
return [...requestIdPrefix, ...messages]
return [...messages]

Choose a reason for hiding this comment

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

medium
maintainability
Removing the requestIdPrefix could impact traceability of logs if requestId was used to correlate log messages with specific requests. Consider whether this change affects the ability to debug or audit logs effectively.

@kkartunov
Copy link
Contributor Author

Wrong base.

@kkartunov kkartunov closed this Jul 10, 2025
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.

3 participants