@elliotding/ai-agent-mcp 0.1.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.
- package/dist/api/cached-client.d.ts +48 -0
- package/dist/api/cached-client.d.ts.map +1 -0
- package/dist/api/cached-client.js +126 -0
- package/dist/api/cached-client.js.map +1 -0
- package/dist/api/client.d.ts +213 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +326 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +26 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +36 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +194 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/permissions.d.ts +60 -0
- package/dist/auth/permissions.d.ts.map +1 -0
- package/dist/auth/permissions.js +256 -0
- package/dist/auth/permissions.js.map +1 -0
- package/dist/auth/token-validator.d.ts +52 -0
- package/dist/auth/token-validator.d.ts.map +1 -0
- package/dist/auth/token-validator.js +217 -0
- package/dist/auth/token-validator.js.map +1 -0
- package/dist/cache/cache-manager.d.ts +49 -0
- package/dist/cache/cache-manager.d.ts.map +1 -0
- package/dist/cache/cache-manager.js +191 -0
- package/dist/cache/cache-manager.js.map +1 -0
- package/dist/cache/index.d.ts +6 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +12 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/redis-client.d.ts +45 -0
- package/dist/cache/redis-client.d.ts.map +1 -0
- package/dist/cache/redis-client.js +210 -0
- package/dist/cache/redis-client.js.map +1 -0
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +31 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.d.ts +54 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +168 -0
- package/dist/config/index.js.map +1 -0
- package/dist/filesystem/manager.d.ts +45 -0
- package/dist/filesystem/manager.d.ts.map +1 -0
- package/dist/filesystem/manager.js +246 -0
- package/dist/filesystem/manager.js.map +1 -0
- package/dist/git/multi-source-manager.d.ts +62 -0
- package/dist/git/multi-source-manager.d.ts.map +1 -0
- package/dist/git/multi-source-manager.js +293 -0
- package/dist/git/multi-source-manager.js.map +1 -0
- package/dist/git/operations.d.ts +27 -0
- package/dist/git/operations.d.ts.map +1 -0
- package/dist/git/operations.js +83 -0
- package/dist/git/operations.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/monitoring/health.d.ts +35 -0
- package/dist/monitoring/health.d.ts.map +1 -0
- package/dist/monitoring/health.js +105 -0
- package/dist/monitoring/health.js.map +1 -0
- package/dist/resources/index.d.ts +6 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +10 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/loader.d.ts +87 -0
- package/dist/resources/loader.d.ts.map +1 -0
- package/dist/resources/loader.js +452 -0
- package/dist/resources/loader.js.map +1 -0
- package/dist/server/http.d.ts +57 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +336 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +157 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +91 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +251 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +27 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/manage-subscription.d.ts +43 -0
- package/dist/tools/manage-subscription.d.ts.map +1 -0
- package/dist/tools/manage-subscription.js +268 -0
- package/dist/tools/manage-subscription.js.map +1 -0
- package/dist/tools/registry.d.ts +40 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +85 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/search-resources.d.ts +31 -0
- package/dist/tools/search-resources.d.ts.map +1 -0
- package/dist/tools/search-resources.js +154 -0
- package/dist/tools/search-resources.js.map +1 -0
- package/dist/tools/sync-resources.d.ts +41 -0
- package/dist/tools/sync-resources.d.ts.map +1 -0
- package/dist/tools/sync-resources.js +606 -0
- package/dist/tools/sync-resources.js.map +1 -0
- package/dist/tools/uninstall-resource.d.ts +30 -0
- package/dist/tools/uninstall-resource.d.ts.map +1 -0
- package/dist/tools/uninstall-resource.js +259 -0
- package/dist/tools/uninstall-resource.js.map +1 -0
- package/dist/tools/upload-resource.d.ts +77 -0
- package/dist/tools/upload-resource.d.ts.map +1 -0
- package/dist/tools/upload-resource.js +252 -0
- package/dist/tools/upload-resource.js.map +1 -0
- package/dist/transport/sse.d.ts +29 -0
- package/dist/transport/sse.d.ts.map +1 -0
- package/dist/transport/sse.js +271 -0
- package/dist/transport/sse.js.map +1 -0
- package/dist/types/errors.d.ts +60 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +112 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mcp.d.ts +50 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +6 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/resources.d.ts +109 -0
- package/dist/types/resources.d.ts.map +1 -0
- package/dist/types/resources.js +7 -0
- package/dist/types/resources.js.map +1 -0
- package/dist/types/tools.d.ts +147 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +6 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/cursor-paths.d.ts +49 -0
- package/dist/utils/cursor-paths.d.ts.map +1 -0
- package/dist/utils/cursor-paths.js +116 -0
- package/dist/utils/cursor-paths.js.map +1 -0
- package/dist/utils/log-cleaner.d.ts +18 -0
- package/dist/utils/log-cleaner.d.ts.map +1 -0
- package/dist/utils/log-cleaner.js +112 -0
- package/dist/utils/log-cleaner.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +292 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validation.d.ts +58 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +214 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +58 -0
- package/src/api/cached-client.ts +144 -0
- package/src/api/client.ts +578 -0
- package/src/auth/index.ts +11 -0
- package/src/auth/middleware.ts +244 -0
- package/src/auth/permissions.ts +317 -0
- package/src/auth/token-validator.ts +294 -0
- package/src/cache/cache-manager.ts +243 -0
- package/src/cache/index.ts +6 -0
- package/src/cache/redis-client.ts +249 -0
- package/src/config/constants.ts +33 -0
- package/src/config/index.ts +228 -0
- package/src/filesystem/manager.ts +235 -0
- package/src/git/multi-source-manager.ts +333 -0
- package/src/git/operations.ts +93 -0
- package/src/index.ts +139 -0
- package/src/monitoring/health.ts +132 -0
- package/src/resources/index.ts +13 -0
- package/src/resources/loader.ts +530 -0
- package/src/server/http.ts +427 -0
- package/src/server.ts +191 -0
- package/src/session/manager.ts +296 -0
- package/src/tools/index.ts +11 -0
- package/src/tools/manage-subscription.ts +332 -0
- package/src/tools/registry.ts +97 -0
- package/src/tools/search-resources.ts +177 -0
- package/src/tools/sync-resources.ts +662 -0
- package/src/tools/uninstall-resource.ts +248 -0
- package/src/tools/upload-resource.ts +258 -0
- package/src/transport/sse.ts +308 -0
- package/src/types/errors.ts +146 -0
- package/src/types/index.ts +7 -0
- package/src/types/mcp.ts +61 -0
- package/src/types/resources.ts +141 -0
- package/src/types/tools.ts +175 -0
- package/src/utils/cursor-paths.ts +83 -0
- package/src/utils/log-cleaner.ts +92 -0
- package/src/utils/logger.ts +333 -0
- package/src/utils/validation.ts +262 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and Permission Middlewares
|
|
3
|
+
* Token authentication and permission checking for HTTP endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
7
|
+
import { verifyToken, TokenPayload } from './token-validator';
|
|
8
|
+
import { checkPermission } from './permissions';
|
|
9
|
+
import { logger } from '../utils/logger';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extended request with user info
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthenticatedRequest extends FastifyRequest {
|
|
15
|
+
user?: TokenPayload;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Token Authentication Middleware
|
|
20
|
+
* Verifies token via external REST API
|
|
21
|
+
*/
|
|
22
|
+
export async function tokenAuthMiddleware(
|
|
23
|
+
request: AuthenticatedRequest,
|
|
24
|
+
reply: FastifyReply
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
// Extract token from Authorization header
|
|
28
|
+
const authHeader = request.headers.authorization;
|
|
29
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
30
|
+
logger.warn(
|
|
31
|
+
{
|
|
32
|
+
type: 'auth',
|
|
33
|
+
operation: 'middleware',
|
|
34
|
+
ip: request.ip,
|
|
35
|
+
url: request.url
|
|
36
|
+
},
|
|
37
|
+
'Missing or invalid Authorization header'
|
|
38
|
+
);
|
|
39
|
+
reply.code(401).send({
|
|
40
|
+
error: 'Unauthorized',
|
|
41
|
+
message: 'Missing or invalid Authorization header. Expected: Bearer <token>',
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
47
|
+
|
|
48
|
+
// Verify token via external API
|
|
49
|
+
const payload = await verifyToken(token);
|
|
50
|
+
if (!payload) {
|
|
51
|
+
logger.warn(
|
|
52
|
+
{
|
|
53
|
+
type: 'auth',
|
|
54
|
+
operation: 'middleware',
|
|
55
|
+
ip: request.ip,
|
|
56
|
+
url: request.url
|
|
57
|
+
},
|
|
58
|
+
'Token validation failed'
|
|
59
|
+
);
|
|
60
|
+
reply.code(401).send({
|
|
61
|
+
error: 'Unauthorized',
|
|
62
|
+
message: 'Invalid or expired token',
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Attach user info to request
|
|
68
|
+
request.user = payload;
|
|
69
|
+
|
|
70
|
+
logger.debug(
|
|
71
|
+
{
|
|
72
|
+
type: 'auth',
|
|
73
|
+
operation: 'middleware',
|
|
74
|
+
userId: payload.userId,
|
|
75
|
+
email: payload.email,
|
|
76
|
+
groups: payload.groups
|
|
77
|
+
},
|
|
78
|
+
`Token authentication successful for user ${payload.userId}`
|
|
79
|
+
);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error({
|
|
82
|
+
type: 'auth',
|
|
83
|
+
operation: 'middleware',
|
|
84
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
85
|
+
}, 'Token authentication error');
|
|
86
|
+
reply.code(500).send({
|
|
87
|
+
error: 'Internal Server Error',
|
|
88
|
+
message: 'Authentication failed',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Token Authentication Middleware with Legacy Bearer Token Support
|
|
95
|
+
* Supports both token validation via API and legacy bearer tokens
|
|
96
|
+
*/
|
|
97
|
+
export async function tokenAuthOrLegacyMiddleware(
|
|
98
|
+
request: AuthenticatedRequest,
|
|
99
|
+
reply: FastifyReply
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
const authHeader = request.headers.authorization;
|
|
103
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
104
|
+
logger.warn(
|
|
105
|
+
{
|
|
106
|
+
type: 'auth',
|
|
107
|
+
operation: 'middleware_legacy',
|
|
108
|
+
ip: request.ip,
|
|
109
|
+
url: request.url
|
|
110
|
+
},
|
|
111
|
+
'Missing or invalid Authorization header'
|
|
112
|
+
);
|
|
113
|
+
reply.code(401).send({
|
|
114
|
+
error: 'Unauthorized',
|
|
115
|
+
message: 'Missing or invalid Authorization header',
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const token = authHeader.substring(7);
|
|
121
|
+
|
|
122
|
+
// Try to validate via API first
|
|
123
|
+
const payload = await verifyToken(token);
|
|
124
|
+
if (payload) {
|
|
125
|
+
// API validation successful
|
|
126
|
+
request.user = payload;
|
|
127
|
+
logger.debug(
|
|
128
|
+
{
|
|
129
|
+
type: 'auth',
|
|
130
|
+
operation: 'middleware_legacy',
|
|
131
|
+
userId: payload.userId,
|
|
132
|
+
email: payload.email,
|
|
133
|
+
groups: payload.groups
|
|
134
|
+
},
|
|
135
|
+
`Token validated via API for user ${payload.userId}`
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fallback to legacy bearer token (for backward compatibility)
|
|
141
|
+
logger.debug(
|
|
142
|
+
{
|
|
143
|
+
type: 'auth',
|
|
144
|
+
operation: 'middleware_legacy',
|
|
145
|
+
ip: request.ip
|
|
146
|
+
},
|
|
147
|
+
'API validation failed, using legacy bearer token mode'
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// In legacy mode, we don't have user info, so continue without setting request.user
|
|
151
|
+
// The endpoint will handle the legacy token separately
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error({
|
|
154
|
+
type: 'auth',
|
|
155
|
+
operation: 'middleware_legacy',
|
|
156
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
157
|
+
}, 'Token authentication error');
|
|
158
|
+
reply.code(500).send({
|
|
159
|
+
error: 'Internal Server Error',
|
|
160
|
+
message: 'Authentication failed',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Permission Check Middleware Factory
|
|
167
|
+
* Creates middleware to check permissions for a specific tool
|
|
168
|
+
*/
|
|
169
|
+
export function requirePermission(toolName: string) {
|
|
170
|
+
return async (request: AuthenticatedRequest, reply: FastifyReply): Promise<void> => {
|
|
171
|
+
try {
|
|
172
|
+
if (!request.user) {
|
|
173
|
+
logger.error(
|
|
174
|
+
{
|
|
175
|
+
type: 'auth',
|
|
176
|
+
operation: 'permission_check',
|
|
177
|
+
url: request.url
|
|
178
|
+
},
|
|
179
|
+
'Permission check called without authentication'
|
|
180
|
+
);
|
|
181
|
+
reply.code(401).send({
|
|
182
|
+
error: 'Unauthorized',
|
|
183
|
+
message: 'Authentication required',
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check permission
|
|
189
|
+
const permissionCheck = checkPermission(toolName, request.user.groups);
|
|
190
|
+
|
|
191
|
+
if (!permissionCheck.allowed) {
|
|
192
|
+
logger.warn(
|
|
193
|
+
{
|
|
194
|
+
type: 'auth',
|
|
195
|
+
operation: 'permission_check',
|
|
196
|
+
userId: request.user.userId,
|
|
197
|
+
email: request.user.email,
|
|
198
|
+
groups: request.user.groups,
|
|
199
|
+
toolName,
|
|
200
|
+
reason: permissionCheck.reason,
|
|
201
|
+
},
|
|
202
|
+
`Permission denied for user ${request.user.userId} to access tool ${toolName}`
|
|
203
|
+
);
|
|
204
|
+
reply.code(403).send({
|
|
205
|
+
error: 'Forbidden',
|
|
206
|
+
message: permissionCheck.reason || 'Insufficient permissions',
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
logger.debug(
|
|
212
|
+
{
|
|
213
|
+
type: 'auth',
|
|
214
|
+
operation: 'permission_check',
|
|
215
|
+
userId: request.user.userId,
|
|
216
|
+
toolName
|
|
217
|
+
},
|
|
218
|
+
`Permission granted for user ${request.user.userId} to access tool ${toolName}`
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error({
|
|
222
|
+
type: 'auth',
|
|
223
|
+
operation: 'permission_check',
|
|
224
|
+
toolName,
|
|
225
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
226
|
+
}, 'Permission check error');
|
|
227
|
+
reply.code(500).send({
|
|
228
|
+
error: 'Internal Server Error',
|
|
229
|
+
message: 'Permission check failed',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Permission Check for Tool Call
|
|
237
|
+
* Checks permission when tools/call is invoked
|
|
238
|
+
*/
|
|
239
|
+
export function checkToolCallPermission(
|
|
240
|
+
toolName: string,
|
|
241
|
+
user: TokenPayload
|
|
242
|
+
): { allowed: boolean; reason?: string } {
|
|
243
|
+
return checkPermission(toolName, user.groups);
|
|
244
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Control System
|
|
3
|
+
* Group-based access control for MCP tools
|
|
4
|
+
* Groups are obtained from CSP API /user/permissions (e.g., "zNet", "Client-Public")
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { logger, logAuthAttempt } from '../utils/logger';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Known groups from CSP
|
|
11
|
+
* Users may belong to one or more groups
|
|
12
|
+
*/
|
|
13
|
+
export const KnownGroups = {
|
|
14
|
+
ZNET: 'zNet', // zNet team - full access
|
|
15
|
+
CLIENT_PUBLIC: 'Client-Public', // Client-Public team - standard access
|
|
16
|
+
ADMIN: 'admin', // Admin group - full access (if exists)
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Permission level for operations
|
|
21
|
+
*/
|
|
22
|
+
export enum PermissionLevel {
|
|
23
|
+
READ = 'read',
|
|
24
|
+
WRITE = 'write',
|
|
25
|
+
ADMIN = 'admin',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tool permission configuration
|
|
30
|
+
*/
|
|
31
|
+
export interface ToolPermission {
|
|
32
|
+
tool: string;
|
|
33
|
+
allowedGroups: string[]; // Changed from requiredRole to allowedGroups
|
|
34
|
+
requiredPermission: PermissionLevel;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default permission rules for each tool
|
|
39
|
+
* All authenticated users (with valid groups) can use these tools
|
|
40
|
+
*/
|
|
41
|
+
const defaultPermissions: ToolPermission[] = [
|
|
42
|
+
// sync_resources - available to all authenticated users
|
|
43
|
+
{
|
|
44
|
+
tool: 'sync_resources',
|
|
45
|
+
allowedGroups: ['*'], // * means all authenticated users
|
|
46
|
+
requiredPermission: PermissionLevel.WRITE,
|
|
47
|
+
},
|
|
48
|
+
// manage_subscription - available to all authenticated users
|
|
49
|
+
{
|
|
50
|
+
tool: 'manage_subscription',
|
|
51
|
+
allowedGroups: ['*'],
|
|
52
|
+
requiredPermission: PermissionLevel.WRITE,
|
|
53
|
+
},
|
|
54
|
+
// search_resources - read-only, all authenticated users
|
|
55
|
+
{
|
|
56
|
+
tool: 'search_resources',
|
|
57
|
+
allowedGroups: ['*'],
|
|
58
|
+
requiredPermission: PermissionLevel.READ,
|
|
59
|
+
},
|
|
60
|
+
// upload_resource - requires write permission
|
|
61
|
+
{
|
|
62
|
+
tool: 'upload_resource',
|
|
63
|
+
allowedGroups: ['*'],
|
|
64
|
+
requiredPermission: PermissionLevel.WRITE,
|
|
65
|
+
},
|
|
66
|
+
// uninstall_resource - requires write permission
|
|
67
|
+
{
|
|
68
|
+
tool: 'uninstall_resource',
|
|
69
|
+
allowedGroups: ['*'],
|
|
70
|
+
requiredPermission: PermissionLevel.WRITE,
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Custom permission rules (can be overridden via config)
|
|
76
|
+
*/
|
|
77
|
+
let permissionRules: Map<string, ToolPermission> = new Map();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize permission system
|
|
81
|
+
*/
|
|
82
|
+
export function initializePermissions(customRules?: ToolPermission[]): void {
|
|
83
|
+
// Load default permissions
|
|
84
|
+
for (const perm of defaultPermissions) {
|
|
85
|
+
permissionRules.set(perm.tool, perm);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Override with custom rules if provided
|
|
89
|
+
if (customRules && customRules.length > 0) {
|
|
90
|
+
logger.info(
|
|
91
|
+
{ count: customRules.length },
|
|
92
|
+
'Loading custom permission rules'
|
|
93
|
+
);
|
|
94
|
+
for (const perm of customRules) {
|
|
95
|
+
permissionRules.set(perm.tool, perm);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
logger.info(
|
|
100
|
+
{ toolCount: permissionRules.size },
|
|
101
|
+
'Permission system initialized'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a user has permission to access a tool
|
|
107
|
+
* @param toolName - The name of the tool to check
|
|
108
|
+
* @param userGroups - The groups the user belongs to (from CSP API)
|
|
109
|
+
*/
|
|
110
|
+
export function checkPermission(
|
|
111
|
+
toolName: string,
|
|
112
|
+
userGroups: string[]
|
|
113
|
+
): { allowed: boolean; reason?: string } {
|
|
114
|
+
const checkStartTime = Date.now();
|
|
115
|
+
|
|
116
|
+
logger.debug({
|
|
117
|
+
type: 'permission_check',
|
|
118
|
+
toolName,
|
|
119
|
+
userGroups,
|
|
120
|
+
timestamp: new Date().toISOString()
|
|
121
|
+
}, `Checking permission for tool: ${toolName}`);
|
|
122
|
+
|
|
123
|
+
// Check if tool has permission rules
|
|
124
|
+
const permission = permissionRules.get(toolName);
|
|
125
|
+
if (!permission) {
|
|
126
|
+
// If no permission rule defined, deny by default
|
|
127
|
+
logger.warn({
|
|
128
|
+
type: 'permission_check',
|
|
129
|
+
toolName,
|
|
130
|
+
userGroups,
|
|
131
|
+
result: 'denied',
|
|
132
|
+
reason: 'no_rule',
|
|
133
|
+
timestamp: new Date().toISOString()
|
|
134
|
+
}, 'No permission rule found for tool, denying access');
|
|
135
|
+
|
|
136
|
+
logAuthAttempt('permission_check', false, {
|
|
137
|
+
toolName,
|
|
138
|
+
userGroups,
|
|
139
|
+
reason: 'no_rule',
|
|
140
|
+
duration: Date.now() - checkStartTime
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
reason: `Tool '${toolName}' has no permission rule defined`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// If no groups provided, deny access
|
|
150
|
+
if (!userGroups || userGroups.length === 0) {
|
|
151
|
+
logger.warn(
|
|
152
|
+
{
|
|
153
|
+
type: 'permission_check',
|
|
154
|
+
toolName,
|
|
155
|
+
result: 'denied',
|
|
156
|
+
reason: 'no_groups',
|
|
157
|
+
timestamp: new Date().toISOString()
|
|
158
|
+
},
|
|
159
|
+
'Permission denied: user has no groups'
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
logAuthAttempt('permission_check', false, {
|
|
163
|
+
toolName,
|
|
164
|
+
reason: 'no_groups',
|
|
165
|
+
duration: Date.now() - checkStartTime
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
allowed: false,
|
|
170
|
+
reason: `User must belong to at least one group to access tools`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Admin group bypasses all checks
|
|
175
|
+
if (userGroups.includes(KnownGroups.ADMIN) || userGroups.includes('admin')) {
|
|
176
|
+
logger.info(
|
|
177
|
+
{
|
|
178
|
+
type: 'permission_check',
|
|
179
|
+
toolName,
|
|
180
|
+
userGroups,
|
|
181
|
+
result: 'granted',
|
|
182
|
+
reason: 'admin_bypass',
|
|
183
|
+
duration: Date.now() - checkStartTime,
|
|
184
|
+
timestamp: new Date().toISOString()
|
|
185
|
+
},
|
|
186
|
+
'Admin group access granted'
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
logAuthAttempt('permission_check', true, {
|
|
190
|
+
toolName,
|
|
191
|
+
userGroups,
|
|
192
|
+
reason: 'admin',
|
|
193
|
+
duration: Date.now() - checkStartTime
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return { allowed: true };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check if tool allows all authenticated users
|
|
200
|
+
if (permission.allowedGroups.includes('*')) {
|
|
201
|
+
logger.info(
|
|
202
|
+
{
|
|
203
|
+
type: 'permission_check',
|
|
204
|
+
toolName,
|
|
205
|
+
userGroups,
|
|
206
|
+
allowedGroups: permission.allowedGroups,
|
|
207
|
+
result: 'granted',
|
|
208
|
+
reason: 'wildcard',
|
|
209
|
+
duration: Date.now() - checkStartTime,
|
|
210
|
+
timestamp: new Date().toISOString()
|
|
211
|
+
},
|
|
212
|
+
'Permission granted (tool allows all authenticated users)'
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
logAuthAttempt('permission_check', true, {
|
|
216
|
+
toolName,
|
|
217
|
+
userGroups,
|
|
218
|
+
reason: 'wildcard',
|
|
219
|
+
duration: Date.now() - checkStartTime
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return { allowed: true };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check if user belongs to any of the allowed groups
|
|
226
|
+
const hasAllowedGroup = userGroups.some((group) =>
|
|
227
|
+
permission.allowedGroups.includes(group)
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (!hasAllowedGroup) {
|
|
231
|
+
logger.warn(
|
|
232
|
+
{
|
|
233
|
+
type: 'permission_check',
|
|
234
|
+
toolName,
|
|
235
|
+
userGroups,
|
|
236
|
+
allowedGroups: permission.allowedGroups,
|
|
237
|
+
result: 'denied',
|
|
238
|
+
reason: 'group_mismatch',
|
|
239
|
+
duration: Date.now() - checkStartTime,
|
|
240
|
+
timestamp: new Date().toISOString()
|
|
241
|
+
},
|
|
242
|
+
'Permission denied: user not in allowed groups'
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
logAuthAttempt('permission_check', false, {
|
|
246
|
+
toolName,
|
|
247
|
+
userGroups,
|
|
248
|
+
allowedGroups: permission.allowedGroups,
|
|
249
|
+
reason: 'group_mismatch',
|
|
250
|
+
duration: Date.now() - checkStartTime
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
allowed: false,
|
|
255
|
+
reason: `Tool '${toolName}' requires membership in one of: ${permission.allowedGroups.join(', ')}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
logger.info(
|
|
260
|
+
{
|
|
261
|
+
type: 'permission_check',
|
|
262
|
+
toolName,
|
|
263
|
+
userGroups,
|
|
264
|
+
allowedGroups: permission.allowedGroups,
|
|
265
|
+
result: 'granted',
|
|
266
|
+
reason: 'group_match',
|
|
267
|
+
duration: Date.now() - checkStartTime,
|
|
268
|
+
timestamp: new Date().toISOString()
|
|
269
|
+
},
|
|
270
|
+
'Permission granted (user in allowed groups)'
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
logAuthAttempt('permission_check', true, {
|
|
274
|
+
toolName,
|
|
275
|
+
userGroups,
|
|
276
|
+
matchedGroups: userGroups.filter(g => permission.allowedGroups.includes(g)),
|
|
277
|
+
duration: Date.now() - checkStartTime
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return { allowed: true };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get permission info for a tool
|
|
285
|
+
*/
|
|
286
|
+
export function getToolPermission(toolName: string): ToolPermission | undefined {
|
|
287
|
+
return permissionRules.get(toolName);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get all permission rules
|
|
292
|
+
*/
|
|
293
|
+
export function getAllPermissions(): ToolPermission[] {
|
|
294
|
+
return Array.from(permissionRules.values());
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Update permission rule for a tool
|
|
299
|
+
*/
|
|
300
|
+
export function updatePermission(permission: ToolPermission): void {
|
|
301
|
+
permissionRules.set(permission.tool, permission);
|
|
302
|
+
logger.info(
|
|
303
|
+
{ tool: permission.tool, permission },
|
|
304
|
+
'Permission rule updated'
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Remove permission rule for a tool
|
|
310
|
+
*/
|
|
311
|
+
export function removePermission(toolName: string): void {
|
|
312
|
+
permissionRules.delete(toolName);
|
|
313
|
+
logger.info({ toolName }, 'Permission rule removed');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Initialize with default permissions
|
|
317
|
+
initializePermissions();
|