@mcp-abap-adt/auth-stores 0.1.7 → 0.2.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 +100 -0
- package/README.md +30 -11
- package/dist/storage/abap/envLoader.js +5 -4
- package/dist/stores/abap/AbapSessionStore.d.ts +3 -1
- package/dist/stores/abap/AbapSessionStore.js +80 -31
- package/dist/stores/abap/SafeAbapSessionStore.d.ts +3 -1
- package/dist/stores/abap/SafeAbapSessionStore.js +72 -21
- package/dist/stores/btp/BtpSessionStore.d.ts +3 -1
- package/dist/stores/btp/BtpSessionStore.js +24 -8
- package/dist/stores/btp/SafeBtpSessionStore.d.ts +3 -1
- package/dist/stores/btp/SafeBtpSessionStore.js +31 -9
- package/dist/stores/xsuaa/SafeXsuaaSessionStore.d.ts +3 -1
- package/dist/stores/xsuaa/SafeXsuaaSessionStore.js +28 -9
- package/dist/stores/xsuaa/XsuaaSessionStore.d.ts +3 -1
- package/dist/stores/xsuaa/XsuaaSessionStore.js +24 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2025-12-08
|
|
11
|
+
|
|
12
|
+
### Breaking Changes
|
|
13
|
+
|
|
14
|
+
- **XsuaaSessionStore Constructor**: `defaultServiceUrl` is now a **required** parameter (second parameter)
|
|
15
|
+
- **Before**: `new XsuaaSessionStore(directory, log?, defaultServiceUrl?)`
|
|
16
|
+
- **After**: `new XsuaaSessionStore(directory, defaultServiceUrl, log?)`
|
|
17
|
+
- **Reason**: `serviceUrl` cannot be obtained from XSUAA service keys, so it must be provided via constructor
|
|
18
|
+
- **Migration**: Update all `XsuaaSessionStore` instantiations to provide `defaultServiceUrl` as second parameter
|
|
19
|
+
|
|
20
|
+
- **SafeXsuaaSessionStore Constructor**: `defaultServiceUrl` is now a **required** parameter (first parameter)
|
|
21
|
+
- **Before**: `new SafeXsuaaSessionStore(log?, defaultServiceUrl?)`
|
|
22
|
+
- **After**: `new SafeXsuaaSessionStore(defaultServiceUrl, log?)`
|
|
23
|
+
- **Migration**: Update all `SafeXsuaaSessionStore` instantiations to provide `defaultServiceUrl` as first parameter
|
|
24
|
+
|
|
25
|
+
- **BtpSessionStore Constructor**: `defaultServiceUrl` is now a **required** parameter (second parameter)
|
|
26
|
+
- **Before**: `new BtpSessionStore(directory, log?, defaultServiceUrl?)`
|
|
27
|
+
- **After**: `new BtpSessionStore(directory, defaultServiceUrl, log?)`
|
|
28
|
+
- **Reason**: `serviceUrl` cannot be obtained from BTP service keys, so it must be provided via constructor
|
|
29
|
+
- **Migration**: Update all `BtpSessionStore` instantiations to provide `defaultServiceUrl` as second parameter
|
|
30
|
+
|
|
31
|
+
- **SafeBtpSessionStore Constructor**: `defaultServiceUrl` is now a **required** parameter (first parameter)
|
|
32
|
+
- **Before**: `new SafeBtpSessionStore(log?, defaultServiceUrl?)`
|
|
33
|
+
- **After**: `new SafeBtpSessionStore(defaultServiceUrl, log?)`
|
|
34
|
+
- **Migration**: Update all `SafeBtpSessionStore` instantiations to provide `defaultServiceUrl` as first parameter
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **AbapSessionStore Constructor**: `defaultServiceUrl` remains **optional** (third parameter)
|
|
39
|
+
- **Reason**: `serviceUrl` can be obtained from ABAP service keys, so it's optional
|
|
40
|
+
- **Signature**: `new AbapSessionStore(directory, log?, defaultServiceUrl?)`
|
|
41
|
+
- No migration needed for `AbapSessionStore`
|
|
42
|
+
|
|
43
|
+
- **SafeAbapSessionStore Constructor**: `defaultServiceUrl` remains **optional** (second parameter)
|
|
44
|
+
- **Signature**: `new SafeAbapSessionStore(log?, defaultServiceUrl?)`
|
|
45
|
+
- No migration needed for `SafeAbapSessionStore`
|
|
46
|
+
|
|
47
|
+
- **Session Creation Logic**: Updated to use `defaultServiceUrl` when creating new sessions
|
|
48
|
+
- When `setConnectionConfig` or `setAuthorizationConfig` creates a new session, `defaultServiceUrl` is used if `config.serviceUrl` is not provided
|
|
49
|
+
- For XSUAA/BTP stores: `defaultServiceUrl` is always used (required parameter)
|
|
50
|
+
- For ABAP stores: `defaultServiceUrl` is used only if provided and `config.serviceUrl` is not provided
|
|
51
|
+
|
|
52
|
+
- **Session Update Logic**: Fixed to not use `defaultServiceUrl` when updating existing sessions
|
|
53
|
+
- When updating an existing session, only `config.serviceUrl` is used if explicitly provided
|
|
54
|
+
- `defaultServiceUrl` is never used to modify `mcpUrl`/`serviceUrl` during updates
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Comprehensive Logging**: Added detailed logging throughout all session stores using `ILogger` with optional chaining
|
|
59
|
+
- All critical operations now log via `logger?.info()`, `logger?.debug()`, `logger?.warn()`, `logger?.error()`
|
|
60
|
+
- Logging covers: session creation, updates, deletions, loading, validation errors, file operations
|
|
61
|
+
- Logging provides critical information for analysis: serviceUrl, token lengths, UAA parameters, operation results
|
|
62
|
+
- Logging is optional - stores work without logger (no-op when logger is not provided)
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **loadEnvFile**: Fixed validation to allow empty string for `jwtToken`
|
|
67
|
+
- **Before**: Rejected empty string `''` as invalid (treated as falsy)
|
|
68
|
+
- **After**: Only rejects `undefined` or `null` - empty string is valid
|
|
69
|
+
- **Reason**: Sessions created via `setAuthorizationConfig` may have empty `jwtToken` initially (set later via `setConnectionConfig`)
|
|
70
|
+
- This fix allows loading sessions with empty tokens, which is valid for authorization-only sessions
|
|
71
|
+
|
|
72
|
+
### Migration Guide
|
|
73
|
+
|
|
74
|
+
#### XsuaaSessionStore and SafeXsuaaSessionStore
|
|
75
|
+
|
|
76
|
+
**Before:**
|
|
77
|
+
```typescript
|
|
78
|
+
const store = new XsuaaSessionStore('/path/to/sessions', logger, 'https://default.mcp.com');
|
|
79
|
+
const safeStore = new SafeXsuaaSessionStore(logger, 'https://default.mcp.com');
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**After:**
|
|
83
|
+
```typescript
|
|
84
|
+
const store = new XsuaaSessionStore('/path/to/sessions', 'https://default.mcp.com', logger);
|
|
85
|
+
const safeStore = new SafeXsuaaSessionStore('https://default.mcp.com', logger);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### BtpSessionStore and SafeBtpSessionStore
|
|
89
|
+
|
|
90
|
+
**Before:**
|
|
91
|
+
```typescript
|
|
92
|
+
const store = new BtpSessionStore('/path/to/sessions', logger, 'https://default.mcp.com');
|
|
93
|
+
const safeStore = new SafeBtpSessionStore(logger, 'https://default.mcp.com');
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**After:**
|
|
97
|
+
```typescript
|
|
98
|
+
const store = new BtpSessionStore('/path/to/sessions', 'https://default.mcp.com', logger);
|
|
99
|
+
const safeStore = new SafeBtpSessionStore('https://default.mcp.com', logger);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### AbapSessionStore and SafeAbapSessionStore
|
|
103
|
+
|
|
104
|
+
No changes needed - `defaultServiceUrl` remains optional:
|
|
105
|
+
```typescript
|
|
106
|
+
const store = new AbapSessionStore('/path/to/sessions', logger, 'https://default.sap.com'); // Optional
|
|
107
|
+
const safeStore = new SafeAbapSessionStore(logger, 'https://default.sap.com'); // Optional
|
|
108
|
+
```
|
|
109
|
+
|
|
10
110
|
## [0.1.7] - 2025-12-08
|
|
11
111
|
|
|
12
112
|
### Added
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @mcp-abap-adt/auth-stores
|
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
15
|
-
This package implements the `IServiceKeyStore` and `ISessionStore` interfaces from `@mcp-abap-adt/
|
|
15
|
+
This package implements the `IServiceKeyStore` and `ISessionStore` interfaces from `@mcp-abap-adt/interfaces`:
|
|
16
16
|
|
|
17
17
|
- **Service Key Stores**: Read service key JSON files from a specified directory
|
|
18
18
|
- **Session Stores**: Read/write session data from/to `.env` files or in-memory storage
|
|
@@ -40,7 +40,7 @@ This principle ensures:
|
|
|
40
40
|
|
|
41
41
|
This package is responsible for:
|
|
42
42
|
|
|
43
|
-
1. **Implementing storage interfaces**: Provides concrete implementations of `IServiceKeyStore` and `ISessionStore` interfaces defined in `@mcp-abap-adt/
|
|
43
|
+
1. **Implementing storage interfaces**: Provides concrete implementations of `IServiceKeyStore` and `ISessionStore` interfaces defined in `@mcp-abap-adt/interfaces`
|
|
44
44
|
2. **File I/O operations**: Handles reading and writing service key JSON files and session `.env` files
|
|
45
45
|
3. **Data format conversion**: Converts between interface types (`IConfig`, `IConnectionConfig`, `IAuthorizationConfig`) and internal storage formats
|
|
46
46
|
4. **Platform-specific handling**: Provides different store implementations for ABAP, BTP, and XSUAA with their specific data formats
|
|
@@ -63,7 +63,7 @@ This package is responsible for:
|
|
|
63
63
|
|
|
64
64
|
This package interacts with external packages **ONLY through interfaces**:
|
|
65
65
|
|
|
66
|
-
- **`@mcp-abap-adt/
|
|
66
|
+
- **`@mcp-abap-adt/interfaces`**: Uses interfaces (`IServiceKeyStore`, `ISessionStore`, `IConfig`, `IConnectionConfig`, `IAuthorizationConfig`, `ILogger`) - does not know about concrete implementations in other packages
|
|
67
67
|
- **No direct dependencies on other packages**: All interactions happen through well-defined interfaces
|
|
68
68
|
|
|
69
69
|
## Store Types
|
|
@@ -101,10 +101,12 @@ import { BtpServiceKeyStore, BtpSessionStore, SafeBtpSessionStore } from '@mcp-a
|
|
|
101
101
|
const serviceKeyStore = new BtpServiceKeyStore('/path/to/service-keys');
|
|
102
102
|
|
|
103
103
|
// File-based session store - reads/writes {destination}.env files
|
|
104
|
-
|
|
104
|
+
// defaultServiceUrl is REQUIRED (cannot be obtained from service key)
|
|
105
|
+
const sessionStore = new BtpSessionStore('/path/to/sessions', 'https://default.mcp.com', logger);
|
|
105
106
|
|
|
106
107
|
// In-memory session store (non-persistent)
|
|
107
|
-
|
|
108
|
+
// defaultServiceUrl is REQUIRED (cannot be obtained from service key)
|
|
109
|
+
const safeSessionStore = new SafeBtpSessionStore('https://default.mcp.com', logger);
|
|
108
110
|
```
|
|
109
111
|
|
|
110
112
|
### ABAP Stores (with sapUrl)
|
|
@@ -131,10 +133,12 @@ import { XsuaaServiceKeyStore, XsuaaSessionStore, SafeXsuaaSessionStore } from '
|
|
|
131
133
|
const serviceKeyStore = new XsuaaServiceKeyStore('/path/to/service-keys');
|
|
132
134
|
|
|
133
135
|
// File-based session store - stores XSUAA sessions
|
|
134
|
-
|
|
136
|
+
// defaultServiceUrl is REQUIRED (cannot be obtained from service key)
|
|
137
|
+
const sessionStore = new XsuaaSessionStore('/path/to/sessions', 'https://default.mcp.com', logger);
|
|
135
138
|
|
|
136
139
|
// In-memory session store
|
|
137
|
-
|
|
140
|
+
// defaultServiceUrl is REQUIRED (cannot be obtained from service key)
|
|
141
|
+
const safeSessionStore = new SafeXsuaaSessionStore('https://default.mcp.com', logger);
|
|
138
142
|
```
|
|
139
143
|
|
|
140
144
|
### Directory Configuration
|
|
@@ -151,6 +155,20 @@ const sessionStore = new AbapSessionStore('/path/to/sessions'); // Directory cre
|
|
|
151
155
|
|
|
152
156
|
**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
157
|
|
|
158
|
+
### Default Service URL Configuration
|
|
159
|
+
|
|
160
|
+
**For XSUAA and BTP stores**: `defaultServiceUrl` is **required** in the constructor because `serviceUrl` cannot be obtained from service keys:
|
|
161
|
+
- `XsuaaSessionStore(directory, defaultServiceUrl, log?)` - `defaultServiceUrl` is required
|
|
162
|
+
- `SafeXsuaaSessionStore(defaultServiceUrl, log?)` - `defaultServiceUrl` is required
|
|
163
|
+
- `BtpSessionStore(directory, defaultServiceUrl, log?)` - `defaultServiceUrl` is required
|
|
164
|
+
- `SafeBtpSessionStore(defaultServiceUrl, log?)` - `defaultServiceUrl` is required
|
|
165
|
+
|
|
166
|
+
**For ABAP stores**: `defaultServiceUrl` is **optional** because `serviceUrl` can be obtained from ABAP service keys:
|
|
167
|
+
- `AbapSessionStore(directory, log?, defaultServiceUrl?)` - `defaultServiceUrl` is optional
|
|
168
|
+
- `SafeAbapSessionStore(log?, defaultServiceUrl?)` - `defaultServiceUrl` is optional
|
|
169
|
+
|
|
170
|
+
The `defaultServiceUrl` is used when creating new sessions via `setConnectionConfig` or `setAuthorizationConfig` if `config.serviceUrl` is not provided. It is never used to modify existing sessions.
|
|
171
|
+
|
|
154
172
|
### Service Key Format
|
|
155
173
|
|
|
156
174
|
**ABAP Service Key** (with nested `uaa` object):
|
|
@@ -354,7 +372,7 @@ Integration tests will skip if `test-config.yaml` is not configured or contains
|
|
|
354
372
|
|
|
355
373
|
### Store Implementation
|
|
356
374
|
|
|
357
|
-
- All stores implement `IServiceKeyStore` or `ISessionStore` interfaces from `@mcp-abap-adt/
|
|
375
|
+
- All stores implement `IServiceKeyStore` or `ISessionStore` interfaces from `@mcp-abap-adt/interfaces`
|
|
358
376
|
- Stores accept a single directory path in constructor
|
|
359
377
|
- File-based session stores automatically create directories in constructor if they don't exist
|
|
360
378
|
- Session stores automatically create sessions when calling `setConnectionConfig` or `setAuthorizationConfig` (no need to call `saveSession` first)
|
|
@@ -366,13 +384,14 @@ Session stores are designed to work seamlessly with `AuthBroker`:
|
|
|
366
384
|
|
|
367
385
|
- **Ready after construction**: File-based stores create directory automatically, stores are ready to use immediately
|
|
368
386
|
- **Automatic session creation**: Calling `setConnectionConfig` or `setAuthorizationConfig` on an empty store creates a new session
|
|
369
|
-
- **ABAP stores**: Require `serviceUrl` when creating new session
|
|
370
|
-
- **BTP/XSUAA stores**: `
|
|
387
|
+
- **ABAP stores**: Require `serviceUrl` when creating new session (from config or `defaultServiceUrl` parameter)
|
|
388
|
+
- **BTP/XSUAA stores**: Require `defaultServiceUrl` in constructor (cannot be obtained from service key), used when creating new sessions if `config.serviceUrl` is not provided
|
|
371
389
|
- **Token updates**: `setConnectionConfig` updates token if provided, preserves existing token if not provided
|
|
390
|
+
- **Session updates**: When updating existing sessions, only `config.serviceUrl` is used if explicitly provided; `defaultServiceUrl` is never used to modify existing sessions
|
|
372
391
|
|
|
373
392
|
## Dependencies
|
|
374
393
|
|
|
375
|
-
- `@mcp-abap-adt/interfaces` (^0.1.
|
|
394
|
+
- `@mcp-abap-adt/interfaces` (^0.1.4) - Interface definitions (`IServiceKeyStore`, `ISessionStore`, `IConfig`, `IConnectionConfig`, `IAuthorizationConfig`, `ILogger`)
|
|
376
395
|
- `dotenv` - Environment variable parsing
|
|
377
396
|
|
|
378
397
|
## License
|
|
@@ -65,14 +65,15 @@ async function loadEnvFile(destination, directory, log) {
|
|
|
65
65
|
// Extract required fields
|
|
66
66
|
const sapUrl = parsed[constants_1.ABAP_CONNECTION_VARS.SERVICE_URL];
|
|
67
67
|
const jwtToken = parsed[constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN];
|
|
68
|
-
log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${jwtToken !== undefined && jwtToken !== null})`);
|
|
69
|
+
// sapUrl is required, jwtToken can be empty string (for authorization-only sessions)
|
|
70
|
+
if (!sapUrl || jwtToken === undefined || jwtToken === null) {
|
|
71
|
+
log?.warn(`Env file missing required fields: sapUrl(${!!sapUrl}), jwtToken(${jwtToken !== undefined && jwtToken !== null})`);
|
|
71
72
|
return null;
|
|
72
73
|
}
|
|
73
74
|
const config = {
|
|
74
75
|
sapUrl: sapUrl.trim(),
|
|
75
|
-
jwtToken: jwtToken.trim(),
|
|
76
|
+
jwtToken: jwtToken.trim(), // Can be empty string for authorization-only sessions
|
|
76
77
|
};
|
|
77
78
|
// Optional fields
|
|
78
79
|
if (parsed[constants_1.ABAP_CONNECTION_VARS.SAP_CLIENT]) {
|
|
@@ -19,12 +19,14 @@ import type { IAuthorizationConfig, IConnectionConfig, ISessionStore, IConfig, I
|
|
|
19
19
|
export declare class AbapSessionStore implements ISessionStore {
|
|
20
20
|
protected directory: string;
|
|
21
21
|
private log?;
|
|
22
|
+
private defaultServiceUrl?;
|
|
22
23
|
/**
|
|
23
24
|
* Create a new AbapSessionStore instance
|
|
24
25
|
* @param directory Directory where session .env files are located
|
|
25
26
|
* @param log Optional logger for logging operations
|
|
27
|
+
* @param defaultServiceUrl Optional default service URL to use when serviceUrl is not provided in config
|
|
26
28
|
*/
|
|
27
|
-
constructor(directory: string, log?: ILogger);
|
|
29
|
+
constructor(directory: string, log?: ILogger, defaultServiceUrl?: string);
|
|
28
30
|
/**
|
|
29
31
|
* Load session from file
|
|
30
32
|
* @param filePath Path to session file
|
|
@@ -58,14 +58,17 @@ const path = __importStar(require("path"));
|
|
|
58
58
|
class AbapSessionStore {
|
|
59
59
|
directory;
|
|
60
60
|
log;
|
|
61
|
+
defaultServiceUrl;
|
|
61
62
|
/**
|
|
62
63
|
* Create a new AbapSessionStore instance
|
|
63
64
|
* @param directory Directory where session .env files are located
|
|
64
65
|
* @param log Optional logger for logging operations
|
|
66
|
+
* @param defaultServiceUrl Optional default service URL to use when serviceUrl is not provided in config
|
|
65
67
|
*/
|
|
66
|
-
constructor(directory, log) {
|
|
68
|
+
constructor(directory, log, defaultServiceUrl) {
|
|
67
69
|
this.directory = directory;
|
|
68
70
|
this.log = log;
|
|
71
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
69
72
|
// Ensure directory exists - create if it doesn't
|
|
70
73
|
if (!fs.existsSync(directory)) {
|
|
71
74
|
fs.mkdirSync(directory, { recursive: true });
|
|
@@ -93,7 +96,7 @@ class AbapSessionStore {
|
|
|
93
96
|
// Convert IConfig format (serviceUrl, authorizationToken) to internal format (sapUrl, jwtToken)
|
|
94
97
|
return {
|
|
95
98
|
sapUrl: (obj.serviceUrl || obj.sapUrl),
|
|
96
|
-
jwtToken: (obj.authorizationToken || obj.jwtToken),
|
|
99
|
+
jwtToken: (obj.authorizationToken || obj.jwtToken || ''), // Ensure jwtToken is always a string
|
|
97
100
|
refreshToken: obj.refreshToken,
|
|
98
101
|
uaaUrl: obj.uaaUrl,
|
|
99
102
|
uaaClientId: obj.uaaClientId,
|
|
@@ -110,6 +113,7 @@ class AbapSessionStore {
|
|
|
110
113
|
async saveToFile(filePath, config) {
|
|
111
114
|
// Type guard - ensure it's EnvConfig (has sapUrl)
|
|
112
115
|
if (!config || typeof config !== 'object' || !('sapUrl' in config)) {
|
|
116
|
+
this.log?.error(`Invalid config format for AbapSessionStore: missing sapUrl`);
|
|
113
117
|
throw new Error('AbapSessionStore can only store ABAP sessions (with sapUrl)');
|
|
114
118
|
}
|
|
115
119
|
// Extract destination from file path
|
|
@@ -159,7 +163,12 @@ class AbapSessionStore {
|
|
|
159
163
|
const fileName = `${destination}.env`;
|
|
160
164
|
const filePath = path.join(this.directory, fileName);
|
|
161
165
|
if (fs.existsSync(filePath)) {
|
|
166
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
162
167
|
fs.unlinkSync(filePath);
|
|
168
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
this.log?.debug(`Session file not found for deletion: ${destination}`);
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
174
|
/**
|
|
@@ -170,20 +179,43 @@ class AbapSessionStore {
|
|
|
170
179
|
*/
|
|
171
180
|
async loadSession(destination) {
|
|
172
181
|
this.log?.debug(`Loading session for destination: ${destination}`);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
// Return null if both are null, otherwise return composition (even if one is null)
|
|
176
|
-
if (!authConfig && !connConfig) {
|
|
182
|
+
const rawSession = await this.loadRawSession(destination);
|
|
183
|
+
if (!rawSession) {
|
|
177
184
|
this.log?.debug(`Session not found for destination: ${destination}`);
|
|
178
185
|
return null;
|
|
179
186
|
}
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
// Convert internal format to IConfig format
|
|
188
|
+
const result = {};
|
|
189
|
+
// Connection config fields
|
|
190
|
+
if (rawSession.sapUrl) {
|
|
191
|
+
result.serviceUrl = rawSession.sapUrl;
|
|
192
|
+
}
|
|
193
|
+
if (rawSession.jwtToken !== undefined) {
|
|
194
|
+
result.authorizationToken = rawSession.jwtToken;
|
|
195
|
+
}
|
|
196
|
+
if (rawSession.sapClient) {
|
|
197
|
+
result.sapClient = rawSession.sapClient;
|
|
198
|
+
}
|
|
199
|
+
if (rawSession.language) {
|
|
200
|
+
result.language = rawSession.language;
|
|
201
|
+
}
|
|
202
|
+
// Authorization config fields
|
|
203
|
+
if (rawSession.uaaUrl) {
|
|
204
|
+
result.uaaUrl = rawSession.uaaUrl;
|
|
205
|
+
}
|
|
206
|
+
if (rawSession.uaaClientId) {
|
|
207
|
+
result.uaaClientId = rawSession.uaaClientId;
|
|
208
|
+
}
|
|
209
|
+
if (rawSession.uaaClientSecret) {
|
|
210
|
+
result.uaaClientSecret = rawSession.uaaClientSecret;
|
|
211
|
+
}
|
|
212
|
+
if (rawSession.refreshToken) {
|
|
213
|
+
result.refreshToken = rawSession.refreshToken;
|
|
214
|
+
}
|
|
215
|
+
const tokenLength = rawSession.jwtToken?.length || 0;
|
|
216
|
+
const hasRefreshToken = !!rawSession.refreshToken;
|
|
217
|
+
this.log?.info(`Session loaded for ${destination}: token(${tokenLength} chars), hasRefreshToken(${hasRefreshToken}), sapUrl(${rawSession.sapUrl ? rawSession.sapUrl.substring(0, 40) + '...' : 'none'})`);
|
|
218
|
+
return result;
|
|
187
219
|
}
|
|
188
220
|
/**
|
|
189
221
|
* Load raw session data (internal representation)
|
|
@@ -198,11 +230,13 @@ class AbapSessionStore {
|
|
|
198
230
|
try {
|
|
199
231
|
const raw = await this.loadFromFile(sessionPath);
|
|
200
232
|
if (!raw || !isEnvConfig(raw)) {
|
|
233
|
+
this.log?.debug(`Invalid session format for ${destination}: missing required fields (sapUrl, jwtToken)`);
|
|
201
234
|
return null;
|
|
202
235
|
}
|
|
203
236
|
return raw;
|
|
204
237
|
}
|
|
205
238
|
catch (error) {
|
|
239
|
+
this.log?.error(`Error loading session for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
240
|
return null;
|
|
207
241
|
}
|
|
208
242
|
}
|
|
@@ -264,34 +298,41 @@ class AbapSessionStore {
|
|
|
264
298
|
async setAuthorizationConfig(destination, config) {
|
|
265
299
|
const current = await this.loadRawSession(destination);
|
|
266
300
|
if (!current) {
|
|
267
|
-
// Session doesn't exist - try to get serviceUrl from connection config
|
|
301
|
+
// Session doesn't exist - try to get serviceUrl from connection config or use defaultServiceUrl
|
|
268
302
|
// For ABAP, we need sapUrl to create session
|
|
269
303
|
const connConfig = await this.getConnectionConfig(destination);
|
|
270
|
-
const sapUrl = connConfig?.serviceUrl;
|
|
304
|
+
const sapUrl = connConfig?.serviceUrl || this.defaultServiceUrl;
|
|
271
305
|
if (!sapUrl) {
|
|
272
|
-
|
|
306
|
+
this.log?.error(`Cannot set authorization config for ${destination}: session does not exist and serviceUrl is required. Missing defaultServiceUrl in constructor.`);
|
|
307
|
+
throw new Error(`Cannot set authorization config for destination "${destination}": session does not exist and serviceUrl is required for ABAP sessions. Call setConnectionConfig first or provide defaultServiceUrl in constructor.`);
|
|
273
308
|
}
|
|
274
309
|
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: sapUrl(${sapUrl.substring(0, 40)}...)`);
|
|
275
310
|
const newSession = {
|
|
276
|
-
sapUrl,
|
|
277
|
-
|
|
311
|
+
serviceUrl: sapUrl,
|
|
312
|
+
authorizationToken: connConfig?.authorizationToken || '', // Use token from connection config if available
|
|
278
313
|
uaaUrl: config.uaaUrl,
|
|
279
314
|
uaaClientId: config.uaaClientId,
|
|
280
315
|
uaaClientSecret: config.uaaClientSecret,
|
|
281
316
|
refreshToken: config.refreshToken,
|
|
282
317
|
};
|
|
283
318
|
await this.saveSession(destination, newSession);
|
|
319
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
284
320
|
return;
|
|
285
321
|
}
|
|
286
|
-
// Update authorization fields
|
|
322
|
+
// Update authorization fields - convert internal format to IConfig
|
|
323
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
287
324
|
const updated = {
|
|
288
|
-
|
|
325
|
+
serviceUrl: current.sapUrl,
|
|
326
|
+
authorizationToken: current.jwtToken,
|
|
327
|
+
sapClient: current.sapClient,
|
|
328
|
+
language: current.language,
|
|
289
329
|
uaaUrl: config.uaaUrl,
|
|
290
330
|
uaaClientId: config.uaaClientId,
|
|
291
331
|
uaaClientSecret: config.uaaClientSecret,
|
|
292
332
|
refreshToken: config.refreshToken || current.refreshToken,
|
|
293
333
|
};
|
|
294
334
|
await this.saveSession(destination, updated);
|
|
335
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
295
336
|
}
|
|
296
337
|
/**
|
|
297
338
|
* Set connection configuration
|
|
@@ -304,30 +345,38 @@ class AbapSessionStore {
|
|
|
304
345
|
const current = await this.loadRawSession(destination);
|
|
305
346
|
if (!current) {
|
|
306
347
|
// Session doesn't exist - create new one
|
|
307
|
-
// For ABAP, serviceUrl is required
|
|
308
|
-
|
|
309
|
-
|
|
348
|
+
// For ABAP, serviceUrl is required - use from config, defaultServiceUrl, or throw error
|
|
349
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
350
|
+
if (!serviceUrl) {
|
|
351
|
+
this.log?.error(`Cannot create session for ${destination}: serviceUrl is required. Missing in config and defaultServiceUrl in constructor.`);
|
|
352
|
+
throw new Error(`Cannot create session for destination "${destination}": serviceUrl is required for ABAP sessions. Provide it in config or constructor.`);
|
|
310
353
|
}
|
|
311
|
-
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: serviceUrl(${
|
|
354
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: serviceUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
312
355
|
const newSession = {
|
|
313
|
-
|
|
314
|
-
|
|
356
|
+
serviceUrl: serviceUrl,
|
|
357
|
+
authorizationToken: config.authorizationToken || '',
|
|
315
358
|
sapClient: config.sapClient,
|
|
316
359
|
language: config.language,
|
|
317
360
|
};
|
|
318
361
|
await this.saveSession(destination, newSession);
|
|
319
|
-
this.log?.info(`Session created for ${destination}: serviceUrl(${
|
|
362
|
+
this.log?.info(`Session created for ${destination}: serviceUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
320
363
|
return;
|
|
321
364
|
}
|
|
322
|
-
// Update connection fields
|
|
365
|
+
// Update connection fields - convert internal format to IConfig
|
|
366
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
323
367
|
const updated = {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
jwtToken: config.authorizationToken,
|
|
368
|
+
serviceUrl: config.serviceUrl || current.sapUrl,
|
|
369
|
+
authorizationToken: config.authorizationToken,
|
|
327
370
|
sapClient: config.sapClient !== undefined ? config.sapClient : current.sapClient,
|
|
328
371
|
language: config.language !== undefined ? config.language : current.language,
|
|
372
|
+
uaaUrl: current.uaaUrl,
|
|
373
|
+
uaaClientId: current.uaaClientId,
|
|
374
|
+
uaaClientSecret: current.uaaClientSecret,
|
|
375
|
+
refreshToken: current.refreshToken,
|
|
329
376
|
};
|
|
330
377
|
await this.saveSession(destination, updated);
|
|
378
|
+
const finalServiceUrl = updated.serviceUrl || current.sapUrl;
|
|
379
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${finalServiceUrl ? finalServiceUrl.substring(0, 40) + '...' : 'none'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
331
380
|
}
|
|
332
381
|
}
|
|
333
382
|
exports.AbapSessionStore = AbapSessionStore;
|
|
@@ -14,11 +14,13 @@ import type { ISessionStore, IConnectionConfig, IAuthorizationConfig, IConfig, I
|
|
|
14
14
|
export declare class SafeAbapSessionStore implements ISessionStore {
|
|
15
15
|
private sessions;
|
|
16
16
|
private log?;
|
|
17
|
+
private defaultServiceUrl?;
|
|
17
18
|
/**
|
|
18
19
|
* Create a new SafeAbapSessionStore instance
|
|
19
20
|
* @param log Optional logger for logging operations
|
|
21
|
+
* @param defaultServiceUrl Optional default service URL to use when serviceUrl is not provided in config
|
|
20
22
|
*/
|
|
21
|
-
constructor(log?: ILogger);
|
|
23
|
+
constructor(log?: ILogger, defaultServiceUrl?: string);
|
|
22
24
|
private loadRawSession;
|
|
23
25
|
private validateSessionConfig;
|
|
24
26
|
private convertToInternalFormat;
|
|
@@ -16,27 +16,33 @@ exports.SafeAbapSessionStore = void 0;
|
|
|
16
16
|
class SafeAbapSessionStore {
|
|
17
17
|
sessions = new Map();
|
|
18
18
|
log;
|
|
19
|
+
defaultServiceUrl;
|
|
19
20
|
/**
|
|
20
21
|
* Create a new SafeAbapSessionStore instance
|
|
21
22
|
* @param log Optional logger for logging operations
|
|
23
|
+
* @param defaultServiceUrl Optional default service URL to use when serviceUrl is not provided in config
|
|
22
24
|
*/
|
|
23
|
-
constructor(log) {
|
|
25
|
+
constructor(log, defaultServiceUrl) {
|
|
24
26
|
this.log = log;
|
|
27
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
25
28
|
}
|
|
26
29
|
loadRawSession(destination) {
|
|
27
30
|
return this.sessions.get(destination) || null;
|
|
28
31
|
}
|
|
29
32
|
validateSessionConfig(config) {
|
|
30
33
|
if (!config || typeof config !== 'object') {
|
|
34
|
+
this.log?.error(`Invalid config format: not an object`);
|
|
31
35
|
throw new Error('SafeAbapSessionStore can only store ABAP sessions (with sapUrl)');
|
|
32
36
|
}
|
|
33
37
|
const obj = config;
|
|
34
38
|
// Accept IConfig format (has serviceUrl) or internal format (has sapUrl)
|
|
35
39
|
const serviceUrl = obj.serviceUrl || obj.sapUrl;
|
|
36
40
|
if (!serviceUrl) {
|
|
41
|
+
this.log?.error(`Validation failed: missing required field serviceUrl or sapUrl`);
|
|
37
42
|
throw new Error('ABAP session config missing required field: serviceUrl or sapUrl');
|
|
38
43
|
}
|
|
39
44
|
if (!obj.authorizationToken && !obj.jwtToken) {
|
|
45
|
+
this.log?.error(`Validation failed: missing required field authorizationToken or jwtToken`);
|
|
40
46
|
throw new Error('ABAP session config missing required field: authorizationToken or jwtToken');
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -60,19 +66,43 @@ class SafeAbapSessionStore {
|
|
|
60
66
|
}
|
|
61
67
|
async loadSession(destination) {
|
|
62
68
|
this.log?.debug(`Loading session for destination: ${destination}`);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (!authConfig && !connConfig) {
|
|
69
|
+
const rawSession = this.loadRawSession(destination);
|
|
70
|
+
if (!rawSession) {
|
|
66
71
|
this.log?.debug(`Session not found for destination: ${destination}`);
|
|
67
72
|
return null;
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// Convert internal format to IConfig format
|
|
75
|
+
const result = {};
|
|
76
|
+
// Connection config fields
|
|
77
|
+
if (rawSession.sapUrl) {
|
|
78
|
+
result.serviceUrl = rawSession.sapUrl;
|
|
79
|
+
}
|
|
80
|
+
if (rawSession.jwtToken !== undefined) {
|
|
81
|
+
result.authorizationToken = rawSession.jwtToken;
|
|
82
|
+
}
|
|
83
|
+
if (rawSession.sapClient) {
|
|
84
|
+
result.sapClient = rawSession.sapClient;
|
|
85
|
+
}
|
|
86
|
+
if (rawSession.language) {
|
|
87
|
+
result.language = rawSession.language;
|
|
88
|
+
}
|
|
89
|
+
// Authorization config fields
|
|
90
|
+
if (rawSession.uaaUrl) {
|
|
91
|
+
result.uaaUrl = rawSession.uaaUrl;
|
|
92
|
+
}
|
|
93
|
+
if (rawSession.uaaClientId) {
|
|
94
|
+
result.uaaClientId = rawSession.uaaClientId;
|
|
95
|
+
}
|
|
96
|
+
if (rawSession.uaaClientSecret) {
|
|
97
|
+
result.uaaClientSecret = rawSession.uaaClientSecret;
|
|
98
|
+
}
|
|
99
|
+
if (rawSession.refreshToken) {
|
|
100
|
+
result.refreshToken = rawSession.refreshToken;
|
|
101
|
+
}
|
|
102
|
+
const tokenLength = rawSession.jwtToken?.length || 0;
|
|
103
|
+
const hasRefreshToken = !!rawSession.refreshToken;
|
|
104
|
+
this.log?.info(`Session loaded for ${destination}: token(${tokenLength} chars), hasRefreshToken(${hasRefreshToken}), sapUrl(${rawSession.sapUrl ? rawSession.sapUrl.substring(0, 40) + '...' : 'none'})`);
|
|
105
|
+
return result;
|
|
76
106
|
}
|
|
77
107
|
async saveSession(destination, config) {
|
|
78
108
|
this.log?.debug(`Saving session for destination: ${destination}`);
|
|
@@ -85,16 +115,26 @@ class SafeAbapSessionStore {
|
|
|
85
115
|
this.sessions.set(destination, internalConfig);
|
|
86
116
|
}
|
|
87
117
|
async deleteSession(destination) {
|
|
88
|
-
this.sessions.
|
|
118
|
+
if (this.sessions.has(destination)) {
|
|
119
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
120
|
+
this.sessions.delete(destination);
|
|
121
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.log?.debug(`Session not found for deletion: ${destination}`);
|
|
125
|
+
}
|
|
89
126
|
}
|
|
90
127
|
async getConnectionConfig(destination) {
|
|
91
128
|
const sessionConfig = this.loadRawSession(destination);
|
|
92
129
|
if (!sessionConfig) {
|
|
130
|
+
this.log?.debug(`Connection config not found for ${destination}`);
|
|
93
131
|
return null;
|
|
94
132
|
}
|
|
95
133
|
if (!sessionConfig.jwtToken || !sessionConfig.sapUrl) {
|
|
134
|
+
this.log?.warn(`Connection config for ${destination} missing required fields: jwtToken(${!!sessionConfig.jwtToken}), sapUrl(${!!sessionConfig.sapUrl})`);
|
|
96
135
|
return null;
|
|
97
136
|
}
|
|
137
|
+
this.log?.debug(`Connection config loaded for ${destination}: token(${sessionConfig.jwtToken.length} chars), sapUrl(${sessionConfig.sapUrl.substring(0, 40)}...)`);
|
|
98
138
|
return {
|
|
99
139
|
serviceUrl: sessionConfig.sapUrl,
|
|
100
140
|
authorizationToken: sessionConfig.jwtToken,
|
|
@@ -106,22 +146,25 @@ class SafeAbapSessionStore {
|
|
|
106
146
|
const current = this.loadRawSession(destination);
|
|
107
147
|
if (!current) {
|
|
108
148
|
// Session doesn't exist - create new one
|
|
109
|
-
// For ABAP, serviceUrl is required
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
// For ABAP, serviceUrl is required - use from config, defaultServiceUrl, or throw error
|
|
150
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
151
|
+
if (!serviceUrl) {
|
|
152
|
+
this.log?.error(`Cannot create session for ${destination}: serviceUrl is required. Missing in config and defaultServiceUrl in constructor.`);
|
|
153
|
+
throw new Error(`Cannot create session for destination "${destination}": serviceUrl is required for ABAP sessions. Provide it in config or constructor.`);
|
|
112
154
|
}
|
|
113
|
-
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: serviceUrl(${
|
|
155
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: serviceUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
114
156
|
const newSession = {
|
|
115
|
-
sapUrl:
|
|
157
|
+
sapUrl: serviceUrl,
|
|
116
158
|
jwtToken: config.authorizationToken || '',
|
|
117
159
|
sapClient: config.sapClient,
|
|
118
160
|
language: config.language,
|
|
119
161
|
};
|
|
120
162
|
// Save directly to Map (internal format)
|
|
121
163
|
this.sessions.set(destination, newSession);
|
|
122
|
-
this.log?.info(`Session created for ${destination}: serviceUrl(${
|
|
164
|
+
this.log?.info(`Session created for ${destination}: serviceUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
123
165
|
return;
|
|
124
166
|
}
|
|
167
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
125
168
|
const updated = {
|
|
126
169
|
...current,
|
|
127
170
|
sapUrl: config.serviceUrl || current.sapUrl,
|
|
@@ -131,15 +174,19 @@ class SafeAbapSessionStore {
|
|
|
131
174
|
};
|
|
132
175
|
// Save directly to Map (internal format)
|
|
133
176
|
this.sessions.set(destination, updated);
|
|
177
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${updated.sapUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
134
178
|
}
|
|
135
179
|
async getAuthorizationConfig(destination) {
|
|
136
180
|
const sessionConfig = this.loadRawSession(destination);
|
|
137
181
|
if (!sessionConfig) {
|
|
182
|
+
this.log?.debug(`Authorization config not found for ${destination}`);
|
|
138
183
|
return null;
|
|
139
184
|
}
|
|
140
185
|
if (!sessionConfig.uaaUrl || !sessionConfig.uaaClientId || !sessionConfig.uaaClientSecret) {
|
|
186
|
+
this.log?.warn(`Authorization config for ${destination} missing required UAA fields`);
|
|
141
187
|
return null;
|
|
142
188
|
}
|
|
189
|
+
this.log?.debug(`Authorization config loaded for ${destination}: uaaUrl(${sessionConfig.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!sessionConfig.refreshToken})`);
|
|
143
190
|
return {
|
|
144
191
|
uaaUrl: sessionConfig.uaaUrl,
|
|
145
192
|
uaaClientId: sessionConfig.uaaClientId,
|
|
@@ -150,12 +197,13 @@ class SafeAbapSessionStore {
|
|
|
150
197
|
async setAuthorizationConfig(destination, config) {
|
|
151
198
|
const current = this.loadRawSession(destination);
|
|
152
199
|
if (!current) {
|
|
153
|
-
// Session doesn't exist - try to get serviceUrl from connection config
|
|
200
|
+
// Session doesn't exist - try to get serviceUrl from connection config or use defaultServiceUrl
|
|
154
201
|
// For ABAP, we need sapUrl to create session
|
|
155
202
|
const connConfig = await this.getConnectionConfig(destination);
|
|
156
|
-
const sapUrl = connConfig?.serviceUrl;
|
|
203
|
+
const sapUrl = connConfig?.serviceUrl || this.defaultServiceUrl;
|
|
157
204
|
if (!sapUrl) {
|
|
158
|
-
|
|
205
|
+
this.log?.error(`Cannot set authorization config for ${destination}: session does not exist and serviceUrl is required. Missing defaultServiceUrl in constructor.`);
|
|
206
|
+
throw new Error(`Cannot set authorization config for destination "${destination}": session does not exist and serviceUrl is required for ABAP sessions. Call setConnectionConfig first or provide defaultServiceUrl in constructor.`);
|
|
159
207
|
}
|
|
160
208
|
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: sapUrl(${sapUrl.substring(0, 40)}...)`);
|
|
161
209
|
const newSession = {
|
|
@@ -168,8 +216,10 @@ class SafeAbapSessionStore {
|
|
|
168
216
|
};
|
|
169
217
|
// Save directly to Map (internal format)
|
|
170
218
|
this.sessions.set(destination, newSession);
|
|
219
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
171
220
|
return;
|
|
172
221
|
}
|
|
222
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
173
223
|
const updated = {
|
|
174
224
|
...current,
|
|
175
225
|
uaaUrl: config.uaaUrl,
|
|
@@ -179,6 +229,7 @@ class SafeAbapSessionStore {
|
|
|
179
229
|
};
|
|
180
230
|
// Save directly to Map (internal format)
|
|
181
231
|
this.sessions.set(destination, updated);
|
|
232
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
182
233
|
}
|
|
183
234
|
}
|
|
184
235
|
exports.SafeAbapSessionStore = SafeAbapSessionStore;
|
|
@@ -17,12 +17,14 @@ import type { IAuthorizationConfig, IConnectionConfig, ISessionStore, IConfig, I
|
|
|
17
17
|
export declare class BtpSessionStore implements ISessionStore {
|
|
18
18
|
protected directory: string;
|
|
19
19
|
private log?;
|
|
20
|
+
private defaultServiceUrl;
|
|
20
21
|
/**
|
|
21
22
|
* Create a new BtpSessionStore instance
|
|
22
23
|
* @param directory Directory where session .env files are located
|
|
24
|
+
* @param defaultServiceUrl Default service URL (required for BTP/XSUAA - cannot be obtained from service key)
|
|
23
25
|
* @param log Optional logger for logging operations
|
|
24
26
|
*/
|
|
25
|
-
constructor(directory: string, log?: ILogger);
|
|
27
|
+
constructor(directory: string, defaultServiceUrl: string, log?: ILogger);
|
|
26
28
|
/**
|
|
27
29
|
* Load session from file
|
|
28
30
|
* @param filePath Path to session file
|
|
@@ -56,13 +56,16 @@ const path = __importStar(require("path"));
|
|
|
56
56
|
class BtpSessionStore {
|
|
57
57
|
directory;
|
|
58
58
|
log;
|
|
59
|
+
defaultServiceUrl;
|
|
59
60
|
/**
|
|
60
61
|
* Create a new BtpSessionStore instance
|
|
61
62
|
* @param directory Directory where session .env files are located
|
|
63
|
+
* @param defaultServiceUrl Default service URL (required for BTP/XSUAA - cannot be obtained from service key)
|
|
62
64
|
* @param log Optional logger for logging operations
|
|
63
65
|
*/
|
|
64
|
-
constructor(directory, log) {
|
|
66
|
+
constructor(directory, defaultServiceUrl, log) {
|
|
65
67
|
this.directory = directory;
|
|
68
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
66
69
|
this.log = log;
|
|
67
70
|
// Ensure directory exists - create if it doesn't
|
|
68
71
|
if (!fs.existsSync(directory)) {
|
|
@@ -164,7 +167,12 @@ class BtpSessionStore {
|
|
|
164
167
|
const fileName = `${destination}.env`;
|
|
165
168
|
const filePath = path.join(this.directory, fileName);
|
|
166
169
|
if (fs.existsSync(filePath)) {
|
|
170
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
167
171
|
fs.unlinkSync(filePath);
|
|
172
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.log?.debug(`Session file not found for deletion: ${destination}`);
|
|
168
176
|
}
|
|
169
177
|
}
|
|
170
178
|
/**
|
|
@@ -203,11 +211,13 @@ class BtpSessionStore {
|
|
|
203
211
|
try {
|
|
204
212
|
const raw = await this.loadFromFile(sessionPath);
|
|
205
213
|
if (!raw || !isBtpBaseSessionConfig(raw)) {
|
|
214
|
+
this.log?.debug(`Invalid session format for ${destination}: missing required fields (jwtToken)`);
|
|
206
215
|
return null;
|
|
207
216
|
}
|
|
208
217
|
return raw;
|
|
209
218
|
}
|
|
210
219
|
catch (error) {
|
|
220
|
+
this.log?.error(`Error loading session for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
|
|
211
221
|
return null;
|
|
212
222
|
}
|
|
213
223
|
}
|
|
@@ -269,10 +279,10 @@ class BtpSessionStore {
|
|
|
269
279
|
const current = await this.loadRawSession(destination);
|
|
270
280
|
if (!current) {
|
|
271
281
|
// Session doesn't exist - create new one
|
|
272
|
-
// For BTP,
|
|
273
|
-
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig`);
|
|
282
|
+
// For BTP, use defaultServiceUrl (required - cannot be obtained from service key)
|
|
283
|
+
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: mcpUrl(${this.defaultServiceUrl.substring(0, 40)}...)`);
|
|
274
284
|
const newSession = {
|
|
275
|
-
mcpUrl:
|
|
285
|
+
mcpUrl: this.defaultServiceUrl,
|
|
276
286
|
jwtToken: '', // Will be set when connection config is set
|
|
277
287
|
uaaUrl: config.uaaUrl,
|
|
278
288
|
uaaClientId: config.uaaClientId,
|
|
@@ -280,9 +290,11 @@ class BtpSessionStore {
|
|
|
280
290
|
refreshToken: config.refreshToken,
|
|
281
291
|
};
|
|
282
292
|
await this.saveSession(destination, newSession);
|
|
293
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
283
294
|
return;
|
|
284
295
|
}
|
|
285
296
|
// Update authorization fields
|
|
297
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
286
298
|
const updated = {
|
|
287
299
|
...current,
|
|
288
300
|
uaaUrl: config.uaaUrl,
|
|
@@ -291,6 +303,7 @@ class BtpSessionStore {
|
|
|
291
303
|
refreshToken: config.refreshToken || current.refreshToken,
|
|
292
304
|
};
|
|
293
305
|
await this.saveSession(destination, updated);
|
|
306
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
294
307
|
}
|
|
295
308
|
/**
|
|
296
309
|
* Set connection configuration
|
|
@@ -302,23 +315,26 @@ class BtpSessionStore {
|
|
|
302
315
|
const current = await this.loadRawSession(destination);
|
|
303
316
|
if (!current) {
|
|
304
317
|
// Session doesn't exist - create new one
|
|
305
|
-
// For BTP,
|
|
306
|
-
|
|
318
|
+
// For BTP, use config.serviceUrl if provided, otherwise use defaultServiceUrl (required)
|
|
319
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
320
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
307
321
|
const newSession = {
|
|
308
|
-
mcpUrl:
|
|
322
|
+
mcpUrl: serviceUrl,
|
|
309
323
|
jwtToken: config.authorizationToken || '',
|
|
310
324
|
};
|
|
311
325
|
await this.saveSession(destination, newSession);
|
|
312
|
-
this.log?.info(`Session created for ${destination}: mcpUrl(${
|
|
326
|
+
this.log?.info(`Session created for ${destination}: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
313
327
|
return;
|
|
314
328
|
}
|
|
315
329
|
// Update connection fields
|
|
330
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
316
331
|
const updated = {
|
|
317
332
|
...current,
|
|
318
333
|
mcpUrl: config.serviceUrl !== undefined ? config.serviceUrl : current.mcpUrl,
|
|
319
334
|
jwtToken: config.authorizationToken !== undefined ? config.authorizationToken : current.jwtToken,
|
|
320
335
|
};
|
|
321
336
|
await this.saveSession(destination, updated);
|
|
337
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${updated.mcpUrl ? updated.mcpUrl.substring(0, 40) + '...' : 'none'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
322
338
|
}
|
|
323
339
|
}
|
|
324
340
|
exports.BtpSessionStore = BtpSessionStore;
|
|
@@ -14,11 +14,13 @@ import type { IConnectionConfig, ISessionStore, IAuthorizationConfig, IConfig, I
|
|
|
14
14
|
export declare class SafeBtpSessionStore implements ISessionStore {
|
|
15
15
|
private sessions;
|
|
16
16
|
private log?;
|
|
17
|
+
private defaultServiceUrl;
|
|
17
18
|
/**
|
|
18
19
|
* Create a new SafeBtpSessionStore instance
|
|
20
|
+
* @param defaultServiceUrl Default service URL (required for BTP/XSUAA - cannot be obtained from service key)
|
|
19
21
|
* @param log Optional logger for logging operations
|
|
20
22
|
*/
|
|
21
|
-
constructor(log?: ILogger);
|
|
23
|
+
constructor(defaultServiceUrl: string, log?: ILogger);
|
|
22
24
|
private loadRawSession;
|
|
23
25
|
private validateSessionConfig;
|
|
24
26
|
private convertToInternalFormat;
|
|
@@ -16,11 +16,14 @@ exports.SafeBtpSessionStore = void 0;
|
|
|
16
16
|
class SafeBtpSessionStore {
|
|
17
17
|
sessions = new Map();
|
|
18
18
|
log;
|
|
19
|
+
defaultServiceUrl;
|
|
19
20
|
/**
|
|
20
21
|
* Create a new SafeBtpSessionStore instance
|
|
22
|
+
* @param defaultServiceUrl Default service URL (required for BTP/XSUAA - cannot be obtained from service key)
|
|
21
23
|
* @param log Optional logger for logging operations
|
|
22
24
|
*/
|
|
23
|
-
constructor(log) {
|
|
25
|
+
constructor(defaultServiceUrl, log) {
|
|
26
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
24
27
|
this.log = log;
|
|
25
28
|
}
|
|
26
29
|
loadRawSession(destination) {
|
|
@@ -90,17 +93,27 @@ class SafeBtpSessionStore {
|
|
|
90
93
|
this.sessions.set(destination, internalConfig);
|
|
91
94
|
}
|
|
92
95
|
async deleteSession(destination) {
|
|
93
|
-
this.sessions.
|
|
96
|
+
if (this.sessions.has(destination)) {
|
|
97
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
98
|
+
this.sessions.delete(destination);
|
|
99
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.log?.debug(`Session not found for deletion: ${destination}`);
|
|
103
|
+
}
|
|
94
104
|
}
|
|
95
105
|
async getConnectionConfig(destination) {
|
|
96
106
|
const sessionConfig = this.loadRawSession(destination);
|
|
97
107
|
if (!sessionConfig) {
|
|
108
|
+
this.log?.debug(`Connection config not found for ${destination}`);
|
|
98
109
|
return null;
|
|
99
110
|
}
|
|
100
111
|
// Return null if jwtToken is undefined or null (but allow empty string)
|
|
101
112
|
if (sessionConfig.jwtToken === undefined || sessionConfig.jwtToken === null) {
|
|
113
|
+
this.log?.warn(`Connection config for ${destination} missing required field: jwtToken`);
|
|
102
114
|
return null;
|
|
103
115
|
}
|
|
116
|
+
this.log?.debug(`Connection config loaded for ${destination}: token(${sessionConfig.jwtToken.length} chars), serviceUrl(${sessionConfig.mcpUrl ? sessionConfig.mcpUrl.substring(0, 40) + '...' : 'none'})`);
|
|
104
117
|
return {
|
|
105
118
|
serviceUrl: sessionConfig.mcpUrl, // May be undefined for base BTP
|
|
106
119
|
authorizationToken: sessionConfig.jwtToken,
|
|
@@ -110,17 +123,19 @@ class SafeBtpSessionStore {
|
|
|
110
123
|
const current = this.loadRawSession(destination);
|
|
111
124
|
if (!current) {
|
|
112
125
|
// Session doesn't exist - create new one
|
|
113
|
-
// For BTP,
|
|
114
|
-
|
|
126
|
+
// For BTP, use config.serviceUrl if provided, otherwise use defaultServiceUrl (required)
|
|
127
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
128
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
115
129
|
const newSession = {
|
|
116
|
-
mcpUrl:
|
|
130
|
+
mcpUrl: serviceUrl,
|
|
117
131
|
jwtToken: config.authorizationToken || '',
|
|
118
132
|
};
|
|
119
133
|
// Save directly to Map (internal format)
|
|
120
134
|
this.sessions.set(destination, newSession);
|
|
121
|
-
this.log?.info(`Session created for ${destination}: mcpUrl(${
|
|
135
|
+
this.log?.info(`Session created for ${destination}: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
122
136
|
return;
|
|
123
137
|
}
|
|
138
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
124
139
|
const updated = {
|
|
125
140
|
...current,
|
|
126
141
|
mcpUrl: config.serviceUrl !== undefined ? config.serviceUrl : current.mcpUrl,
|
|
@@ -128,15 +143,19 @@ class SafeBtpSessionStore {
|
|
|
128
143
|
};
|
|
129
144
|
// Save directly to Map (internal format)
|
|
130
145
|
this.sessions.set(destination, updated);
|
|
146
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${updated.mcpUrl ? updated.mcpUrl.substring(0, 40) + '...' : 'none'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
131
147
|
}
|
|
132
148
|
async getAuthorizationConfig(destination) {
|
|
133
149
|
const sessionConfig = this.loadRawSession(destination);
|
|
134
150
|
if (!sessionConfig) {
|
|
151
|
+
this.log?.debug(`Authorization config not found for ${destination}`);
|
|
135
152
|
return null;
|
|
136
153
|
}
|
|
137
154
|
if (!sessionConfig.uaaUrl || !sessionConfig.uaaClientId || !sessionConfig.uaaClientSecret) {
|
|
155
|
+
this.log?.warn(`Authorization config for ${destination} missing required UAA fields`);
|
|
138
156
|
return null;
|
|
139
157
|
}
|
|
158
|
+
this.log?.debug(`Authorization config loaded for ${destination}: uaaUrl(${sessionConfig.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!sessionConfig.refreshToken})`);
|
|
140
159
|
return {
|
|
141
160
|
uaaUrl: sessionConfig.uaaUrl,
|
|
142
161
|
uaaClientId: sessionConfig.uaaClientId,
|
|
@@ -148,10 +167,10 @@ class SafeBtpSessionStore {
|
|
|
148
167
|
const current = this.loadRawSession(destination);
|
|
149
168
|
if (!current) {
|
|
150
169
|
// Session doesn't exist - create new one
|
|
151
|
-
// For BTP,
|
|
152
|
-
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig`);
|
|
170
|
+
// For BTP, use defaultServiceUrl (required - cannot be obtained from service key)
|
|
171
|
+
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: mcpUrl(${this.defaultServiceUrl.substring(0, 40)}...)`);
|
|
153
172
|
const newSession = {
|
|
154
|
-
mcpUrl:
|
|
173
|
+
mcpUrl: this.defaultServiceUrl,
|
|
155
174
|
jwtToken: '', // Will be set when connection config is set
|
|
156
175
|
uaaUrl: config.uaaUrl,
|
|
157
176
|
uaaClientId: config.uaaClientId,
|
|
@@ -160,8 +179,10 @@ class SafeBtpSessionStore {
|
|
|
160
179
|
};
|
|
161
180
|
// Save directly to Map (internal format)
|
|
162
181
|
this.sessions.set(destination, newSession);
|
|
182
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
163
183
|
return;
|
|
164
184
|
}
|
|
185
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
165
186
|
const updated = {
|
|
166
187
|
...current,
|
|
167
188
|
uaaUrl: config.uaaUrl,
|
|
@@ -171,6 +192,7 @@ class SafeBtpSessionStore {
|
|
|
171
192
|
};
|
|
172
193
|
// Save directly to Map (internal format)
|
|
173
194
|
this.sessions.set(destination, updated);
|
|
195
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
174
196
|
}
|
|
175
197
|
}
|
|
176
198
|
exports.SafeBtpSessionStore = SafeBtpSessionStore;
|
|
@@ -14,11 +14,13 @@ import type { IConnectionConfig, ISessionStore, IAuthorizationConfig, IConfig, I
|
|
|
14
14
|
export declare class SafeXsuaaSessionStore implements ISessionStore {
|
|
15
15
|
private sessions;
|
|
16
16
|
private log?;
|
|
17
|
+
private defaultServiceUrl;
|
|
17
18
|
/**
|
|
18
19
|
* Create a new SafeXsuaaSessionStore instance
|
|
20
|
+
* @param defaultServiceUrl Default service URL (required for XSUAA - cannot be obtained from service key)
|
|
19
21
|
* @param log Optional logger for logging operations
|
|
20
22
|
*/
|
|
21
|
-
constructor(log?: ILogger);
|
|
23
|
+
constructor(defaultServiceUrl: string, log?: ILogger);
|
|
22
24
|
private loadRawSession;
|
|
23
25
|
private validateSessionConfig;
|
|
24
26
|
private convertToInternalFormat;
|
|
@@ -16,11 +16,14 @@ exports.SafeXsuaaSessionStore = void 0;
|
|
|
16
16
|
class SafeXsuaaSessionStore {
|
|
17
17
|
sessions = new Map();
|
|
18
18
|
log;
|
|
19
|
+
defaultServiceUrl;
|
|
19
20
|
/**
|
|
20
21
|
* Create a new SafeXsuaaSessionStore instance
|
|
22
|
+
* @param defaultServiceUrl Default service URL (required for XSUAA - cannot be obtained from service key)
|
|
21
23
|
* @param log Optional logger for logging operations
|
|
22
24
|
*/
|
|
23
|
-
constructor(log) {
|
|
25
|
+
constructor(defaultServiceUrl, log) {
|
|
26
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
24
27
|
this.log = log;
|
|
25
28
|
}
|
|
26
29
|
loadRawSession(destination) {
|
|
@@ -90,7 +93,14 @@ class SafeXsuaaSessionStore {
|
|
|
90
93
|
this.sessions.set(destination, internalConfig);
|
|
91
94
|
}
|
|
92
95
|
async deleteSession(destination) {
|
|
93
|
-
this.sessions.
|
|
96
|
+
if (this.sessions.has(destination)) {
|
|
97
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
98
|
+
this.sessions.delete(destination);
|
|
99
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.log?.debug(`Session not found for deletion: ${destination}`);
|
|
103
|
+
}
|
|
94
104
|
}
|
|
95
105
|
async getConnectionConfig(destination) {
|
|
96
106
|
const sessionConfig = this.loadRawSession(destination);
|
|
@@ -110,17 +120,19 @@ class SafeXsuaaSessionStore {
|
|
|
110
120
|
const current = this.loadRawSession(destination);
|
|
111
121
|
if (!current) {
|
|
112
122
|
// Session doesn't exist - create new one
|
|
113
|
-
// For XSUAA,
|
|
114
|
-
|
|
123
|
+
// For XSUAA, use config.serviceUrl if provided, otherwise use defaultServiceUrl (required)
|
|
124
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
125
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
115
126
|
const newSession = {
|
|
116
|
-
mcpUrl:
|
|
127
|
+
mcpUrl: serviceUrl,
|
|
117
128
|
jwtToken: config.authorizationToken || '',
|
|
118
129
|
};
|
|
119
130
|
// Save directly to Map (internal format)
|
|
120
131
|
this.sessions.set(destination, newSession);
|
|
121
|
-
this.log?.info(`Session created for ${destination}: mcpUrl(${
|
|
132
|
+
this.log?.info(`Session created for ${destination}: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
122
133
|
return;
|
|
123
134
|
}
|
|
135
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
124
136
|
const updated = {
|
|
125
137
|
...current,
|
|
126
138
|
mcpUrl: config.serviceUrl !== undefined ? config.serviceUrl : current.mcpUrl,
|
|
@@ -128,15 +140,19 @@ class SafeXsuaaSessionStore {
|
|
|
128
140
|
};
|
|
129
141
|
// Save directly to Map (internal format)
|
|
130
142
|
this.sessions.set(destination, updated);
|
|
143
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${updated.mcpUrl ? updated.mcpUrl.substring(0, 40) + '...' : 'none'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
131
144
|
}
|
|
132
145
|
async getAuthorizationConfig(destination) {
|
|
133
146
|
const sessionConfig = this.loadRawSession(destination);
|
|
134
147
|
if (!sessionConfig) {
|
|
148
|
+
this.log?.debug(`Authorization config not found for ${destination}`);
|
|
135
149
|
return null;
|
|
136
150
|
}
|
|
137
151
|
if (!sessionConfig.uaaUrl || !sessionConfig.uaaClientId || !sessionConfig.uaaClientSecret) {
|
|
152
|
+
this.log?.warn(`Authorization config for ${destination} missing required UAA fields`);
|
|
138
153
|
return null;
|
|
139
154
|
}
|
|
155
|
+
this.log?.debug(`Authorization config loaded for ${destination}: uaaUrl(${sessionConfig.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!sessionConfig.refreshToken})`);
|
|
140
156
|
return {
|
|
141
157
|
uaaUrl: sessionConfig.uaaUrl,
|
|
142
158
|
uaaClientId: sessionConfig.uaaClientId,
|
|
@@ -148,10 +164,10 @@ class SafeXsuaaSessionStore {
|
|
|
148
164
|
const current = this.loadRawSession(destination);
|
|
149
165
|
if (!current) {
|
|
150
166
|
// Session doesn't exist - create new one
|
|
151
|
-
// For XSUAA,
|
|
152
|
-
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig`);
|
|
167
|
+
// For XSUAA, use defaultServiceUrl (required - cannot be obtained from service key)
|
|
168
|
+
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: mcpUrl(${this.defaultServiceUrl.substring(0, 40)}...)`);
|
|
153
169
|
const newSession = {
|
|
154
|
-
mcpUrl:
|
|
170
|
+
mcpUrl: this.defaultServiceUrl,
|
|
155
171
|
jwtToken: '', // Will be set when connection config is set
|
|
156
172
|
uaaUrl: config.uaaUrl,
|
|
157
173
|
uaaClientId: config.uaaClientId,
|
|
@@ -160,8 +176,10 @@ class SafeXsuaaSessionStore {
|
|
|
160
176
|
};
|
|
161
177
|
// Save directly to Map (internal format)
|
|
162
178
|
this.sessions.set(destination, newSession);
|
|
179
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
163
180
|
return;
|
|
164
181
|
}
|
|
182
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
165
183
|
const updated = {
|
|
166
184
|
...current,
|
|
167
185
|
uaaUrl: config.uaaUrl,
|
|
@@ -171,6 +189,7 @@ class SafeXsuaaSessionStore {
|
|
|
171
189
|
};
|
|
172
190
|
// Save directly to Map (internal format)
|
|
173
191
|
this.sessions.set(destination, updated);
|
|
192
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
174
193
|
}
|
|
175
194
|
}
|
|
176
195
|
exports.SafeXsuaaSessionStore = SafeXsuaaSessionStore;
|
|
@@ -17,12 +17,14 @@ import type { IAuthorizationConfig, IConnectionConfig, ISessionStore, IConfig, I
|
|
|
17
17
|
export declare class XsuaaSessionStore implements ISessionStore {
|
|
18
18
|
protected directory: string;
|
|
19
19
|
private log?;
|
|
20
|
+
private defaultServiceUrl;
|
|
20
21
|
/**
|
|
21
22
|
* Create a new XsuaaSessionStore instance
|
|
22
23
|
* @param directory Directory where session .env files are located
|
|
24
|
+
* @param defaultServiceUrl Default service URL (required for XSUAA - cannot be obtained from service key)
|
|
23
25
|
* @param log Optional logger for logging operations
|
|
24
26
|
*/
|
|
25
|
-
constructor(directory: string, log?: ILogger);
|
|
27
|
+
constructor(directory: string, defaultServiceUrl: string, log?: ILogger);
|
|
26
28
|
/**
|
|
27
29
|
* Get file name for destination
|
|
28
30
|
* @param destination Destination name
|
|
@@ -56,13 +56,16 @@ const path = __importStar(require("path"));
|
|
|
56
56
|
class XsuaaSessionStore {
|
|
57
57
|
directory;
|
|
58
58
|
log;
|
|
59
|
+
defaultServiceUrl;
|
|
59
60
|
/**
|
|
60
61
|
* Create a new XsuaaSessionStore instance
|
|
61
62
|
* @param directory Directory where session .env files are located
|
|
63
|
+
* @param defaultServiceUrl Default service URL (required for XSUAA - cannot be obtained from service key)
|
|
62
64
|
* @param log Optional logger for logging operations
|
|
63
65
|
*/
|
|
64
|
-
constructor(directory, log) {
|
|
66
|
+
constructor(directory, defaultServiceUrl, log) {
|
|
65
67
|
this.directory = directory;
|
|
68
|
+
this.defaultServiceUrl = defaultServiceUrl;
|
|
66
69
|
this.log = log;
|
|
67
70
|
// Ensure directory exists - create if it doesn't
|
|
68
71
|
if (!fs.existsSync(directory)) {
|
|
@@ -172,7 +175,12 @@ class XsuaaSessionStore {
|
|
|
172
175
|
const fileName = `${destination}.env`;
|
|
173
176
|
const filePath = path.join(this.directory, fileName);
|
|
174
177
|
if (fs.existsSync(filePath)) {
|
|
178
|
+
this.log?.debug(`Deleting session for destination: ${destination}`);
|
|
175
179
|
fs.unlinkSync(filePath);
|
|
180
|
+
this.log?.info(`Session deleted for destination: ${destination}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.log?.debug(`Session file not found for deletion: ${destination}`);
|
|
176
184
|
}
|
|
177
185
|
}
|
|
178
186
|
/**
|
|
@@ -211,11 +219,13 @@ class XsuaaSessionStore {
|
|
|
211
219
|
try {
|
|
212
220
|
const raw = await this.loadFromFile(sessionPath);
|
|
213
221
|
if (!raw || !isXsuaaSessionConfig(raw)) {
|
|
222
|
+
this.log?.debug(`Invalid session format for ${destination}: missing required fields (jwtToken)`);
|
|
214
223
|
return null;
|
|
215
224
|
}
|
|
216
225
|
return raw;
|
|
217
226
|
}
|
|
218
227
|
catch (error) {
|
|
228
|
+
this.log?.error(`Error loading session for ${destination}: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
229
|
return null;
|
|
220
230
|
}
|
|
221
231
|
}
|
|
@@ -240,23 +250,26 @@ class XsuaaSessionStore {
|
|
|
240
250
|
const current = await this.loadRawSession(destination);
|
|
241
251
|
if (!current) {
|
|
242
252
|
// Session doesn't exist - create new one
|
|
243
|
-
// For XSUAA,
|
|
244
|
-
|
|
253
|
+
// For XSUAA, use config.serviceUrl if provided, otherwise use defaultServiceUrl (required)
|
|
254
|
+
const serviceUrl = config.serviceUrl || this.defaultServiceUrl;
|
|
255
|
+
this.log?.debug(`Creating new session for ${destination} via setConnectionConfig: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
245
256
|
const newSession = {
|
|
246
|
-
mcpUrl:
|
|
257
|
+
mcpUrl: serviceUrl,
|
|
247
258
|
jwtToken: config.authorizationToken || '',
|
|
248
259
|
};
|
|
249
260
|
await this.saveSession(destination, newSession);
|
|
250
|
-
this.log?.info(`Session created for ${destination}: mcpUrl(${
|
|
261
|
+
this.log?.info(`Session created for ${destination}: mcpUrl(${serviceUrl.substring(0, 40)}...), token(${config.authorizationToken?.length || 0} chars)`);
|
|
251
262
|
return;
|
|
252
263
|
}
|
|
253
264
|
// Update connection fields
|
|
265
|
+
this.log?.debug(`Updating connection config for existing session ${destination}: serviceUrl(${config.serviceUrl ? config.serviceUrl.substring(0, 40) + '...' : 'unchanged'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
254
266
|
const updated = {
|
|
255
267
|
...current,
|
|
256
268
|
mcpUrl: config.serviceUrl !== undefined ? config.serviceUrl : current.mcpUrl,
|
|
257
269
|
jwtToken: config.authorizationToken !== undefined ? config.authorizationToken : current.jwtToken,
|
|
258
270
|
};
|
|
259
271
|
await this.saveSession(destination, updated);
|
|
272
|
+
this.log?.info(`Connection config updated for ${destination}: serviceUrl(${updated.mcpUrl ? updated.mcpUrl.substring(0, 40) + '...' : 'none'}), token(${config.authorizationToken?.length || 0} chars)`);
|
|
260
273
|
}
|
|
261
274
|
async getAuthorizationConfig(destination) {
|
|
262
275
|
const sessionConfig = await this.loadRawSession(destination);
|
|
@@ -280,10 +293,10 @@ class XsuaaSessionStore {
|
|
|
280
293
|
const current = await this.loadRawSession(destination);
|
|
281
294
|
if (!current) {
|
|
282
295
|
// Session doesn't exist - create new one
|
|
283
|
-
// For XSUAA,
|
|
284
|
-
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig`);
|
|
296
|
+
// For XSUAA, use defaultServiceUrl (required - cannot be obtained from service key)
|
|
297
|
+
this.log?.debug(`Creating new session for ${destination} via setAuthorizationConfig: mcpUrl(${this.defaultServiceUrl.substring(0, 40)}...)`);
|
|
285
298
|
const newSession = {
|
|
286
|
-
mcpUrl:
|
|
299
|
+
mcpUrl: this.defaultServiceUrl,
|
|
287
300
|
jwtToken: '', // Will be set when connection config is set
|
|
288
301
|
uaaUrl: config.uaaUrl,
|
|
289
302
|
uaaClientId: config.uaaClientId,
|
|
@@ -291,9 +304,11 @@ class XsuaaSessionStore {
|
|
|
291
304
|
refreshToken: config.refreshToken,
|
|
292
305
|
};
|
|
293
306
|
await this.saveSession(destination, newSession);
|
|
307
|
+
this.log?.info(`New session created for ${destination} via setAuthorizationConfig: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
294
308
|
return;
|
|
295
309
|
}
|
|
296
310
|
// Update authorization fields
|
|
311
|
+
this.log?.debug(`Updating authorization config for existing session ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
297
312
|
const updated = {
|
|
298
313
|
...current,
|
|
299
314
|
uaaUrl: config.uaaUrl,
|
|
@@ -302,6 +317,7 @@ class XsuaaSessionStore {
|
|
|
302
317
|
refreshToken: config.refreshToken || current.refreshToken,
|
|
303
318
|
};
|
|
304
319
|
await this.saveSession(destination, updated);
|
|
320
|
+
this.log?.info(`Authorization config updated for ${destination}: uaaUrl(${config.uaaUrl.substring(0, 30)}...), hasRefreshToken(${!!config.refreshToken})`);
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
323
|
exports.XsuaaSessionStore = XsuaaSessionStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/auth-stores",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Stores for MCP ABAP ADT auth-broker - BTP, ABAP, and XSUAA implementations",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"node": ">=18.0.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@mcp-abap-adt/interfaces": "^0.1.
|
|
52
|
+
"@mcp-abap-adt/interfaces": "^0.1.4",
|
|
53
53
|
"dotenv": "^17.2.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|