@rekog/mcp-nest 1.7.0-alpha.0 → 1.7.0-alpha.2
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.
- package/dist/authz/guards/jwt-auth.guard.d.ts +13 -0
- package/dist/authz/guards/jwt-auth.guard.d.ts.map +1 -0
- package/dist/authz/guards/jwt-auth.guard.js +46 -0
- package/dist/authz/guards/jwt-auth.guard.js.map +1 -0
- package/dist/authz/index.d.ts +8 -1
- package/dist/authz/index.d.ts.map +1 -1
- package/dist/authz/index.js +22 -0
- package/dist/authz/index.js.map +1 -1
- package/dist/authz/interfaces/index.d.ts +3 -0
- package/dist/authz/interfaces/index.d.ts.map +1 -0
- package/dist/authz/interfaces/index.js +19 -0
- package/dist/authz/interfaces/index.js.map +1 -0
- package/dist/authz/mcp-oauth.controller.d.ts +53 -0
- package/dist/authz/mcp-oauth.controller.d.ts.map +1 -0
- package/dist/authz/mcp-oauth.controller.js +305 -0
- package/dist/authz/mcp-oauth.controller.js.map +1 -0
- package/dist/authz/mcp-oauth.module.d.ts +11 -0
- package/dist/authz/mcp-oauth.module.d.ts.map +1 -0
- package/dist/authz/mcp-oauth.module.js +173 -0
- package/dist/authz/mcp-oauth.module.js.map +1 -0
- package/dist/authz/providers/github.provider.d.ts +3 -0
- package/dist/authz/providers/github.provider.d.ts.map +1 -0
- package/dist/authz/providers/github.provider.js +23 -0
- package/dist/authz/providers/github.provider.js.map +1 -0
- package/dist/authz/providers/google.provider.d.ts +3 -0
- package/dist/authz/providers/google.provider.d.ts.map +1 -0
- package/dist/authz/providers/google.provider.js +24 -0
- package/dist/authz/providers/google.provider.js.map +1 -0
- package/dist/authz/providers/oauth-provider.interface.d.ts +75 -0
- package/dist/authz/providers/oauth-provider.interface.d.ts.map +1 -0
- package/dist/authz/providers/oauth-provider.interface.js +3 -0
- package/dist/authz/providers/oauth-provider.interface.js.map +1 -0
- package/dist/authz/services/client.service.d.ts +10 -0
- package/dist/authz/services/client.service.d.ts.map +1 -0
- package/dist/authz/services/client.service.js +84 -0
- package/dist/authz/services/client.service.js.map +1 -0
- package/dist/authz/services/jwt-token.service.d.ts +29 -0
- package/dist/authz/services/jwt-token.service.d.ts.map +1 -0
- package/dist/authz/services/jwt-token.service.js +103 -0
- package/dist/authz/services/jwt-token.service.js.map +1 -0
- package/dist/authz/services/oauth-strategy.service.d.ts +11 -0
- package/dist/authz/services/oauth-strategy.service.d.ts.map +1 -0
- package/dist/authz/services/oauth-strategy.service.js +64 -0
- package/dist/authz/services/oauth-strategy.service.js.map +1 -0
- package/dist/authz/stores/memory-store.service.d.ts +19 -0
- package/dist/authz/stores/memory-store.service.d.ts.map +1 -0
- package/dist/authz/stores/memory-store.service.js +84 -0
- package/dist/authz/stores/memory-store.service.js.map +1 -0
- package/dist/authz/stores/memory-store.service.spec.d.ts +2 -0
- package/dist/authz/stores/memory-store.service.spec.d.ts.map +1 -0
- package/dist/authz/stores/memory-store.service.spec.js +195 -0
- package/dist/authz/stores/memory-store.service.spec.js.map +1 -0
- package/dist/authz/stores/oauth-store.interface.d.ts +52 -0
- package/dist/authz/stores/oauth-store.interface.d.ts.map +1 -0
- package/dist/authz/stores/oauth-store.interface.js +3 -0
- package/dist/authz/stores/oauth-store.interface.js.map +1 -0
- package/dist/authz/stores/sqlite/sqlite-store.service.d.ts +18 -0
- package/dist/authz/stores/sqlite/sqlite-store.service.d.ts.map +1 -0
- package/dist/authz/stores/sqlite/sqlite-store.service.js +206 -0
- package/dist/authz/stores/sqlite/sqlite-store.service.js.map +1 -0
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts +13 -0
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts.map +1 -0
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.js +60 -0
- package/dist/authz/stores/typeorm/entities/authorization-code.entity.js.map +1 -0
- package/dist/authz/stores/typeorm/entities/index.d.ts +4 -0
- package/dist/authz/stores/typeorm/entities/index.d.ts.map +1 -0
- package/dist/authz/stores/typeorm/entities/index.js +10 -0
- package/dist/authz/stores/typeorm/entities/index.js.map +1 -0
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts +16 -0
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts.map +1 -0
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.js +72 -0
- package/dist/authz/stores/typeorm/entities/oauth-client.entity.js.map +1 -0
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts +13 -0
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts.map +1 -0
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.js +60 -0
- package/dist/authz/stores/typeorm/entities/oauth-session.entity.js.map +1 -0
- package/dist/authz/stores/typeorm/typeorm-store.service.d.ts +21 -0
- package/dist/authz/stores/typeorm/typeorm-store.service.d.ts.map +1 -0
- package/dist/authz/stores/typeorm/typeorm-store.service.js +87 -0
- package/dist/authz/stores/typeorm/typeorm-store.service.js.map +1 -0
- package/dist/mcp/services/handlers/mcp-tools.handler.d.ts.map +1 -1
- package/dist/mcp/services/handlers/mcp-tools.handler.js.map +1 -1
- package/package.json +17 -2
- package/src/authz/guards/jwt-auth.guard.ts +45 -0
- package/src/authz/index.ts +8 -1
- package/src/authz/mcp-oauth.controller.ts +394 -0
- package/src/authz/mcp-oauth.module.ts +235 -0
- package/src/authz/providers/github.provider.ts +24 -0
- package/src/authz/providers/google.provider.ts +22 -0
- package/src/authz/providers/oauth-provider.interface.ts +102 -0
- package/src/authz/services/client.service.ts +110 -0
- package/src/authz/services/jwt-token.service.ts +127 -0
- package/src/authz/services/oauth-strategy.service.ts +55 -0
- package/src/authz/stores/memory-store.service.spec.ts +240 -0
- package/src/authz/stores/memory-store.service.ts +100 -0
- package/src/authz/stores/oauth-store.interface.ts +105 -0
- package/src/authz/stores/sqlite/sqlite-store.service.ts +279 -0
- package/src/authz/stores/typeorm/README.md +133 -0
- package/src/authz/stores/typeorm/entities/authorization-code.entity.ts +34 -0
- package/src/authz/stores/typeorm/entities/index.ts +3 -0
- package/src/authz/stores/typeorm/entities/oauth-client.entity.ts +49 -0
- package/src/authz/stores/typeorm/entities/oauth-session.entity.ts +34 -0
- package/src/authz/stores/typeorm/typeorm-store.service.ts +98 -0
- package/src/mcp/services/handlers/mcp-tools.handler.ts +5 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TypeOrmStore = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const typeorm_1 = require("@nestjs/typeorm");
|
|
18
|
+
const typeorm_2 = require("typeorm");
|
|
19
|
+
const crypto_1 = require("crypto");
|
|
20
|
+
const entities_1 = require("./entities");
|
|
21
|
+
let TypeOrmStore = class TypeOrmStore {
|
|
22
|
+
constructor(clientRepository, authCodeRepository, sessionRepository) {
|
|
23
|
+
this.clientRepository = clientRepository;
|
|
24
|
+
this.authCodeRepository = authCodeRepository;
|
|
25
|
+
this.sessionRepository = sessionRepository;
|
|
26
|
+
}
|
|
27
|
+
async storeClient(client) {
|
|
28
|
+
const savedClient = await this.clientRepository.save(client);
|
|
29
|
+
return savedClient;
|
|
30
|
+
}
|
|
31
|
+
async getClient(client_id) {
|
|
32
|
+
return ((await this.clientRepository.findOne({ where: { client_id } })) ??
|
|
33
|
+
undefined);
|
|
34
|
+
}
|
|
35
|
+
async findClient(client_name) {
|
|
36
|
+
return ((await this.clientRepository.findOne({ where: { client_name } })) ??
|
|
37
|
+
undefined);
|
|
38
|
+
}
|
|
39
|
+
async storeAuthCode(code) {
|
|
40
|
+
await this.authCodeRepository.save(code);
|
|
41
|
+
}
|
|
42
|
+
async getAuthCode(code) {
|
|
43
|
+
const authCode = await this.authCodeRepository.findOne({ where: { code } });
|
|
44
|
+
if (authCode && authCode.expires_at < Date.now()) {
|
|
45
|
+
await this.authCodeRepository.delete({ code });
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return authCode ?? undefined;
|
|
49
|
+
}
|
|
50
|
+
async removeAuthCode(code) {
|
|
51
|
+
await this.authCodeRepository.delete({ code });
|
|
52
|
+
}
|
|
53
|
+
async storeOAuthSession(sessionId, session) {
|
|
54
|
+
await this.sessionRepository.save({ ...session, sessionId });
|
|
55
|
+
}
|
|
56
|
+
async getOAuthSession(sessionId) {
|
|
57
|
+
const session = await this.sessionRepository.findOne({
|
|
58
|
+
where: { sessionId },
|
|
59
|
+
});
|
|
60
|
+
if (session && session.expiresAt < Date.now()) {
|
|
61
|
+
await this.sessionRepository.delete({ sessionId });
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return session ?? undefined;
|
|
65
|
+
}
|
|
66
|
+
async removeOAuthSession(sessionId) {
|
|
67
|
+
await this.sessionRepository.delete({ sessionId });
|
|
68
|
+
}
|
|
69
|
+
generateClientId(client) {
|
|
70
|
+
const normalizedName = client.client_name
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/[^a-z0-9]/g, '');
|
|
73
|
+
const salt = (0, crypto_1.randomBytes)(4).toString('hex');
|
|
74
|
+
return `${normalizedName}_${salt}`;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.TypeOrmStore = TypeOrmStore;
|
|
78
|
+
exports.TypeOrmStore = TypeOrmStore = __decorate([
|
|
79
|
+
(0, common_1.Injectable)(),
|
|
80
|
+
__param(0, (0, typeorm_1.InjectRepository)(entities_1.OAuthClientEntity)),
|
|
81
|
+
__param(1, (0, typeorm_1.InjectRepository)(entities_1.AuthorizationCodeEntity)),
|
|
82
|
+
__param(2, (0, typeorm_1.InjectRepository)(entities_1.OAuthSessionEntity)),
|
|
83
|
+
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
84
|
+
typeorm_2.Repository,
|
|
85
|
+
typeorm_2.Repository])
|
|
86
|
+
], TypeOrmStore);
|
|
87
|
+
//# sourceMappingURL=typeorm-store.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeorm-store.service.js","sourceRoot":"","sources":["../../../../src/authz/stores/typeorm/typeorm-store.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6CAAmD;AACnD,qCAAqC;AACrC,mCAAqC;AACrC,yCAIoB;AASb,IAAM,YAAY,GAAlB,MAAM,YAAY;IACvB,YAEmB,gBAA+C,EAE/C,kBAAuD,EAEvD,iBAAiD;QAJjD,qBAAgB,GAAhB,gBAAgB,CAA+B;QAE/C,uBAAkB,GAAlB,kBAAkB,CAAqC;QAEvD,sBAAiB,GAAjB,iBAAiB,CAAgC;IACjE,CAAC;IAGJ,KAAK,CAAC,WAAW,CAAC,MAAmB;QACnC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAAiB;QAC/B,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;YAC/D,SAAS,CACV,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;YACjE,SAAS,CACV,CAAC;IACJ,CAAC;IAGD,KAAK,CAAC,aAAa,CAAC,IAAuB;QACzC,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAE5E,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACjD,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,QAAQ,IAAI,SAAS,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAY;QAC/B,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IAGD,KAAK,CAAC,iBAAiB,CACrB,SAAiB,EACjB,OAAqB;QAErB,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAiB;QACrC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;YACnD,KAAK,EAAE,EAAE,SAAS,EAAE;SACrB,CAAC,CAAC;QACH,IAAI,OAAO,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;YACnD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,OAAO,IAAI,SAAS,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,gBAAgB,CAAC,MAAmB;QAElC,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW;aACtC,WAAW,EAAE;aACb,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAA,oBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,GAAG,cAAc,IAAI,IAAI,EAAE,CAAC;IACrC,CAAC;CACF,CAAA;AAhFY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,0BAAgB,EAAC,4BAAiB,CAAC,CAAA;IAEnC,WAAA,IAAA,0BAAgB,EAAC,kCAAuB,CAAC,CAAA;IAEzC,WAAA,IAAA,0BAAgB,EAAC,6BAAkB,CAAC,CAAA;qCAHF,oBAAU;QAER,oBAAU;QAEX,oBAAU;GAPrC,YAAY,CAgFxB","sourcesContent":["import { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { Repository } from 'typeorm';\nimport { randomBytes } from 'crypto';\nimport {\n OAuthClientEntity,\n AuthorizationCodeEntity,\n OAuthSessionEntity,\n} from './entities';\nimport { OAuthSession } from '../../providers/oauth-provider.interface';\nimport {\n AuthorizationCode,\n IOAuthStore,\n OAuthClient,\n} from '../oauth-store.interface';\n\n@Injectable()\nexport class TypeOrmStore implements IOAuthStore {\n constructor(\n @InjectRepository(OAuthClientEntity)\n private readonly clientRepository: Repository<OAuthClientEntity>,\n @InjectRepository(AuthorizationCodeEntity)\n private readonly authCodeRepository: Repository<AuthorizationCodeEntity>,\n @InjectRepository(OAuthSessionEntity)\n private readonly sessionRepository: Repository<OAuthSessionEntity>,\n ) {}\n\n // Client management\n async storeClient(client: OAuthClient): Promise<OAuthClient> {\n const savedClient = await this.clientRepository.save(client);\n return savedClient;\n }\n\n async getClient(client_id: string): Promise<OAuthClient | undefined> {\n return (\n (await this.clientRepository.findOne({ where: { client_id } })) ??\n undefined\n );\n }\n\n async findClient(client_name: string): Promise<OAuthClient | undefined> {\n return (\n (await this.clientRepository.findOne({ where: { client_name } })) ??\n undefined\n );\n }\n\n // Authorization code management\n async storeAuthCode(code: AuthorizationCode): Promise<void> {\n await this.authCodeRepository.save(code);\n }\n\n async getAuthCode(code: string): Promise<AuthorizationCode | undefined> {\n const authCode = await this.authCodeRepository.findOne({ where: { code } });\n // Check if expired\n if (authCode && authCode.expires_at < Date.now()) {\n await this.authCodeRepository.delete({ code });\n return undefined;\n }\n return authCode ?? undefined;\n }\n\n async removeAuthCode(code: string): Promise<void> {\n await this.authCodeRepository.delete({ code });\n }\n\n // OAuth session management\n async storeOAuthSession(\n sessionId: string,\n session: OAuthSession,\n ): Promise<void> {\n await this.sessionRepository.save({ ...session, sessionId });\n }\n\n async getOAuthSession(sessionId: string): Promise<OAuthSession | undefined> {\n const session = await this.sessionRepository.findOne({\n where: { sessionId },\n });\n if (session && session.expiresAt < Date.now()) {\n await this.sessionRepository.delete({ sessionId });\n return undefined;\n }\n return session ?? undefined;\n }\n\n async removeOAuthSession(sessionId: string): Promise<void> {\n await this.sessionRepository.delete({ sessionId });\n }\n\n generateClientId(client: OAuthClient): string {\n // Create deterministic client ID based on name + random salt\n const normalizedName = client.client_name\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n const salt = randomBytes(4).toString('hex');\n return `${normalizedName}_${salt}`;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.handler.d.ts","sourceRoot":"","sources":["../../../../src/mcp/services/handlers/mcp-tools.handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpD,qBACa,eAAgB,SAAQ,cAAc;IAItB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAFrD,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,kBAAkB,EACc,WAAW,EAAE,MAAM;IAK/D,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,gBAAgB;IAwBxB,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"mcp-tools.handler.d.ts","sourceRoot":"","sources":["../../../../src/mcp/services/handlers/mcp-tools.handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpD,qBACa,eAAgB,SAAQ,cAAc;IAItB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAFrD,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,kBAAkB,EACc,WAAW,EAAE,MAAM;IAK/D,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,gBAAgB;IAwBxB,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO;CAuG5D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.handler.js","sourceRoot":"","sources":["../../../../src/mcp/services/handlers/mcp-tools.handler.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"mcp-tools.handler.js","sourceRoot":"","sources":["../../../../src/mcp/services/handlers/mcp-tools.handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { Inject, Injectable, Scope } from '@nestjs/common';\nimport { ContextIdFactory, ModuleRef } from '@nestjs/core';\nimport { Request } from 'express';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport { McpRegistryService } from '../mcp-registry.service';\nimport { McpHandlerBase } from './mcp-handler.base';\nimport { ZodTypeAny } from 'zod';\n\n@Injectable({ scope: Scope.REQUEST })\nexport class McpToolsHandler extends McpHandlerBase {\n constructor(\n moduleRef: ModuleRef,\n registry: McpRegistryService,\n @Inject('MCP_MODULE_ID') private readonly mcpModuleId: string,\n ) {\n super(moduleRef, registry, McpToolsHandler.name);\n }\n\n private buildDefaultContentBlock(result: any) {\n return [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ];\n }\n\n private formatToolResult(result: any, outputSchema?: ZodTypeAny): any {\n if (result && typeof result === 'object' && Array.isArray(result.content)) {\n return result;\n }\n\n if (outputSchema) {\n const validation = outputSchema.safeParse(result);\n if (!validation.success) {\n throw new McpError(\n ErrorCode.InternalError,\n `Tool result does not match outputSchema: ${validation.error.message}`,\n );\n }\n return {\n structuredContent: result,\n content: this.buildDefaultContentBlock(result),\n };\n }\n\n return {\n content: this.buildDefaultContentBlock(result),\n };\n }\n\n registerHandlers(mcpServer: McpServer, httpRequest: Request) {\n if (this.registry.getTools(this.mcpModuleId).length === 0) {\n this.logger.debug('No tools registered, skipping tool handlers');\n return;\n }\n\n mcpServer.server.setRequestHandler(ListToolsRequestSchema, () => {\n const tools = this.registry.getTools(this.mcpModuleId).map((tool) => {\n // Create base schema\n const toolSchema = {\n name: tool.metadata.name,\n description: tool.metadata.description,\n annotations: tool.metadata.annotations,\n };\n\n // Add input schema if defined\n if (tool.metadata.parameters) {\n toolSchema['inputSchema'] = zodToJsonSchema(tool.metadata.parameters);\n }\n\n // Add output schema if defined, ensuring it has type: 'object'\n if (tool.metadata.outputSchema) {\n const outputSchema = zodToJsonSchema(tool.metadata.outputSchema);\n\n // Create a new object that explicitly includes type: 'object'\n const jsonSchema = {\n ...outputSchema,\n type: 'object',\n };\n\n toolSchema['outputSchema'] = jsonSchema;\n }\n\n return toolSchema;\n });\n\n return {\n tools,\n };\n });\n\n mcpServer.server.setRequestHandler(\n CallToolRequestSchema,\n async (request) => {\n this.logger.debug('CallToolRequestSchema is being called');\n\n const toolInfo = this.registry.findTool(\n this.mcpModuleId,\n request.params.name,\n );\n\n if (!toolInfo) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown tool: ${request.params.name}`,\n );\n }\n\n try {\n const contextId = ContextIdFactory.getByRequest(httpRequest);\n this.moduleRef.registerRequestByContextId(httpRequest, contextId);\n\n const toolInstance = await this.moduleRef.resolve(\n toolInfo.providerClass,\n contextId,\n { strict: false },\n );\n\n const context = this.createContext(mcpServer, request);\n\n if (!toolInstance) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown tool: ${request.params.name}`,\n );\n }\n\n const result = await toolInstance[toolInfo.methodName].call(\n toolInstance,\n request.params.arguments,\n context,\n httpRequest,\n );\n\n const transformedResult = this.formatToolResult(\n result,\n toolInfo.metadata.outputSchema,\n );\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n this.logger.debug(transformedResult, 'CallToolRequestSchema result');\n\n return transformedResult;\n } catch (error) {\n this.logger.error(error);\n return {\n content: [{ type: 'text', text: error.message }],\n isError: true,\n };\n }\n },\n );\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rekog/mcp-nest",
|
|
3
|
-
"version": "1.7.0-alpha.
|
|
3
|
+
"version": "1.7.0-alpha.2",
|
|
4
4
|
"description": "NestJS module for creating Model Context Protocol (MCP) servers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,8 +31,12 @@
|
|
|
31
31
|
"@modelcontextprotocol/sdk": ">=1.10.0",
|
|
32
32
|
"@nestjs/common": ">=9.0.0",
|
|
33
33
|
"@nestjs/core": ">=9.0.0",
|
|
34
|
+
"@nestjs/jwt": "^11.0.0",
|
|
35
|
+
"@nestjs/passport": "^11.0.5",
|
|
36
|
+
"@nestjs/typeorm": ">=9.0.0",
|
|
34
37
|
"express": ">=4.0.0",
|
|
35
38
|
"reflect-metadata": ">=0.1.14",
|
|
39
|
+
"typeorm": ">=0.3.25",
|
|
36
40
|
"zod": "^3.0.0",
|
|
37
41
|
"zod-to-json-schema": ">=3.23.0"
|
|
38
42
|
},
|
|
@@ -44,9 +48,12 @@
|
|
|
44
48
|
"@nestjs/core": "^11.1.1",
|
|
45
49
|
"@nestjs/platform-express": "^11.1.1",
|
|
46
50
|
"@nestjs/testing": "^11.1.1",
|
|
51
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
52
|
+
"@types/cookie-parser": "^1.4.9",
|
|
47
53
|
"@types/express": "^5.0.2",
|
|
48
54
|
"@types/jest": "^29.5.14",
|
|
49
55
|
"@types/node": "^22.10.10",
|
|
56
|
+
"@types/passport-google-oauth20": "^2.0.16",
|
|
50
57
|
"@types/supertest": "^6.0.3",
|
|
51
58
|
"eslint": "^9.18.0",
|
|
52
59
|
"eslint-config-prettier": "^10.0.1",
|
|
@@ -60,6 +67,14 @@
|
|
|
60
67
|
"typescript-eslint": "^8.20.0"
|
|
61
68
|
},
|
|
62
69
|
"dependencies": {
|
|
63
|
-
"
|
|
70
|
+
"@nestjs/config": "^4.0.2",
|
|
71
|
+
"better-sqlite3": "11.10.0",
|
|
72
|
+
"cookie-parser": "^1.4.7",
|
|
73
|
+
"passport": "^0.7.0",
|
|
74
|
+
"passport-github": "^1.1.0",
|
|
75
|
+
"passport-google-oauth20": "^2.0.0",
|
|
76
|
+
"path-to-regexp": "^8.2.0",
|
|
77
|
+
"rxjs": "^7.8.2",
|
|
78
|
+
"sqlite3": "^5.1.7"
|
|
64
79
|
}
|
|
65
80
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
CanActivate,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
UnauthorizedException,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { Request } from 'express';
|
|
8
|
+
import { JwtPayload, JwtTokenService } from '../services/jwt-token.service';
|
|
9
|
+
|
|
10
|
+
export interface AuthenticatedRequest extends Request {
|
|
11
|
+
user: JwtPayload;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class JwtAuthGuard implements CanActivate {
|
|
16
|
+
constructor(private readonly jwtTokenService: JwtTokenService) {}
|
|
17
|
+
|
|
18
|
+
canActivate(context: ExecutionContext): boolean {
|
|
19
|
+
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
|
20
|
+
const token = this.extractTokenFromHeader(request);
|
|
21
|
+
|
|
22
|
+
if (!token) {
|
|
23
|
+
throw new UnauthorizedException('Access token required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const payload = this.jwtTokenService.validateToken(token);
|
|
27
|
+
|
|
28
|
+
if (!payload || payload.type !== 'access') {
|
|
29
|
+
throw new UnauthorizedException('Invalid or expired access token');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
request.user = payload;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private extractTokenFromHeader(request: Request): string | undefined {
|
|
37
|
+
const authHeader = request.headers.authorization;
|
|
38
|
+
if (!authHeader) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [type, token] = authHeader.split(' ');
|
|
43
|
+
return type === 'Bearer' ? token : undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/authz/index.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
export * from './mcp-oauth.controller';
|
|
2
|
+
export * from './mcp-oauth.module';
|
|
3
|
+
export * from './providers/oauth-provider.interface';
|
|
4
|
+
export * from './providers/google.provider';
|
|
5
|
+
export * from './providers/github.provider';
|
|
6
|
+
export * from './stores/oauth-store.interface';
|
|
7
|
+
export * from './stores/memory-store.service';
|
|
8
|
+
export * from './stores/typeorm/typeorm-store.service';
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Get,
|
|
6
|
+
Inject,
|
|
7
|
+
Next,
|
|
8
|
+
Post,
|
|
9
|
+
Query,
|
|
10
|
+
Req,
|
|
11
|
+
Res,
|
|
12
|
+
UseGuards,
|
|
13
|
+
} from '@nestjs/common';
|
|
14
|
+
import { createHash, randomBytes } from 'crypto';
|
|
15
|
+
import { Request as ExpressRequest, NextFunction, Response } from 'express';
|
|
16
|
+
import passport from 'passport';
|
|
17
|
+
import { AuthenticatedRequest, JwtAuthGuard } from './guards/jwt-auth.guard';
|
|
18
|
+
import {
|
|
19
|
+
OAuthModuleOptions,
|
|
20
|
+
OAuthSession,
|
|
21
|
+
OAuthUserProfile,
|
|
22
|
+
} from './providers/oauth-provider.interface';
|
|
23
|
+
import { ClientService } from './services/client.service';
|
|
24
|
+
import { JwtTokenService, TokenPair } from './services/jwt-token.service';
|
|
25
|
+
import { STRATEGY_NAME } from './services/oauth-strategy.service';
|
|
26
|
+
import {
|
|
27
|
+
ClientRegistrationDto,
|
|
28
|
+
IOAuthStore,
|
|
29
|
+
} from './stores/oauth-store.interface';
|
|
30
|
+
|
|
31
|
+
interface OAuthCallbackRequest extends ExpressRequest {
|
|
32
|
+
user?: {
|
|
33
|
+
profile: OAuthUserProfile;
|
|
34
|
+
accessToken: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Controller()
|
|
40
|
+
export class McpOAuthController {
|
|
41
|
+
private readonly serverUrl: string;
|
|
42
|
+
private readonly isProduction: boolean;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
@Inject('OAUTH_MODULE_OPTIONS') private options: OAuthModuleOptions,
|
|
46
|
+
@Inject('IOAuthStore') private readonly store: IOAuthStore,
|
|
47
|
+
private readonly jwtTokenService: JwtTokenService,
|
|
48
|
+
private readonly clientService: ClientService,
|
|
49
|
+
) {
|
|
50
|
+
this.serverUrl = this.options.serverUrl;
|
|
51
|
+
this.isProduction = this.options.cookieSecure;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// OAuth endpoints
|
|
55
|
+
@Get('/.well-known/oauth-authorization-server')
|
|
56
|
+
getAuthorizationServerMetadata() {
|
|
57
|
+
return {
|
|
58
|
+
issuer: this.serverUrl,
|
|
59
|
+
authorization_endpoint: `${this.serverUrl}/authorize`,
|
|
60
|
+
token_endpoint: `${this.serverUrl}/token`,
|
|
61
|
+
registration_endpoint: `${this.serverUrl}/register`,
|
|
62
|
+
response_types_supported: ['code'],
|
|
63
|
+
response_modes_supported: ['query'],
|
|
64
|
+
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
65
|
+
token_endpoint_auth_methods_supported: [
|
|
66
|
+
'client_secret_basic',
|
|
67
|
+
'client_secret_post',
|
|
68
|
+
'none',
|
|
69
|
+
],
|
|
70
|
+
revocation_endpoint: `${this.serverUrl}/revoke`,
|
|
71
|
+
code_challenge_methods_supported: ['plain', 'S256'],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Post('/register')
|
|
76
|
+
async registerClient(@Body() registrationDto: ClientRegistrationDto) {
|
|
77
|
+
return await this.clientService.registerClient(registrationDto);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Get('/authorize')
|
|
81
|
+
async authorize(@Query() query: any, @Res() res: Response) {
|
|
82
|
+
const {
|
|
83
|
+
response_type,
|
|
84
|
+
client_id,
|
|
85
|
+
redirect_uri,
|
|
86
|
+
code_challenge,
|
|
87
|
+
code_challenge_method,
|
|
88
|
+
state,
|
|
89
|
+
resource,
|
|
90
|
+
} = query;
|
|
91
|
+
|
|
92
|
+
// Validate parameters
|
|
93
|
+
if (response_type !== 'code') {
|
|
94
|
+
throw new BadRequestException('Only response_type=code is supported');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!client_id || !redirect_uri || !code_challenge) {
|
|
98
|
+
throw new BadRequestException('Missing required parameters');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Validate client and redirect URI
|
|
102
|
+
const client = await this.clientService.getClient(client_id);
|
|
103
|
+
if (!client) {
|
|
104
|
+
throw new BadRequestException('Invalid client_id');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const validRedirect = await this.clientService.validateRedirectUri(
|
|
108
|
+
client_id,
|
|
109
|
+
redirect_uri,
|
|
110
|
+
);
|
|
111
|
+
if (!validRedirect) {
|
|
112
|
+
throw new BadRequestException('Invalid redirect_uri');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create OAuth session
|
|
116
|
+
const sessionId = randomBytes(32).toString('base64url');
|
|
117
|
+
const sessionState = randomBytes(32).toString('base64url');
|
|
118
|
+
|
|
119
|
+
const oauthSession: OAuthSession = {
|
|
120
|
+
sessionId,
|
|
121
|
+
state: sessionState,
|
|
122
|
+
clientId: client_id,
|
|
123
|
+
redirectUri: redirect_uri,
|
|
124
|
+
codeChallenge: code_challenge,
|
|
125
|
+
codeChallengeMethod: code_challenge_method || 'plain',
|
|
126
|
+
oauthState: state,
|
|
127
|
+
resource,
|
|
128
|
+
expiresAt: Date.now() + this.options.oauthSessionExpiresIn,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.store.storeOAuthSession(sessionId, oauthSession);
|
|
132
|
+
|
|
133
|
+
// Set session cookie
|
|
134
|
+
res.cookie('oauth_session', sessionId, {
|
|
135
|
+
httpOnly: true,
|
|
136
|
+
secure: this.isProduction,
|
|
137
|
+
maxAge: this.options.oauthSessionExpiresIn,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Store state for passport
|
|
141
|
+
res.cookie('oauth_state', sessionState, {
|
|
142
|
+
httpOnly: true,
|
|
143
|
+
secure: this.isProduction,
|
|
144
|
+
maxAge: this.options.oauthSessionExpiresIn,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Redirect to the provider's auth endpoint
|
|
148
|
+
res.redirect(`/auth`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Get('/auth')
|
|
152
|
+
authenticate(
|
|
153
|
+
@Req() req: any,
|
|
154
|
+
@Res() res: Response,
|
|
155
|
+
@Next() next: NextFunction,
|
|
156
|
+
) {
|
|
157
|
+
passport.authenticate(STRATEGY_NAME, {
|
|
158
|
+
state: req.cookies?.oauth_state,
|
|
159
|
+
})(req, res, next);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Get('/auth/callback')
|
|
163
|
+
async handleProviderCallback(
|
|
164
|
+
@Req() req: OAuthCallbackRequest,
|
|
165
|
+
@Res() res: Response,
|
|
166
|
+
@Next() next: NextFunction,
|
|
167
|
+
) {
|
|
168
|
+
// Use a custom callback to handle the authentication result
|
|
169
|
+
passport.authenticate(
|
|
170
|
+
STRATEGY_NAME,
|
|
171
|
+
{ session: false },
|
|
172
|
+
async (err: any, user: any) => {
|
|
173
|
+
try {
|
|
174
|
+
if (err) {
|
|
175
|
+
console.error('OAuth callback error:', err);
|
|
176
|
+
throw new BadRequestException('Authentication failed');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!user) {
|
|
180
|
+
throw new BadRequestException('Authentication failed');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
req.user = user;
|
|
184
|
+
await this.processAuthenticationSuccess(req, res);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
next(error);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
)(req, res, next);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async processAuthenticationSuccess(
|
|
193
|
+
req: OAuthCallbackRequest,
|
|
194
|
+
res: Response,
|
|
195
|
+
) {
|
|
196
|
+
const user = req.user;
|
|
197
|
+
if (!user) {
|
|
198
|
+
throw new BadRequestException('Authentication failed');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const sessionId = req.cookies?.oauth_session;
|
|
202
|
+
if (!sessionId) {
|
|
203
|
+
throw new BadRequestException('Missing OAuth session');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const session = await this.store.getOAuthSession(sessionId);
|
|
207
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
208
|
+
throw new BadRequestException('Invalid or expired OAuth session');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Verify state
|
|
212
|
+
const stateFromCookie = req.cookies?.oauth_state;
|
|
213
|
+
if (session.state !== stateFromCookie) {
|
|
214
|
+
throw new BadRequestException('Invalid state parameter');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Generate JWT for UI access
|
|
218
|
+
const jwt = this.jwtTokenService.generateUserToken(
|
|
219
|
+
user.profile.username,
|
|
220
|
+
user.profile,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Set JWT token as cookie for UI endpoints
|
|
224
|
+
res.cookie('auth_token', jwt, {
|
|
225
|
+
httpOnly: true,
|
|
226
|
+
secure: this.isProduction,
|
|
227
|
+
maxAge: this.options.cookieMaxAge,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Clear temporary cookies
|
|
231
|
+
res.clearCookie('oauth_session');
|
|
232
|
+
res.clearCookie('oauth_state');
|
|
233
|
+
|
|
234
|
+
// Generate authorization code
|
|
235
|
+
const authCode = randomBytes(32).toString('base64url');
|
|
236
|
+
|
|
237
|
+
// Store the auth code
|
|
238
|
+
this.store.storeAuthCode({
|
|
239
|
+
code: authCode,
|
|
240
|
+
user_id: user.profile.username,
|
|
241
|
+
client_id: session.clientId!,
|
|
242
|
+
redirect_uri: session.redirectUri!,
|
|
243
|
+
code_challenge: session.codeChallenge!,
|
|
244
|
+
code_challenge_method: session.codeChallengeMethod!,
|
|
245
|
+
expires_at: Date.now() + this.options.authCodeExpiresIn,
|
|
246
|
+
github_access_token: '', // No longer provider-specific
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Build redirect URL with authorization code
|
|
250
|
+
const redirectUrl = new URL(session.redirectUri!);
|
|
251
|
+
redirectUrl.searchParams.set('code', authCode);
|
|
252
|
+
if (session.oauthState) {
|
|
253
|
+
redirectUrl.searchParams.set('state', session.oauthState);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Clean up session
|
|
257
|
+
this.store.removeOAuthSession(sessionId);
|
|
258
|
+
|
|
259
|
+
res.redirect(redirectUrl.toString());
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Token endpoints (remain the same)
|
|
263
|
+
@Post('/token')
|
|
264
|
+
async exchangeToken(@Body() body: any): Promise<TokenPair> {
|
|
265
|
+
const {
|
|
266
|
+
grant_type,
|
|
267
|
+
code,
|
|
268
|
+
code_verifier,
|
|
269
|
+
redirect_uri,
|
|
270
|
+
client_id,
|
|
271
|
+
refresh_token,
|
|
272
|
+
} = body;
|
|
273
|
+
|
|
274
|
+
if (grant_type === 'authorization_code') {
|
|
275
|
+
return this.handleAuthorizationCodeGrant(
|
|
276
|
+
code,
|
|
277
|
+
code_verifier,
|
|
278
|
+
redirect_uri,
|
|
279
|
+
client_id,
|
|
280
|
+
);
|
|
281
|
+
} else if (grant_type === 'refresh_token') {
|
|
282
|
+
return this.handleRefreshTokenGrant(refresh_token);
|
|
283
|
+
} else {
|
|
284
|
+
throw new BadRequestException('Unsupported grant_type');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async handleAuthorizationCodeGrant(
|
|
289
|
+
code: string,
|
|
290
|
+
code_verifier: string,
|
|
291
|
+
redirect_uri: string,
|
|
292
|
+
client_id: string,
|
|
293
|
+
): Promise<TokenPair> {
|
|
294
|
+
// Validate the authorization code
|
|
295
|
+
const authCode = await this.store.getAuthCode(code);
|
|
296
|
+
if (!authCode) {
|
|
297
|
+
throw new BadRequestException('Invalid authorization code');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if code has expired
|
|
301
|
+
if (authCode.expires_at < Date.now()) {
|
|
302
|
+
await this.store.removeAuthCode(code);
|
|
303
|
+
throw new BadRequestException('Authorization code has expired');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Validate client_id matches
|
|
307
|
+
if (authCode.client_id !== client_id) {
|
|
308
|
+
throw new BadRequestException('Client ID mismatch');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Validate PKCE if required
|
|
312
|
+
if (authCode.code_challenge) {
|
|
313
|
+
const isValid = this.validatePKCE(
|
|
314
|
+
code_verifier,
|
|
315
|
+
authCode.code_challenge,
|
|
316
|
+
authCode.code_challenge_method,
|
|
317
|
+
);
|
|
318
|
+
if (!isValid) {
|
|
319
|
+
throw new BadRequestException('Invalid PKCE verification');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Generate tokens
|
|
324
|
+
const tokens = this.jwtTokenService.generateTokenPair(
|
|
325
|
+
authCode.user_id,
|
|
326
|
+
client_id,
|
|
327
|
+
'mcp:access',
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Remove the used authorization code
|
|
331
|
+
this.store.removeAuthCode(code);
|
|
332
|
+
|
|
333
|
+
return tokens;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private async handleRefreshTokenGrant(
|
|
337
|
+
refresh_token: string,
|
|
338
|
+
): Promise<TokenPair> {
|
|
339
|
+
const newTokens = this.jwtTokenService.refreshAccessToken(refresh_token);
|
|
340
|
+
if (!newTokens) {
|
|
341
|
+
throw new BadRequestException('Failed to refresh token');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return newTokens;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@Get('/validate')
|
|
348
|
+
@UseGuards(JwtAuthGuard)
|
|
349
|
+
validateToken(@Req() req: AuthenticatedRequest) {
|
|
350
|
+
return {
|
|
351
|
+
valid: true,
|
|
352
|
+
user_id: req.user.sub,
|
|
353
|
+
client_id: req.user.client_id,
|
|
354
|
+
scope: req.user.scope,
|
|
355
|
+
expires_at: req.user.exp! * 1000,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private parseExpiresInToMs(expiresIn: string): number {
|
|
360
|
+
// Handle formats like "60s", "30d", "24h", "1440m"
|
|
361
|
+
const match = expiresIn.match(/^(\d+)([smhd]?)$/);
|
|
362
|
+
if (!match) {
|
|
363
|
+
throw new Error(`Invalid expiresIn format: ${expiresIn}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const value = parseInt(match[1], 10);
|
|
367
|
+
const unit = match[2] || 's'; // default to seconds
|
|
368
|
+
|
|
369
|
+
const multipliers = {
|
|
370
|
+
s: 1000,
|
|
371
|
+
m: 60 * 1000,
|
|
372
|
+
h: 60 * 60 * 1000,
|
|
373
|
+
d: 24 * 60 * 60 * 1000,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
return value * multipliers[unit as keyof typeof multipliers];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private validatePKCE(
|
|
380
|
+
code_verifier: string,
|
|
381
|
+
code_challenge: string,
|
|
382
|
+
method: string,
|
|
383
|
+
): boolean {
|
|
384
|
+
if (method === 'plain') {
|
|
385
|
+
return code_verifier === code_challenge;
|
|
386
|
+
} else if (method === 'S256') {
|
|
387
|
+
const hash = createHash('sha256')
|
|
388
|
+
.update(code_verifier)
|
|
389
|
+
.digest('base64url');
|
|
390
|
+
return hash === code_challenge;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|