@mcp-abap-adt/auth-stores 0.1.5 → 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 +76 -0
- package/README.md +91 -5
- package/dist/parsers/abap/AbapServiceKeyParser.d.ts +7 -0
- package/dist/parsers/abap/AbapServiceKeyParser.js +20 -2
- package/dist/parsers/xsuaa/XsuaaServiceKeyParser.d.ts +7 -0
- package/dist/parsers/xsuaa/XsuaaServiceKeyParser.js +21 -7
- package/dist/storage/abap/envLoader.d.ts +3 -1
- package/dist/storage/abap/envLoader.js +10 -1
- package/dist/storage/abap/tokenStorage.d.ts +3 -1
- package/dist/storage/abap/tokenStorage.js +14 -3
- package/dist/storage/xsuaa/xsuaaEnvLoader.d.ts +3 -1
- package/dist/storage/xsuaa/xsuaaEnvLoader.js +13 -2
- package/dist/storage/xsuaa/xsuaaTokenStorage.d.ts +3 -1
- package/dist/storage/xsuaa/xsuaaTokenStorage.js +14 -3
- package/dist/stores/abap/AbapServiceKeyStore.d.ts +4 -2
- package/dist/stores/abap/AbapServiceKeyStore.js +68 -8
- package/dist/stores/abap/AbapSessionStore.d.ts +6 -2
- package/dist/stores/abap/AbapSessionStore.js +62 -4
- package/dist/stores/abap/SafeAbapSessionStore.d.ts +7 -2
- package/dist/stores/abap/SafeAbapSessionStore.js +61 -15
- package/dist/stores/btp/BtpServiceKeyStore.d.ts +4 -2
- package/dist/stores/btp/BtpServiceKeyStore.js +16 -2
- package/dist/stores/btp/BtpSessionStore.d.ts +4 -2
- package/dist/stores/btp/BtpSessionStore.js +56 -8
- package/dist/stores/btp/SafeBtpSessionStore.d.ts +7 -2
- package/dist/stores/btp/SafeBtpSessionStore.js +58 -19
- package/dist/stores/xsuaa/SafeXsuaaSessionStore.d.ts +7 -2
- package/dist/stores/xsuaa/SafeXsuaaSessionStore.js +58 -19
- package/dist/stores/xsuaa/XsuaaServiceKeyStore.d.ts +4 -2
- package/dist/stores/xsuaa/XsuaaServiceKeyStore.js +16 -2
- package/dist/stores/xsuaa/XsuaaSessionStore.d.ts +4 -2
- package/dist/stores/xsuaa/XsuaaSessionStore.js +56 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.7] - 2025-12-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Broker Usage Tests**: Added comprehensive test suites for broker usage scenarios
|
|
14
|
+
- Tests verify stores work correctly when used as in `AuthBroker` (without `saveSession`)
|
|
15
|
+
- Tests cover `setConnectionConfig` and `setAuthorizationConfig` on empty stores
|
|
16
|
+
- Tests verify session creation and updates in broker flow scenarios
|
|
17
|
+
- Test files: `*SessionStore.broker.test.ts` for all store types
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Session Store Initialization**: File-based session stores now automatically create directory in constructor
|
|
21
|
+
- `AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore` create directory if it doesn't exist
|
|
22
|
+
- Stores are ready to use immediately after construction
|
|
23
|
+
- Directory creation is logged at debug level
|
|
24
|
+
- **Session Creation Logic**: Session stores now automatically create sessions when calling `setConnectionConfig` or `setAuthorizationConfig`
|
|
25
|
+
- No need to call `saveSession` first - stores handle session creation internally
|
|
26
|
+
- `setConnectionConfig` creates new session if none exists (requires `serviceUrl` for ABAP)
|
|
27
|
+
- `setAuthorizationConfig` creates new session if none exists (for BTP/XSUAA, `mcpUrl` is optional)
|
|
28
|
+
- For ABAP: `setAuthorizationConfig` requires existing `serviceUrl` (from `setConnectionConfig` or throws error)
|
|
29
|
+
- This matches how `AuthBroker` uses stores - stores are now fully ready after construction
|
|
30
|
+
- **Token Validation**: Updated validation to allow empty string for `jwtToken` in BTP/XSUAA stores
|
|
31
|
+
- Empty token is allowed (can be set later via `setConnectionConfig`)
|
|
32
|
+
- Only `undefined` or `null` tokens are rejected
|
|
33
|
+
- This enables creating sessions with authorization config first, then adding connection config
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- **getConnectionConfig**: Fixed to allow empty string tokens (not just non-empty strings)
|
|
37
|
+
- Returns `null` only if token is `undefined` or `null`
|
|
38
|
+
- Empty string tokens are valid (can be set later)
|
|
39
|
+
- **setConnectionConfig Updates**: Fixed to preserve existing token when updating connection config
|
|
40
|
+
- Only updates `jwtToken` if `authorizationToken` is provided in config
|
|
41
|
+
- Preserves existing token if `authorizationToken` is `undefined`
|
|
42
|
+
- **Safe Session Stores**: Fixed session creation in `setConnectionConfig` and `setAuthorizationConfig`
|
|
43
|
+
- Now saves directly to Map (internal format) instead of calling `saveSession` with wrong format
|
|
44
|
+
- This fixes issues where `mcpUrl`/`serviceUrl` was not being saved correctly
|
|
45
|
+
- **loadXsuaaEnvFile**: Fixed to allow empty string for `jwtToken` (can be set later)
|
|
46
|
+
- Only rejects `undefined` or `null` tokens
|
|
47
|
+
- Empty string tokens are valid and can be set later via `setConnectionConfig`
|
|
48
|
+
- **testLogger**: Fixed to not output by default in test environment
|
|
49
|
+
- Now requires explicit enable via `DEBUG_AUTH_STORES=true` or `DEBUG=true`
|
|
50
|
+
- No longer enables logging automatically when `NODE_ENV === 'test'`
|
|
51
|
+
|
|
52
|
+
## [0.1.6] - 2025-12-08
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- **Comprehensive Logging System**: Added optional logging support throughout the package
|
|
56
|
+
- All stores, parsers, and storage functions now accept optional `ILogger` parameter
|
|
57
|
+
- Logging shows detailed information: file paths, file sizes, parsed data structure, operation results
|
|
58
|
+
- Logging works by default in test environment (`NODE_ENV === 'test'`)
|
|
59
|
+
- Controlled via environment variables: `DEBUG_AUTH_STORES`, `LOG_LEVEL`
|
|
60
|
+
- **Test Logger Helper**: Added `createTestLogger` helper for tests
|
|
61
|
+
- Respects `DEBUG_AUTH_STORES`, `DEBUG`, and `LOG_LEVEL` environment variables
|
|
62
|
+
- Formats messages and meta into single-line output
|
|
63
|
+
- Shows stack traces for debug and error levels
|
|
64
|
+
- **Logging in Parsers**: Added logging to `AbapServiceKeyParser` and `XsuaaServiceKeyParser`
|
|
65
|
+
- Logs parsing operations, validation checks, and results
|
|
66
|
+
- Shows structure of parsed data (keys, fields, validation results)
|
|
67
|
+
- **Logging in Storage**: Added logging to all storage functions
|
|
68
|
+
- `loadEnvFile`, `saveTokenToEnv` (ABAP)
|
|
69
|
+
- `loadXsuaaEnvFile`, `saveXsuaaTokenToEnv` (XSUAA)
|
|
70
|
+
- Logs file operations: reading, writing, file sizes, preserved variables
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
- **Store Constructors**: All stores now accept optional `log?: ILogger` parameter
|
|
74
|
+
- `AbapServiceKeyStore`, `BtpServiceKeyStore`, `XsuaaServiceKeyStore`
|
|
75
|
+
- `AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore`
|
|
76
|
+
- `SafeAbapSessionStore`, `SafeBtpSessionStore`, `SafeXsuaaSessionStore`
|
|
77
|
+
- **Parser Constructors**: Parsers now accept optional `log?: ILogger` parameter
|
|
78
|
+
- `AbapServiceKeyParser`, `XsuaaServiceKeyParser`
|
|
79
|
+
- **Storage Functions**: Storage functions now accept optional `log?: ILogger` parameter
|
|
80
|
+
- All storage functions pass logger through to enable detailed logging
|
|
81
|
+
- **Logging Format**: All log messages are concise, single-line strings with embedded key data
|
|
82
|
+
- Example: `Reading service key file: /path/to/file.json`
|
|
83
|
+
- Example: `File read successfully, size: 121 bytes, keys: uaa`
|
|
84
|
+
- Example: `Session saved: token(2263 chars), hasRefreshToken(true), sapUrl(https://...)`
|
|
85
|
+
|
|
10
86
|
## [0.1.5] - 2025-12-07
|
|
11
87
|
|
|
12
88
|
### Changed
|
package/README.md
CHANGED
|
@@ -145,11 +145,12 @@ All stores accept a single directory path in the constructor:
|
|
|
145
145
|
// Single directory path
|
|
146
146
|
const store = new BtpServiceKeyStore('/path/to/service-keys');
|
|
147
147
|
|
|
148
|
-
//
|
|
149
|
-
const sessionStore = new AbapSessionStore('/path/to/sessions');
|
|
150
|
-
await sessionStore.saveSession('TRIAL', config); // Creates directory if needed
|
|
148
|
+
// File-based session stores automatically create directory in constructor if it doesn't exist
|
|
149
|
+
const sessionStore = new AbapSessionStore('/path/to/sessions'); // Directory created automatically
|
|
151
150
|
```
|
|
152
151
|
|
|
152
|
+
**Note**: File-based session stores (`AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore`) automatically create the directory in the constructor if it doesn't exist. Stores are ready to use immediately after construction.
|
|
153
|
+
|
|
153
154
|
### Service Key Format
|
|
154
155
|
|
|
155
156
|
**ABAP Service Key** (with nested `uaa` object):
|
|
@@ -235,6 +236,80 @@ const abapKey = await loadServiceKey('TRIAL', '/path/to/service-keys');
|
|
|
235
236
|
const xsuaaKey = await loadXSUAAServiceKey('mcp', '/path/to/service-keys');
|
|
236
237
|
```
|
|
237
238
|
|
|
239
|
+
## Debug Logging
|
|
240
|
+
|
|
241
|
+
Stores support optional logging through the `ILogger` interface. To enable detailed logging:
|
|
242
|
+
|
|
243
|
+
### Using Logger in Code
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { AbapServiceKeyStore } from '@mcp-abap-adt/auth-stores';
|
|
247
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
248
|
+
|
|
249
|
+
// Create logger (or use your own implementation)
|
|
250
|
+
const logger: ILogger = {
|
|
251
|
+
debug: (msg) => console.debug(msg),
|
|
252
|
+
info: (msg) => console.info(msg),
|
|
253
|
+
warn: (msg) => console.warn(msg),
|
|
254
|
+
error: (msg) => console.error(msg),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Pass logger to store constructor
|
|
258
|
+
const store = new AbapServiceKeyStore('/path/to/service-keys', logger);
|
|
259
|
+
const sessionStore = new AbapSessionStore('/path/to/sessions', logger);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Using Test Logger in Tests
|
|
263
|
+
|
|
264
|
+
For tests, use the `createTestLogger` helper which respects environment variables:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { createTestLogger } from './__tests__/helpers/testLogger';
|
|
268
|
+
|
|
269
|
+
// Logger will output only if DEBUG_AUTH_STORES=true is set
|
|
270
|
+
const logger = createTestLogger('MY-TEST');
|
|
271
|
+
const store = new AbapServiceKeyStore('/path/to/service-keys', logger);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Environment Variables
|
|
275
|
+
|
|
276
|
+
To enable logging in tests or when using `createTestLogger`:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Enable logging for auth stores
|
|
280
|
+
DEBUG_AUTH_STORES=true npm test
|
|
281
|
+
|
|
282
|
+
# Or enable via general DEBUG variable
|
|
283
|
+
DEBUG=true npm test
|
|
284
|
+
|
|
285
|
+
# Or include in DEBUG list
|
|
286
|
+
DEBUG=auth-stores npm test
|
|
287
|
+
|
|
288
|
+
# Set log level (debug, info, warn, error)
|
|
289
|
+
LOG_LEVEL=debug npm test
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Note**: Logging is enabled by default in test environment (`NODE_ENV === 'test'`). To disable, set `DEBUG_AUTH_STORES=false`.
|
|
293
|
+
|
|
294
|
+
Logging shows:
|
|
295
|
+
- **File operations**: Which files are read/written, file sizes, file paths
|
|
296
|
+
- **Parsing operations**: Structure of parsed data, validation results, keys found
|
|
297
|
+
- **Storage operations**: What data is saved/loaded, token lengths, refresh token presence, URLs
|
|
298
|
+
- **Errors**: Detailed error information with context
|
|
299
|
+
|
|
300
|
+
Example output with `LOG_LEVEL=debug`:
|
|
301
|
+
```
|
|
302
|
+
[TEST-STORE] [DEBUG] Reading service key file: /path/to/TRIAL.json
|
|
303
|
+
[TEST-STORE] [DEBUG] File read successfully, size: 121 bytes, keys: uaa
|
|
304
|
+
[TEST-STORE] [DEBUG] Parsed service key structure: hasUaa(true), uaaKeys(url, clientid, clientsecret)
|
|
305
|
+
[TEST-STORE] Authorization config loaded from /path/to/TRIAL.json: uaaUrl(https://...authentication...), clientId(test-client...)
|
|
306
|
+
[TEST-STORE] [DEBUG] Reading env file: /path/to/TRIAL.env
|
|
307
|
+
[TEST-STORE] [DEBUG] Env file read successfully, size: 245 bytes
|
|
308
|
+
[TEST-STORE] Session loaded for TRIAL: token(2263 chars), hasRefreshToken(true), sapUrl(https://...abap...)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Note**: Logging only works when a logger is explicitly provided. Stores will not output anything to console if no logger is passed.
|
|
312
|
+
|
|
238
313
|
## Testing
|
|
239
314
|
|
|
240
315
|
The package includes both unit tests (with mocked file system) and integration tests (with real files).
|
|
@@ -281,12 +356,23 @@ Integration tests will skip if `test-config.yaml` is not configured or contains
|
|
|
281
356
|
|
|
282
357
|
- All stores implement `IServiceKeyStore` or `ISessionStore` interfaces from `@mcp-abap-adt/auth-broker`
|
|
283
358
|
- Stores accept a single directory path in constructor
|
|
284
|
-
- File-based stores automatically create directories
|
|
359
|
+
- File-based session stores automatically create directories in constructor if they don't exist
|
|
360
|
+
- Session stores automatically create sessions when calling `setConnectionConfig` or `setAuthorizationConfig` (no need to call `saveSession` first)
|
|
285
361
|
- In-memory stores (`Safe*SessionStore`) don't persist data to disk
|
|
286
362
|
|
|
363
|
+
### Session Store Behavior
|
|
364
|
+
|
|
365
|
+
Session stores are designed to work seamlessly with `AuthBroker`:
|
|
366
|
+
|
|
367
|
+
- **Ready after construction**: File-based stores create directory automatically, stores are ready to use immediately
|
|
368
|
+
- **Automatic session creation**: Calling `setConnectionConfig` or `setAuthorizationConfig` on an empty store creates a new session
|
|
369
|
+
- **ABAP stores**: Require `serviceUrl` when creating new session via `setConnectionConfig` or `setAuthorizationConfig`
|
|
370
|
+
- **BTP/XSUAA stores**: `mcpUrl` is optional - can create session with authorization config first, then add connection config
|
|
371
|
+
- **Token updates**: `setConnectionConfig` updates token if provided, preserves existing token if not provided
|
|
372
|
+
|
|
287
373
|
## Dependencies
|
|
288
374
|
|
|
289
|
-
- `@mcp-abap-adt/
|
|
375
|
+
- `@mcp-abap-adt/interfaces` (^0.1.3) - Interface definitions (`IServiceKeyStore`, `ISessionStore`, `IConfig`, `IConnectionConfig`, `IAuthorizationConfig`, `ILogger`)
|
|
290
376
|
- `dotenv` - Environment variable parsing
|
|
291
377
|
|
|
292
378
|
## License
|
|
@@ -15,10 +15,17 @@
|
|
|
15
15
|
* ...
|
|
16
16
|
* }
|
|
17
17
|
*/
|
|
18
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
18
19
|
/**
|
|
19
20
|
* Parser for standard ABAP service key format
|
|
20
21
|
*/
|
|
21
22
|
export declare class AbapServiceKeyParser {
|
|
23
|
+
private log?;
|
|
24
|
+
/**
|
|
25
|
+
* Create a new AbapServiceKeyParser instance
|
|
26
|
+
* @param log Optional logger for logging operations
|
|
27
|
+
*/
|
|
28
|
+
constructor(log?: ILogger);
|
|
22
29
|
/**
|
|
23
30
|
* Check if this parser can handle the given raw service key data
|
|
24
31
|
* @param rawData Raw JSON data from service key file
|
|
@@ -22,13 +22,23 @@ exports.AbapServiceKeyParser = void 0;
|
|
|
22
22
|
* Parser for standard ABAP service key format
|
|
23
23
|
*/
|
|
24
24
|
class AbapServiceKeyParser {
|
|
25
|
+
log;
|
|
26
|
+
/**
|
|
27
|
+
* Create a new AbapServiceKeyParser instance
|
|
28
|
+
* @param log Optional logger for logging operations
|
|
29
|
+
*/
|
|
30
|
+
constructor(log) {
|
|
31
|
+
this.log = log;
|
|
32
|
+
}
|
|
25
33
|
/**
|
|
26
34
|
* Check if this parser can handle the given raw service key data
|
|
27
35
|
* @param rawData Raw JSON data from service key file
|
|
28
36
|
* @returns true if data has nested uaa object, false otherwise
|
|
29
37
|
*/
|
|
30
38
|
canParse(rawData) {
|
|
31
|
-
|
|
39
|
+
const result = rawData && typeof rawData === 'object' && rawData.uaa && typeof rawData.uaa === 'object';
|
|
40
|
+
this.log?.debug(`canParse check: hasUaa(${!!rawData?.uaa}), result(${result})`);
|
|
41
|
+
return result;
|
|
32
42
|
}
|
|
33
43
|
/**
|
|
34
44
|
* Parse raw service key data
|
|
@@ -37,13 +47,21 @@ class AbapServiceKeyParser {
|
|
|
37
47
|
* @throws Error if data cannot be parsed or is invalid
|
|
38
48
|
*/
|
|
39
49
|
parse(rawData) {
|
|
50
|
+
this.log?.debug(`Parsing ABAP service key: hasUaa(${!!rawData?.uaa}), keys(${rawData ? Object.keys(rawData).join(', ') : 'none'})`);
|
|
40
51
|
if (!this.canParse(rawData)) {
|
|
52
|
+
this.log?.error(`Service key does not match ABAP format: missing uaa object`);
|
|
41
53
|
throw new Error('Service key does not match ABAP format (missing uaa object)');
|
|
42
54
|
}
|
|
43
55
|
// Validate UAA configuration
|
|
44
|
-
|
|
56
|
+
const hasUrl = !!rawData.uaa.url;
|
|
57
|
+
const hasClientId = !!rawData.uaa.clientid;
|
|
58
|
+
const hasClientSecret = !!rawData.uaa.clientsecret;
|
|
59
|
+
this.log?.debug(`UAA validation: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret})`);
|
|
60
|
+
if (!hasUrl || !hasClientId || !hasClientSecret) {
|
|
61
|
+
this.log?.error(`Service key uaa object missing required fields: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret})`);
|
|
45
62
|
throw new Error('Service key "uaa" object missing required fields: url, clientid, clientsecret');
|
|
46
63
|
}
|
|
64
|
+
this.log?.debug(`ABAP service key parsed successfully: uaaUrl(${rawData.uaa.url.substring(0, 40)}...), hasAbap(${!!rawData.abap})`);
|
|
47
65
|
return rawData;
|
|
48
66
|
}
|
|
49
67
|
}
|
|
@@ -10,10 +10,17 @@
|
|
|
10
10
|
* ...
|
|
11
11
|
* }
|
|
12
12
|
*/
|
|
13
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
13
14
|
/**
|
|
14
15
|
* Parser for direct XSUAA service key format from BTP
|
|
15
16
|
*/
|
|
16
17
|
export declare class XsuaaServiceKeyParser {
|
|
18
|
+
private log?;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new XsuaaServiceKeyParser instance
|
|
21
|
+
* @param log Optional logger for logging operations
|
|
22
|
+
*/
|
|
23
|
+
constructor(log?: ILogger);
|
|
17
24
|
/**
|
|
18
25
|
* Check if this parser can handle the given raw service key data
|
|
19
26
|
* @param rawData Raw JSON data from service key file
|
|
@@ -17,6 +17,14 @@ exports.XsuaaServiceKeyParser = void 0;
|
|
|
17
17
|
* Parser for direct XSUAA service key format from BTP
|
|
18
18
|
*/
|
|
19
19
|
class XsuaaServiceKeyParser {
|
|
20
|
+
log;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new XsuaaServiceKeyParser instance
|
|
23
|
+
* @param log Optional logger for logging operations
|
|
24
|
+
*/
|
|
25
|
+
constructor(log) {
|
|
26
|
+
this.log = log;
|
|
27
|
+
}
|
|
20
28
|
/**
|
|
21
29
|
* Check if this parser can handle the given raw service key data
|
|
22
30
|
* @param rawData Raw JSON data from service key file
|
|
@@ -24,19 +32,21 @@ class XsuaaServiceKeyParser {
|
|
|
24
32
|
*/
|
|
25
33
|
canParse(rawData) {
|
|
26
34
|
if (!rawData || typeof rawData !== 'object' || Array.isArray(rawData)) {
|
|
35
|
+
this.log?.debug(`canParse check: invalid type, result(false)`);
|
|
27
36
|
return false;
|
|
28
37
|
}
|
|
29
38
|
// Check for nested uaa object (ABAP format) - should not have it
|
|
30
39
|
if (rawData.uaa) {
|
|
40
|
+
this.log?.debug(`canParse check: has nested uaa (ABAP format), result(false)`);
|
|
31
41
|
return false;
|
|
32
42
|
}
|
|
33
43
|
// Check for required XSUAA fields at root level
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
const hasUrl = typeof rawData.url === 'string' && rawData.url.length > 0;
|
|
45
|
+
const hasClientId = typeof rawData.clientid === 'string' && rawData.clientid.length > 0;
|
|
46
|
+
const hasClientSecret = typeof rawData.clientsecret === 'string' && rawData.clientsecret.length > 0;
|
|
47
|
+
const result = hasUrl && hasClientId && hasClientSecret;
|
|
48
|
+
this.log?.debug(`canParse check: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret}), result(${result})`);
|
|
49
|
+
return result;
|
|
40
50
|
}
|
|
41
51
|
/**
|
|
42
52
|
* Parse raw service key data
|
|
@@ -45,14 +55,16 @@ class XsuaaServiceKeyParser {
|
|
|
45
55
|
* @throws Error if data cannot be parsed or is invalid
|
|
46
56
|
*/
|
|
47
57
|
parse(rawData) {
|
|
58
|
+
this.log?.debug(`Parsing XSUAA service key: hasUrl(${!!rawData?.url}), hasClientId(${!!rawData?.clientid}), hasClientSecret(${!!rawData?.clientsecret}), keys(${rawData ? Object.keys(rawData).join(', ') : 'none'})`);
|
|
48
59
|
if (!this.canParse(rawData)) {
|
|
60
|
+
this.log?.error(`Service key does not match XSUAA format: missing required fields at root level`);
|
|
49
61
|
throw new Error('Service key does not match XSUAA format (missing url, clientid, or clientsecret at root level)');
|
|
50
62
|
}
|
|
51
63
|
// Normalize to standard format
|
|
52
64
|
// For authorization (OAuth2 authorize endpoint), use 'url' (not 'apiurl')
|
|
53
65
|
// 'apiurl' is for token endpoint, but authorization uses base 'url'
|
|
54
66
|
const uaaUrl = rawData.url;
|
|
55
|
-
|
|
67
|
+
const result = {
|
|
56
68
|
uaa: {
|
|
57
69
|
url: uaaUrl,
|
|
58
70
|
clientid: rawData.clientid,
|
|
@@ -68,6 +80,8 @@ class XsuaaServiceKeyParser {
|
|
|
68
80
|
sap_client: rawData.sap_client,
|
|
69
81
|
language: rawData.language,
|
|
70
82
|
};
|
|
83
|
+
this.log?.debug(`XSUAA service key parsed successfully: uaaUrl(${uaaUrl.substring(0, 40)}...), hasAbap(${!!rawData.abap})`);
|
|
84
|
+
return result;
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
exports.XsuaaServiceKeyParser = XsuaaServiceKeyParser;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Environment file loader - loads .env files by destination name for ABAP
|
|
3
3
|
*/
|
|
4
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
4
5
|
interface EnvConfig {
|
|
5
6
|
sapUrl: string;
|
|
6
7
|
sapClient?: string;
|
|
@@ -15,7 +16,8 @@ interface EnvConfig {
|
|
|
15
16
|
* Load environment configuration from {destination}.env file
|
|
16
17
|
* @param destination Destination name
|
|
17
18
|
* @param directory Directory where the file is located
|
|
19
|
+
* @param log Optional logger for logging operations
|
|
18
20
|
* @returns EnvConfig object or null if file not found
|
|
19
21
|
*/
|
|
20
|
-
export declare function loadEnvFile(destination: string, directory: string): Promise<EnvConfig | null>;
|
|
22
|
+
export declare function loadEnvFile(destination: string, directory: string, log?: ILogger): Promise<EnvConfig | null>;
|
|
21
23
|
export {};
|
|
@@ -45,22 +45,29 @@ const constants_1 = require("../../utils/constants");
|
|
|
45
45
|
* Load environment configuration from {destination}.env file
|
|
46
46
|
* @param destination Destination name
|
|
47
47
|
* @param directory Directory where the file is located
|
|
48
|
+
* @param log Optional logger for logging operations
|
|
48
49
|
* @returns EnvConfig object or null if file not found
|
|
49
50
|
*/
|
|
50
|
-
async function loadEnvFile(destination, directory) {
|
|
51
|
+
async function loadEnvFile(destination, directory, log) {
|
|
51
52
|
const fileName = `${destination}.env`;
|
|
52
53
|
const envFilePath = path.join(directory, fileName);
|
|
54
|
+
log?.debug(`Reading env file: ${envFilePath}`);
|
|
53
55
|
if (!fs.existsSync(envFilePath)) {
|
|
56
|
+
log?.debug(`Env file not found: ${envFilePath}`);
|
|
54
57
|
return null;
|
|
55
58
|
}
|
|
56
59
|
try {
|
|
57
60
|
// Read and parse .env file
|
|
58
61
|
const envContent = fs.readFileSync(envFilePath, 'utf8');
|
|
62
|
+
log?.debug(`Env file read successfully, size: ${envContent.length} bytes`);
|
|
59
63
|
const parsed = dotenv.parse(envContent);
|
|
64
|
+
log?.debug(`Parsed env variables: ${Object.keys(parsed).join(', ')}`);
|
|
60
65
|
// Extract required fields
|
|
61
66
|
const sapUrl = parsed[constants_1.ABAP_CONNECTION_VARS.SERVICE_URL];
|
|
62
67
|
const jwtToken = parsed[constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN];
|
|
68
|
+
log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${!!jwtToken})`);
|
|
63
69
|
if (!sapUrl || !jwtToken) {
|
|
70
|
+
log?.warn(`Env file missing required fields: sapUrl(${!!sapUrl}), jwtToken(${!!jwtToken})`);
|
|
64
71
|
return null;
|
|
65
72
|
}
|
|
66
73
|
const config = {
|
|
@@ -86,9 +93,11 @@ async function loadEnvFile(destination, directory) {
|
|
|
86
93
|
if (parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE]) {
|
|
87
94
|
config.language = parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE].trim();
|
|
88
95
|
}
|
|
96
|
+
log?.info(`Env config loaded from ${envFilePath}: sapUrl(${config.sapUrl.substring(0, 50)}...), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
|
|
89
97
|
return config;
|
|
90
98
|
}
|
|
91
99
|
catch (error) {
|
|
100
|
+
log?.error(`Failed to load env file from ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
92
101
|
throw new Error(`Failed to load environment file for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
|
|
93
102
|
}
|
|
94
103
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token storage - saves tokens to .env files for ABAP
|
|
3
3
|
*/
|
|
4
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
4
5
|
interface EnvConfig {
|
|
5
6
|
sapUrl: string;
|
|
6
7
|
sapClient?: string;
|
|
@@ -16,9 +17,10 @@ interface EnvConfig {
|
|
|
16
17
|
* @param destination Destination name
|
|
17
18
|
* @param savePath Path where to save the file
|
|
18
19
|
* @param config Configuration to save
|
|
20
|
+
* @param log Optional logger for logging operations
|
|
19
21
|
*/
|
|
20
22
|
export declare function saveTokenToEnv(destination: string, savePath: string, config: Partial<EnvConfig> & {
|
|
21
23
|
sapUrl?: string;
|
|
22
24
|
jwtToken: string;
|
|
23
|
-
}): Promise<void>;
|
|
25
|
+
}, log?: ILogger): Promise<void>;
|
|
24
26
|
export {};
|
|
@@ -45,18 +45,26 @@ const constants_1 = require("../../utils/constants");
|
|
|
45
45
|
* @param destination Destination name
|
|
46
46
|
* @param savePath Path where to save the file
|
|
47
47
|
* @param config Configuration to save
|
|
48
|
+
* @param log Optional logger for logging operations
|
|
48
49
|
*/
|
|
49
|
-
async function saveTokenToEnv(destination, savePath, config) {
|
|
50
|
+
async function saveTokenToEnv(destination, savePath, config, log) {
|
|
51
|
+
const envFilePath = path.join(savePath, `${destination}.env`);
|
|
52
|
+
const tempFilePath = `${envFilePath}.tmp`;
|
|
53
|
+
log?.debug(`Saving token to env file: ${envFilePath}`);
|
|
54
|
+
log?.debug(`Config to save: hasSapUrl(${!!config.sapUrl}), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
|
|
50
55
|
// Ensure directory exists
|
|
51
56
|
if (!fs.existsSync(savePath)) {
|
|
57
|
+
log?.debug(`Creating directory: ${savePath}`);
|
|
52
58
|
fs.mkdirSync(savePath, { recursive: true });
|
|
53
59
|
}
|
|
54
|
-
const envFilePath = path.join(savePath, `${destination}.env`);
|
|
55
|
-
const tempFilePath = `${envFilePath}.tmp`;
|
|
56
60
|
// Read existing .env file if it exists
|
|
57
61
|
let existingContent = '';
|
|
58
62
|
if (fs.existsSync(envFilePath)) {
|
|
59
63
|
existingContent = fs.readFileSync(envFilePath, 'utf8');
|
|
64
|
+
log?.debug(`Reading existing env file, size: ${existingContent.length} bytes`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
log?.debug(`Env file does not exist, creating new one`);
|
|
60
68
|
}
|
|
61
69
|
// Parse existing content to preserve other values
|
|
62
70
|
const lines = existingContent.split('\n');
|
|
@@ -73,6 +81,7 @@ async function saveTokenToEnv(destination, savePath, config) {
|
|
|
73
81
|
existingVars.set(key, value);
|
|
74
82
|
}
|
|
75
83
|
}
|
|
84
|
+
log?.debug(`Preserved ${existingVars.size} existing variables from env file`);
|
|
76
85
|
// Update with new values
|
|
77
86
|
if (config.sapUrl) {
|
|
78
87
|
existingVars.set(constants_1.ABAP_CONNECTION_VARS.SERVICE_URL, config.sapUrl);
|
|
@@ -106,8 +115,10 @@ async function saveTokenToEnv(destination, savePath, config) {
|
|
|
106
115
|
envLines.push(`${key}=${escapedValue}`);
|
|
107
116
|
}
|
|
108
117
|
const envContent = envLines.join('\n') + '\n';
|
|
118
|
+
log?.debug(`Writing ${envLines.length} variables to env file: ${Object.keys(config).join(', ')}`);
|
|
109
119
|
// Write to temp file
|
|
110
120
|
fs.writeFileSync(tempFilePath, envContent, 'utf8');
|
|
111
121
|
// Atomic rename
|
|
112
122
|
fs.renameSync(tempFilePath, envFilePath);
|
|
123
|
+
log?.info(`Token saved to ${envFilePath}: token(${config.jwtToken.length} chars), sapUrl(${config.sapUrl ? config.sapUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
|
|
113
124
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* XSUAA Environment file loader - loads .env files with XSUAA_* variables for XSUAA
|
|
3
3
|
*/
|
|
4
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
4
5
|
interface XsuaaSessionConfig {
|
|
5
6
|
mcpUrl?: string;
|
|
6
7
|
jwtToken: string;
|
|
@@ -14,7 +15,8 @@ interface XsuaaSessionConfig {
|
|
|
14
15
|
* Reads XSUAA_* variables instead of SAP_* variables
|
|
15
16
|
* @param destination Destination name
|
|
16
17
|
* @param directory Directory where the file is located
|
|
18
|
+
* @param log Optional logger for logging operations
|
|
17
19
|
* @returns XsuaaSessionConfig object or null if file not found
|
|
18
20
|
*/
|
|
19
|
-
export declare function loadXsuaaEnvFile(destination: string, directory: string): Promise<XsuaaSessionConfig | null>;
|
|
21
|
+
export declare function loadXsuaaEnvFile(destination: string, directory: string, log?: ILogger): Promise<XsuaaSessionConfig | null>;
|
|
20
22
|
export {};
|
|
@@ -46,21 +46,30 @@ const constants_1 = require("../../utils/constants");
|
|
|
46
46
|
* Reads XSUAA_* variables instead of SAP_* variables
|
|
47
47
|
* @param destination Destination name
|
|
48
48
|
* @param directory Directory where the file is located
|
|
49
|
+
* @param log Optional logger for logging operations
|
|
49
50
|
* @returns XsuaaSessionConfig object or null if file not found
|
|
50
51
|
*/
|
|
51
|
-
async function loadXsuaaEnvFile(destination, directory) {
|
|
52
|
+
async function loadXsuaaEnvFile(destination, directory, log) {
|
|
52
53
|
const fileName = `${destination}.env`;
|
|
53
54
|
const envFilePath = path.join(directory, fileName);
|
|
55
|
+
log?.debug(`Reading XSUAA env file: ${envFilePath}`);
|
|
54
56
|
if (!fs.existsSync(envFilePath)) {
|
|
57
|
+
log?.debug(`XSUAA env file not found: ${envFilePath}`);
|
|
55
58
|
return null;
|
|
56
59
|
}
|
|
57
60
|
try {
|
|
58
61
|
// Read and parse .env file
|
|
59
62
|
const envContent = fs.readFileSync(envFilePath, 'utf8');
|
|
63
|
+
log?.debug(`XSUAA env file read successfully, size: ${envContent.length} bytes`);
|
|
60
64
|
const parsed = dotenv.parse(envContent);
|
|
65
|
+
log?.debug(`Parsed XSUAA env variables: ${Object.keys(parsed).filter(k => k.startsWith('XSUAA_')).join(', ')}`);
|
|
61
66
|
// Extract required fields (XSUAA_* variables)
|
|
62
67
|
const jwtToken = parsed[constants_1.XSUAA_CONNECTION_VARS.AUTHORIZATION_TOKEN];
|
|
63
|
-
|
|
68
|
+
log?.debug(`Extracted fields: hasJwtToken(${jwtToken !== undefined && jwtToken !== null})`);
|
|
69
|
+
// Allow empty string for jwtToken (can be set later via setConnectionConfig)
|
|
70
|
+
// Only reject if jwtToken is undefined or null
|
|
71
|
+
if (jwtToken === undefined || jwtToken === null) {
|
|
72
|
+
log?.warn(`XSUAA env file missing required field: jwtToken`);
|
|
64
73
|
return null;
|
|
65
74
|
}
|
|
66
75
|
const config = {
|
|
@@ -83,9 +92,11 @@ async function loadXsuaaEnvFile(destination, directory) {
|
|
|
83
92
|
if (parsed[constants_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_SECRET]) {
|
|
84
93
|
config.uaaClientSecret = parsed[constants_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_SECRET].trim();
|
|
85
94
|
}
|
|
95
|
+
log?.info(`XSUAA env config loaded from ${envFilePath}: token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl}), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'})`);
|
|
86
96
|
return config;
|
|
87
97
|
}
|
|
88
98
|
catch (error) {
|
|
99
|
+
log?.error(`Failed to load XSUAA env file from ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
100
|
throw new Error(`Failed to load XSUAA environment file for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
|
|
90
101
|
}
|
|
91
102
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* XSUAA Token storage - saves tokens to .env files with XSUAA_* variables
|
|
3
3
|
*/
|
|
4
|
+
import type { ILogger } from '@mcp-abap-adt/interfaces';
|
|
4
5
|
interface XsuaaSessionConfig {
|
|
5
6
|
mcpUrl?: string;
|
|
6
7
|
jwtToken: string;
|
|
@@ -14,6 +15,7 @@ interface XsuaaSessionConfig {
|
|
|
14
15
|
* @param destination Destination name
|
|
15
16
|
* @param savePath Path where to save the file
|
|
16
17
|
* @param config XSUAA session configuration to save
|
|
18
|
+
* @param log Optional logger for logging operations
|
|
17
19
|
*/
|
|
18
|
-
export declare function saveXsuaaTokenToEnv(destination: string, savePath: string, config: XsuaaSessionConfig): Promise<void>;
|
|
20
|
+
export declare function saveXsuaaTokenToEnv(destination: string, savePath: string, config: XsuaaSessionConfig, log?: ILogger): Promise<void>;
|
|
19
21
|
export {};
|
|
@@ -45,18 +45,26 @@ const constants_1 = require("../../utils/constants");
|
|
|
45
45
|
* @param destination Destination name
|
|
46
46
|
* @param savePath Path where to save the file
|
|
47
47
|
* @param config XSUAA session configuration to save
|
|
48
|
+
* @param log Optional logger for logging operations
|
|
48
49
|
*/
|
|
49
|
-
async function saveXsuaaTokenToEnv(destination, savePath, config) {
|
|
50
|
+
async function saveXsuaaTokenToEnv(destination, savePath, config, log) {
|
|
51
|
+
const envFilePath = path.join(savePath, `${destination}.env`);
|
|
52
|
+
const tempFilePath = `${envFilePath}.tmp`;
|
|
53
|
+
log?.debug(`Saving XSUAA token to env file: ${envFilePath}`);
|
|
54
|
+
log?.debug(`Config to save: token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl}), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'})`);
|
|
50
55
|
// Ensure directory exists
|
|
51
56
|
if (!fs.existsSync(savePath)) {
|
|
57
|
+
log?.debug(`Creating directory: ${savePath}`);
|
|
52
58
|
fs.mkdirSync(savePath, { recursive: true });
|
|
53
59
|
}
|
|
54
|
-
const envFilePath = path.join(savePath, `${destination}.env`);
|
|
55
|
-
const tempFilePath = `${envFilePath}.tmp`;
|
|
56
60
|
// Read existing .env file if it exists
|
|
57
61
|
let existingContent = '';
|
|
58
62
|
if (fs.existsSync(envFilePath)) {
|
|
59
63
|
existingContent = fs.readFileSync(envFilePath, 'utf8');
|
|
64
|
+
log?.debug(`Reading existing XSUAA env file, size: ${existingContent.length} bytes`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
log?.debug(`XSUAA env file does not exist, creating new one`);
|
|
60
68
|
}
|
|
61
69
|
// Parse existing content to preserve other values
|
|
62
70
|
// Remove old SAP_* variables for XSUAA (use XSUAA_* instead)
|
|
@@ -79,6 +87,7 @@ async function saveXsuaaTokenToEnv(destination, savePath, config) {
|
|
|
79
87
|
existingVars.set(key, value);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
90
|
+
log?.debug(`Preserved ${existingVars.size} existing variables from XSUAA env file`);
|
|
82
91
|
// Update with new values (XSUAA_* variables)
|
|
83
92
|
// mcpUrl can be saved as additional variable (not part of CONNECTION_VARS, but can be stored for convenience)
|
|
84
93
|
// URL comes from elsewhere (YAML config, parameter, or request header), but can be stored in .env
|
|
@@ -108,8 +117,10 @@ async function saveXsuaaTokenToEnv(destination, savePath, config) {
|
|
|
108
117
|
envLines.push(`${key}=${escapedValue}`);
|
|
109
118
|
}
|
|
110
119
|
const envContent = envLines.join('\n') + '\n';
|
|
120
|
+
log?.debug(`Writing ${envLines.length} XSUAA variables to env file: ${Object.keys(config).join(', ')}`);
|
|
111
121
|
// Write to temp file
|
|
112
122
|
fs.writeFileSync(tempFilePath, envContent, 'utf8');
|
|
113
123
|
// Atomic rename
|
|
114
124
|
fs.renameSync(tempFilePath, envFilePath);
|
|
125
|
+
log?.info(`XSUAA token saved to ${envFilePath}: token(${config.jwtToken.length} chars), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
|
|
115
126
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ABAP Service key store - reads ABAP service keys from {destination}.json files
|
|
3
3
|
*/
|
|
4
|
-
import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig } from '@mcp-abap-adt/interfaces';
|
|
4
|
+
import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig, ILogger } from '@mcp-abap-adt/interfaces';
|
|
5
5
|
/**
|
|
6
6
|
* ABAP Service key store implementation
|
|
7
7
|
*
|
|
@@ -10,11 +10,13 @@ import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig
|
|
|
10
10
|
export declare class AbapServiceKeyStore implements IServiceKeyStore {
|
|
11
11
|
private directory;
|
|
12
12
|
private parser;
|
|
13
|
+
private log?;
|
|
13
14
|
/**
|
|
14
15
|
* Create a new AbapServiceKeyStore instance
|
|
15
16
|
* @param directory Directory where service key .json files are located
|
|
17
|
+
* @param log Optional logger for logging operations
|
|
16
18
|
*/
|
|
17
|
-
constructor(directory: string);
|
|
19
|
+
constructor(directory: string, log?: ILogger);
|
|
18
20
|
/**
|
|
19
21
|
* Get service key for destination
|
|
20
22
|
* @param destination Destination name (e.g., "TRIAL")
|