@rekog/mcp-nest 1.7.1 → 1.7.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,9 +37,13 @@ export declare function createMcpOAuthController(endpoints?: OAuthEndpointConfig
37
37
  authorize(query: any, req: any, res: Response, next: NextFunction): Promise<void>;
38
38
  handleProviderCallback(req: OAuthCallbackRequest, res: Response, next: NextFunction): void;
39
39
  processAuthenticationSuccess(req: OAuthCallbackRequest, res: Response): Promise<void>;
40
- exchangeToken(body: any): Promise<TokenPair>;
41
- handleAuthorizationCodeGrant(code: string, code_verifier: string, _redirect_uri: string, client_id: string): Promise<TokenPair>;
42
- handleRefreshTokenGrant(refresh_token: string): TokenPair;
40
+ exchangeToken(body: any, req: ExpressRequest): Promise<TokenPair>;
41
+ extractClientCredentials(req: ExpressRequest, body: any): {
42
+ clientId: string;
43
+ clientSecret?: string;
44
+ };
45
+ handleAuthorizationCodeGrant(code: string, code_verifier: string, _redirect_uri: string, client_id: string, client_secret?: string): Promise<TokenPair>;
46
+ handleRefreshTokenGrant(refresh_token: string, client_id?: string, client_secret?: string): Promise<TokenPair>;
43
47
  validateToken(req: AuthenticatedRequest): {
44
48
  valid: boolean;
45
49
  user_id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-oauth.controller.d.ts","sourceRoot":"","sources":["../../src/authz/mcp-oauth.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,MAAM,EAOP,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5E,OAAO,EAAE,oBAAoB,EAAmB,MAAM,yBAAyB,CAAC;AAChF,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,EACL,qBAAqB,EACrB,WAAW,EACZ,MAAM,gCAAgC,CAAC;AAGxC,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,gBAAgB,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,0BAA+B;kBASG,kBAAkB,SACpB,WAAW,mBACxB,eAAe,iBACjB,aAAa;;4BAPnB,MAAM;+BACH,OAAO;0BACZ,kBAAkB;wBAGK,WAAW;kCACxB,eAAe;gCACjB,aAAa;;;;;;;;;;;;;wCAqCO,qBAAqB;yBAMjD,GAAG,OAEd,GAAG,OACI,QAAQ,QACN,YAAY;oCA0Ed,oBAAoB,OACpB,QAAQ,QACN,YAAY;0CA2BrB,oBAAoB,OACpB,QAAQ;4BAwEmB,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC;2CAyBlD,MAAM,iBACG,MAAM,iBACN,MAAM,aACV,MAAM,GAChB,OAAO,CAAC,SAAS,CAAC;+CA+DkB,MAAM,GAAG,SAAS;2BAW/B,oBAAoB;;;;;;;oCAW7B,MAAM,kBACL,MAAM,UACd,MAAM,GACb,OAAO;;EAcb"}
1
+ {"version":3,"file":"mcp-oauth.controller.d.ts","sourceRoot":"","sources":["../../src/authz/mcp-oauth.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,MAAM,EAOP,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5E,OAAO,EAAE,oBAAoB,EAAmB,MAAM,yBAAyB,CAAC;AAChF,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE1E,OAAO,EACL,qBAAqB,EACrB,WAAW,EACZ,MAAM,gCAAgC,CAAC;AAGxC,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,gBAAgB,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,0BAA+B;kBASG,kBAAkB,SACpB,WAAW,mBACxB,eAAe,iBACjB,aAAa;;4BAPnB,MAAM;+BACH,OAAO;0BACZ,kBAAkB;wBAGK,WAAW;kCACxB,eAAe;gCACjB,aAAa;;;;;;;;;;;;;wCAqCO,qBAAqB;yBAMjD,GAAG,OAEd,GAAG,OACI,QAAQ,QACN,YAAY;oCA0Ed,oBAAoB,OACpB,QAAQ,QACN,YAAY;0CA2BrB,oBAAoB,OACpB,QAAQ;4BAyEC,GAAG,OACL,cAAc,GACzB,OAAO,CAAC,SAAS,CAAC;sCA2Bd,cAAc,QACb,GAAG,GACR;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE;2CA0BtC,MAAM,iBACG,MAAM,iBACN,MAAM,aACV,MAAM,kBACD,MAAM,GACrB,OAAO,CAAC,SAAS,CAAC;+CA8EJ,MAAM,cACT,MAAM,kBACF,MAAM,GACrB,OAAO,CAAC,SAAS,CAAC;2BA0BK,oBAAoB;;;;;;;oCAW7B,MAAM,kBACL,MAAM,UACd,MAAM,GACb,OAAO;;EAcb"}
@@ -167,23 +167,47 @@ function createMcpOAuthController(endpoints = {}) {
167
167
  await this.store.removeOAuthSession(sessionId);
168
168
  res.redirect(redirectUrl.toString());
169
169
  }
170
- async exchangeToken(body) {
171
- const { grant_type, code, code_verifier, redirect_uri, client_id, refresh_token, } = body;
170
+ async exchangeToken(body, req) {
171
+ const { grant_type, code, code_verifier, redirect_uri, refresh_token } = body;
172
+ const clientCredentials = this.extractClientCredentials(req, body);
172
173
  if (grant_type === 'authorization_code') {
173
- return this.handleAuthorizationCodeGrant(code, code_verifier, redirect_uri, client_id);
174
+ return this.handleAuthorizationCodeGrant(code, code_verifier, redirect_uri, clientCredentials.clientId, clientCredentials.clientSecret);
174
175
  }
175
176
  else if (grant_type === 'refresh_token') {
176
- return this.handleRefreshTokenGrant(refresh_token);
177
+ return this.handleRefreshTokenGrant(refresh_token, clientCredentials.clientId, clientCredentials.clientSecret);
177
178
  }
178
179
  else {
179
180
  throw new common_1.BadRequestException('Unsupported grant_type');
180
181
  }
181
182
  }
182
- async handleAuthorizationCodeGrant(code, code_verifier, _redirect_uri, client_id) {
183
+ extractClientCredentials(req, body) {
184
+ const authHeader = req.headers.authorization;
185
+ if (authHeader && authHeader.startsWith('Basic ')) {
186
+ const base64Credentials = authHeader.slice('Basic '.length);
187
+ const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
188
+ const [clientId, clientSecret] = credentials.split(':');
189
+ if (clientId) {
190
+ return { clientId, clientSecret };
191
+ }
192
+ }
193
+ if (body.client_id) {
194
+ return {
195
+ clientId: body.client_id,
196
+ clientSecret: body.client_secret,
197
+ };
198
+ }
199
+ throw new common_1.BadRequestException('Client authentication required');
200
+ }
201
+ async handleAuthorizationCodeGrant(code, code_verifier, _redirect_uri, client_id, client_secret) {
183
202
  this.logger.debug('handleAuthorizationCodeGrant - Params:', {
184
203
  code,
185
204
  client_id,
186
205
  });
206
+ const authenticatedClient = await this.clientService.authenticateClient(client_id, client_secret);
207
+ if (!authenticatedClient) {
208
+ this.logger.error('handleAuthorizationCodeGrant - Client authentication failed:', client_id);
209
+ throw new common_1.BadRequestException('Client authentication failed');
210
+ }
187
211
  const authCode = await this.store.getAuthCode(code);
188
212
  if (!authCode) {
189
213
  this.logger.error('handleAuthorizationCodeGrant - Invalid authorization code:', code);
@@ -214,7 +238,14 @@ function createMcpOAuthController(endpoints = {}) {
214
238
  this.logger.log('handleAuthorizationCodeGrant - Token pair generated for user:', authCode.user_id);
215
239
  return tokens;
216
240
  }
217
- handleRefreshTokenGrant(refresh_token) {
241
+ async handleRefreshTokenGrant(refresh_token, client_id, client_secret) {
242
+ if (client_id) {
243
+ const authenticatedClient = await this.clientService.authenticateClient(client_id, client_secret);
244
+ if (!authenticatedClient) {
245
+ this.logger.error('handleRefreshTokenGrant - Client authentication failed:', client_id);
246
+ throw new common_1.BadRequestException('Client authentication failed');
247
+ }
248
+ }
218
249
  const newTokens = this.jwtTokenService.refreshAccessToken(refresh_token);
219
250
  if (!newTokens) {
220
251
  throw new common_1.BadRequestException('Failed to refresh token');
@@ -278,8 +309,9 @@ function createMcpOAuthController(endpoints = {}) {
278
309
  __decorate([
279
310
  (0, common_1.Post)(endpoints.token),
280
311
  __param(0, (0, common_1.Body)()),
312
+ __param(1, (0, common_1.Req)()),
281
313
  __metadata("design:type", Function),
282
- __metadata("design:paramtypes", [Object]),
314
+ __metadata("design:paramtypes", [Object, Object]),
283
315
  __metadata("design:returntype", Promise)
284
316
  ], McpOAuthController.prototype, "exchangeToken", null);
285
317
  __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-oauth.controller.js","sourceRoot":"","sources":["../../src/authz/mcp-oauth.controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BadRequestException,\n Body,\n Controller,\n Get,\n Inject,\n Logger,\n Next,\n Post,\n Query,\n Req,\n Res,\n UseGuards,\n} from '@nestjs/common';\nimport { createHash, randomBytes } from 'crypto';\nimport { Request as ExpressRequest, NextFunction, Response } from 'express';\nimport passport from 'passport';\nimport { AuthenticatedRequest, McpAuthJwtGuard } from './guards/jwt-auth.guard';\nimport {\n OAuthEndpointConfiguration,\n OAuthModuleOptions,\n OAuthSession,\n OAuthUserProfile,\n} from './providers/oauth-provider.interface';\nimport { ClientService } from './services/client.service';\nimport { JwtTokenService, TokenPair } from './services/jwt-token.service';\nimport { STRATEGY_NAME } from './services/oauth-strategy.service';\nimport {\n ClientRegistrationDto,\n IOAuthStore,\n} from './stores/oauth-store.interface';\nimport { normalizeEndpoint } from '../mcp/utils/normalize-endpoint';\n\ninterface OAuthCallbackRequest extends ExpressRequest {\n user?: {\n profile: OAuthUserProfile;\n accessToken: string;\n provider: string;\n };\n}\n\nexport function createMcpOAuthController(\n endpoints: OAuthEndpointConfiguration = {},\n) {\n @Controller()\n class McpOAuthController {\n readonly logger = new Logger(McpOAuthController.name);\n readonly serverUrl: string;\n readonly isProduction: boolean;\n readonly options: OAuthModuleOptions;\n constructor(\n @Inject('OAUTH_MODULE_OPTIONS') options: OAuthModuleOptions,\n @Inject('IOAuthStore') readonly store: IOAuthStore,\n readonly jwtTokenService: JwtTokenService,\n readonly clientService: ClientService,\n ) {\n this.serverUrl = options.serverUrl;\n this.isProduction = options.cookieSecure;\n this.options = options;\n }\n\n // OAuth endpoints\n @Get(endpoints.wellKnown)\n getAuthorizationServerMetadata() {\n return {\n issuer: this.serverUrl,\n authorization_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.authorize}`,\n ),\n token_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.token}`,\n ),\n registration_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.register}`,\n ),\n response_types_supported: ['code'],\n response_modes_supported: ['query'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n token_endpoint_auth_methods_supported: [\n 'client_secret_basic',\n 'client_secret_post',\n 'none',\n ],\n revocation_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints?.revoke}`,\n ),\n code_challenge_methods_supported: ['plain', 'S256'],\n };\n }\n\n @Post(endpoints.register)\n async registerClient(@Body() registrationDto: ClientRegistrationDto) {\n return await this.clientService.registerClient(registrationDto);\n }\n\n @Get(endpoints.authorize)\n async authorize(\n @Query() query: any,\n @Req()\n req: any,\n @Res() res: Response,\n @Next() next: NextFunction,\n ) {\n const {\n response_type,\n client_id,\n redirect_uri,\n code_challenge,\n code_challenge_method,\n state,\n } = query;\n const resource = this.options.resource;\n if (response_type !== 'code') {\n throw new BadRequestException('Only response_type=code is supported');\n }\n\n if (!client_id) {\n throw new BadRequestException('Missing required parameters');\n }\n\n // Validate client and redirect URI\n const client = await this.clientService.getClient(client_id);\n if (!client) {\n throw new BadRequestException('Invalid client_id');\n }\n\n const validRedirect = await this.clientService.validateRedirectUri(\n client_id,\n redirect_uri,\n );\n if (!validRedirect) {\n throw new BadRequestException('Invalid redirect_uri');\n }\n\n // Create OAuth session\n const sessionId = randomBytes(32).toString('base64url');\n const sessionState = randomBytes(32).toString('base64url');\n\n const oauthSession: OAuthSession = {\n sessionId,\n state: sessionState,\n clientId: client_id,\n redirectUri: redirect_uri,\n codeChallenge: code_challenge,\n codeChallengeMethod: code_challenge_method || 'plain',\n oauthState: state,\n scope: query.scope || '',\n resource,\n expiresAt: Date.now() + this.options.oauthSessionExpiresIn,\n };\n\n await this.store.storeOAuthSession(sessionId, oauthSession);\n\n // Set session cookie\n res.cookie('oauth_session', sessionId, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.oauthSessionExpiresIn,\n });\n\n // Store state for passport\n res.cookie('oauth_state', sessionState, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.oauthSessionExpiresIn,\n });\n\n // Redirect to the provider's auth endpoint\n passport.authenticate(STRATEGY_NAME, {\n state: req.cookies?.oauth_state,\n })(req, res, next);\n }\n\n @Get(endpoints.callback)\n handleProviderCallback(\n @Req() req: OAuthCallbackRequest,\n @Res() res: Response,\n @Next() next: NextFunction,\n ) {\n // Use a custom callback to handle the authentication result\n passport.authenticate(\n STRATEGY_NAME,\n { session: false },\n async (err: any, user: any) => {\n try {\n if (err) {\n this.logger.error('OAuth callback error:', err);\n throw new BadRequestException('Authentication failed');\n }\n\n if (!user) {\n throw new BadRequestException('Authentication failed');\n }\n\n req.user = user;\n await this.processAuthenticationSuccess(req, res);\n } catch (error) {\n next(error);\n }\n },\n )(req, res, next);\n }\n\n async processAuthenticationSuccess(\n req: OAuthCallbackRequest,\n res: Response,\n ) {\n const user = req.user;\n if (!user) {\n throw new BadRequestException('Authentication failed');\n }\n\n const sessionId = req.cookies?.oauth_session;\n if (!sessionId) {\n throw new BadRequestException('Missing OAuth session');\n }\n\n const session = await this.store.getOAuthSession(sessionId);\n if (!session) {\n throw new BadRequestException('Invalid or expired OAuth session');\n }\n\n // Verify state\n const stateFromCookie = req.cookies?.oauth_state;\n if (session.state !== stateFromCookie) {\n throw new BadRequestException('Invalid state parameter');\n }\n\n // Generate JWT for UI access\n const jwt = this.jwtTokenService.generateUserToken(\n user.profile.username,\n user.profile,\n );\n\n // Set JWT token as cookie for UI endpoints\n res.cookie('auth_token', jwt, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.cookieMaxAge,\n });\n\n // Clear temporary cookies\n res.clearCookie('oauth_session');\n res.clearCookie('oauth_state');\n\n // Generate authorization code\n const authCode = randomBytes(32).toString('base64url');\n\n // Store the auth code\n await this.store.storeAuthCode({\n code: authCode,\n user_id: user.profile.username,\n client_id: session.clientId!,\n redirect_uri: session.redirectUri!,\n code_challenge: session.codeChallenge!,\n code_challenge_method: session.codeChallengeMethod!,\n expires_at: Date.now() + this.options.authCodeExpiresIn,\n resource: session.resource,\n scope: session.scope,\n github_access_token: '', // No longer provider-specific\n });\n\n // Build redirect URL with authorization code\n const redirectUrl = new URL(session.redirectUri!);\n redirectUrl.searchParams.set('code', authCode);\n if (session.oauthState) {\n redirectUrl.searchParams.set('state', session.oauthState);\n }\n\n // Clean up session\n await this.store.removeOAuthSession(sessionId);\n\n res.redirect(redirectUrl.toString());\n }\n\n // Token endpoints (remain the same)\n @Post(endpoints.token)\n async exchangeToken(@Body() body: any): Promise<TokenPair> {\n const {\n grant_type,\n code,\n code_verifier,\n redirect_uri,\n client_id,\n refresh_token,\n } = body;\n\n if (grant_type === 'authorization_code') {\n return this.handleAuthorizationCodeGrant(\n code,\n code_verifier,\n redirect_uri,\n client_id,\n );\n } else if (grant_type === 'refresh_token') {\n return this.handleRefreshTokenGrant(refresh_token);\n } else {\n throw new BadRequestException('Unsupported grant_type');\n }\n }\n\n async handleAuthorizationCodeGrant(\n code: string,\n code_verifier: string,\n _redirect_uri: string,\n client_id: string,\n ): Promise<TokenPair> {\n this.logger.debug('handleAuthorizationCodeGrant - Params:', {\n code,\n client_id,\n });\n const authCode = await this.store.getAuthCode(code);\n if (!authCode) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Invalid authorization code:',\n code,\n );\n throw new BadRequestException('Invalid authorization code');\n }\n if (authCode.expires_at < Date.now()) {\n await this.store.removeAuthCode(code);\n this.logger.error(\n 'handleAuthorizationCodeGrant - Authorization code expired:',\n code,\n );\n throw new BadRequestException('Authorization code has expired');\n }\n if (authCode.client_id !== client_id) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Client ID mismatch:',\n { expected: authCode.client_id, got: client_id },\n );\n throw new BadRequestException('Client ID mismatch');\n }\n if (authCode.code_challenge) {\n const isValid = this.validatePKCE(\n code_verifier,\n authCode.code_challenge,\n authCode.code_challenge_method,\n );\n if (!isValid) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Invalid PKCE verification',\n );\n throw new BadRequestException('Invalid PKCE verification');\n }\n }\n if (!authCode.resource) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - No resource associated with code',\n );\n throw new BadRequestException(\n 'Authorization code is not associated with a resource',\n );\n }\n const tokens = this.jwtTokenService.generateTokenPair(\n authCode.user_id,\n client_id,\n authCode.scope,\n authCode.resource,\n );\n await this.store.removeAuthCode(code);\n this.logger.log(\n 'handleAuthorizationCodeGrant - Token pair generated for user:',\n authCode.user_id,\n );\n return tokens;\n }\n\n handleRefreshTokenGrant(refresh_token: string): TokenPair {\n const newTokens = this.jwtTokenService.refreshAccessToken(refresh_token);\n if (!newTokens) {\n throw new BadRequestException('Failed to refresh token');\n }\n\n return newTokens;\n }\n\n @Get(endpoints.validate)\n @UseGuards(McpAuthJwtGuard)\n validateToken(@Req() req: AuthenticatedRequest) {\n return {\n valid: true,\n user_id: req.user.sub,\n client_id: req.user.client_id,\n scope: req.user.scope,\n expires_at: req.user.exp! * 1000,\n };\n }\n\n validatePKCE(\n code_verifier: string,\n code_challenge: string,\n method: string,\n ): boolean {\n if (method === 'plain') {\n return code_verifier === code_challenge;\n } else if (method === 'S256') {\n const hash = createHash('sha256')\n .update(code_verifier)\n .digest('base64url');\n return hash === code_challenge;\n }\n return false;\n }\n }\n\n return McpOAuthController;\n}\n"]}
1
+ {"version":3,"file":"mcp-oauth.controller.js","sourceRoot":"","sources":["../../src/authz/mcp-oauth.controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BadRequestException,\n Body,\n Controller,\n Get,\n Inject,\n Logger,\n Next,\n Post,\n Query,\n Req,\n Res,\n UseGuards,\n} from '@nestjs/common';\nimport { createHash, randomBytes } from 'crypto';\nimport { Request as ExpressRequest, NextFunction, Response } from 'express';\nimport passport from 'passport';\nimport { AuthenticatedRequest, McpAuthJwtGuard } from './guards/jwt-auth.guard';\nimport {\n OAuthEndpointConfiguration,\n OAuthModuleOptions,\n OAuthSession,\n OAuthUserProfile,\n} from './providers/oauth-provider.interface';\nimport { ClientService } from './services/client.service';\nimport { JwtTokenService, TokenPair } from './services/jwt-token.service';\nimport { STRATEGY_NAME } from './services/oauth-strategy.service';\nimport {\n ClientRegistrationDto,\n IOAuthStore,\n} from './stores/oauth-store.interface';\nimport { normalizeEndpoint } from '../mcp/utils/normalize-endpoint';\n\ninterface OAuthCallbackRequest extends ExpressRequest {\n user?: {\n profile: OAuthUserProfile;\n accessToken: string;\n provider: string;\n };\n}\n\nexport function createMcpOAuthController(\n endpoints: OAuthEndpointConfiguration = {},\n) {\n @Controller()\n class McpOAuthController {\n readonly logger = new Logger(McpOAuthController.name);\n readonly serverUrl: string;\n readonly isProduction: boolean;\n readonly options: OAuthModuleOptions;\n constructor(\n @Inject('OAUTH_MODULE_OPTIONS') options: OAuthModuleOptions,\n @Inject('IOAuthStore') readonly store: IOAuthStore,\n readonly jwtTokenService: JwtTokenService,\n readonly clientService: ClientService,\n ) {\n this.serverUrl = options.serverUrl;\n this.isProduction = options.cookieSecure;\n this.options = options;\n }\n\n // OAuth endpoints\n @Get(endpoints.wellKnown)\n getAuthorizationServerMetadata() {\n return {\n issuer: this.serverUrl,\n authorization_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.authorize}`,\n ),\n token_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.token}`,\n ),\n registration_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints.register}`,\n ),\n response_types_supported: ['code'],\n response_modes_supported: ['query'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n token_endpoint_auth_methods_supported: [\n 'client_secret_basic',\n 'client_secret_post',\n 'none',\n ],\n revocation_endpoint: normalizeEndpoint(\n `${this.serverUrl}/${endpoints?.revoke}`,\n ),\n code_challenge_methods_supported: ['plain', 'S256'],\n };\n }\n\n @Post(endpoints.register)\n async registerClient(@Body() registrationDto: ClientRegistrationDto) {\n return await this.clientService.registerClient(registrationDto);\n }\n\n @Get(endpoints.authorize)\n async authorize(\n @Query() query: any,\n @Req()\n req: any,\n @Res() res: Response,\n @Next() next: NextFunction,\n ) {\n const {\n response_type,\n client_id,\n redirect_uri,\n code_challenge,\n code_challenge_method,\n state,\n } = query;\n const resource = this.options.resource;\n if (response_type !== 'code') {\n throw new BadRequestException('Only response_type=code is supported');\n }\n\n if (!client_id) {\n throw new BadRequestException('Missing required parameters');\n }\n\n // Validate client and redirect URI\n const client = await this.clientService.getClient(client_id);\n if (!client) {\n throw new BadRequestException('Invalid client_id');\n }\n\n const validRedirect = await this.clientService.validateRedirectUri(\n client_id,\n redirect_uri,\n );\n if (!validRedirect) {\n throw new BadRequestException('Invalid redirect_uri');\n }\n\n // Create OAuth session\n const sessionId = randomBytes(32).toString('base64url');\n const sessionState = randomBytes(32).toString('base64url');\n\n const oauthSession: OAuthSession = {\n sessionId,\n state: sessionState,\n clientId: client_id,\n redirectUri: redirect_uri,\n codeChallenge: code_challenge,\n codeChallengeMethod: code_challenge_method || 'plain',\n oauthState: state,\n scope: query.scope || '',\n resource,\n expiresAt: Date.now() + this.options.oauthSessionExpiresIn,\n };\n\n await this.store.storeOAuthSession(sessionId, oauthSession);\n\n // Set session cookie\n res.cookie('oauth_session', sessionId, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.oauthSessionExpiresIn,\n });\n\n // Store state for passport\n res.cookie('oauth_state', sessionState, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.oauthSessionExpiresIn,\n });\n\n // Redirect to the provider's auth endpoint\n passport.authenticate(STRATEGY_NAME, {\n state: req.cookies?.oauth_state,\n })(req, res, next);\n }\n\n @Get(endpoints.callback)\n handleProviderCallback(\n @Req() req: OAuthCallbackRequest,\n @Res() res: Response,\n @Next() next: NextFunction,\n ) {\n // Use a custom callback to handle the authentication result\n passport.authenticate(\n STRATEGY_NAME,\n { session: false },\n async (err: any, user: any) => {\n try {\n if (err) {\n this.logger.error('OAuth callback error:', err);\n throw new BadRequestException('Authentication failed');\n }\n\n if (!user) {\n throw new BadRequestException('Authentication failed');\n }\n\n req.user = user;\n await this.processAuthenticationSuccess(req, res);\n } catch (error) {\n next(error);\n }\n },\n )(req, res, next);\n }\n\n async processAuthenticationSuccess(\n req: OAuthCallbackRequest,\n res: Response,\n ) {\n const user = req.user;\n if (!user) {\n throw new BadRequestException('Authentication failed');\n }\n\n const sessionId = req.cookies?.oauth_session;\n if (!sessionId) {\n throw new BadRequestException('Missing OAuth session');\n }\n\n const session = await this.store.getOAuthSession(sessionId);\n if (!session) {\n throw new BadRequestException('Invalid or expired OAuth session');\n }\n\n // Verify state\n const stateFromCookie = req.cookies?.oauth_state;\n if (session.state !== stateFromCookie) {\n throw new BadRequestException('Invalid state parameter');\n }\n\n // Generate JWT for UI access\n const jwt = this.jwtTokenService.generateUserToken(\n user.profile.username,\n user.profile,\n );\n\n // Set JWT token as cookie for UI endpoints\n res.cookie('auth_token', jwt, {\n httpOnly: true,\n secure: this.isProduction,\n maxAge: this.options.cookieMaxAge,\n });\n\n // Clear temporary cookies\n res.clearCookie('oauth_session');\n res.clearCookie('oauth_state');\n\n // Generate authorization code\n const authCode = randomBytes(32).toString('base64url');\n\n // Store the auth code\n await this.store.storeAuthCode({\n code: authCode,\n user_id: user.profile.username,\n client_id: session.clientId!,\n redirect_uri: session.redirectUri!,\n code_challenge: session.codeChallenge!,\n code_challenge_method: session.codeChallengeMethod!,\n expires_at: Date.now() + this.options.authCodeExpiresIn,\n resource: session.resource,\n scope: session.scope,\n github_access_token: '', // No longer provider-specific\n });\n\n // Build redirect URL with authorization code\n const redirectUrl = new URL(session.redirectUri!);\n redirectUrl.searchParams.set('code', authCode);\n if (session.oauthState) {\n redirectUrl.searchParams.set('state', session.oauthState);\n }\n\n // Clean up session\n await this.store.removeOAuthSession(sessionId);\n\n res.redirect(redirectUrl.toString());\n }\n\n // Token endpoints (remain the same)\n @Post(endpoints.token)\n async exchangeToken(\n @Body() body: any,\n @Req() req: ExpressRequest,\n ): Promise<TokenPair> {\n const { grant_type, code, code_verifier, redirect_uri, refresh_token } =\n body;\n\n // Extract client credentials based on authentication method\n const clientCredentials = this.extractClientCredentials(req, body);\n\n if (grant_type === 'authorization_code') {\n return this.handleAuthorizationCodeGrant(\n code as string,\n code_verifier as string,\n redirect_uri as string,\n clientCredentials.clientId,\n clientCredentials.clientSecret,\n );\n } else if (grant_type === 'refresh_token') {\n return this.handleRefreshTokenGrant(\n refresh_token as string,\n clientCredentials.clientId,\n clientCredentials.clientSecret,\n );\n } else {\n throw new BadRequestException('Unsupported grant_type');\n }\n }\n\n extractClientCredentials(\n req: ExpressRequest,\n body: any,\n ): { clientId: string; clientSecret?: string } {\n // Check for client_secret_basic (Authorization header)\n const authHeader = req.headers.authorization;\n if (authHeader && authHeader.startsWith('Basic ')) {\n const base64Credentials = authHeader.slice('Basic '.length);\n const credentials = Buffer.from(base64Credentials, 'base64').toString(\n 'ascii',\n );\n const [clientId, clientSecret] = credentials.split(':');\n if (clientId) {\n return { clientId, clientSecret };\n }\n }\n\n // Check for client_secret_post (body parameters)\n if (body.client_id) {\n return {\n clientId: body.client_id,\n clientSecret: body.client_secret,\n };\n }\n\n throw new BadRequestException('Client authentication required');\n }\n\n async handleAuthorizationCodeGrant(\n code: string,\n code_verifier: string,\n _redirect_uri: string,\n client_id: string,\n client_secret?: string,\n ): Promise<TokenPair> {\n this.logger.debug('handleAuthorizationCodeGrant - Params:', {\n code,\n client_id,\n });\n\n // Authenticate the client\n const authenticatedClient = await this.clientService.authenticateClient(\n client_id,\n client_secret,\n );\n if (!authenticatedClient) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Client authentication failed:',\n client_id,\n );\n throw new BadRequestException('Client authentication failed');\n }\n\n const authCode = await this.store.getAuthCode(code);\n if (!authCode) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Invalid authorization code:',\n code,\n );\n throw new BadRequestException('Invalid authorization code');\n }\n if (authCode.expires_at < Date.now()) {\n await this.store.removeAuthCode(code);\n this.logger.error(\n 'handleAuthorizationCodeGrant - Authorization code expired:',\n code,\n );\n throw new BadRequestException('Authorization code has expired');\n }\n if (authCode.client_id !== client_id) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Client ID mismatch:',\n { expected: authCode.client_id, got: client_id },\n );\n throw new BadRequestException('Client ID mismatch');\n }\n if (authCode.code_challenge) {\n const isValid = this.validatePKCE(\n code_verifier,\n authCode.code_challenge,\n authCode.code_challenge_method,\n );\n if (!isValid) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - Invalid PKCE verification',\n );\n throw new BadRequestException('Invalid PKCE verification');\n }\n }\n if (!authCode.resource) {\n this.logger.error(\n 'handleAuthorizationCodeGrant - No resource associated with code',\n );\n throw new BadRequestException(\n 'Authorization code is not associated with a resource',\n );\n }\n const tokens = this.jwtTokenService.generateTokenPair(\n authCode.user_id,\n client_id,\n authCode.scope,\n authCode.resource,\n );\n await this.store.removeAuthCode(code);\n this.logger.log(\n 'handleAuthorizationCodeGrant - Token pair generated for user:',\n authCode.user_id,\n );\n return tokens;\n }\n\n async handleRefreshTokenGrant(\n refresh_token: string,\n client_id?: string,\n client_secret?: string,\n ): Promise<TokenPair> {\n // If client_id is provided, authenticate the client\n if (client_id) {\n const authenticatedClient = await this.clientService.authenticateClient(\n client_id,\n client_secret,\n );\n if (!authenticatedClient) {\n this.logger.error(\n 'handleRefreshTokenGrant - Client authentication failed:',\n client_id,\n );\n throw new BadRequestException('Client authentication failed');\n }\n }\n\n const newTokens = this.jwtTokenService.refreshAccessToken(refresh_token);\n if (!newTokens) {\n throw new BadRequestException('Failed to refresh token');\n }\n\n return newTokens;\n }\n\n @Get(endpoints.validate)\n @UseGuards(McpAuthJwtGuard)\n validateToken(@Req() req: AuthenticatedRequest) {\n return {\n valid: true,\n user_id: req.user.sub,\n client_id: req.user.client_id,\n scope: req.user.scope,\n expires_at: req.user.exp! * 1000,\n };\n }\n\n validatePKCE(\n code_verifier: string,\n code_challenge: string,\n method: string,\n ): boolean {\n if (method === 'plain') {\n return code_verifier === code_challenge;\n } else if (method === 'S256') {\n const hash = createHash('sha256')\n .update(code_verifier)\n .digest('base64url');\n return hash === code_challenge;\n }\n return false;\n }\n }\n\n return McpOAuthController;\n}\n"]}
@@ -5,6 +5,7 @@ export declare class ClientService {
5
5
  registerClient(registrationDto: ClientRegistrationDto): Promise<OAuthClient>;
6
6
  getClient(clientId: string): Promise<OAuthClient | null>;
7
7
  validateRedirectUri(clientId: string, redirectUri: string): Promise<boolean>;
8
+ authenticateClient(clientId: string, clientSecret?: string, authMethod?: string): Promise<OAuthClient | null>;
8
9
  private findClientByName;
9
10
  }
10
11
  //# sourceMappingURL=client.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.service.d.ts","sourceRoot":"","sources":["../../../src/authz/services/client.service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,WAAW,EACZ,MAAM,iCAAiC,CAAC;AAEzC,qBACa,aAAa;IACW,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAMhE,cAAc,CAClB,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,WAAW,CAAC;IAgEjB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAcxD,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;YAKL,gBAAgB;CAM/B"}
1
+ {"version":3,"file":"client.service.d.ts","sourceRoot":"","sources":["../../../src/authz/services/client.service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,WAAW,EACZ,MAAM,iCAAiC,CAAC;AAGzC,qBACa,aAAa;IACW,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,WAAW;IAMhE,cAAc,CAClB,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,WAAW,CAAC;IAmEjB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAcxD,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;IAQb,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAiChB,gBAAgB;CAM/B"}
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.ClientService = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
+ const crypto_1 = require("crypto");
17
18
  let ClientService = class ClientService {
18
19
  constructor(store) {
19
20
  this.store = store;
@@ -48,10 +49,12 @@ let ClientService = class ClientService {
48
49
  return filteredClient;
49
50
  }
50
51
  const client_id = this.store.generateClientId(registrationDto);
52
+ const client_secret = (0, crypto_1.randomBytes)(32).toString('hex');
51
53
  const newClient = {
52
54
  ...defaultClientValues,
53
55
  ...registrationDto,
54
56
  client_id,
57
+ client_secret,
55
58
  created_at: now,
56
59
  updated_at: now,
57
60
  };
@@ -71,6 +74,28 @@ let ClientService = class ClientService {
71
74
  const client = await this.getClient(clientId);
72
75
  return client ? client.redirect_uris.includes(redirectUri) : false;
73
76
  }
77
+ async authenticateClient(clientId, clientSecret, authMethod) {
78
+ const client = await this.getClient(clientId);
79
+ if (!client) {
80
+ return null;
81
+ }
82
+ const authMethodToUse = authMethod || client.token_endpoint_auth_method;
83
+ switch (authMethodToUse) {
84
+ case 'none':
85
+ return client;
86
+ case 'client_secret_post':
87
+ case 'client_secret_basic':
88
+ if (!clientSecret || !client.client_secret) {
89
+ return null;
90
+ }
91
+ if (clientSecret !== client.client_secret) {
92
+ return null;
93
+ }
94
+ return client;
95
+ default:
96
+ return null;
97
+ }
98
+ }
74
99
  async findClientByName(clientName) {
75
100
  return await this.store.findClient(clientName);
76
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.service.js","sourceRoot":"","sources":["../../../src/authz/services/client.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { BadRequestException, Injectable, Inject } from '@nestjs/common';\nimport {\n ClientRegistrationDto,\n IOAuthStore,\n OAuthClient,\n} from '../stores/oauth-store.interface';\n\n@Injectable()\nexport class ClientService {\n constructor(@Inject('IOAuthStore') private readonly store: IOAuthStore) {}\n\n /**\n * Register or update a client application\n * If client with same name exists, update it; otherwise create new\n */\n async registerClient(\n registrationDto: ClientRegistrationDto,\n ): Promise<OAuthClient> {\n // Validate required fields\n if (\n !registrationDto.redirect_uris ||\n !Array.isArray(registrationDto.redirect_uris)\n ) {\n throw new BadRequestException(\n 'redirect_uris is required and must be an array',\n );\n }\n\n // Default values for new clients\n const defaultClientValues = {\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n };\n\n // Check if client with same name already exists\n const existingClient = await this.findClientByName(\n registrationDto.client_name,\n );\n const now = new Date();\n\n if (existingClient) {\n // Update existing client - merge existing, defaults, and new values\n const updatedClient: OAuthClient = {\n ...existingClient,\n ...defaultClientValues,\n ...registrationDto,\n redirect_uris: [\n ...new Set([\n ...existingClient.redirect_uris,\n ...registrationDto.redirect_uris,\n ]),\n ],\n updated_at: now,\n };\n const nc = await this.store.storeClient(updatedClient);\n const filteredClient = Object.fromEntries(\n Object.entries(nc).filter(([, value]) => value !== null),\n ) as OAuthClient;\n return filteredClient;\n }\n\n // Create new client - merge defaults with registration data\n const client_id = this.store.generateClientId(\n registrationDto as OAuthClient,\n );\n const newClient: OAuthClient = {\n ...defaultClientValues,\n ...registrationDto,\n client_id,\n created_at: now,\n updated_at: now,\n };\n const client = await this.store.storeClient(newClient);\n const filteredClient = Object.fromEntries(\n Object.entries(client).filter(([, value]) => value !== null),\n ) as OAuthClient;\n\n return filteredClient;\n }\n\n async getClient(clientId: string): Promise<OAuthClient | null> {\n const client = await this.store.getClient(clientId);\n if (!client) {\n return null;\n }\n\n // Remove null fields from the client object\n const filteredClient = Object.fromEntries(\n Object.entries(client).filter(([, value]) => value !== null),\n ) as OAuthClient;\n\n return filteredClient;\n }\n\n async validateRedirectUri(\n clientId: string,\n redirectUri: string,\n ): Promise<boolean> {\n const client = await this.getClient(clientId);\n return client ? client.redirect_uris.includes(redirectUri) : false;\n }\n\n private async findClientByName(\n clientName: string,\n ): Promise<OAuthClient | undefined> {\n // Use the new findClient method for efficient lookup\n return await this.store.findClient(clientName);\n }\n}\n"]}
1
+ {"version":3,"file":"client.service.js","sourceRoot":"","sources":["../../../src/authz/services/client.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { BadRequestException, Injectable, Inject } from '@nestjs/common';\nimport {\n ClientRegistrationDto,\n IOAuthStore,\n OAuthClient,\n} from '../stores/oauth-store.interface';\nimport { randomBytes } from 'crypto';\n\n@Injectable()\nexport class ClientService {\n constructor(@Inject('IOAuthStore') private readonly store: IOAuthStore) {}\n\n /**\n * Register or update a client application\n * If client with same name exists, update it; otherwise create new\n */\n async registerClient(\n registrationDto: ClientRegistrationDto,\n ): Promise<OAuthClient> {\n // Validate required fields\n if (\n !registrationDto.redirect_uris ||\n !Array.isArray(registrationDto.redirect_uris)\n ) {\n throw new BadRequestException(\n 'redirect_uris is required and must be an array',\n );\n }\n\n // Default values for new clients\n const defaultClientValues = {\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n };\n\n // Check if client with same name already exists\n const existingClient = await this.findClientByName(\n registrationDto.client_name,\n );\n const now = new Date();\n\n if (existingClient) {\n // Update existing client - merge existing, defaults, and new values\n const updatedClient: OAuthClient = {\n ...existingClient,\n ...defaultClientValues,\n ...registrationDto,\n redirect_uris: [\n ...new Set([\n ...existingClient.redirect_uris,\n ...registrationDto.redirect_uris,\n ]),\n ],\n updated_at: now,\n };\n const nc = await this.store.storeClient(updatedClient);\n const filteredClient = Object.fromEntries(\n Object.entries(nc).filter(([, value]) => value !== null),\n ) as OAuthClient;\n return filteredClient;\n }\n\n // Create new client - merge defaults with registration data\n const client_id = this.store.generateClientId(\n registrationDto as OAuthClient,\n );\n const client_secret = randomBytes(32).toString('hex');\n\n const newClient: OAuthClient = {\n ...defaultClientValues,\n ...registrationDto,\n client_id,\n client_secret,\n created_at: now,\n updated_at: now,\n };\n const client = await this.store.storeClient(newClient);\n const filteredClient = Object.fromEntries(\n Object.entries(client).filter(([, value]) => value !== null),\n ) as OAuthClient;\n\n return filteredClient;\n }\n\n async getClient(clientId: string): Promise<OAuthClient | null> {\n const client = await this.store.getClient(clientId);\n if (!client) {\n return null;\n }\n\n // Remove null fields from the client object\n const filteredClient = Object.fromEntries(\n Object.entries(client).filter(([, value]) => value !== null),\n ) as OAuthClient;\n\n return filteredClient;\n }\n\n async validateRedirectUri(\n clientId: string,\n redirectUri: string,\n ): Promise<boolean> {\n const client = await this.getClient(clientId);\n return client ? client.redirect_uris.includes(redirectUri) : false;\n }\n\n /**\n * Authenticate client using various methods\n */\n async authenticateClient(\n clientId: string,\n clientSecret?: string,\n authMethod?: string,\n ): Promise<OAuthClient | null> {\n const client = await this.getClient(clientId);\n if (!client) {\n return null;\n }\n\n const authMethodToUse = authMethod || client.token_endpoint_auth_method;\n\n switch (authMethodToUse) {\n case 'none':\n // Public client - no secret required\n return client;\n\n case 'client_secret_post':\n case 'client_secret_basic':\n // Client secret required\n if (!clientSecret || !client.client_secret) {\n return null;\n }\n\n // Constant-time comparison to prevent timing attacks\n if (clientSecret !== client.client_secret) {\n return null;\n }\n\n return client;\n\n default:\n // Unsupported authentication method\n return null;\n }\n }\n\n private async findClientByName(\n clientName: string,\n ): Promise<OAuthClient | undefined> {\n // Use the new findClient method for efficient lookup\n return await this.store.findClient(clientName);\n }\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { OAuthSession } from '../providers/oauth-provider.interface';
2
2
  export interface OAuthClient {
3
3
  client_id: string;
4
+ client_secret?: string;
4
5
  client_name: string;
5
6
  client_description?: string;
6
7
  logo_uri?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-store.interface.d.ts","sourceRoot":"","sources":["../../../src/authz/stores/oauth-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,0BAA0B,EAAE,MAAM,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AA+CD,MAAM,WAAW,WAAW;IAE1B,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC/D,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAClE,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC;IAG9C,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IAClE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG5C,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;IACtE,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
1
+ {"version":3,"file":"oauth-store.interface.d.ts","sourceRoot":"","sources":["../../../src/authz/stores/oauth-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,0BAA0B,EAAE,MAAM,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AA+CD,MAAM,WAAW,WAAW;IAE1B,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC/D,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAClE,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC;IAG9C,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IAClE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG5C,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;IACtE,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-store.interface.js","sourceRoot":"","sources":["../../../src/authz/stores/oauth-store.interface.ts"],"names":[],"mappings":"","sourcesContent":["import { OAuthSession } from '../providers/oauth-provider.interface';\n\nexport interface OAuthClient {\n client_id: string;\n client_name: string;\n client_description?: string;\n logo_uri?: string;\n client_uri?: string;\n developer_name?: string;\n developer_email?: string;\n redirect_uris: string[];\n grant_types: string[];\n response_types: string[];\n token_endpoint_auth_method: string;\n created_at: Date;\n updated_at: Date;\n}\n\nexport interface AuthorizationCode {\n code: string;\n user_id: string;\n client_id: string;\n redirect_uri: string;\n code_challenge: string;\n code_challenge_method: string;\n resource?: string;\n scope?: string;\n expires_at: number;\n used_at?: Date;\n github_access_token: string;\n}\n\nexport interface ClientRegistrationDto {\n client_name: string;\n client_description?: string;\n logo_uri?: string;\n client_uri?: string;\n developer_name?: string;\n developer_email?: string;\n redirect_uris: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n}\n\n/**\n * Interface for OAuth store implementations.\n *\n * Implement this interface to create custom storage solutions (e.g., Redis, Database, etc.).\n * The default implementation is an in-memory store suitable for development.\n *\n * @example\n * ```typescript\n * class RedisOAuthStore implements IOAuthStore {\n * constructor(private redisClient: RedisClient) {}\n *\n * async storeClient(client: OAuthClient): Promise<void> {\n * await this.redisClient.set(`client:${client.client_id}`, JSON.stringify(client));\n * }\n *\n * async getClient(client_id: string): Promise<OAuthClient | undefined> {\n * const data = await this.redisClient.get(`client:${client_id}`);\n * return data ? JSON.parse(data) : undefined;\n * }\n *\n * async findClient(client_name: string): Promise<OAuthClient | undefined> {\n * const data = await this.redisClient.get(`client_name:${client_name}`);\n * return data ? JSON.parse(data) : undefined;\n * }\n *\n * generateClientId(client: OAuthClient): string {\n * // Custom client ID generation logic\n * const normalizedName = client.client_name.toLowerCase().replace(/[^a-z0-9]/g, '');\n * const timestamp = Date.now().toString(36);\n * return `${normalizedName}_${timestamp}`;\n * }\n *\n * // ... implement other methods\n * }\n *\n * // Usage in module:\n * McpOAuthModule.forRoot({\n * provider: GoogleOAuthProvider,\n * clientId: process.env.GOOGLE_CLIENT_ID!,\n * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n * jwtSecret: process.env.JWT_SECRET!,\n * memoryStore: new RedisOAuthStore(redisClient), // Custom implementation\n * })\n * ```\n */\nexport interface IOAuthStore {\n // Client management\n storeClient(client: OAuthClient): Promise<OAuthClient>;\n getClient(client_id: string): Promise<OAuthClient | undefined>;\n findClient(client_name: string): Promise<OAuthClient | undefined>;\n generateClientId(client: OAuthClient): string;\n\n // Authorization code management\n storeAuthCode(code: AuthorizationCode): Promise<void>;\n getAuthCode(code: string): Promise<AuthorizationCode | undefined>;\n removeAuthCode(code: string): Promise<void>;\n\n // OAuth session management\n storeOAuthSession(sessionId: string, session: OAuthSession): Promise<void>;\n getOAuthSession(sessionId: string): Promise<OAuthSession | undefined>;\n removeOAuthSession(sessionId: string): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"oauth-store.interface.js","sourceRoot":"","sources":["../../../src/authz/stores/oauth-store.interface.ts"],"names":[],"mappings":"","sourcesContent":["import { OAuthSession } from '../providers/oauth-provider.interface';\n\nexport interface OAuthClient {\n client_id: string;\n client_secret?: string;\n client_name: string;\n client_description?: string;\n logo_uri?: string;\n client_uri?: string;\n developer_name?: string;\n developer_email?: string;\n redirect_uris: string[];\n grant_types: string[];\n response_types: string[];\n token_endpoint_auth_method: string;\n created_at: Date;\n updated_at: Date;\n}\n\nexport interface AuthorizationCode {\n code: string;\n user_id: string;\n client_id: string;\n redirect_uri: string;\n code_challenge: string;\n code_challenge_method: string;\n resource?: string;\n scope?: string;\n expires_at: number;\n used_at?: Date;\n github_access_token: string;\n}\n\nexport interface ClientRegistrationDto {\n client_name: string;\n client_description?: string;\n logo_uri?: string;\n client_uri?: string;\n developer_name?: string;\n developer_email?: string;\n redirect_uris: string[];\n grant_types?: string[];\n response_types?: string[];\n token_endpoint_auth_method?: string;\n}\n\n/**\n * Interface for OAuth store implementations.\n *\n * Implement this interface to create custom storage solutions (e.g., Redis, Database, etc.).\n * The default implementation is an in-memory store suitable for development.\n *\n * @example\n * ```typescript\n * class RedisOAuthStore implements IOAuthStore {\n * constructor(private redisClient: RedisClient) {}\n *\n * async storeClient(client: OAuthClient): Promise<void> {\n * await this.redisClient.set(`client:${client.client_id}`, JSON.stringify(client));\n * }\n *\n * async getClient(client_id: string): Promise<OAuthClient | undefined> {\n * const data = await this.redisClient.get(`client:${client_id}`);\n * return data ? JSON.parse(data) : undefined;\n * }\n *\n * async findClient(client_name: string): Promise<OAuthClient | undefined> {\n * const data = await this.redisClient.get(`client_name:${client_name}`);\n * return data ? JSON.parse(data) : undefined;\n * }\n *\n * generateClientId(client: OAuthClient): string {\n * // Custom client ID generation logic\n * const normalizedName = client.client_name.toLowerCase().replace(/[^a-z0-9]/g, '');\n * const timestamp = Date.now().toString(36);\n * return `${normalizedName}_${timestamp}`;\n * }\n *\n * // ... implement other methods\n * }\n *\n * // Usage in module:\n * McpOAuthModule.forRoot({\n * provider: GoogleOAuthProvider,\n * clientId: process.env.GOOGLE_CLIENT_ID!,\n * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n * jwtSecret: process.env.JWT_SECRET!,\n * memoryStore: new RedisOAuthStore(redisClient), // Custom implementation\n * })\n * ```\n */\nexport interface IOAuthStore {\n // Client management\n storeClient(client: OAuthClient): Promise<OAuthClient>;\n getClient(client_id: string): Promise<OAuthClient | undefined>;\n findClient(client_name: string): Promise<OAuthClient | undefined>;\n generateClientId(client: OAuthClient): string;\n\n // Authorization code management\n storeAuthCode(code: AuthorizationCode): Promise<void>;\n getAuthCode(code: string): Promise<AuthorizationCode | undefined>;\n removeAuthCode(code: string): Promise<void>;\n\n // OAuth session management\n storeOAuthSession(sessionId: string, session: OAuthSession): Promise<void>;\n getOAuthSession(sessionId: string): Promise<OAuthSession | undefined>;\n removeOAuthSession(sessionId: string): Promise<void>;\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  export declare class OAuthClientEntity {
2
2
  client_id: string;
3
+ client_secret?: string;
3
4
  client_name: string;
4
5
  client_description?: string;
5
6
  logo_uri?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-client.entity.d.ts","sourceRoot":"","sources":["../../../../../src/authz/stores/typeorm/entities/oauth-client.entity.ts"],"names":[],"mappings":"AAQA,qBACa,iBAAiB;IAE5B,SAAS,EAAE,MAAM,CAAC;IAGlB,WAAW,EAAE,MAAM,CAAC;IAGpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,aAAa,EAAE,MAAM,EAAE,CAAC;IAGxB,WAAW,EAAE,MAAM,EAAE,CAAC;IAGtB,cAAc,EAAE,MAAM,EAAE,CAAC;IAGzB,0BAA0B,EAAE,MAAM,CAAC;IAGnC,UAAU,EAAE,IAAI,CAAC;IAGjB,UAAU,EAAE,IAAI,CAAC;CAClB"}
1
+ {"version":3,"file":"oauth-client.entity.d.ts","sourceRoot":"","sources":["../../../../../src/authz/stores/typeorm/entities/oauth-client.entity.ts"],"names":[],"mappings":"AAQA,qBACa,iBAAiB;IAE5B,SAAS,EAAE,MAAM,CAAC;IAGlB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,WAAW,EAAE,MAAM,CAAC;IAGpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,aAAa,EAAE,MAAM,EAAE,CAAC;IAGxB,WAAW,EAAE,MAAM,EAAE,CAAC;IAGtB,cAAc,EAAE,MAAM,EAAE,CAAC;IAGzB,0BAA0B,EAAE,MAAM,CAAC;IAGnC,UAAU,EAAE,IAAI,CAAC;IAGjB,UAAU,EAAE,IAAI,CAAC;CAClB"}
@@ -18,6 +18,10 @@ __decorate([
18
18
  (0, typeorm_1.PrimaryColumn)(),
19
19
  __metadata("design:type", String)
20
20
  ], OAuthClientEntity.prototype, "client_id", void 0);
21
+ __decorate([
22
+ (0, typeorm_1.Column)({ nullable: true }),
23
+ __metadata("design:type", String)
24
+ ], OAuthClientEntity.prototype, "client_secret", void 0);
21
25
  __decorate([
22
26
  (0, typeorm_1.Column)(),
23
27
  __metadata("design:type", String)
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-client.entity.js","sourceRoot":"","sources":["../../../../../src/authz/stores/typeorm/entities/oauth-client.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAMiB;AAGV,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAuC7B,CAAA;AAvCY,8CAAiB;AAE5B;IADC,IAAA,uBAAa,GAAE;;oDACE;AAGlB;IADC,IAAA,gBAAM,GAAE;;sDACW;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6DACC;AAG5B;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACT;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACP;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yDACH;AAGxB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0DACF;AAGzB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;wDACC;AAGxB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;sDACD;AAGtB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;yDACE;AAGzB;IADC,IAAA,gBAAM,GAAE;;qEAC0B;AAGnC;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;qDAAC;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;qDAAC;4BAtCN,iBAAiB;IAD7B,IAAA,gBAAM,EAAC,eAAe,CAAC;GACX,iBAAiB,CAuC7B","sourcesContent":["import {\n Entity,\n Column,\n PrimaryColumn,\n CreateDateColumn,\n UpdateDateColumn,\n} from 'typeorm';\n\n@Entity('oauth_clients')\nexport class OAuthClientEntity {\n @PrimaryColumn()\n client_id: string;\n\n @Column()\n client_name: string;\n\n @Column({ nullable: true })\n client_description?: string;\n\n @Column({ nullable: true })\n logo_uri?: string;\n\n @Column({ nullable: true })\n client_uri?: string;\n\n @Column({ nullable: true })\n developer_name?: string;\n\n @Column({ nullable: true })\n developer_email?: string;\n\n @Column('simple-array')\n redirect_uris: string[];\n\n @Column('simple-array')\n grant_types: string[];\n\n @Column('simple-array')\n response_types: string[];\n\n @Column()\n token_endpoint_auth_method: string;\n\n @CreateDateColumn()\n created_at: Date;\n\n @UpdateDateColumn()\n updated_at: Date;\n}\n"]}
1
+ {"version":3,"file":"oauth-client.entity.js","sourceRoot":"","sources":["../../../../../src/authz/stores/typeorm/entities/oauth-client.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAMiB;AAGV,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CA0C7B,CAAA;AA1CY,8CAAiB;AAE5B;IADC,IAAA,uBAAa,GAAE;;oDACE;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wDACJ;AAGvB;IADC,IAAA,gBAAM,GAAE;;sDACW;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6DACC;AAG5B;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACT;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACP;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yDACH;AAGxB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0DACF;AAGzB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;wDACC;AAGxB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;sDACD;AAGtB;IADC,IAAA,gBAAM,EAAC,cAAc,CAAC;;yDACE;AAGzB;IADC,IAAA,gBAAM,GAAE;;qEAC0B;AAGnC;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;qDAAC;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACP,IAAI;qDAAC;4BAzCN,iBAAiB;IAD7B,IAAA,gBAAM,EAAC,eAAe,CAAC;GACX,iBAAiB,CA0C7B","sourcesContent":["import {\n Entity,\n Column,\n PrimaryColumn,\n CreateDateColumn,\n UpdateDateColumn,\n} from 'typeorm';\n\n@Entity('oauth_clients')\nexport class OAuthClientEntity {\n @PrimaryColumn()\n client_id: string;\n\n @Column({ nullable: true })\n client_secret?: string;\n\n @Column()\n client_name: string;\n\n @Column({ nullable: true })\n client_description?: string;\n\n @Column({ nullable: true })\n logo_uri?: string;\n\n @Column({ nullable: true })\n client_uri?: string;\n\n @Column({ nullable: true })\n developer_name?: string;\n\n @Column({ nullable: true })\n developer_email?: string;\n\n @Column('simple-array')\n redirect_uris: string[];\n\n @Column('simple-array')\n grant_types: string[];\n\n @Column('simple-array')\n response_types: string[];\n\n @Column()\n token_endpoint_auth_method: string;\n\n @CreateDateColumn()\n created_at: Date;\n\n @UpdateDateColumn()\n updated_at: Date;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rekog/mcp-nest",
3
- "version": "1.7.1",
3
+ "version": "1.7.2-alpha.0",
4
4
  "description": "NestJS module for creating Model Context Protocol (MCP) servers",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -275,40 +275,88 @@ export function createMcpOAuthController(
275
275
 
276
276
  // Token endpoints (remain the same)
277
277
  @Post(endpoints.token)
278
- async exchangeToken(@Body() body: any): Promise<TokenPair> {
279
- const {
280
- grant_type,
281
- code,
282
- code_verifier,
283
- redirect_uri,
284
- client_id,
285
- refresh_token,
286
- } = body;
278
+ async exchangeToken(
279
+ @Body() body: any,
280
+ @Req() req: ExpressRequest,
281
+ ): Promise<TokenPair> {
282
+ const { grant_type, code, code_verifier, redirect_uri, refresh_token } =
283
+ body;
284
+
285
+ // Extract client credentials based on authentication method
286
+ const clientCredentials = this.extractClientCredentials(req, body);
287
287
 
288
288
  if (grant_type === 'authorization_code') {
289
289
  return this.handleAuthorizationCodeGrant(
290
- code,
291
- code_verifier,
292
- redirect_uri,
293
- client_id,
290
+ code as string,
291
+ code_verifier as string,
292
+ redirect_uri as string,
293
+ clientCredentials.clientId,
294
+ clientCredentials.clientSecret,
294
295
  );
295
296
  } else if (grant_type === 'refresh_token') {
296
- return this.handleRefreshTokenGrant(refresh_token);
297
+ return this.handleRefreshTokenGrant(
298
+ refresh_token as string,
299
+ clientCredentials.clientId,
300
+ clientCredentials.clientSecret,
301
+ );
297
302
  } else {
298
303
  throw new BadRequestException('Unsupported grant_type');
299
304
  }
300
305
  }
301
306
 
307
+ extractClientCredentials(
308
+ req: ExpressRequest,
309
+ body: any,
310
+ ): { clientId: string; clientSecret?: string } {
311
+ // Check for client_secret_basic (Authorization header)
312
+ const authHeader = req.headers.authorization;
313
+ if (authHeader && authHeader.startsWith('Basic ')) {
314
+ const base64Credentials = authHeader.slice('Basic '.length);
315
+ const credentials = Buffer.from(base64Credentials, 'base64').toString(
316
+ 'ascii',
317
+ );
318
+ const [clientId, clientSecret] = credentials.split(':');
319
+ if (clientId) {
320
+ return { clientId, clientSecret };
321
+ }
322
+ }
323
+
324
+ // Check for client_secret_post (body parameters)
325
+ if (body.client_id) {
326
+ return {
327
+ clientId: body.client_id,
328
+ clientSecret: body.client_secret,
329
+ };
330
+ }
331
+
332
+ throw new BadRequestException('Client authentication required');
333
+ }
334
+
302
335
  async handleAuthorizationCodeGrant(
303
336
  code: string,
304
337
  code_verifier: string,
305
338
  _redirect_uri: string,
306
339
  client_id: string,
340
+ client_secret?: string,
307
341
  ): Promise<TokenPair> {
308
342
  this.logger.debug('handleAuthorizationCodeGrant - Params:', {
309
343
  code,
310
344
  client_id,
311
345
  });
346
+
347
+ // Authenticate the client
348
+ const authenticatedClient = await this.clientService.authenticateClient(
349
+ client_id,
350
+ client_secret,
351
+ );
352
+ if (!authenticatedClient) {
353
+ this.logger.error(
354
+ 'handleAuthorizationCodeGrant - Client authentication failed:',
355
+ client_id,
356
+ );
357
+ throw new BadRequestException('Client authentication failed');
358
+ }
359
+
312
360
  const authCode = await this.store.getAuthCode(code);
313
361
  if (!authCode) {
314
362
  this.logger.error(
@@ -367,7 +415,26 @@ export function createMcpOAuthController(
367
415
  return tokens;
368
416
  }
369
417
 
370
- handleRefreshTokenGrant(refresh_token: string): TokenPair {
418
+ async handleRefreshTokenGrant(
419
+ refresh_token: string,
420
+ client_id?: string,
421
+ client_secret?: string,
422
+ ): Promise<TokenPair> {
423
+ // If client_id is provided, authenticate the client
424
+ if (client_id) {
425
+ const authenticatedClient = await this.clientService.authenticateClient(
426
+ client_id,
427
+ client_secret,
428
+ );
429
+ if (!authenticatedClient) {
430
+ this.logger.error(
431
+ 'handleRefreshTokenGrant - Client authentication failed:',
432
+ client_id,
433
+ );
434
+ throw new BadRequestException('Client authentication failed');
435
+ }
436
+ }
437
+
371
438
  const newTokens = this.jwtTokenService.refreshAccessToken(refresh_token);
372
439
  if (!newTokens) {
373
440
  throw new BadRequestException('Failed to refresh token');
@@ -4,6 +4,7 @@ import {
4
4
  IOAuthStore,
5
5
  OAuthClient,
6
6
  } from '../stores/oauth-store.interface';
7
+ import { randomBytes } from 'crypto';
7
8
 
8
9
  @Injectable()
9
10
  export class ClientService {
@@ -64,10 +65,13 @@ export class ClientService {
64
65
  const client_id = this.store.generateClientId(
65
66
  registrationDto as OAuthClient,
66
67
  );
68
+ const client_secret = randomBytes(32).toString('hex');
69
+
67
70
  const newClient: OAuthClient = {
68
71
  ...defaultClientValues,
69
72
  ...registrationDto,
70
73
  client_id,
74
+ client_secret,
71
75
  created_at: now,
72
76
  updated_at: now,
73
77
  };
@@ -101,6 +105,46 @@ export class ClientService {
101
105
  return client ? client.redirect_uris.includes(redirectUri) : false;
102
106
  }
103
107
 
108
+ /**
109
+ * Authenticate client using various methods
110
+ */
111
+ async authenticateClient(
112
+ clientId: string,
113
+ clientSecret?: string,
114
+ authMethod?: string,
115
+ ): Promise<OAuthClient | null> {
116
+ const client = await this.getClient(clientId);
117
+ if (!client) {
118
+ return null;
119
+ }
120
+
121
+ const authMethodToUse = authMethod || client.token_endpoint_auth_method;
122
+
123
+ switch (authMethodToUse) {
124
+ case 'none':
125
+ // Public client - no secret required
126
+ return client;
127
+
128
+ case 'client_secret_post':
129
+ case 'client_secret_basic':
130
+ // Client secret required
131
+ if (!clientSecret || !client.client_secret) {
132
+ return null;
133
+ }
134
+
135
+ // Constant-time comparison to prevent timing attacks
136
+ if (clientSecret !== client.client_secret) {
137
+ return null;
138
+ }
139
+
140
+ return client;
141
+
142
+ default:
143
+ // Unsupported authentication method
144
+ return null;
145
+ }
146
+ }
147
+
104
148
  private async findClientByName(
105
149
  clientName: string,
106
150
  ): Promise<OAuthClient | undefined> {
@@ -2,6 +2,7 @@ import { OAuthSession } from '../providers/oauth-provider.interface';
2
2
 
3
3
  export interface OAuthClient {
4
4
  client_id: string;
5
+ client_secret?: string;
5
6
  client_name: string;
6
7
  client_description?: string;
7
8
  logo_uri?: string;
@@ -11,6 +11,9 @@ export class OAuthClientEntity {
11
11
  @PrimaryColumn()
12
12
  client_id: string;
13
13
 
14
+ @Column({ nullable: true })
15
+ client_secret?: string;
16
+
14
17
  @Column()
15
18
  client_name: string;
16
19