@mcp-abap-adt/auth-broker 0.1.11 → 0.1.12
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 +23 -0
- package/README.md +29 -1
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +129 -25
- package/dist/__tests__/helpers/configHelpers.d.ts.map +1 -1
- package/dist/__tests__/helpers/configHelpers.js +24 -2
- package/dist/__tests__/helpers/testLogger.d.ts +6 -0
- package/dist/__tests__/helpers/testLogger.d.ts.map +1 -0
- package/dist/__tests__/helpers/testLogger.js +81 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,29 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.1.12] - 2025-12-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Debugging Environment Variables**: Added comprehensive debugging support via environment variables
|
|
18
|
+
- `DEBUG_AUTH_BROKER` - Enable/disable logging for auth-broker package (default: `false`)
|
|
19
|
+
- `LOG_LEVEL` - Control log verbosity: `debug`, `info`, `warn`, `error` (default: `info`)
|
|
20
|
+
- `DEBUG` - Alternative way to enable debugging (set to `true` or string containing `auth-broker`)
|
|
21
|
+
- Logging is disabled by default to avoid misleading output in tests
|
|
22
|
+
- Tests that expect errors use no-op logger to prevent error message output
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **Test Logger Behavior**: Modified `createTestLogger` to require explicit enable via environment variables
|
|
26
|
+
- No longer enabled by default in test environment (`NODE_ENV === 'test'`)
|
|
27
|
+
- Requires `DEBUG_AUTH_BROKER=true` or `DEBUG=true` to enable logging
|
|
28
|
+
- Prevents misleading error output in tests that expect errors
|
|
29
|
+
- Tests that expect errors now use `noOpLogger` to avoid false error messages
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- **Service URL Handling**: Fixed `serviceUrl` propagation for ABAP sessions
|
|
33
|
+
- `AuthBroker` now retrieves `serviceUrl` from `serviceKeyStore` if not provided by `tokenProvider`
|
|
34
|
+
- Ensures ABAP session stores receive required `serviceUrl` even when token provider doesn't return it
|
|
35
|
+
- Applied to all authentication flows (refresh token, UAA, browser auth)
|
|
36
|
+
|
|
14
37
|
## [0.1.11] - 2025-12-07
|
|
15
38
|
|
|
16
39
|
### Changed
|
package/README.md
CHANGED
|
@@ -63,8 +63,36 @@ const newToken = await broker.refreshToken('TRIAL');
|
|
|
63
63
|
|
|
64
64
|
### Environment Variables
|
|
65
65
|
|
|
66
|
+
#### Configuration Variables
|
|
67
|
+
|
|
66
68
|
- `AUTH_BROKER_PATH` - Colon/semicolon-separated paths for searching `.env` and `.json` files (default: current working directory)
|
|
67
|
-
|
|
69
|
+
|
|
70
|
+
#### Debugging Variables
|
|
71
|
+
|
|
72
|
+
- `DEBUG_AUTH_BROKER` - Enable debug logging for `auth-broker` package
|
|
73
|
+
- Set to `true` to enable logging (default: `false`)
|
|
74
|
+
- When enabled, logs authentication steps, token operations, and error details
|
|
75
|
+
- Can be explicitly disabled by setting to `false`
|
|
76
|
+
- Example: `DEBUG_AUTH_BROKER=true npm test`
|
|
77
|
+
|
|
78
|
+
- `LOG_LEVEL` - Control log verbosity level
|
|
79
|
+
- Values: `debug`, `info`, `warn`, `error` (default: `info`)
|
|
80
|
+
- `debug` - All messages including detailed debug information
|
|
81
|
+
- `info` - Informational messages, warnings, and errors
|
|
82
|
+
- `warn` - Warnings and errors only
|
|
83
|
+
- `error` - Errors only
|
|
84
|
+
- Example: `LOG_LEVEL=debug DEBUG_AUTH_BROKER=true npm test`
|
|
85
|
+
|
|
86
|
+
- `DEBUG` - Alternative way to enable debugging
|
|
87
|
+
- Set to `true` to enable all debug logging
|
|
88
|
+
- Or set to a string containing `auth-broker` to enable only this package
|
|
89
|
+
- Example: `DEBUG=true npm test` or `DEBUG=auth-broker npm test`
|
|
90
|
+
|
|
91
|
+
**Note**: For debugging related packages:
|
|
92
|
+
- `DEBUG_AUTH_STORES` - Enable logging for `@mcp-abap-adt/auth-stores` package
|
|
93
|
+
- `DEBUG_AUTH_PROVIDERS` - Enable logging for `@mcp-abap-adt/auth-providers` package
|
|
94
|
+
|
|
95
|
+
**Legacy Support**: `DEBUG_AUTH_LOG` is still supported for backward compatibility (equivalent to `DEBUG_AUTH_BROKER=true LOG_LEVEL=debug`)
|
|
68
96
|
|
|
69
97
|
### File Structure
|
|
70
98
|
|
package/dist/AuthBroker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAW,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAY7C;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IAEtC;;;;;;;;;;OAUG;gBAED,MAAM,EAAE;QAAE,eAAe,EAAE,gBAAgB,CAAC;QAAC,YAAY,EAAE,aAAa,CAAC;QAAC,aAAa,EAAE,cAAc,CAAA;KAAE,EACzG,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,OAAO;IA8DlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA6KpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuDxD;;;;OAIG;IACG,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAsBvF;;;;OAIG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAoBlF"}
|
package/dist/AuthBroker.js
CHANGED
|
@@ -47,11 +47,45 @@ class AuthBroker {
|
|
|
47
47
|
if (!stores.tokenProvider) {
|
|
48
48
|
throw new Error('AuthBroker: tokenProvider is required');
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Validate that stores and provider are correctly instantiated (have required methods)
|
|
51
|
+
const serviceKeyStore = stores.serviceKeyStore;
|
|
52
|
+
const sessionStore = stores.sessionStore;
|
|
53
|
+
const tokenProvider = stores.tokenProvider;
|
|
54
|
+
// Check serviceKeyStore methods
|
|
55
|
+
if (typeof serviceKeyStore.getServiceKey !== 'function') {
|
|
56
|
+
throw new Error('AuthBroker: serviceKeyStore.getServiceKey must be a function');
|
|
57
|
+
}
|
|
58
|
+
if (typeof serviceKeyStore.getAuthorizationConfig !== 'function') {
|
|
59
|
+
throw new Error('AuthBroker: serviceKeyStore.getAuthorizationConfig must be a function');
|
|
60
|
+
}
|
|
61
|
+
if (typeof serviceKeyStore.getConnectionConfig !== 'function') {
|
|
62
|
+
throw new Error('AuthBroker: serviceKeyStore.getConnectionConfig must be a function');
|
|
63
|
+
}
|
|
64
|
+
// Check sessionStore methods
|
|
65
|
+
if (typeof sessionStore.getAuthorizationConfig !== 'function') {
|
|
66
|
+
throw new Error('AuthBroker: sessionStore.getAuthorizationConfig must be a function');
|
|
67
|
+
}
|
|
68
|
+
if (typeof sessionStore.getConnectionConfig !== 'function') {
|
|
69
|
+
throw new Error('AuthBroker: sessionStore.getConnectionConfig must be a function');
|
|
70
|
+
}
|
|
71
|
+
if (typeof sessionStore.setAuthorizationConfig !== 'function') {
|
|
72
|
+
throw new Error('AuthBroker: sessionStore.setAuthorizationConfig must be a function');
|
|
73
|
+
}
|
|
74
|
+
if (typeof sessionStore.setConnectionConfig !== 'function') {
|
|
75
|
+
throw new Error('AuthBroker: sessionStore.setConnectionConfig must be a function');
|
|
76
|
+
}
|
|
77
|
+
// Check tokenProvider methods
|
|
78
|
+
if (typeof tokenProvider.getConnectionConfig !== 'function') {
|
|
79
|
+
throw new Error('AuthBroker: tokenProvider.getConnectionConfig must be a function');
|
|
80
|
+
}
|
|
81
|
+
// validateToken is optional, so we don't check it
|
|
82
|
+
this.serviceKeyStore = serviceKeyStore;
|
|
83
|
+
this.sessionStore = sessionStore;
|
|
84
|
+
this.tokenProvider = tokenProvider;
|
|
53
85
|
this.browser = browser || 'system';
|
|
54
86
|
this.logger = logger || noOpLogger;
|
|
87
|
+
// Log successful initialization
|
|
88
|
+
this.logger?.debug('AuthBroker initialized: serviceKeyStore(ok), sessionStore(ok), tokenProvider(ok)');
|
|
55
89
|
}
|
|
56
90
|
/**
|
|
57
91
|
* Get authentication token for destination.
|
|
@@ -87,50 +121,71 @@ class AuthBroker {
|
|
|
87
121
|
* @throws Error if neither session data nor service key found, or if all authentication methods failed
|
|
88
122
|
*/
|
|
89
123
|
async getToken(destination) {
|
|
124
|
+
this.logger?.debug(`Getting token for destination: ${destination}`);
|
|
90
125
|
// Step 1: Check if session exists and token is valid
|
|
91
126
|
const connConfig = await this.sessionStore.getConnectionConfig(destination);
|
|
92
127
|
if (connConfig?.authorizationToken) {
|
|
128
|
+
this.logger?.debug(`Session found: token(${connConfig.authorizationToken.length} chars), serviceUrl(${connConfig.serviceUrl ? 'yes' : 'no'})`);
|
|
93
129
|
// Validate token if provider supports validation and we have service URL
|
|
94
130
|
if (this.tokenProvider.validateToken && connConfig.serviceUrl) {
|
|
131
|
+
this.logger?.debug(`Validating token for ${destination}`);
|
|
95
132
|
const isValid = await this.tokenProvider.validateToken(connConfig.authorizationToken, connConfig.serviceUrl);
|
|
96
133
|
if (isValid) {
|
|
134
|
+
this.logger?.info(`Token valid for ${destination}: token(${connConfig.authorizationToken.length} chars)`);
|
|
97
135
|
return connConfig.authorizationToken;
|
|
98
136
|
}
|
|
137
|
+
this.logger?.debug(`Token invalid for ${destination}, continuing to refresh`);
|
|
99
138
|
}
|
|
100
139
|
else {
|
|
101
140
|
// No service URL or provider doesn't support validation - just return token
|
|
141
|
+
this.logger?.info(`Token found for ${destination} (no validation): token(${connConfig.authorizationToken.length} chars)`);
|
|
102
142
|
return connConfig.authorizationToken;
|
|
103
143
|
}
|
|
104
144
|
}
|
|
145
|
+
else {
|
|
146
|
+
this.logger?.debug(`No session found for ${destination}`);
|
|
147
|
+
}
|
|
105
148
|
// Step 2: No valid session, check if we have service key
|
|
149
|
+
this.logger?.debug(`Checking service key for ${destination}`);
|
|
106
150
|
const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
|
|
107
151
|
if (!serviceKey) {
|
|
108
|
-
|
|
152
|
+
this.logger?.error(`No service key found for ${destination}`);
|
|
109
153
|
throw new Error(`No authentication found for destination "${destination}". ` +
|
|
110
154
|
`No session data and no service key found.`);
|
|
111
155
|
}
|
|
112
156
|
// Get authorization config from service key
|
|
113
157
|
const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
|
|
114
158
|
if (!authConfig) {
|
|
159
|
+
this.logger?.error(`Service key for ${destination} missing UAA credentials`);
|
|
115
160
|
throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
|
|
116
161
|
}
|
|
162
|
+
this.logger?.debug(`Service key loaded for ${destination}: uaaUrl(${authConfig.uaaUrl.substring(0, 40)}...)`);
|
|
117
163
|
// Get refresh token from session (if exists)
|
|
118
164
|
const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
|
|
119
165
|
const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
|
|
166
|
+
this.logger?.debug(`Refresh token check for ${destination}: hasRefreshToken(${!!refreshToken})`);
|
|
120
167
|
let tokenResult;
|
|
121
168
|
let lastError = null;
|
|
122
169
|
// Step 3: Try to refresh using refresh token (if available) via tokenProvider
|
|
123
170
|
if (refreshToken) {
|
|
124
171
|
try {
|
|
125
|
-
this.logger
|
|
172
|
+
this.logger?.debug(`Trying refresh token flow for ${destination}`);
|
|
126
173
|
const authConfigWithRefresh = { ...authConfig, refreshToken };
|
|
127
174
|
tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
|
|
128
175
|
browser: this.browser,
|
|
129
176
|
logger: this.logger,
|
|
130
177
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
|
|
179
|
+
this.logger?.info(`Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
180
|
+
// Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
|
|
181
|
+
// For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
|
|
182
|
+
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
183
|
+
const connectionConfigWithServiceUrl = {
|
|
184
|
+
...tokenResult.connectionConfig,
|
|
185
|
+
serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
|
|
186
|
+
};
|
|
187
|
+
// Update or create session with new token (stores handle creation if session doesn't exist)
|
|
188
|
+
await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
|
|
134
189
|
if (tokenResult.refreshToken) {
|
|
135
190
|
await this.sessionStore.setAuthorizationConfig(destination, {
|
|
136
191
|
...authConfig,
|
|
@@ -141,22 +196,30 @@ class AuthBroker {
|
|
|
141
196
|
}
|
|
142
197
|
catch (error) {
|
|
143
198
|
lastError = error;
|
|
144
|
-
this.logger
|
|
199
|
+
this.logger?.debug(`Refresh token flow failed for ${destination}: ${error.message}, trying UAA`);
|
|
145
200
|
// Continue to next step
|
|
146
201
|
}
|
|
147
202
|
}
|
|
148
203
|
// Step 4: Try UAA (client_credentials) via tokenProvider (without refresh token)
|
|
149
204
|
// TokenProvider should try client_credentials if refresh token is not provided
|
|
150
205
|
try {
|
|
151
|
-
this.logger
|
|
206
|
+
this.logger?.debug(`Trying UAA (client_credentials) flow for ${destination}`);
|
|
152
207
|
const authConfigWithoutRefresh = { ...authConfig, refreshToken: undefined };
|
|
153
208
|
tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithoutRefresh, {
|
|
154
209
|
browser: this.browser,
|
|
155
210
|
logger: this.logger,
|
|
156
211
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
|
|
213
|
+
this.logger?.info(`Token obtained via UAA for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
214
|
+
// Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
|
|
215
|
+
// For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
|
|
216
|
+
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
217
|
+
const connectionConfigWithServiceUrl = {
|
|
218
|
+
...tokenResult.connectionConfig,
|
|
219
|
+
serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
|
|
220
|
+
};
|
|
221
|
+
// Update or create session with new token (stores handle creation if session doesn't exist)
|
|
222
|
+
await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
|
|
160
223
|
if (tokenResult.refreshToken) {
|
|
161
224
|
await this.sessionStore.setAuthorizationConfig(destination, {
|
|
162
225
|
...authConfig,
|
|
@@ -167,21 +230,29 @@ class AuthBroker {
|
|
|
167
230
|
}
|
|
168
231
|
catch (error) {
|
|
169
232
|
lastError = error;
|
|
170
|
-
this.logger
|
|
233
|
+
this.logger?.debug(`UAA flow failed for ${destination}: ${error.message}, trying browser`);
|
|
171
234
|
// Continue to next step
|
|
172
235
|
}
|
|
173
236
|
// Step 5: Try browser authentication via tokenProvider (should be last resort)
|
|
174
237
|
// TokenProvider should use browser auth if refresh token and client_credentials don't work
|
|
175
238
|
try {
|
|
176
|
-
this.logger
|
|
239
|
+
this.logger?.debug(`Trying browser authentication flow for ${destination}`);
|
|
177
240
|
const authConfigForBrowser = { ...authConfig, refreshToken: undefined };
|
|
178
241
|
tokenResult = await this.tokenProvider.getConnectionConfig(authConfigForBrowser, {
|
|
179
242
|
browser: this.browser,
|
|
180
243
|
logger: this.logger,
|
|
181
244
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
245
|
+
const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
|
|
246
|
+
this.logger?.info(`Token obtained via browser for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
247
|
+
// Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
|
|
248
|
+
// For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
|
|
249
|
+
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
250
|
+
const connectionConfigWithServiceUrl = {
|
|
251
|
+
...tokenResult.connectionConfig,
|
|
252
|
+
serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
|
|
253
|
+
};
|
|
254
|
+
// Update or create session with new token (stores handle creation if session doesn't exist)
|
|
255
|
+
await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
|
|
185
256
|
if (tokenResult.refreshToken) {
|
|
186
257
|
await this.sessionStore.setAuthorizationConfig(destination, {
|
|
187
258
|
...authConfig,
|
|
@@ -197,7 +268,7 @@ class AuthBroker {
|
|
|
197
268
|
`Refresh token: ${refreshToken ? 'failed' : 'not available'}. ` +
|
|
198
269
|
`UAA: ${authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret ? 'failed' : 'parameters missing'}. ` +
|
|
199
270
|
`Browser authentication: failed (${error.message})`;
|
|
200
|
-
this.logger
|
|
271
|
+
this.logger?.error(`All auth methods failed for ${destination}: refreshToken(${refreshToken ? 'failed' : 'none'}), UAA(${authConfig.uaaUrl && authConfig.uaaClientId && authConfig.uaaClientSecret ? 'failed' : 'missing'}), browser(failed: ${error.message})`);
|
|
201
272
|
throw new Error(errorMessage);
|
|
202
273
|
}
|
|
203
274
|
}
|
|
@@ -208,27 +279,40 @@ class AuthBroker {
|
|
|
208
279
|
* @returns Promise that resolves to new JWT token string
|
|
209
280
|
*/
|
|
210
281
|
async refreshToken(destination) {
|
|
282
|
+
this.logger?.debug(`Force refreshing token for destination: ${destination}`);
|
|
211
283
|
// Load service key
|
|
212
284
|
const serviceKey = await this.serviceKeyStore.getServiceKey(destination);
|
|
213
285
|
if (!serviceKey) {
|
|
286
|
+
this.logger?.error(`Service key not found for ${destination}`);
|
|
214
287
|
throw new Error(`Service key not found for destination "${destination}".`);
|
|
215
288
|
}
|
|
216
289
|
// Get authorization config from service key
|
|
217
290
|
const authConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
|
|
218
291
|
if (!authConfig) {
|
|
292
|
+
this.logger?.error(`Service key for ${destination} missing UAA credentials`);
|
|
219
293
|
throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
|
|
220
294
|
}
|
|
221
295
|
// Get refresh token from session
|
|
222
296
|
const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
|
|
223
|
-
const
|
|
297
|
+
const refreshToken = sessionAuthConfig?.refreshToken || authConfig.refreshToken;
|
|
298
|
+
this.logger?.debug(`Refresh token check for ${destination}: hasRefreshToken(${!!refreshToken})`);
|
|
299
|
+
const authConfigWithRefresh = { ...authConfig, refreshToken };
|
|
224
300
|
// Get connection config with token from provider
|
|
225
301
|
const tokenResult = await this.tokenProvider.getConnectionConfig(authConfigWithRefresh, {
|
|
226
302
|
browser: this.browser,
|
|
227
303
|
logger: this.logger,
|
|
228
304
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
//
|
|
305
|
+
const tokenLength = tokenResult.connectionConfig.authorizationToken?.length || 0;
|
|
306
|
+
this.logger?.info(`Token refreshed for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
307
|
+
// Get serviceUrl from service key store if not in connectionConfig (required for ABAP stores)
|
|
308
|
+
// For XSUAA service keys, serviceUrl may not exist, which is fine for BTP/XSUAA stores
|
|
309
|
+
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
310
|
+
const connectionConfigWithServiceUrl = {
|
|
311
|
+
...tokenResult.connectionConfig,
|
|
312
|
+
serviceUrl: tokenResult.connectionConfig.serviceUrl || serviceKeyConnConfig?.serviceUrl,
|
|
313
|
+
};
|
|
314
|
+
// Update or create session with new token (stores handle creation if session doesn't exist)
|
|
315
|
+
await this.sessionStore.setConnectionConfig(destination, connectionConfigWithServiceUrl);
|
|
232
316
|
if (tokenResult.refreshToken) {
|
|
233
317
|
await this.sessionStore.setAuthorizationConfig(destination, {
|
|
234
318
|
...authConfig,
|
|
@@ -243,13 +327,24 @@ class AuthBroker {
|
|
|
243
327
|
* @returns Promise that resolves to IAuthorizationConfig or null if not found
|
|
244
328
|
*/
|
|
245
329
|
async getAuthorizationConfig(destination) {
|
|
330
|
+
this.logger?.debug(`Getting authorization config for ${destination}`);
|
|
246
331
|
// Try session store first (has tokens)
|
|
332
|
+
this.logger?.debug(`Checking session store for authorization config: ${destination}`);
|
|
247
333
|
const sessionAuthConfig = await this.sessionStore.getAuthorizationConfig(destination);
|
|
248
334
|
if (sessionAuthConfig) {
|
|
335
|
+
this.logger?.debug(`Authorization config from session for ${destination}: hasUaaUrl(${!!sessionAuthConfig.uaaUrl}), hasRefreshToken(${!!sessionAuthConfig.refreshToken})`);
|
|
249
336
|
return sessionAuthConfig;
|
|
250
337
|
}
|
|
251
338
|
// Fall back to service key store (has UAA credentials)
|
|
252
|
-
|
|
339
|
+
this.logger?.debug(`Checking service key store for authorization config: ${destination}`);
|
|
340
|
+
const serviceKeyAuthConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
|
|
341
|
+
if (serviceKeyAuthConfig) {
|
|
342
|
+
this.logger?.debug(`Authorization config from service key for ${destination}: hasUaaUrl(${!!serviceKeyAuthConfig.uaaUrl})`);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
this.logger?.debug(`No authorization config found for ${destination}`);
|
|
346
|
+
}
|
|
347
|
+
return serviceKeyAuthConfig;
|
|
253
348
|
}
|
|
254
349
|
/**
|
|
255
350
|
* Get connection configuration for destination
|
|
@@ -257,13 +352,22 @@ class AuthBroker {
|
|
|
257
352
|
* @returns Promise that resolves to IConnectionConfig or null if not found
|
|
258
353
|
*/
|
|
259
354
|
async getConnectionConfig(destination) {
|
|
355
|
+
this.logger?.debug(`Getting connection config for ${destination}`);
|
|
260
356
|
// Try session store first (has tokens and URLs)
|
|
261
357
|
const sessionConnConfig = await this.sessionStore.getConnectionConfig(destination);
|
|
262
358
|
if (sessionConnConfig) {
|
|
359
|
+
this.logger?.debug(`Connection config from session for ${destination}: token(${sessionConnConfig.authorizationToken?.length || 0} chars), serviceUrl(${sessionConnConfig.serviceUrl ? 'yes' : 'no'})`);
|
|
263
360
|
return sessionConnConfig;
|
|
264
361
|
}
|
|
265
362
|
// Fall back to service key store (has URLs but no tokens)
|
|
266
|
-
|
|
363
|
+
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
364
|
+
if (serviceKeyConnConfig) {
|
|
365
|
+
this.logger?.debug(`Connection config from service key for ${destination}: serviceUrl(${serviceKeyConnConfig.serviceUrl ? 'yes' : 'no'}), token(none)`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
this.logger?.debug(`No connection config found for ${destination}`);
|
|
369
|
+
}
|
|
370
|
+
return serviceKeyConnConfig;
|
|
267
371
|
}
|
|
268
372
|
}
|
|
269
373
|
exports.AuthBroker = AuthBroker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configHelpers.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/configHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE;QACZ,KAAK,CAAC,EAAE;YACN,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,IAAI,CAAC,EAAE;YACL,WAAW,CAAC,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,KAAK,CAAC,EAAE;YACN,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;CACH;AAkBD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,CA6C3C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CA2BpF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGrE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG;IACzD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAOA;
|
|
1
|
+
{"version":3,"file":"configHelpers.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/configHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE;QACZ,KAAK,CAAC,EAAE;YACN,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,IAAI,CAAC,EAAE;YACL,WAAW,CAAC,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,KAAK,CAAC,EAAE;YACN,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;CACH;AAkBD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,CA6C3C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CA2BpF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGrE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG;IACzD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAOA;AAaD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CASpE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CASjE"}
|
|
@@ -153,17 +153,39 @@ function getXsuaaDestinations(config) {
|
|
|
153
153
|
mcp_url: xsuaa?.mcp_url || null,
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Expand tilde (~) to home directory
|
|
158
|
+
*/
|
|
159
|
+
function expandTilde(filePath) {
|
|
160
|
+
if (filePath.startsWith('~')) {
|
|
161
|
+
const os = require('os');
|
|
162
|
+
return path.join(os.homedir(), filePath.slice(1));
|
|
163
|
+
}
|
|
164
|
+
return filePath;
|
|
165
|
+
}
|
|
156
166
|
/**
|
|
157
167
|
* Get service keys directory from config
|
|
158
168
|
*/
|
|
159
169
|
function getServiceKeysDir(config) {
|
|
160
170
|
const cfg = config || loadTestConfig();
|
|
161
|
-
|
|
171
|
+
const dir = cfg.auth_broker?.paths?.service_keys_dir;
|
|
172
|
+
if (!dir) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
// Expand ~ and normalize path
|
|
176
|
+
const expanded = expandTilde(dir);
|
|
177
|
+
return path.resolve(expanded);
|
|
162
178
|
}
|
|
163
179
|
/**
|
|
164
180
|
* Get sessions directory from config
|
|
165
181
|
*/
|
|
166
182
|
function getSessionsDir(config) {
|
|
167
183
|
const cfg = config || loadTestConfig();
|
|
168
|
-
|
|
184
|
+
const dir = cfg.auth_broker?.paths?.sessions_dir;
|
|
185
|
+
if (!dir) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Expand ~ and normalize path
|
|
189
|
+
const expanded = expandTilde(dir);
|
|
190
|
+
return path.resolve(expanded);
|
|
169
191
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testLogger.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/testLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAgBxD,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,MAAe,GAAG,OAAO,CAgEjE"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test logger with environment variable control
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTestLogger = createTestLogger;
|
|
7
|
+
function getLogLevel() {
|
|
8
|
+
const level = process.env.LOG_LEVEL?.toLowerCase() || 'info';
|
|
9
|
+
const levels = ['debug', 'info', 'warn', 'error'];
|
|
10
|
+
return levels.includes(level) ? level : 'info';
|
|
11
|
+
}
|
|
12
|
+
function shouldLog(level) {
|
|
13
|
+
const currentLevel = getLogLevel();
|
|
14
|
+
const levels = ['debug', 'info', 'warn', 'error'];
|
|
15
|
+
return levels.indexOf(level) >= levels.indexOf(currentLevel);
|
|
16
|
+
}
|
|
17
|
+
function createTestLogger(prefix = 'TEST') {
|
|
18
|
+
// Check if logging is enabled - requires explicit enable
|
|
19
|
+
const isEnabled = () => {
|
|
20
|
+
// Explicitly disabled
|
|
21
|
+
if (process.env.DEBUG_AUTH_BROKER === 'false') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// Explicitly enabled
|
|
25
|
+
if (process.env.DEBUG_AUTH_BROKER === 'true' ||
|
|
26
|
+
process.env.DEBUG === 'true' ||
|
|
27
|
+
process.env.DEBUG?.includes('auth-broker') === true) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// Do not enable by default - require explicit enable
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
// Format message and meta into single line
|
|
34
|
+
const formatMessage = (message, meta) => {
|
|
35
|
+
if (!meta || meta === '') {
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
// If meta is an object, format it concisely
|
|
39
|
+
if (typeof meta === 'object' && !Array.isArray(meta)) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
42
|
+
if (value !== undefined && value !== null) {
|
|
43
|
+
if (typeof value === 'string' && value.length > 50) {
|
|
44
|
+
parts.push(`${key}(${value.substring(0, 50)}...)`);
|
|
45
|
+
}
|
|
46
|
+
else if (typeof value === 'boolean') {
|
|
47
|
+
parts.push(`${key}(${value})`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
parts.push(`${key}(${value})`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return parts.length > 0 ? `${message} ${parts.join(', ')}` : message;
|
|
55
|
+
}
|
|
56
|
+
// If meta is a string or other type, append it
|
|
57
|
+
return `${message} ${String(meta)}`;
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
debug: (message, meta) => {
|
|
61
|
+
if (isEnabled() && shouldLog('debug')) {
|
|
62
|
+
console.debug(`[${prefix}] [DEBUG] ${formatMessage(message, meta)}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
info: (message, meta) => {
|
|
66
|
+
if (isEnabled() && shouldLog('info')) {
|
|
67
|
+
console.info(`[${prefix}] ${formatMessage(message, meta)}`);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
warn: (message, meta) => {
|
|
71
|
+
if (isEnabled() && shouldLog('warn')) {
|
|
72
|
+
console.warn(`[${prefix}] [WARN] ${formatMessage(message, meta)}`);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
error: (message, meta) => {
|
|
76
|
+
if (isEnabled() && shouldLog('error')) {
|
|
77
|
+
console.error(`[${prefix}] [ERROR] ${formatMessage(message, meta)}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/auth-broker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"build": "npm run clean --silent && npx tsc -p tsconfig.json",
|
|
41
41
|
"build:fast": "npx tsc -p tsconfig.json",
|
|
42
42
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
43
|
-
"test:check": "npx tsc --noEmit",
|
|
43
|
+
"test:check": "npx tsc --noEmit && npx tsc --noEmit -p tsconfig.test.json",
|
|
44
44
|
"prepublishOnly": "npm run build",
|
|
45
45
|
"generate-env": "tsx bin/generate-env-from-service-key.ts"
|
|
46
46
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@mcp-abap-adt/auth-providers": "^0.1.3",
|
|
58
|
-
"@mcp-abap-adt/auth-stores": "^0.1.
|
|
58
|
+
"@mcp-abap-adt/auth-stores": "^0.1.7",
|
|
59
59
|
"@types/express": "^5.0.5",
|
|
60
60
|
"@types/jest": "^30.0.0",
|
|
61
61
|
"@types/js-yaml": "^4.0.9",
|