@mcp-abap-adt/header-validator 0.1.4 → 0.1.7

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 CHANGED
@@ -5,7 +5,49 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.4] - 2024-12-04
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.7] - 2025-01-XX
11
+
12
+ ### Added
13
+ - **Header Sets Documentation**: Added comprehensive documentation section on header sets in `USAGE.md`
14
+ - **Set 1: Basic Authentication Set** - Documents that `x-sap-login`, `x-sap-password`, and `x-sap-auth-type: basic` must be provided together
15
+ - **Set 2: UAA Refresh Token Set** - Documents that UAA headers (`x-sap-uaa-url`, `x-sap-uaa-client-id`, `x-sap-uaa-client-secret`) must be provided together when used (optional)
16
+ - **Set 3: Direct JWT Authentication** - Documents minimal set requirements for JWT authentication
17
+ - Includes validation rules, examples, and error scenarios for each set
18
+
19
+ ### Changed
20
+ - **Header Constants Migration**: Replaced all hardcoded header strings with constants from `@mcp-abap-adt/interfaces`
21
+ - Updated `@mcp-abap-adt/interfaces` dependency from `^0.1.1` to `^0.1.2`
22
+ - All header references now use constants (e.g., `HEADER_SAP_LOGIN`, `HEADER_SAP_PASSWORD`, `HEADER_SAP_AUTH_TYPE`, etc.)
23
+ - Improved type safety and consistency across packages
24
+
25
+ ### Fixed
26
+ - **Basic Auth Validation**: Enhanced validation to ensure `x-sap-login` and `x-sap-password` are provided together as a cohesive set
27
+ - Validation now checks if either header is present, both must be present
28
+ - Improved error messages for missing basic auth headers
29
+ - **UAA Headers Validation**: Enhanced validation for UAA refresh token headers
30
+ - Added warning when UAA headers are partially provided
31
+ - Clarified that UAA headers are optional and only used for token refresh
32
+ - UAA headers do not affect authorization validation
33
+
34
+ ### Technical
35
+ - **Test Updates**: Updated all tests to use header constants instead of hardcoded strings
36
+ - **Jest Configuration**: Updated Jest to version 30.2.0 and ts-jest to 29.2.5 (aligned with auth-providers package)
37
+ - **Dependencies**: Added `jest-util@^30.2.0` as dev dependency to resolve Jest compatibility issues
38
+
39
+ ## [0.1.6] - 2025-12-05
40
+
41
+ ### Changed
42
+ - **Version Alignment**: Updated `@mcp-abap-adt/interfaces` dependency from `^0.1.0` to `^0.1.1`
43
+
44
+ ## [0.1.5] - 2025-12-05
45
+
46
+ ### Changed
47
+ - **Dependencies Update**: Updated dependencies and added `.npmrc` configuration
48
+ - Added `.npmrc` with `prefer-online=true` to ensure packages are fetched from npm registry
49
+
50
+ ## [0.1.4] - 2025-12-04
9
51
 
10
52
  ### Added
11
53
  - **Interfaces Package Integration**: Migrated to use `@mcp-abap-adt/interfaces` package for all interface definitions
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Header validator for MCP ABAP ADT authentication headers
3
3
  *
4
- * Validates and prioritizes authentication headers according to:
5
- * 1. Destination-based auth (x-mcp-destination) - highest priority
4
+ * Provides validation functions for different contexts:
5
+ * - MCP server headers: x-sap-destination, x-mcp-destination, x-sap-url, x-sap-jwt-token, etc.
6
+ * - Proxy headers: x-btp-destination, x-mcp-destination, x-mcp-url
7
+ *
8
+ * MCP authentication headers are validated and prioritized according to:
9
+ * 1. Destination-based auth (x-sap-destination, x-mcp-destination) - highest priority
6
10
  * 2. Direct JWT token (x-sap-jwt-token) - medium priority
7
11
  * 3. Basic auth (x-sap-login + x-sap-password) - lowest priority
8
12
  */
@@ -15,3 +19,52 @@ import type { HeaderValidationResult } from './types';
15
19
  * @returns Validation result with prioritized authentication configuration
16
20
  */
17
21
  export declare function validateAuthHeaders(headers?: IncomingHttpHeaders): HeaderValidationResult;
22
+ /**
23
+ * Proxy Header Validation
24
+ *
25
+ * Functions to validate proxy-specific headers (x-btp-destination, x-mcp-destination, x-mcp-url)
26
+ * These are separate from MCP authentication headers and used for proxy routing decisions.
27
+ */
28
+ export interface ProxyHeaderValidationResult {
29
+ isValid: boolean;
30
+ hasBtpDestination: boolean;
31
+ hasMcpDestination: boolean;
32
+ hasMcpUrl: boolean;
33
+ errors: string[];
34
+ warnings: string[];
35
+ }
36
+ /**
37
+ * Validate proxy routing headers
38
+ *
39
+ * Checks for proxy-specific headers:
40
+ * - x-btp-destination: BTP Cloud authorization destination
41
+ * - x-mcp-destination: SAP ABAP connection destination
42
+ * - x-mcp-url: Direct MCP server URL
43
+ *
44
+ * @param headers HTTP headers
45
+ * @returns Validation result indicating which proxy headers are present
46
+ */
47
+ export declare function validateProxyHeaders(headers?: IncomingHttpHeaders): ProxyHeaderValidationResult;
48
+ /**
49
+ * Check if headers indicate a proxy request
50
+ *
51
+ * A request is considered a proxy request if it has at least one of:
52
+ * - x-btp-destination
53
+ * - x-mcp-destination
54
+ * - x-mcp-url
55
+ *
56
+ * @param headers HTTP headers
57
+ * @returns true if headers indicate a proxy request
58
+ */
59
+ export declare function isProxyRequest(headers?: IncomingHttpHeaders): boolean;
60
+ /**
61
+ * Check if headers indicate an MCP server request (not proxy)
62
+ *
63
+ * An MCP server request has MCP authentication headers but no proxy headers:
64
+ * - Has x-sap-destination, x-mcp-destination, x-sap-url, x-sap-jwt-token, etc.
65
+ * - Does NOT have x-btp-destination or x-mcp-url (proxy-specific)
66
+ *
67
+ * @param headers HTTP headers
68
+ * @returns true if headers indicate an MCP server request
69
+ */
70
+ export declare function isMcpServerRequest(headers?: IncomingHttpHeaders): boolean;
@@ -2,13 +2,20 @@
2
2
  /**
3
3
  * Header validator for MCP ABAP ADT authentication headers
4
4
  *
5
- * Validates and prioritizes authentication headers according to:
6
- * 1. Destination-based auth (x-mcp-destination) - highest priority
5
+ * Provides validation functions for different contexts:
6
+ * - MCP server headers: x-sap-destination, x-mcp-destination, x-sap-url, x-sap-jwt-token, etc.
7
+ * - Proxy headers: x-btp-destination, x-mcp-destination, x-mcp-url
8
+ *
9
+ * MCP authentication headers are validated and prioritized according to:
10
+ * 1. Destination-based auth (x-sap-destination, x-mcp-destination) - highest priority
7
11
  * 2. Direct JWT token (x-sap-jwt-token) - medium priority
8
12
  * 3. Basic auth (x-sap-login + x-sap-password) - lowest priority
9
13
  */
10
14
  Object.defineProperty(exports, "__esModule", { value: true });
11
15
  exports.validateAuthHeaders = validateAuthHeaders;
16
+ exports.validateProxyHeaders = validateProxyHeaders;
17
+ exports.isProxyRequest = isProxyRequest;
18
+ exports.isMcpServerRequest = isMcpServerRequest;
12
19
  const interfaces_1 = require("@mcp-abap-adt/interfaces");
13
20
  /**
14
21
  * Extract header value (handles array values)
@@ -48,47 +55,47 @@ function isValidUrl(url) {
48
55
  */
49
56
  function validateSapDestinationAuth(headers) {
50
57
  // Check both lowercase and original case (Node.js normalizes to lowercase, but check both for safety)
51
- const destinationRaw = headers['x-sap-destination'] || headers['X-SAP-Destination'];
58
+ const destinationRaw = headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE.toLowerCase()] || headers[interfaces_1.HEADER_SAP_DESTINATION_SERVICE];
52
59
  if (!destinationRaw) {
53
60
  return null;
54
61
  }
55
- const destination = getHeaderValue(headers, 'x-sap-destination');
62
+ const destination = getHeaderValue(headers, interfaces_1.HEADER_SAP_DESTINATION_SERVICE);
56
63
  const errors = [];
57
64
  const warnings = [];
58
65
  // Validate destination name (check if empty after trim)
59
66
  if (!destination || destination.length === 0) {
60
- errors.push('x-sap-destination header is empty');
67
+ errors.push(`${interfaces_1.HEADER_SAP_DESTINATION_SERVICE} header is empty`);
61
68
  return {
62
69
  priority: interfaces_1.AuthMethodPriority.NONE,
63
- authType: 'jwt', // SAP destination always uses JWT
70
+ authType: interfaces_1.AUTH_TYPE_JWT, // SAP destination always uses JWT
64
71
  sapUrl: '', // URL will be loaded from destination
65
72
  errors,
66
73
  warnings,
67
74
  };
68
75
  }
69
76
  // Extract optional SAP client
70
- const sapClient = getHeaderValue(headers, 'x-sap-client');
77
+ const sapClient = getHeaderValue(headers, interfaces_1.HEADER_SAP_CLIENT);
71
78
  // Optional: x-sap-login and x-sap-password (for cloud systems)
72
- const username = getHeaderValue(headers, 'x-sap-login');
73
- const password = getHeaderValue(headers, 'x-sap-password');
79
+ const username = getHeaderValue(headers, interfaces_1.HEADER_SAP_LOGIN);
80
+ const password = getHeaderValue(headers, interfaces_1.HEADER_SAP_PASSWORD);
74
81
  // Warning if x-sap-url is provided (URL comes from destination, not header)
75
- const sapUrl = getHeaderValue(headers, 'x-sap-url');
82
+ const sapUrl = getHeaderValue(headers, interfaces_1.HEADER_SAP_URL);
76
83
  if (sapUrl) {
77
- warnings.push('x-sap-url is ignored when x-sap-destination is present (URL is loaded from destination service key or .env file)');
84
+ warnings.push(`${interfaces_1.HEADER_SAP_URL} is ignored when ${interfaces_1.HEADER_SAP_DESTINATION_SERVICE} is present (URL is loaded from destination service key or .env file)`);
78
85
  }
79
86
  // Warning if direct JWT token is also provided (destination takes priority)
80
- const jwtToken = getHeaderValue(headers, 'x-sap-jwt-token');
87
+ const jwtToken = getHeaderValue(headers, interfaces_1.HEADER_SAP_JWT_TOKEN);
81
88
  if (jwtToken) {
82
- warnings.push('x-sap-jwt-token is ignored when x-sap-destination is present (destination-based auth takes priority)');
89
+ warnings.push(`${interfaces_1.HEADER_SAP_JWT_TOKEN} is ignored when ${interfaces_1.HEADER_SAP_DESTINATION_SERVICE} is present (destination-based auth takes priority)`);
83
90
  }
84
91
  // Warning if auth-type is provided (not needed for x-sap-destination)
85
- const authType = getHeaderValue(headers, 'x-sap-auth-type');
92
+ const authType = getHeaderValue(headers, interfaces_1.HEADER_SAP_AUTH_TYPE);
86
93
  if (authType) {
87
- warnings.push('x-sap-auth-type is ignored when x-sap-destination is present (always uses JWT)');
94
+ warnings.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} is ignored when ${interfaces_1.HEADER_SAP_DESTINATION_SERVICE} is present (always uses JWT)`);
88
95
  }
89
96
  return {
90
97
  priority: interfaces_1.AuthMethodPriority.SAP_DESTINATION,
91
- authType: 'jwt', // Always JWT for x-sap-destination
98
+ authType: interfaces_1.AUTH_TYPE_JWT, // Always JWT for x-sap-destination
92
99
  sapUrl: '', // URL will be loaded from destination (service key or .env)
93
100
  sapClient,
94
101
  destination,
@@ -106,19 +113,19 @@ function validateSapDestinationAuth(headers) {
106
113
  */
107
114
  function validateMcpDestinationAuth(headers, sapUrl) {
108
115
  // Check both lowercase and original case (Node.js normalizes to lowercase, but check both for safety)
109
- const destinationRaw = headers['x-mcp-destination'] || headers['X-MCP-Destination'];
116
+ const destinationRaw = headers[interfaces_1.HEADER_MCP_DESTINATION.toLowerCase()] || headers[interfaces_1.HEADER_MCP_DESTINATION];
110
117
  if (!destinationRaw) {
111
118
  return null;
112
119
  }
113
- const destination = getHeaderValue(headers, 'x-mcp-destination');
120
+ const destination = getHeaderValue(headers, interfaces_1.HEADER_MCP_DESTINATION);
114
121
  const errors = [];
115
122
  const warnings = [];
116
123
  // Validate destination name (check if empty after trim)
117
124
  if (!destination || destination.length === 0) {
118
- errors.push('x-mcp-destination header is empty');
125
+ errors.push(`${interfaces_1.HEADER_MCP_DESTINATION} header is empty`);
119
126
  return {
120
127
  priority: interfaces_1.AuthMethodPriority.NONE,
121
- authType: 'jwt', // MCP destination always uses JWT
128
+ authType: interfaces_1.AUTH_TYPE_JWT, // MCP destination always uses JWT
122
129
  sapUrl: '', // URL will be loaded from destination
123
130
  errors,
124
131
  warnings,
@@ -126,23 +133,23 @@ function validateMcpDestinationAuth(headers, sapUrl) {
126
133
  }
127
134
  // Warning if x-sap-url is provided (URL comes from destination, not header)
128
135
  if (sapUrl) {
129
- warnings.push('x-sap-url is ignored when x-mcp-destination is present (URL is loaded from destination service key or .env file)');
136
+ warnings.push(`${interfaces_1.HEADER_SAP_URL} is ignored when ${interfaces_1.HEADER_MCP_DESTINATION} is present (URL is loaded from destination service key or .env file)`);
130
137
  }
131
138
  // Warning if x-sap-auth-type is provided (not needed for x-mcp-destination)
132
- const authType = getHeaderValue(headers, 'x-sap-auth-type');
139
+ const authType = getHeaderValue(headers, interfaces_1.HEADER_SAP_AUTH_TYPE);
133
140
  if (authType) {
134
- warnings.push('x-sap-auth-type is ignored when x-mcp-destination is present (always uses JWT)');
141
+ warnings.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} is ignored when ${interfaces_1.HEADER_MCP_DESTINATION} is present (always uses JWT)`);
135
142
  }
136
143
  // Extract optional SAP client
137
- const sapClient = getHeaderValue(headers, 'x-sap-client');
144
+ const sapClient = getHeaderValue(headers, interfaces_1.HEADER_SAP_CLIENT);
138
145
  // Warning if direct JWT token is also provided (destination takes priority)
139
- const jwtToken = getHeaderValue(headers, 'x-sap-jwt-token');
146
+ const jwtToken = getHeaderValue(headers, interfaces_1.HEADER_SAP_JWT_TOKEN);
140
147
  if (jwtToken) {
141
- warnings.push('x-sap-jwt-token is ignored when x-mcp-destination is present (destination-based auth takes priority)');
148
+ warnings.push(`${interfaces_1.HEADER_SAP_JWT_TOKEN} is ignored when ${interfaces_1.HEADER_MCP_DESTINATION} is present (destination-based auth takes priority)`);
142
149
  }
143
150
  return {
144
151
  priority: interfaces_1.AuthMethodPriority.MCP_DESTINATION,
145
- authType: 'jwt', // Always JWT for x-mcp-destination
152
+ authType: interfaces_1.AUTH_TYPE_JWT, // Always JWT for x-mcp-destination
146
153
  sapUrl: '', // URL will be loaded from destination (service key or .env)
147
154
  sapClient,
148
155
  destination,
@@ -152,12 +159,16 @@ function validateMcpDestinationAuth(headers, sapUrl) {
152
159
  }
153
160
  /**
154
161
  * Validate direct JWT authentication (medium priority)
162
+ *
163
+ * For authorization, only x-sap-jwt-token is required.
164
+ * UAA headers (x-sap-uaa-url, x-sap-uaa-client-id, x-sap-uaa-client-secret) are optional
165
+ * and only used for token refresh - they are a separate set of headers.
155
166
  */
156
167
  function validateDirectJwtAuth(headers, sapUrl, authType) {
157
- if (authType !== 'jwt' && authType !== 'xsuaa') {
168
+ if (authType !== interfaces_1.AUTH_TYPE_JWT && authType !== interfaces_1.AUTH_TYPE_XSUAA) {
158
169
  return null;
159
170
  }
160
- const jwtToken = getHeaderValue(headers, 'x-sap-jwt-token');
171
+ const jwtToken = getHeaderValue(headers, interfaces_1.HEADER_SAP_JWT_TOKEN);
161
172
  if (!jwtToken) {
162
173
  return null;
163
174
  }
@@ -165,15 +176,23 @@ function validateDirectJwtAuth(headers, sapUrl, authType) {
165
176
  const warnings = [];
166
177
  // Validate JWT token format (basic check - should start with eyJ)
167
178
  if (jwtToken.length < 10) {
168
- errors.push('x-sap-jwt-token appears to be invalid (too short)');
179
+ errors.push(`${interfaces_1.HEADER_SAP_JWT_TOKEN} appears to be invalid (too short)`);
180
+ }
181
+ // Extract optional refresh token
182
+ const refreshToken = getHeaderValue(headers, interfaces_1.HEADER_SAP_REFRESH_TOKEN);
183
+ // Extract optional UAA config (for token refresh only - separate set of headers)
184
+ // These are optional and don't affect authorization validation
185
+ const uaaUrl = getHeaderValue(headers, interfaces_1.HEADER_SAP_UAA_URL) || getHeaderValue(headers, interfaces_1.HEADER_UAA_URL);
186
+ const uaaClientId = getHeaderValue(headers, interfaces_1.HEADER_SAP_UAA_CLIENT_ID) || getHeaderValue(headers, interfaces_1.HEADER_UAA_CLIENT_ID);
187
+ const uaaClientSecret = getHeaderValue(headers, interfaces_1.HEADER_SAP_UAA_CLIENT_SECRET) || getHeaderValue(headers, interfaces_1.HEADER_UAA_CLIENT_SECRET);
188
+ // Validate UAA config completeness if any UAA header is present
189
+ if (uaaUrl || uaaClientId || uaaClientSecret) {
190
+ if (!uaaUrl || !uaaClientId || !uaaClientSecret) {
191
+ warnings.push(`UAA headers (${interfaces_1.HEADER_SAP_UAA_URL}, ${interfaces_1.HEADER_SAP_UAA_CLIENT_ID}, ${interfaces_1.HEADER_SAP_UAA_CLIENT_SECRET}) should be provided together for token refresh`);
192
+ }
169
193
  }
170
- // Extract optional tokens and UAA config
171
- const refreshToken = getHeaderValue(headers, 'x-sap-refresh-token');
172
- const uaaUrl = getHeaderValue(headers, 'x-sap-uaa-url') || getHeaderValue(headers, 'uaa-url');
173
- const uaaClientId = getHeaderValue(headers, 'x-sap-uaa-client-id') || getHeaderValue(headers, 'uaa-client-id');
174
- const uaaClientSecret = getHeaderValue(headers, 'x-sap-uaa-client-secret') || getHeaderValue(headers, 'uaa-client-secret');
175
194
  // Extract optional SAP client
176
- const sapClient = getHeaderValue(headers, 'x-sap-client');
195
+ const sapClient = getHeaderValue(headers, interfaces_1.HEADER_SAP_CLIENT);
177
196
  return {
178
197
  priority: interfaces_1.AuthMethodPriority.DIRECT_JWT,
179
198
  authType,
@@ -192,24 +211,24 @@ function validateDirectJwtAuth(headers, sapUrl, authType) {
192
211
  * Validate basic authentication (lowest priority)
193
212
  */
194
213
  function validateBasicAuth(headers, sapUrl, authType) {
195
- if (authType !== 'basic') {
214
+ if (authType !== interfaces_1.AUTH_TYPE_BASIC) {
196
215
  return null;
197
216
  }
198
- const usernameRaw = headers['x-sap-login'];
199
- const passwordRaw = headers['x-sap-password'];
217
+ const usernameRaw = headers[interfaces_1.HEADER_SAP_LOGIN.toLowerCase()] || headers[interfaces_1.HEADER_SAP_LOGIN];
218
+ const passwordRaw = headers[interfaces_1.HEADER_SAP_PASSWORD.toLowerCase()] || headers[interfaces_1.HEADER_SAP_PASSWORD];
200
219
  if (!usernameRaw || !passwordRaw) {
201
220
  return null;
202
221
  }
203
- const username = getHeaderValue(headers, 'x-sap-login');
204
- const password = getHeaderValue(headers, 'x-sap-password');
222
+ const username = getHeaderValue(headers, interfaces_1.HEADER_SAP_LOGIN);
223
+ const password = getHeaderValue(headers, interfaces_1.HEADER_SAP_PASSWORD);
205
224
  const errors = [];
206
225
  const warnings = [];
207
226
  // Validate username and password (check if empty after trim)
208
227
  if (!username || username.length === 0) {
209
- errors.push('x-sap-login header is empty');
228
+ errors.push(`${interfaces_1.HEADER_SAP_LOGIN} header is empty`);
210
229
  }
211
230
  if (!password || password.length === 0) {
212
- errors.push('x-sap-password header is empty');
231
+ errors.push(`${interfaces_1.HEADER_SAP_PASSWORD} header is empty`);
213
232
  }
214
233
  // Return config with errors if validation failed
215
234
  if (errors.length > 0) {
@@ -270,7 +289,7 @@ function validateAuthHeaders(headers) {
270
289
  }
271
290
  }
272
291
  // Check for MCP destination (doesn't require x-sap-url, URL comes from destination)
273
- const sapUrl = getHeaderValue(headers, 'x-sap-url');
292
+ const sapUrl = getHeaderValue(headers, interfaces_1.HEADER_SAP_URL);
274
293
  const mcpDestinationConfig = validateMcpDestinationAuth(headers, sapUrl);
275
294
  if (mcpDestinationConfig) {
276
295
  // MCP destination found - URL comes from destination, not header
@@ -302,7 +321,7 @@ function validateAuthHeaders(headers) {
302
321
  }
303
322
  // Validate URL format
304
323
  if (!isValidUrl(sapUrl)) {
305
- errors.push(`x-sap-url is not a valid URL: ${sapUrl}`);
324
+ errors.push(`${interfaces_1.HEADER_SAP_URL} is not a valid URL: ${sapUrl}`);
306
325
  return {
307
326
  isValid: false,
308
327
  errors,
@@ -311,16 +330,32 @@ function validateAuthHeaders(headers) {
311
330
  }
312
331
  // Try to validate authentication methods in priority order
313
332
  const configs = [];
333
+ // Check if basic auth headers are present (x-sap-login, x-sap-password)
334
+ // These should come together with x-sap-auth-type: basic
335
+ const hasSapLogin = !!getHeaderValue(headers, interfaces_1.HEADER_SAP_LOGIN);
336
+ const hasSapPassword = !!getHeaderValue(headers, interfaces_1.HEADER_SAP_PASSWORD);
337
+ const hasBasicAuthHeaders = hasSapLogin || hasSapPassword;
338
+ if (hasBasicAuthHeaders && (!hasSapLogin || !hasSapPassword)) {
339
+ errors.push(`${interfaces_1.HEADER_SAP_LOGIN} and ${interfaces_1.HEADER_SAP_PASSWORD} must be provided together`);
340
+ }
314
341
  // 3. Other auth methods require x-sap-auth-type
315
- const sapAuthType = getHeaderValue(headers, 'x-sap-auth-type');
342
+ const sapAuthType = getHeaderValue(headers, interfaces_1.HEADER_SAP_AUTH_TYPE);
316
343
  if (sapAuthType) {
317
344
  // Validate auth type
318
- const validAuthTypes = ['jwt', 'xsuaa', 'basic'];
345
+ const validAuthTypes = [interfaces_1.AUTH_TYPE_JWT, interfaces_1.AUTH_TYPE_XSUAA, interfaces_1.AUTH_TYPE_BASIC];
319
346
  const authType = sapAuthType.toLowerCase();
320
347
  if (!validAuthTypes.includes(authType)) {
321
- errors.push(`x-sap-auth-type must be one of: ${validAuthTypes.join(', ')}, got: ${sapAuthType}`);
348
+ errors.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} must be one of: ${validAuthTypes.join(', ')}, got: ${sapAuthType}`);
322
349
  }
323
350
  else {
351
+ // Check if basic auth headers are present but auth-type is not basic
352
+ if (hasBasicAuthHeaders && authType !== interfaces_1.AUTH_TYPE_BASIC) {
353
+ warnings.push(`${interfaces_1.HEADER_SAP_LOGIN} and ${interfaces_1.HEADER_SAP_PASSWORD} are present but ${interfaces_1.HEADER_SAP_AUTH_TYPE} is not "${interfaces_1.AUTH_TYPE_BASIC}"`);
354
+ }
355
+ // Check if auth-type is basic but headers are missing
356
+ if (authType === interfaces_1.AUTH_TYPE_BASIC && !hasBasicAuthHeaders) {
357
+ errors.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} is "${interfaces_1.AUTH_TYPE_BASIC}" but ${interfaces_1.HEADER_SAP_LOGIN} and ${interfaces_1.HEADER_SAP_PASSWORD} are missing`);
358
+ }
324
359
  // Only validate direct JWT and basic auth if MCP destination is not present
325
360
  // (MCP destination already handled above)
326
361
  if (!mcpDestinationConfig) {
@@ -338,20 +373,25 @@ function validateAuthHeaders(headers) {
338
373
  }
339
374
  }
340
375
  else {
341
- // No auth-type provided - check if we have MCP destination or need to error
342
- if (!mcpDestinationConfig && !sapDestinationConfig) {
343
- errors.push('x-sap-auth-type header is required when x-sap-destination and x-mcp-destination are not present');
376
+ // No auth-type provided
377
+ // If basic auth headers are present, auth-type must be basic
378
+ if (hasBasicAuthHeaders) {
379
+ errors.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} must be "${interfaces_1.AUTH_TYPE_BASIC}" when ${interfaces_1.HEADER_SAP_LOGIN} and ${interfaces_1.HEADER_SAP_PASSWORD} are present`);
380
+ }
381
+ else if (!mcpDestinationConfig && !sapDestinationConfig) {
382
+ // No auth-type and no destination - error
383
+ errors.push(`${interfaces_1.HEADER_SAP_AUTH_TYPE} header is required when ${interfaces_1.HEADER_SAP_DESTINATION_SERVICE} and ${interfaces_1.HEADER_MCP_DESTINATION} are not present`);
344
384
  }
345
385
  }
346
386
  // No valid authentication method found
347
387
  if (configs.length === 0) {
348
388
  if (sapAuthType) {
349
389
  const authType = sapAuthType.toLowerCase();
350
- if (authType === 'jwt' || authType === 'xsuaa') {
351
- errors.push('JWT authentication requires either x-sap-destination, x-mcp-destination, or x-sap-jwt-token header');
390
+ if (authType === interfaces_1.AUTH_TYPE_JWT || authType === interfaces_1.AUTH_TYPE_XSUAA) {
391
+ errors.push(`JWT authentication requires either ${interfaces_1.HEADER_SAP_DESTINATION_SERVICE}, ${interfaces_1.HEADER_MCP_DESTINATION}, or ${interfaces_1.HEADER_SAP_JWT_TOKEN} header`);
352
392
  }
353
- else if (authType === 'basic') {
354
- errors.push('Basic authentication requires x-sap-login and x-sap-password headers');
393
+ else if (authType === interfaces_1.AUTH_TYPE_BASIC) {
394
+ errors.push(`Basic authentication requires ${interfaces_1.HEADER_SAP_LOGIN} and ${interfaces_1.HEADER_SAP_PASSWORD} headers`);
355
395
  }
356
396
  }
357
397
  else {
@@ -384,3 +424,105 @@ function validateAuthHeaders(headers) {
384
424
  warnings: allWarnings,
385
425
  };
386
426
  }
427
+ /**
428
+ * Validate proxy routing headers
429
+ *
430
+ * Checks for proxy-specific headers:
431
+ * - x-btp-destination: BTP Cloud authorization destination
432
+ * - x-mcp-destination: SAP ABAP connection destination
433
+ * - x-mcp-url: Direct MCP server URL
434
+ *
435
+ * @param headers HTTP headers
436
+ * @returns Validation result indicating which proxy headers are present
437
+ */
438
+ function validateProxyHeaders(headers) {
439
+ const errors = [];
440
+ const warnings = [];
441
+ if (!headers) {
442
+ return {
443
+ isValid: false,
444
+ hasBtpDestination: false,
445
+ hasMcpDestination: false,
446
+ hasMcpUrl: false,
447
+ errors: [],
448
+ warnings: [],
449
+ };
450
+ }
451
+ const btpDestination = getHeaderValue(headers, interfaces_1.HEADER_BTP_DESTINATION);
452
+ const mcpDestination = getHeaderValue(headers, interfaces_1.HEADER_MCP_DESTINATION);
453
+ const mcpUrl = getHeaderValue(headers, interfaces_1.HEADER_MCP_URL);
454
+ const hasBtpDestination = !!btpDestination;
455
+ const hasMcpDestination = !!mcpDestination;
456
+ const hasMcpUrl = !!mcpUrl;
457
+ // Validate destination names if present
458
+ if (hasBtpDestination && (!btpDestination || btpDestination.trim().length === 0)) {
459
+ errors.push(`${interfaces_1.HEADER_BTP_DESTINATION} header is empty`);
460
+ }
461
+ if (hasMcpDestination && (!mcpDestination || mcpDestination.trim().length === 0)) {
462
+ errors.push(`${interfaces_1.HEADER_MCP_DESTINATION} header is empty`);
463
+ }
464
+ // Validate mcpUrl format if present
465
+ if (hasMcpUrl) {
466
+ if (!isValidUrl(mcpUrl)) {
467
+ errors.push(`${interfaces_1.HEADER_MCP_URL} is not a valid URL: ${mcpUrl}`);
468
+ }
469
+ }
470
+ // At least one proxy header should be present for proxy routing
471
+ const hasAnyProxyHeader = hasBtpDestination || hasMcpDestination || hasMcpUrl;
472
+ if (!hasAnyProxyHeader) {
473
+ warnings.push(`No proxy headers found (${interfaces_1.HEADER_BTP_DESTINATION}, ${interfaces_1.HEADER_MCP_DESTINATION}, or ${interfaces_1.HEADER_MCP_URL})`);
474
+ }
475
+ return {
476
+ isValid: errors.length === 0,
477
+ hasBtpDestination,
478
+ hasMcpDestination,
479
+ hasMcpUrl,
480
+ errors,
481
+ warnings,
482
+ };
483
+ }
484
+ /**
485
+ * Check if headers indicate a proxy request
486
+ *
487
+ * A request is considered a proxy request if it has at least one of:
488
+ * - x-btp-destination
489
+ * - x-mcp-destination
490
+ * - x-mcp-url
491
+ *
492
+ * @param headers HTTP headers
493
+ * @returns true if headers indicate a proxy request
494
+ */
495
+ function isProxyRequest(headers) {
496
+ if (!headers) {
497
+ return false;
498
+ }
499
+ const validation = validateProxyHeaders(headers);
500
+ return validation.hasBtpDestination || validation.hasMcpDestination || validation.hasMcpUrl;
501
+ }
502
+ /**
503
+ * Check if headers indicate an MCP server request (not proxy)
504
+ *
505
+ * An MCP server request has MCP authentication headers but no proxy headers:
506
+ * - Has x-sap-destination, x-mcp-destination, x-sap-url, x-sap-jwt-token, etc.
507
+ * - Does NOT have x-btp-destination or x-mcp-url (proxy-specific)
508
+ *
509
+ * @param headers HTTP headers
510
+ * @returns true if headers indicate an MCP server request
511
+ */
512
+ function isMcpServerRequest(headers) {
513
+ if (!headers) {
514
+ return false;
515
+ }
516
+ // Check for MCP authentication headers
517
+ const hasSapDestination = !!getHeaderValue(headers, interfaces_1.HEADER_SAP_DESTINATION_SERVICE);
518
+ const hasMcpDestination = !!getHeaderValue(headers, interfaces_1.HEADER_MCP_DESTINATION);
519
+ const hasSapUrl = !!getHeaderValue(headers, interfaces_1.HEADER_SAP_URL);
520
+ const hasSapJwtToken = !!getHeaderValue(headers, interfaces_1.HEADER_SAP_JWT_TOKEN);
521
+ // Check for proxy-specific headers
522
+ const hasBtpDestination = !!getHeaderValue(headers, interfaces_1.HEADER_BTP_DESTINATION);
523
+ const hasMcpUrl = !!getHeaderValue(headers, interfaces_1.HEADER_MCP_URL);
524
+ // MCP server request has MCP auth headers but no proxy headers
525
+ const hasMcpAuthHeaders = hasSapDestination || hasMcpDestination || hasSapUrl || hasSapJwtToken;
526
+ const hasProxyHeaders = hasBtpDestination || hasMcpUrl;
527
+ return hasMcpAuthHeaders && !hasProxyHeaders;
528
+ }
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 { validateAuthHeaders } from './headerValidator';
6
+ export { validateAuthHeaders, validateProxyHeaders, isProxyRequest, isMcpServerRequest, type ProxyHeaderValidationResult, } from './headerValidator';
7
7
  export * from './types';
package/dist/index.js CHANGED
@@ -19,7 +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.validateAuthHeaders = void 0;
22
+ exports.isMcpServerRequest = exports.isProxyRequest = exports.validateProxyHeaders = exports.validateAuthHeaders = void 0;
23
23
  var headerValidator_1 = require("./headerValidator");
24
24
  Object.defineProperty(exports, "validateAuthHeaders", { enumerable: true, get: function () { return headerValidator_1.validateAuthHeaders; } });
25
+ 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; } });
25
28
  __exportStar(require("./types"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/header-validator",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
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",
@@ -37,7 +37,7 @@
37
37
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
38
38
  "build": "npm run clean --silent && npx tsc -p tsconfig.json",
39
39
  "build:fast": "npx tsc -p tsconfig.json",
40
- "test": "jest",
40
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
41
41
  "test:check": "npx tsc --noEmit",
42
42
  "prepublishOnly": "npm run build"
43
43
  },
@@ -45,12 +45,13 @@
45
45
  "node": ">=18.0.0"
46
46
  },
47
47
  "dependencies": {
48
- "@mcp-abap-adt/interfaces": "^0.1.0"
48
+ "@mcp-abap-adt/interfaces": "^0.1.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/jest": "^30.0.0",
52
52
  "@types/node": "^24.2.1",
53
53
  "jest": "^30.2.0",
54
+ "jest-util": "^30.2.0",
54
55
  "ts-jest": "^29.2.5",
55
56
  "typescript": "^5.9.2"
56
57
  }