@mcp-abap-adt/header-validator 0.1.7 → 0.3.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/CHANGELOG.md +72 -2
- package/COPYING +674 -0
- package/LICENSE +160 -17
- package/README.md +28 -3
- package/dist/headerValidator.d.ts +1 -1
- package/dist/headerValidator.js +101 -86
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/types.d.ts +2 -2
- package/dist/types.js +2 -2
- package/package.json +13 -5
package/dist/headerValidator.js
CHANGED
|
@@ -16,7 +16,9 @@ exports.validateAuthHeaders = validateAuthHeaders;
|
|
|
16
16
|
exports.validateProxyHeaders = validateProxyHeaders;
|
|
17
17
|
exports.isProxyRequest = isProxyRequest;
|
|
18
18
|
exports.isMcpServerRequest = isMcpServerRequest;
|
|
19
|
-
const
|
|
19
|
+
const interfaces_auth_1 = require("@mcp-abap-adt/interfaces-auth");
|
|
20
|
+
const interfaces_auth_sap_1 = require("@mcp-abap-adt/interfaces-auth-sap");
|
|
21
|
+
const interfaces_network_1 = require("@mcp-abap-adt/interfaces-network");
|
|
20
22
|
/**
|
|
21
23
|
* Extract header value (handles array values)
|
|
22
24
|
* Node.js normalizes headers to lowercase, but check both cases for safety
|
|
@@ -55,47 +57,48 @@ function isValidUrl(url) {
|
|
|
55
57
|
*/
|
|
56
58
|
function validateSapDestinationAuth(headers) {
|
|
57
59
|
// Check both lowercase and original case (Node.js normalizes to lowercase, but check both for safety)
|
|
58
|
-
const destinationRaw = headers[
|
|
60
|
+
const destinationRaw = headers[interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE.toLowerCase()] ||
|
|
61
|
+
headers[interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE];
|
|
59
62
|
if (!destinationRaw) {
|
|
60
63
|
return null;
|
|
61
64
|
}
|
|
62
|
-
const destination = getHeaderValue(headers,
|
|
65
|
+
const destination = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE);
|
|
63
66
|
const errors = [];
|
|
64
67
|
const warnings = [];
|
|
65
68
|
// Validate destination name (check if empty after trim)
|
|
66
69
|
if (!destination || destination.length === 0) {
|
|
67
|
-
errors.push(`${
|
|
70
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE} header is empty`);
|
|
68
71
|
return {
|
|
69
|
-
priority:
|
|
70
|
-
authType:
|
|
72
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.NONE,
|
|
73
|
+
authType: interfaces_auth_1.AUTH_TYPE_JWT, // SAP destination always uses JWT
|
|
71
74
|
sapUrl: '', // URL will be loaded from destination
|
|
72
75
|
errors,
|
|
73
76
|
warnings,
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
// Extract optional SAP client
|
|
77
|
-
const sapClient = getHeaderValue(headers,
|
|
80
|
+
const sapClient = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_CLIENT);
|
|
78
81
|
// Optional: x-sap-login and x-sap-password (for cloud systems)
|
|
79
|
-
const username = getHeaderValue(headers,
|
|
80
|
-
const password = getHeaderValue(headers,
|
|
82
|
+
const username = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_LOGIN);
|
|
83
|
+
const password = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_PASSWORD);
|
|
81
84
|
// Warning if x-sap-url is provided (URL comes from destination, not header)
|
|
82
|
-
const sapUrl = getHeaderValue(headers,
|
|
85
|
+
const sapUrl = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_URL);
|
|
83
86
|
if (sapUrl) {
|
|
84
|
-
warnings.push(`${
|
|
87
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_URL} is ignored when ${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE} is present (URL is loaded from destination service key or .env file)`);
|
|
85
88
|
}
|
|
86
89
|
// Warning if direct JWT token is also provided (destination takes priority)
|
|
87
|
-
const jwtToken = getHeaderValue(headers,
|
|
90
|
+
const jwtToken = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_JWT_TOKEN);
|
|
88
91
|
if (jwtToken) {
|
|
89
|
-
warnings.push(`${
|
|
92
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_JWT_TOKEN} is ignored when ${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE} is present (destination-based auth takes priority)`);
|
|
90
93
|
}
|
|
91
94
|
// Warning if auth-type is provided (not needed for x-sap-destination)
|
|
92
|
-
const authType = getHeaderValue(headers,
|
|
95
|
+
const authType = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_AUTH_TYPE);
|
|
93
96
|
if (authType) {
|
|
94
|
-
warnings.push(`${
|
|
97
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} is ignored when ${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE} is present (always uses JWT)`);
|
|
95
98
|
}
|
|
96
99
|
return {
|
|
97
|
-
priority:
|
|
98
|
-
authType:
|
|
100
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.SAP_DESTINATION,
|
|
101
|
+
authType: interfaces_auth_1.AUTH_TYPE_JWT, // Always JWT for x-sap-destination
|
|
99
102
|
sapUrl: '', // URL will be loaded from destination (service key or .env)
|
|
100
103
|
sapClient,
|
|
101
104
|
destination,
|
|
@@ -113,19 +116,20 @@ function validateSapDestinationAuth(headers) {
|
|
|
113
116
|
*/
|
|
114
117
|
function validateMcpDestinationAuth(headers, sapUrl) {
|
|
115
118
|
// Check both lowercase and original case (Node.js normalizes to lowercase, but check both for safety)
|
|
116
|
-
const destinationRaw = headers[
|
|
119
|
+
const destinationRaw = headers[interfaces_network_1.HEADER_MCP_DESTINATION.toLowerCase()] ||
|
|
120
|
+
headers[interfaces_network_1.HEADER_MCP_DESTINATION];
|
|
117
121
|
if (!destinationRaw) {
|
|
118
122
|
return null;
|
|
119
123
|
}
|
|
120
|
-
const destination = getHeaderValue(headers,
|
|
124
|
+
const destination = getHeaderValue(headers, interfaces_network_1.HEADER_MCP_DESTINATION);
|
|
121
125
|
const errors = [];
|
|
122
126
|
const warnings = [];
|
|
123
127
|
// Validate destination name (check if empty after trim)
|
|
124
128
|
if (!destination || destination.length === 0) {
|
|
125
|
-
errors.push(`${
|
|
129
|
+
errors.push(`${interfaces_network_1.HEADER_MCP_DESTINATION} header is empty`);
|
|
126
130
|
return {
|
|
127
|
-
priority:
|
|
128
|
-
authType:
|
|
131
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.NONE,
|
|
132
|
+
authType: interfaces_auth_1.AUTH_TYPE_JWT, // MCP destination always uses JWT
|
|
129
133
|
sapUrl: '', // URL will be loaded from destination
|
|
130
134
|
errors,
|
|
131
135
|
warnings,
|
|
@@ -133,23 +137,23 @@ function validateMcpDestinationAuth(headers, sapUrl) {
|
|
|
133
137
|
}
|
|
134
138
|
// Warning if x-sap-url is provided (URL comes from destination, not header)
|
|
135
139
|
if (sapUrl) {
|
|
136
|
-
warnings.push(`${
|
|
140
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_URL} is ignored when ${interfaces_network_1.HEADER_MCP_DESTINATION} is present (URL is loaded from destination service key or .env file)`);
|
|
137
141
|
}
|
|
138
142
|
// Warning if x-sap-auth-type is provided (not needed for x-mcp-destination)
|
|
139
|
-
const authType = getHeaderValue(headers,
|
|
143
|
+
const authType = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_AUTH_TYPE);
|
|
140
144
|
if (authType) {
|
|
141
|
-
warnings.push(`${
|
|
145
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} is ignored when ${interfaces_network_1.HEADER_MCP_DESTINATION} is present (always uses JWT)`);
|
|
142
146
|
}
|
|
143
147
|
// Extract optional SAP client
|
|
144
|
-
const sapClient = getHeaderValue(headers,
|
|
148
|
+
const sapClient = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_CLIENT);
|
|
145
149
|
// Warning if direct JWT token is also provided (destination takes priority)
|
|
146
|
-
const jwtToken = getHeaderValue(headers,
|
|
150
|
+
const jwtToken = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_JWT_TOKEN);
|
|
147
151
|
if (jwtToken) {
|
|
148
|
-
warnings.push(`${
|
|
152
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_JWT_TOKEN} is ignored when ${interfaces_network_1.HEADER_MCP_DESTINATION} is present (destination-based auth takes priority)`);
|
|
149
153
|
}
|
|
150
154
|
return {
|
|
151
|
-
priority:
|
|
152
|
-
authType:
|
|
155
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.MCP_DESTINATION,
|
|
156
|
+
authType: interfaces_auth_1.AUTH_TYPE_JWT, // Always JWT for x-mcp-destination
|
|
153
157
|
sapUrl: '', // URL will be loaded from destination (service key or .env)
|
|
154
158
|
sapClient,
|
|
155
159
|
destination,
|
|
@@ -165,10 +169,10 @@ function validateMcpDestinationAuth(headers, sapUrl) {
|
|
|
165
169
|
* and only used for token refresh - they are a separate set of headers.
|
|
166
170
|
*/
|
|
167
171
|
function validateDirectJwtAuth(headers, sapUrl, authType) {
|
|
168
|
-
if (authType !==
|
|
172
|
+
if (authType !== interfaces_auth_1.AUTH_TYPE_JWT && authType !== interfaces_auth_sap_1.AUTH_TYPE_XSUAA) {
|
|
169
173
|
return null;
|
|
170
174
|
}
|
|
171
|
-
const jwtToken = getHeaderValue(headers,
|
|
175
|
+
const jwtToken = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_JWT_TOKEN);
|
|
172
176
|
if (!jwtToken) {
|
|
173
177
|
return null;
|
|
174
178
|
}
|
|
@@ -176,25 +180,28 @@ function validateDirectJwtAuth(headers, sapUrl, authType) {
|
|
|
176
180
|
const warnings = [];
|
|
177
181
|
// Validate JWT token format (basic check - should start with eyJ)
|
|
178
182
|
if (jwtToken.length < 10) {
|
|
179
|
-
errors.push(`${
|
|
183
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_JWT_TOKEN} appears to be invalid (too short)`);
|
|
180
184
|
}
|
|
181
185
|
// Extract optional refresh token
|
|
182
|
-
const refreshToken = getHeaderValue(headers,
|
|
186
|
+
const refreshToken = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_REFRESH_TOKEN);
|
|
183
187
|
// Extract optional UAA config (for token refresh only - separate set of headers)
|
|
184
188
|
// These are optional and don't affect authorization validation
|
|
185
|
-
const uaaUrl = getHeaderValue(headers,
|
|
186
|
-
|
|
187
|
-
const
|
|
189
|
+
const uaaUrl = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_UAA_URL) ||
|
|
190
|
+
getHeaderValue(headers, interfaces_network_1.HEADER_UAA_URL);
|
|
191
|
+
const uaaClientId = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_UAA_CLIENT_ID) ||
|
|
192
|
+
getHeaderValue(headers, interfaces_network_1.HEADER_UAA_CLIENT_ID);
|
|
193
|
+
const uaaClientSecret = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_UAA_CLIENT_SECRET) ||
|
|
194
|
+
getHeaderValue(headers, interfaces_network_1.HEADER_UAA_CLIENT_SECRET);
|
|
188
195
|
// Validate UAA config completeness if any UAA header is present
|
|
189
196
|
if (uaaUrl || uaaClientId || uaaClientSecret) {
|
|
190
197
|
if (!uaaUrl || !uaaClientId || !uaaClientSecret) {
|
|
191
|
-
warnings.push(`UAA headers (${
|
|
198
|
+
warnings.push(`UAA headers (${interfaces_network_1.HEADER_SAP_UAA_URL}, ${interfaces_network_1.HEADER_SAP_UAA_CLIENT_ID}, ${interfaces_network_1.HEADER_SAP_UAA_CLIENT_SECRET}) should be provided together for token refresh`);
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
// Extract optional SAP client
|
|
195
|
-
const sapClient = getHeaderValue(headers,
|
|
202
|
+
const sapClient = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_CLIENT);
|
|
196
203
|
return {
|
|
197
|
-
priority:
|
|
204
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.DIRECT_JWT,
|
|
198
205
|
authType,
|
|
199
206
|
sapUrl,
|
|
200
207
|
sapClient,
|
|
@@ -211,29 +218,29 @@ function validateDirectJwtAuth(headers, sapUrl, authType) {
|
|
|
211
218
|
* Validate basic authentication (lowest priority)
|
|
212
219
|
*/
|
|
213
220
|
function validateBasicAuth(headers, sapUrl, authType) {
|
|
214
|
-
if (authType !==
|
|
221
|
+
if (authType !== interfaces_auth_1.AUTH_TYPE_BASIC) {
|
|
215
222
|
return null;
|
|
216
223
|
}
|
|
217
|
-
const usernameRaw = headers[
|
|
218
|
-
const passwordRaw = headers[
|
|
224
|
+
const usernameRaw = headers[interfaces_network_1.HEADER_SAP_LOGIN.toLowerCase()] || headers[interfaces_network_1.HEADER_SAP_LOGIN];
|
|
225
|
+
const passwordRaw = headers[interfaces_network_1.HEADER_SAP_PASSWORD.toLowerCase()] || headers[interfaces_network_1.HEADER_SAP_PASSWORD];
|
|
219
226
|
if (!usernameRaw || !passwordRaw) {
|
|
220
227
|
return null;
|
|
221
228
|
}
|
|
222
|
-
const username = getHeaderValue(headers,
|
|
223
|
-
const password = getHeaderValue(headers,
|
|
229
|
+
const username = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_LOGIN);
|
|
230
|
+
const password = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_PASSWORD);
|
|
224
231
|
const errors = [];
|
|
225
232
|
const warnings = [];
|
|
226
233
|
// Validate username and password (check if empty after trim)
|
|
227
234
|
if (!username || username.length === 0) {
|
|
228
|
-
errors.push(`${
|
|
235
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_LOGIN} header is empty`);
|
|
229
236
|
}
|
|
230
237
|
if (!password || password.length === 0) {
|
|
231
|
-
errors.push(`${
|
|
238
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_PASSWORD} header is empty`);
|
|
232
239
|
}
|
|
233
240
|
// Return config with errors if validation failed
|
|
234
241
|
if (errors.length > 0) {
|
|
235
242
|
return {
|
|
236
|
-
priority:
|
|
243
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.NONE,
|
|
237
244
|
authType,
|
|
238
245
|
sapUrl,
|
|
239
246
|
errors,
|
|
@@ -241,7 +248,7 @@ function validateBasicAuth(headers, sapUrl, authType) {
|
|
|
241
248
|
};
|
|
242
249
|
}
|
|
243
250
|
return {
|
|
244
|
-
priority:
|
|
251
|
+
priority: interfaces_auth_sap_1.AuthMethodPriority.BASIC,
|
|
245
252
|
authType,
|
|
246
253
|
sapUrl,
|
|
247
254
|
username,
|
|
@@ -289,7 +296,7 @@ function validateAuthHeaders(headers) {
|
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
// Check for MCP destination (doesn't require x-sap-url, URL comes from destination)
|
|
292
|
-
const sapUrl = getHeaderValue(headers,
|
|
299
|
+
const sapUrl = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_URL);
|
|
293
300
|
const mcpDestinationConfig = validateMcpDestinationAuth(headers, sapUrl);
|
|
294
301
|
if (mcpDestinationConfig) {
|
|
295
302
|
// MCP destination found - URL comes from destination, not header
|
|
@@ -321,7 +328,7 @@ function validateAuthHeaders(headers) {
|
|
|
321
328
|
}
|
|
322
329
|
// Validate URL format
|
|
323
330
|
if (!isValidUrl(sapUrl)) {
|
|
324
|
-
errors.push(`${
|
|
331
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_URL} is not a valid URL: ${sapUrl}`);
|
|
325
332
|
return {
|
|
326
333
|
isValid: false,
|
|
327
334
|
errors,
|
|
@@ -332,29 +339,33 @@ function validateAuthHeaders(headers) {
|
|
|
332
339
|
const configs = [];
|
|
333
340
|
// Check if basic auth headers are present (x-sap-login, x-sap-password)
|
|
334
341
|
// These should come together with x-sap-auth-type: basic
|
|
335
|
-
const hasSapLogin = !!getHeaderValue(headers,
|
|
336
|
-
const hasSapPassword = !!getHeaderValue(headers,
|
|
342
|
+
const hasSapLogin = !!getHeaderValue(headers, interfaces_network_1.HEADER_SAP_LOGIN);
|
|
343
|
+
const hasSapPassword = !!getHeaderValue(headers, interfaces_network_1.HEADER_SAP_PASSWORD);
|
|
337
344
|
const hasBasicAuthHeaders = hasSapLogin || hasSapPassword;
|
|
338
345
|
if (hasBasicAuthHeaders && (!hasSapLogin || !hasSapPassword)) {
|
|
339
|
-
errors.push(`${
|
|
346
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_LOGIN} and ${interfaces_network_1.HEADER_SAP_PASSWORD} must be provided together`);
|
|
340
347
|
}
|
|
341
348
|
// 3. Other auth methods require x-sap-auth-type
|
|
342
|
-
const sapAuthType = getHeaderValue(headers,
|
|
349
|
+
const sapAuthType = getHeaderValue(headers, interfaces_network_1.HEADER_SAP_AUTH_TYPE);
|
|
343
350
|
if (sapAuthType) {
|
|
344
351
|
// Validate auth type
|
|
345
|
-
const validAuthTypes = [
|
|
352
|
+
const validAuthTypes = [
|
|
353
|
+
interfaces_auth_1.AUTH_TYPE_JWT,
|
|
354
|
+
interfaces_auth_sap_1.AUTH_TYPE_XSUAA,
|
|
355
|
+
interfaces_auth_1.AUTH_TYPE_BASIC,
|
|
356
|
+
];
|
|
346
357
|
const authType = sapAuthType.toLowerCase();
|
|
347
358
|
if (!validAuthTypes.includes(authType)) {
|
|
348
|
-
errors.push(`${
|
|
359
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} must be one of: ${validAuthTypes.join(', ')}, got: ${sapAuthType}`);
|
|
349
360
|
}
|
|
350
361
|
else {
|
|
351
362
|
// Check if basic auth headers are present but auth-type is not basic
|
|
352
|
-
if (hasBasicAuthHeaders && authType !==
|
|
353
|
-
warnings.push(`${
|
|
363
|
+
if (hasBasicAuthHeaders && authType !== interfaces_auth_1.AUTH_TYPE_BASIC) {
|
|
364
|
+
warnings.push(`${interfaces_network_1.HEADER_SAP_LOGIN} and ${interfaces_network_1.HEADER_SAP_PASSWORD} are present but ${interfaces_network_1.HEADER_SAP_AUTH_TYPE} is not "${interfaces_auth_1.AUTH_TYPE_BASIC}"`);
|
|
354
365
|
}
|
|
355
366
|
// Check if auth-type is basic but headers are missing
|
|
356
|
-
if (authType ===
|
|
357
|
-
errors.push(`${
|
|
367
|
+
if (authType === interfaces_auth_1.AUTH_TYPE_BASIC && !hasBasicAuthHeaders) {
|
|
368
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} is "${interfaces_auth_1.AUTH_TYPE_BASIC}" but ${interfaces_network_1.HEADER_SAP_LOGIN} and ${interfaces_network_1.HEADER_SAP_PASSWORD} are missing`);
|
|
358
369
|
}
|
|
359
370
|
// Only validate direct JWT and basic auth if MCP destination is not present
|
|
360
371
|
// (MCP destination already handled above)
|
|
@@ -376,22 +387,22 @@ function validateAuthHeaders(headers) {
|
|
|
376
387
|
// No auth-type provided
|
|
377
388
|
// If basic auth headers are present, auth-type must be basic
|
|
378
389
|
if (hasBasicAuthHeaders) {
|
|
379
|
-
errors.push(`${
|
|
390
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} must be "${interfaces_auth_1.AUTH_TYPE_BASIC}" when ${interfaces_network_1.HEADER_SAP_LOGIN} and ${interfaces_network_1.HEADER_SAP_PASSWORD} are present`);
|
|
380
391
|
}
|
|
381
392
|
else if (!mcpDestinationConfig && !sapDestinationConfig) {
|
|
382
393
|
// No auth-type and no destination - error
|
|
383
|
-
errors.push(`${
|
|
394
|
+
errors.push(`${interfaces_network_1.HEADER_SAP_AUTH_TYPE} header is required when ${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE} and ${interfaces_network_1.HEADER_MCP_DESTINATION} are not present`);
|
|
384
395
|
}
|
|
385
396
|
}
|
|
386
397
|
// No valid authentication method found
|
|
387
398
|
if (configs.length === 0) {
|
|
388
399
|
if (sapAuthType) {
|
|
389
400
|
const authType = sapAuthType.toLowerCase();
|
|
390
|
-
if (authType ===
|
|
391
|
-
errors.push(`JWT authentication requires either ${
|
|
401
|
+
if (authType === interfaces_auth_1.AUTH_TYPE_JWT || authType === interfaces_auth_sap_1.AUTH_TYPE_XSUAA) {
|
|
402
|
+
errors.push(`JWT authentication requires either ${interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE}, ${interfaces_network_1.HEADER_MCP_DESTINATION}, or ${interfaces_network_1.HEADER_SAP_JWT_TOKEN} header`);
|
|
392
403
|
}
|
|
393
|
-
else if (authType ===
|
|
394
|
-
errors.push(`Basic authentication requires ${
|
|
404
|
+
else if (authType === interfaces_auth_1.AUTH_TYPE_BASIC) {
|
|
405
|
+
errors.push(`Basic authentication requires ${interfaces_network_1.HEADER_SAP_LOGIN} and ${interfaces_network_1.HEADER_SAP_PASSWORD} headers`);
|
|
395
406
|
}
|
|
396
407
|
}
|
|
397
408
|
else {
|
|
@@ -410,9 +421,9 @@ function validateAuthHeaders(headers) {
|
|
|
410
421
|
// Select highest priority configuration
|
|
411
422
|
const selectedConfig = configs.reduce((prev, current) => current.priority > prev.priority ? current : prev);
|
|
412
423
|
// Check for conflicts (multiple auth methods with same priority shouldn't happen, but check anyway)
|
|
413
|
-
const samePriorityConfigs = configs.filter(c => c.priority === selectedConfig.priority);
|
|
424
|
+
const samePriorityConfigs = configs.filter((c) => c.priority === selectedConfig.priority);
|
|
414
425
|
if (samePriorityConfigs.length > 1) {
|
|
415
|
-
warnings.push(`Multiple authentication methods with same priority detected, using: ${
|
|
426
|
+
warnings.push(`Multiple authentication methods with same priority detected, using: ${interfaces_auth_sap_1.AuthMethodPriority[selectedConfig.priority]}`);
|
|
416
427
|
}
|
|
417
428
|
// Merge errors and warnings
|
|
418
429
|
const allErrors = [...errors, ...selectedConfig.errors];
|
|
@@ -448,29 +459,31 @@ function validateProxyHeaders(headers) {
|
|
|
448
459
|
warnings: [],
|
|
449
460
|
};
|
|
450
461
|
}
|
|
451
|
-
const btpDestination = getHeaderValue(headers,
|
|
452
|
-
const mcpDestination = getHeaderValue(headers,
|
|
453
|
-
const mcpUrl = getHeaderValue(headers,
|
|
462
|
+
const btpDestination = getHeaderValue(headers, interfaces_network_1.HEADER_BTP_DESTINATION);
|
|
463
|
+
const mcpDestination = getHeaderValue(headers, interfaces_network_1.HEADER_MCP_DESTINATION);
|
|
464
|
+
const mcpUrl = getHeaderValue(headers, interfaces_network_1.HEADER_MCP_URL);
|
|
454
465
|
const hasBtpDestination = !!btpDestination;
|
|
455
466
|
const hasMcpDestination = !!mcpDestination;
|
|
456
467
|
const hasMcpUrl = !!mcpUrl;
|
|
457
468
|
// Validate destination names if present
|
|
458
|
-
if (hasBtpDestination &&
|
|
459
|
-
|
|
469
|
+
if (hasBtpDestination &&
|
|
470
|
+
(!btpDestination || btpDestination.trim().length === 0)) {
|
|
471
|
+
errors.push(`${interfaces_network_1.HEADER_BTP_DESTINATION} header is empty`);
|
|
460
472
|
}
|
|
461
|
-
if (hasMcpDestination &&
|
|
462
|
-
|
|
473
|
+
if (hasMcpDestination &&
|
|
474
|
+
(!mcpDestination || mcpDestination.trim().length === 0)) {
|
|
475
|
+
errors.push(`${interfaces_network_1.HEADER_MCP_DESTINATION} header is empty`);
|
|
463
476
|
}
|
|
464
477
|
// Validate mcpUrl format if present
|
|
465
|
-
if (hasMcpUrl) {
|
|
478
|
+
if (hasMcpUrl && mcpUrl) {
|
|
466
479
|
if (!isValidUrl(mcpUrl)) {
|
|
467
|
-
errors.push(`${
|
|
480
|
+
errors.push(`${interfaces_network_1.HEADER_MCP_URL} is not a valid URL: ${mcpUrl}`);
|
|
468
481
|
}
|
|
469
482
|
}
|
|
470
483
|
// At least one proxy header should be present for proxy routing
|
|
471
484
|
const hasAnyProxyHeader = hasBtpDestination || hasMcpDestination || hasMcpUrl;
|
|
472
485
|
if (!hasAnyProxyHeader) {
|
|
473
|
-
warnings.push(`No proxy headers found (${
|
|
486
|
+
warnings.push(`No proxy headers found (${interfaces_network_1.HEADER_BTP_DESTINATION}, ${interfaces_network_1.HEADER_MCP_DESTINATION}, or ${interfaces_network_1.HEADER_MCP_URL})`);
|
|
474
487
|
}
|
|
475
488
|
return {
|
|
476
489
|
isValid: errors.length === 0,
|
|
@@ -497,7 +510,9 @@ function isProxyRequest(headers) {
|
|
|
497
510
|
return false;
|
|
498
511
|
}
|
|
499
512
|
const validation = validateProxyHeaders(headers);
|
|
500
|
-
return validation.hasBtpDestination ||
|
|
513
|
+
return (validation.hasBtpDestination ||
|
|
514
|
+
validation.hasMcpDestination ||
|
|
515
|
+
validation.hasMcpUrl);
|
|
501
516
|
}
|
|
502
517
|
/**
|
|
503
518
|
* Check if headers indicate an MCP server request (not proxy)
|
|
@@ -514,13 +529,13 @@ function isMcpServerRequest(headers) {
|
|
|
514
529
|
return false;
|
|
515
530
|
}
|
|
516
531
|
// Check for MCP authentication headers
|
|
517
|
-
const hasSapDestination = !!getHeaderValue(headers,
|
|
518
|
-
const hasMcpDestination = !!getHeaderValue(headers,
|
|
519
|
-
const hasSapUrl = !!getHeaderValue(headers,
|
|
520
|
-
const hasSapJwtToken = !!getHeaderValue(headers,
|
|
532
|
+
const hasSapDestination = !!getHeaderValue(headers, interfaces_network_1.HEADER_SAP_DESTINATION_SERVICE);
|
|
533
|
+
const hasMcpDestination = !!getHeaderValue(headers, interfaces_network_1.HEADER_MCP_DESTINATION);
|
|
534
|
+
const hasSapUrl = !!getHeaderValue(headers, interfaces_network_1.HEADER_SAP_URL);
|
|
535
|
+
const hasSapJwtToken = !!getHeaderValue(headers, interfaces_network_1.HEADER_SAP_JWT_TOKEN);
|
|
521
536
|
// Check for proxy-specific headers
|
|
522
|
-
const hasBtpDestination = !!getHeaderValue(headers,
|
|
523
|
-
const hasMcpUrl = !!getHeaderValue(headers,
|
|
537
|
+
const hasBtpDestination = !!getHeaderValue(headers, interfaces_network_1.HEADER_BTP_DESTINATION);
|
|
538
|
+
const hasMcpUrl = !!getHeaderValue(headers, interfaces_network_1.HEADER_MCP_URL);
|
|
524
539
|
// MCP server request has MCP auth headers but no proxy headers
|
|
525
540
|
const hasMcpAuthHeaders = hasSapDestination || hasMcpDestination || hasSapUrl || hasSapJwtToken;
|
|
526
541
|
const hasProxyHeaders = hasBtpDestination || hasMcpUrl;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Validates and prioritizes authentication headers for MCP ABAP ADT servers
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
6
|
+
export { isMcpServerRequest, isProxyRequest, type ProxyHeaderValidationResult, validateAuthHeaders, validateProxyHeaders, } from './headerValidator';
|
|
7
7
|
export * from './types';
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
19
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.
|
|
22
|
+
exports.validateProxyHeaders = exports.validateAuthHeaders = exports.isProxyRequest = exports.isMcpServerRequest = void 0;
|
|
23
23
|
var headerValidator_1 = require("./headerValidator");
|
|
24
|
+
Object.defineProperty(exports, "isMcpServerRequest", { enumerable: true, get: function () { return headerValidator_1.isMcpServerRequest; } });
|
|
25
|
+
Object.defineProperty(exports, "isProxyRequest", { enumerable: true, get: function () { return headerValidator_1.isProxyRequest; } });
|
|
24
26
|
Object.defineProperty(exports, "validateAuthHeaders", { enumerable: true, get: function () { return headerValidator_1.validateAuthHeaders; } });
|
|
25
27
|
Object.defineProperty(exports, "validateProxyHeaders", { enumerable: true, get: function () { return headerValidator_1.validateProxyHeaders; } });
|
|
26
|
-
Object.defineProperty(exports, "isProxyRequest", { enumerable: true, get: function () { return headerValidator_1.isProxyRequest; } });
|
|
27
|
-
Object.defineProperty(exports, "isMcpServerRequest", { enumerable: true, get: function () { return headerValidator_1.isMcpServerRequest; } });
|
|
28
28
|
__exportStar(require("./types"), exports);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for header validator
|
|
3
3
|
*/
|
|
4
|
-
import type { AuthType } from '@mcp-abap-adt/interfaces';
|
|
5
|
-
import { AuthMethodPriority
|
|
4
|
+
import type { AuthType, IHeaderValidationResult, IValidatedAuthConfig } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
5
|
+
import { AuthMethodPriority } from '@mcp-abap-adt/interfaces-auth-sap';
|
|
6
6
|
export type { AuthType };
|
|
7
7
|
export { AuthMethodPriority };
|
|
8
8
|
export type ValidatedAuthConfig = IValidatedAuthConfig;
|
package/dist/types.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AuthMethodPriority = void 0;
|
|
7
|
-
const
|
|
8
|
-
Object.defineProperty(exports, "AuthMethodPriority", { enumerable: true, get: function () { return
|
|
7
|
+
const interfaces_auth_sap_1 = require("@mcp-abap-adt/interfaces-auth-sap");
|
|
8
|
+
Object.defineProperty(exports, "AuthMethodPriority", { enumerable: true, get: function () { return interfaces_auth_sap_1.AuthMethodPriority; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/header-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Header validator for MCP ABAP ADT - validates and prioritizes authentication headers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"dist",
|
|
9
9
|
"README.md",
|
|
10
10
|
"CHANGELOG.md",
|
|
11
|
-
"LICENSE"
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"COPYING"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
|
14
15
|
"abap",
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"abap-adt"
|
|
22
23
|
],
|
|
23
24
|
"author": "Oleksii Kyslytsia <oleksij.kyslytsja@gmail.com>",
|
|
24
|
-
"license": "
|
|
25
|
+
"license": "LGPL-3.0-only",
|
|
25
26
|
"homepage": "https://github.com/fr0ster/mcp-abap-adt-header-validator#readme",
|
|
26
27
|
"bugs": {
|
|
27
28
|
"url": "https://github.com/fr0ster/mcp-abap-adt-header-validator/issues"
|
|
@@ -34,8 +35,12 @@
|
|
|
34
35
|
"access": "public"
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
|
38
|
+
"chrono": "./tools/version-stats.sh",
|
|
37
39
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
38
|
-
"
|
|
40
|
+
"lint": "npx biome check --write src",
|
|
41
|
+
"lint:check": "npx biome check src",
|
|
42
|
+
"format": "npx biome format --write src",
|
|
43
|
+
"build": "npm run --silent clean && npx biome check src --diagnostic-level=error && npx tsc -p tsconfig.json",
|
|
39
44
|
"build:fast": "npx tsc -p tsconfig.json",
|
|
40
45
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
41
46
|
"test:check": "npx tsc --noEmit",
|
|
@@ -45,9 +50,12 @@
|
|
|
45
50
|
"node": ">=18.0.0"
|
|
46
51
|
},
|
|
47
52
|
"dependencies": {
|
|
48
|
-
"@mcp-abap-adt/interfaces": "^
|
|
53
|
+
"@mcp-abap-adt/interfaces-auth": "^1.2.0",
|
|
54
|
+
"@mcp-abap-adt/interfaces-auth-sap": "^1.0.0",
|
|
55
|
+
"@mcp-abap-adt/interfaces-network": "^2.0.0"
|
|
49
56
|
},
|
|
50
57
|
"devDependencies": {
|
|
58
|
+
"@biomejs/biome": "^2.3.10",
|
|
51
59
|
"@types/jest": "^30.0.0",
|
|
52
60
|
"@types/node": "^24.2.1",
|
|
53
61
|
"jest": "^30.2.0",
|