@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,279 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { OAuthSession } from '../../providers/oauth-provider.interface';
|
|
5
|
+
import {
|
|
6
|
+
AuthorizationCode,
|
|
7
|
+
IOAuthStore,
|
|
8
|
+
OAuthClient,
|
|
9
|
+
} from '../oauth-store.interface';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class SQLiteStore implements IOAuthStore {
|
|
13
|
+
private sqlite: any;
|
|
14
|
+
|
|
15
|
+
constructor(databasePath: string = './oauth-sqlite.db') {
|
|
16
|
+
this.sqlite = new Database(databasePath);
|
|
17
|
+
this.initializeTables();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private initializeTables() {
|
|
21
|
+
// Enable foreign keys
|
|
22
|
+
this.sqlite.pragma('foreign_keys = ON');
|
|
23
|
+
|
|
24
|
+
// Create tables if they don't exist
|
|
25
|
+
this.sqlite.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
27
|
+
client_id TEXT PRIMARY KEY,
|
|
28
|
+
client_name TEXT NOT NULL,
|
|
29
|
+
client_description TEXT,
|
|
30
|
+
logo_uri TEXT,
|
|
31
|
+
client_uri TEXT,
|
|
32
|
+
developer_name TEXT,
|
|
33
|
+
developer_email TEXT,
|
|
34
|
+
redirect_uris TEXT NOT NULL,
|
|
35
|
+
grant_types TEXT NOT NULL,
|
|
36
|
+
response_types TEXT NOT NULL,
|
|
37
|
+
token_endpoint_auth_method TEXT NOT NULL,
|
|
38
|
+
created_at INTEGER NOT NULL,
|
|
39
|
+
updated_at INTEGER NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
this.sqlite.exec(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS authorization_codes (
|
|
45
|
+
code TEXT PRIMARY KEY,
|
|
46
|
+
user_id TEXT NOT NULL,
|
|
47
|
+
client_id TEXT NOT NULL,
|
|
48
|
+
redirect_uri TEXT NOT NULL,
|
|
49
|
+
code_challenge TEXT NOT NULL,
|
|
50
|
+
code_challenge_method TEXT NOT NULL,
|
|
51
|
+
expires_at INTEGER NOT NULL,
|
|
52
|
+
used_at INTEGER,
|
|
53
|
+
github_access_token TEXT NOT NULL
|
|
54
|
+
)
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
this.sqlite.exec(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS oauth_sessions (
|
|
59
|
+
session_id TEXT PRIMARY KEY,
|
|
60
|
+
client_id TEXT NOT NULL,
|
|
61
|
+
redirect_uri TEXT NOT NULL,
|
|
62
|
+
code_challenge TEXT NOT NULL,
|
|
63
|
+
code_challenge_method TEXT NOT NULL,
|
|
64
|
+
state TEXT,
|
|
65
|
+
resource TEXT,
|
|
66
|
+
expires_at INTEGER NOT NULL
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Client management
|
|
72
|
+
async storeClient(client: OAuthClient): Promise<OAuthClient> {
|
|
73
|
+
const stmt = this.sqlite.prepare(`
|
|
74
|
+
INSERT INTO oauth_clients (
|
|
75
|
+
client_id, client_name, client_description, logo_uri, client_uri,
|
|
76
|
+
developer_name, developer_email, redirect_uris, grant_types,
|
|
77
|
+
response_types, token_endpoint_auth_method, created_at, updated_at
|
|
78
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
stmt.run(
|
|
82
|
+
client.client_id,
|
|
83
|
+
client.client_name,
|
|
84
|
+
client.client_description || null,
|
|
85
|
+
client.logo_uri || null,
|
|
86
|
+
client.client_uri || null,
|
|
87
|
+
client.developer_name || null,
|
|
88
|
+
client.developer_email || null,
|
|
89
|
+
JSON.stringify(client.redirect_uris),
|
|
90
|
+
JSON.stringify(client.grant_types),
|
|
91
|
+
JSON.stringify(client.response_types),
|
|
92
|
+
client.token_endpoint_auth_method,
|
|
93
|
+
client.created_at.getTime(),
|
|
94
|
+
client.updated_at.getTime(),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return client;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getClient(client_id: string): Promise<OAuthClient | undefined> {
|
|
101
|
+
const stmt = this.sqlite.prepare(
|
|
102
|
+
'SELECT * FROM oauth_clients WHERE client_id = ?',
|
|
103
|
+
);
|
|
104
|
+
const result = stmt.get(client_id) as any;
|
|
105
|
+
|
|
106
|
+
if (!result) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
client_id: result.client_id,
|
|
112
|
+
client_name: result.client_name,
|
|
113
|
+
client_description: result.client_description || undefined,
|
|
114
|
+
logo_uri: result.logo_uri || undefined,
|
|
115
|
+
client_uri: result.client_uri || undefined,
|
|
116
|
+
developer_name: result.developer_name || undefined,
|
|
117
|
+
developer_email: result.developer_email || undefined,
|
|
118
|
+
redirect_uris: JSON.parse(result.redirect_uris),
|
|
119
|
+
grant_types: JSON.parse(result.grant_types),
|
|
120
|
+
response_types: JSON.parse(result.response_types),
|
|
121
|
+
token_endpoint_auth_method: result.token_endpoint_auth_method,
|
|
122
|
+
created_at: new Date(result.created_at),
|
|
123
|
+
updated_at: new Date(result.updated_at),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async findClient(client_name: string): Promise<OAuthClient | undefined> {
|
|
128
|
+
const stmt = this.sqlite.prepare(
|
|
129
|
+
'SELECT * FROM oauth_clients WHERE client_name = ?',
|
|
130
|
+
);
|
|
131
|
+
const result = stmt.get(client_name) as any;
|
|
132
|
+
|
|
133
|
+
if (!result) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
client_id: result.client_id,
|
|
139
|
+
client_name: result.client_name,
|
|
140
|
+
client_description: result.client_description || undefined,
|
|
141
|
+
logo_uri: result.logo_uri || undefined,
|
|
142
|
+
client_uri: result.client_uri || undefined,
|
|
143
|
+
developer_name: result.developer_name || undefined,
|
|
144
|
+
developer_email: result.developer_email || undefined,
|
|
145
|
+
redirect_uris: JSON.parse(result.redirect_uris),
|
|
146
|
+
grant_types: JSON.parse(result.grant_types),
|
|
147
|
+
response_types: JSON.parse(result.response_types),
|
|
148
|
+
token_endpoint_auth_method: result.token_endpoint_auth_method,
|
|
149
|
+
created_at: new Date(result.created_at),
|
|
150
|
+
updated_at: new Date(result.updated_at),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Authorization code management
|
|
155
|
+
async storeAuthCode(code: AuthorizationCode): Promise<void> {
|
|
156
|
+
const stmt = this.sqlite.prepare(`
|
|
157
|
+
INSERT INTO authorization_codes (
|
|
158
|
+
code, user_id, client_id, redirect_uri, code_challenge,
|
|
159
|
+
code_challenge_method, expires_at, used_at, github_access_token
|
|
160
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
161
|
+
`);
|
|
162
|
+
|
|
163
|
+
stmt.run(
|
|
164
|
+
code.code,
|
|
165
|
+
code.user_id,
|
|
166
|
+
code.client_id,
|
|
167
|
+
code.redirect_uri,
|
|
168
|
+
code.code_challenge,
|
|
169
|
+
code.code_challenge_method,
|
|
170
|
+
code.expires_at,
|
|
171
|
+
code.used_at ? code.used_at.getTime() : null,
|
|
172
|
+
code.github_access_token,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async getAuthCode(code: string): Promise<AuthorizationCode | undefined> {
|
|
177
|
+
const stmt = this.sqlite.prepare(
|
|
178
|
+
'SELECT * FROM authorization_codes WHERE code = ?',
|
|
179
|
+
);
|
|
180
|
+
const result = stmt.get(code) as any;
|
|
181
|
+
|
|
182
|
+
if (!result) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check if expired
|
|
187
|
+
if (result.expires_at < Date.now()) {
|
|
188
|
+
await this.removeAuthCode(code);
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
code: result.code,
|
|
194
|
+
user_id: result.user_id,
|
|
195
|
+
client_id: result.client_id,
|
|
196
|
+
redirect_uri: result.redirect_uri,
|
|
197
|
+
code_challenge: result.code_challenge,
|
|
198
|
+
code_challenge_method: result.code_challenge_method,
|
|
199
|
+
expires_at: result.expires_at,
|
|
200
|
+
used_at: result.used_at ? new Date(result.used_at) : undefined,
|
|
201
|
+
github_access_token: result.github_access_token,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async removeAuthCode(code: string): Promise<void> {
|
|
206
|
+
const stmt = this.sqlite.prepare(
|
|
207
|
+
'DELETE FROM authorization_codes WHERE code = ?',
|
|
208
|
+
);
|
|
209
|
+
stmt.run(code);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// OAuth session management
|
|
213
|
+
async storeOAuthSession(
|
|
214
|
+
sessionId: string,
|
|
215
|
+
session: OAuthSession,
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const stmt = this.sqlite.prepare(`
|
|
218
|
+
INSERT INTO oauth_sessions (
|
|
219
|
+
session_id, client_id, redirect_uri, code_challenge,
|
|
220
|
+
code_challenge_method, state, resource, expires_at
|
|
221
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
222
|
+
`);
|
|
223
|
+
|
|
224
|
+
stmt.run(
|
|
225
|
+
sessionId,
|
|
226
|
+
session.clientId,
|
|
227
|
+
session.redirectUri,
|
|
228
|
+
session.codeChallenge,
|
|
229
|
+
session.codeChallengeMethod,
|
|
230
|
+
session.state || null,
|
|
231
|
+
session.resource || null,
|
|
232
|
+
session.expiresAt,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async getOAuthSession(sessionId: string): Promise<OAuthSession | undefined> {
|
|
237
|
+
const stmt = this.sqlite.prepare(
|
|
238
|
+
'SELECT * FROM oauth_sessions WHERE session_id = ?',
|
|
239
|
+
);
|
|
240
|
+
const result = stmt.get(sessionId) as any;
|
|
241
|
+
|
|
242
|
+
if (!result) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check if expired
|
|
247
|
+
if (result.expires_at < Date.now()) {
|
|
248
|
+
await this.removeOAuthSession(sessionId);
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
sessionId: result.session_id,
|
|
254
|
+
clientId: result.client_id,
|
|
255
|
+
redirectUri: result.redirect_uri,
|
|
256
|
+
codeChallenge: result.code_challenge,
|
|
257
|
+
codeChallengeMethod: result.code_challenge_method,
|
|
258
|
+
state: result.state || undefined,
|
|
259
|
+
resource: result.resource || undefined,
|
|
260
|
+
expiresAt: result.expires_at,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async removeOAuthSession(sessionId: string): Promise<void> {
|
|
265
|
+
const stmt = this.sqlite.prepare(
|
|
266
|
+
'DELETE FROM oauth_sessions WHERE session_id = ?',
|
|
267
|
+
);
|
|
268
|
+
stmt.run(sessionId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
generateClientId(client: OAuthClient): string {
|
|
272
|
+
// Create deterministic client ID based on name + random salt
|
|
273
|
+
const normalizedName = client.client_name
|
|
274
|
+
.toLowerCase()
|
|
275
|
+
.replace(/[^a-z0-9]/g, '');
|
|
276
|
+
const salt = randomBytes(4).toString('hex');
|
|
277
|
+
return `${normalizedName}_${salt}`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# TypeORM OAuth Store Implementation
|
|
2
|
+
|
|
3
|
+
This directory contains a TypeORM implementation of the `IOAuthStore` interface for persistent OAuth data storage.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Persistent Storage**: Unlike the in-memory store, data survives server restarts
|
|
8
|
+
- **Database Support**: Works with any database supported by TypeORM (PostgreSQL, MySQL, SQLite, etc.)
|
|
9
|
+
- **Automatic Schema Management**: Entities automatically create the required database tables
|
|
10
|
+
- **Expiration Handling**: Automatic cleanup of expired sessions and authorization codes
|
|
11
|
+
|
|
12
|
+
## Entities
|
|
13
|
+
|
|
14
|
+
### OAuthClientEntity
|
|
15
|
+
Stores OAuth client information including:
|
|
16
|
+
- Client credentials and metadata
|
|
17
|
+
- Redirect URIs and grant types
|
|
18
|
+
- Creation and update timestamps
|
|
19
|
+
|
|
20
|
+
### AuthorizationCodeEntity
|
|
21
|
+
Stores authorization codes with:
|
|
22
|
+
- Code challenge and method for PKCE
|
|
23
|
+
- Expiration and usage tracking
|
|
24
|
+
- Associated user and client information
|
|
25
|
+
|
|
26
|
+
### OAuthSessionEntity
|
|
27
|
+
Stores OAuth session data including:
|
|
28
|
+
- Session state and parameters
|
|
29
|
+
- Expiration handling
|
|
30
|
+
- Client association
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Basic SQLite Configuration
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
McpOAuthModule.forRoot({
|
|
38
|
+
provider: GoogleOAuthProvider,
|
|
39
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
40
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
41
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
42
|
+
typeormOptions: {
|
|
43
|
+
type: 'sqlite',
|
|
44
|
+
database: 'oauth.db',
|
|
45
|
+
synchronize: true,
|
|
46
|
+
logging: false,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### PostgreSQL Configuration
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
McpOAuthModule.forRoot({
|
|
55
|
+
provider: GoogleOAuthProvider,
|
|
56
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
57
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
58
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
59
|
+
typeormOptions: {
|
|
60
|
+
type: 'postgres',
|
|
61
|
+
host: process.env.DB_HOST || 'localhost',
|
|
62
|
+
port: parseInt(process.env.DB_PORT) || 5432,
|
|
63
|
+
username: process.env.DB_USERNAME || 'postgres',
|
|
64
|
+
password: process.env.DB_PASSWORD || 'password',
|
|
65
|
+
database: process.env.DB_NAME || 'oauth_db',
|
|
66
|
+
synchronize: process.env.NODE_ENV !== 'production',
|
|
67
|
+
logging: process.env.NODE_ENV === 'development',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### MySQL Configuration
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
McpOAuthModule.forRoot({
|
|
76
|
+
provider: GoogleOAuthProvider,
|
|
77
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
78
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
79
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
80
|
+
typeormOptions: {
|
|
81
|
+
type: 'mysql',
|
|
82
|
+
host: process.env.DB_HOST || 'localhost',
|
|
83
|
+
port: parseInt(process.env.DB_PORT) || 3306,
|
|
84
|
+
username: process.env.DB_USERNAME || 'root',
|
|
85
|
+
password: process.env.DB_PASSWORD || 'password',
|
|
86
|
+
database: process.env.DB_NAME || 'oauth_db',
|
|
87
|
+
synchronize: process.env.NODE_ENV !== 'production',
|
|
88
|
+
logging: process.env.NODE_ENV === 'development',
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Configuration Options
|
|
94
|
+
|
|
95
|
+
All standard TypeORM options are supported:
|
|
96
|
+
|
|
97
|
+
- `type`: Database type (sqlite, postgres, mysql, etc.)
|
|
98
|
+
- `host`: Database host
|
|
99
|
+
- `port`: Database port
|
|
100
|
+
- `username`: Database username
|
|
101
|
+
- `password`: Database password
|
|
102
|
+
- `database`: Database name or file path (for SQLite)
|
|
103
|
+
- `synchronize`: Auto-create tables (disable in production)
|
|
104
|
+
- `logging`: Enable query logging
|
|
105
|
+
- `entities`: Additional entities (OAuth entities are automatically included)
|
|
106
|
+
- `migrations`: Database migrations
|
|
107
|
+
- `ssl`: SSL configuration for production databases
|
|
108
|
+
|
|
109
|
+
## Production Considerations
|
|
110
|
+
|
|
111
|
+
1. **Disable synchronize**: Use migrations instead of auto-sync in production
|
|
112
|
+
2. **Enable SSL**: Configure SSL for production database connections
|
|
113
|
+
3. **Connection Pooling**: Configure appropriate connection pool settings
|
|
114
|
+
4. **Logging**: Disable query logging in production for performance
|
|
115
|
+
5. **Backup Strategy**: Implement regular database backups
|
|
116
|
+
|
|
117
|
+
## Migrations
|
|
118
|
+
|
|
119
|
+
For production use, create migrations instead of using `synchronize: true`:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx typeorm migration:generate -n CreateOAuthTables
|
|
123
|
+
npx typeorm migration:run
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Interface Compatibility
|
|
127
|
+
|
|
128
|
+
The TypeORM implementation provides both synchronous and asynchronous methods:
|
|
129
|
+
|
|
130
|
+
- Synchronous methods (for IOAuthStore compatibility): Log warnings and use fire-and-forget async operations
|
|
131
|
+
- Asynchronous methods (recommended): `*Async` versions that return promises
|
|
132
|
+
|
|
133
|
+
Consider updating your application to use the async methods for better error handling and performance.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Entity, Column, PrimaryColumn, CreateDateColumn } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
@Entity('authorization_codes')
|
|
4
|
+
export class AuthorizationCodeEntity {
|
|
5
|
+
@PrimaryColumn()
|
|
6
|
+
code: string;
|
|
7
|
+
|
|
8
|
+
@Column()
|
|
9
|
+
user_id: string;
|
|
10
|
+
|
|
11
|
+
@Column()
|
|
12
|
+
client_id: string;
|
|
13
|
+
|
|
14
|
+
@Column()
|
|
15
|
+
redirect_uri: string;
|
|
16
|
+
|
|
17
|
+
@Column()
|
|
18
|
+
code_challenge: string;
|
|
19
|
+
|
|
20
|
+
@Column()
|
|
21
|
+
code_challenge_method: string;
|
|
22
|
+
|
|
23
|
+
@Column('bigint')
|
|
24
|
+
expires_at: number;
|
|
25
|
+
|
|
26
|
+
@Column({ nullable: true })
|
|
27
|
+
used_at?: Date;
|
|
28
|
+
|
|
29
|
+
@Column()
|
|
30
|
+
github_access_token: string;
|
|
31
|
+
|
|
32
|
+
@CreateDateColumn()
|
|
33
|
+
created_at: Date;
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Entity,
|
|
3
|
+
Column,
|
|
4
|
+
PrimaryColumn,
|
|
5
|
+
CreateDateColumn,
|
|
6
|
+
UpdateDateColumn,
|
|
7
|
+
} from 'typeorm';
|
|
8
|
+
|
|
9
|
+
@Entity('oauth_clients')
|
|
10
|
+
export class OAuthClientEntity {
|
|
11
|
+
@PrimaryColumn()
|
|
12
|
+
client_id: string;
|
|
13
|
+
|
|
14
|
+
@Column()
|
|
15
|
+
client_name: string;
|
|
16
|
+
|
|
17
|
+
@Column({ nullable: true })
|
|
18
|
+
client_description?: string;
|
|
19
|
+
|
|
20
|
+
@Column({ nullable: true })
|
|
21
|
+
logo_uri?: string;
|
|
22
|
+
|
|
23
|
+
@Column({ nullable: true })
|
|
24
|
+
client_uri?: string;
|
|
25
|
+
|
|
26
|
+
@Column({ nullable: true })
|
|
27
|
+
developer_name?: string;
|
|
28
|
+
|
|
29
|
+
@Column({ nullable: true })
|
|
30
|
+
developer_email?: string;
|
|
31
|
+
|
|
32
|
+
@Column('simple-array')
|
|
33
|
+
redirect_uris: string[];
|
|
34
|
+
|
|
35
|
+
@Column('simple-array')
|
|
36
|
+
grant_types: string[];
|
|
37
|
+
|
|
38
|
+
@Column('simple-array')
|
|
39
|
+
response_types: string[];
|
|
40
|
+
|
|
41
|
+
@Column()
|
|
42
|
+
token_endpoint_auth_method: string;
|
|
43
|
+
|
|
44
|
+
@CreateDateColumn()
|
|
45
|
+
created_at: Date;
|
|
46
|
+
|
|
47
|
+
@UpdateDateColumn()
|
|
48
|
+
updated_at: Date;
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Entity, Column, PrimaryColumn, CreateDateColumn } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
@Entity('oauth_sessions')
|
|
4
|
+
export class OAuthSessionEntity {
|
|
5
|
+
@PrimaryColumn()
|
|
6
|
+
sessionId: string;
|
|
7
|
+
|
|
8
|
+
@Column()
|
|
9
|
+
state: string;
|
|
10
|
+
|
|
11
|
+
@Column({ nullable: true })
|
|
12
|
+
clientId?: string;
|
|
13
|
+
|
|
14
|
+
@Column({ nullable: true })
|
|
15
|
+
redirectUri?: string;
|
|
16
|
+
|
|
17
|
+
@Column({ nullable: true })
|
|
18
|
+
codeChallenge?: string;
|
|
19
|
+
|
|
20
|
+
@Column({ nullable: true })
|
|
21
|
+
codeChallengeMethod?: string;
|
|
22
|
+
|
|
23
|
+
@Column({ nullable: true })
|
|
24
|
+
oauthState?: string;
|
|
25
|
+
|
|
26
|
+
@Column({ nullable: true })
|
|
27
|
+
resource?: string;
|
|
28
|
+
|
|
29
|
+
@Column('bigint')
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
|
|
32
|
+
@CreateDateColumn()
|
|
33
|
+
created_at: Date;
|
|
34
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { Repository } from 'typeorm';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
import {
|
|
6
|
+
OAuthClientEntity,
|
|
7
|
+
AuthorizationCodeEntity,
|
|
8
|
+
OAuthSessionEntity,
|
|
9
|
+
} from './entities';
|
|
10
|
+
import { OAuthSession } from '../../providers/oauth-provider.interface';
|
|
11
|
+
import {
|
|
12
|
+
AuthorizationCode,
|
|
13
|
+
IOAuthStore,
|
|
14
|
+
OAuthClient,
|
|
15
|
+
} from '../oauth-store.interface';
|
|
16
|
+
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class TypeOrmStore implements IOAuthStore {
|
|
19
|
+
constructor(
|
|
20
|
+
@InjectRepository(OAuthClientEntity)
|
|
21
|
+
private readonly clientRepository: Repository<OAuthClientEntity>,
|
|
22
|
+
@InjectRepository(AuthorizationCodeEntity)
|
|
23
|
+
private readonly authCodeRepository: Repository<AuthorizationCodeEntity>,
|
|
24
|
+
@InjectRepository(OAuthSessionEntity)
|
|
25
|
+
private readonly sessionRepository: Repository<OAuthSessionEntity>,
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
// Client management
|
|
29
|
+
async storeClient(client: OAuthClient): Promise<OAuthClient> {
|
|
30
|
+
const savedClient = await this.clientRepository.save(client);
|
|
31
|
+
return savedClient;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getClient(client_id: string): Promise<OAuthClient | undefined> {
|
|
35
|
+
return (
|
|
36
|
+
(await this.clientRepository.findOne({ where: { client_id } })) ??
|
|
37
|
+
undefined
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async findClient(client_name: string): Promise<OAuthClient | undefined> {
|
|
42
|
+
return (
|
|
43
|
+
(await this.clientRepository.findOne({ where: { client_name } })) ??
|
|
44
|
+
undefined
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Authorization code management
|
|
49
|
+
async storeAuthCode(code: AuthorizationCode): Promise<void> {
|
|
50
|
+
await this.authCodeRepository.save(code);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getAuthCode(code: string): Promise<AuthorizationCode | undefined> {
|
|
54
|
+
const authCode = await this.authCodeRepository.findOne({ where: { code } });
|
|
55
|
+
// Check if expired
|
|
56
|
+
if (authCode && authCode.expires_at < Date.now()) {
|
|
57
|
+
await this.authCodeRepository.delete({ code });
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return authCode ?? undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async removeAuthCode(code: string): Promise<void> {
|
|
64
|
+
await this.authCodeRepository.delete({ code });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// OAuth session management
|
|
68
|
+
async storeOAuthSession(
|
|
69
|
+
sessionId: string,
|
|
70
|
+
session: OAuthSession,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
await this.sessionRepository.save({ ...session, sessionId });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getOAuthSession(sessionId: string): Promise<OAuthSession | undefined> {
|
|
76
|
+
const session = await this.sessionRepository.findOne({
|
|
77
|
+
where: { sessionId },
|
|
78
|
+
});
|
|
79
|
+
if (session && session.expiresAt < Date.now()) {
|
|
80
|
+
await this.sessionRepository.delete({ sessionId });
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return session ?? undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async removeOAuthSession(sessionId: string): Promise<void> {
|
|
87
|
+
await this.sessionRepository.delete({ sessionId });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
generateClientId(client: OAuthClient): string {
|
|
91
|
+
// Create deterministic client ID based on name + random salt
|
|
92
|
+
const normalizedName = client.client_name
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/[^a-z0-9]/g, '');
|
|
95
|
+
const salt = randomBytes(4).toString('hex');
|
|
96
|
+
return `${normalizedName}_${salt}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -42,7 +42,7 @@ export class McpToolsHandler extends McpHandlerBase {
|
|
|
42
42
|
if (!validation.success) {
|
|
43
43
|
throw new McpError(
|
|
44
44
|
ErrorCode.InternalError,
|
|
45
|
-
`Tool result does not match outputSchema: ${validation.error.message}
|
|
45
|
+
`Tool result does not match outputSchema: ${validation.error.message}`,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
return {
|
|
@@ -140,7 +140,10 @@ export class McpToolsHandler extends McpHandlerBase {
|
|
|
140
140
|
httpRequest,
|
|
141
141
|
);
|
|
142
142
|
|
|
143
|
-
const transformedResult = this.formatToolResult(
|
|
143
|
+
const transformedResult = this.formatToolResult(
|
|
144
|
+
result,
|
|
145
|
+
toolInfo.metadata.outputSchema,
|
|
146
|
+
);
|
|
144
147
|
|
|
145
148
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
146
149
|
this.logger.debug(transformedResult, 'CallToolRequestSchema result');
|