@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 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
- - `DEBUG_AUTH_LOG` - Set to `true` to enable debug logging (default: `false`)
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
 
@@ -1 +1 @@
1
- {"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,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;IAuBlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmIpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuCxD;;;;OAIG;IACG,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAWvF;;;;OAIG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAWlF"}
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"}
@@ -47,11 +47,45 @@ class AuthBroker {
47
47
  if (!stores.tokenProvider) {
48
48
  throw new Error('AuthBroker: tokenProvider is required');
49
49
  }
50
- this.serviceKeyStore = stores.serviceKeyStore;
51
- this.sessionStore = stores.sessionStore;
52
- this.tokenProvider = stores.tokenProvider;
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
- // No service key and no valid token
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.debug(`Attempting to refresh token using refresh token for destination "${destination}"...`);
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
- this.logger.debug(`Token refreshed successfully using refresh token for destination "${destination}"`);
132
- // Update session with new token
133
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
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.debug(`Token refresh failed for destination "${destination}": ${error.message}. Trying without refresh token...`);
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.debug(`Attempting to get token using UAA (client_credentials) for destination "${destination}"...`);
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
- this.logger.debug(`Token obtained successfully using UAA for destination "${destination}"`);
158
- // Update session with new token
159
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
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.debug(`UAA authentication failed for destination "${destination}": ${error.message}. Trying browser authentication...`);
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.debug(`Starting browser authentication flow for destination "${destination}"...`);
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
- this.logger.debug(`Token obtained successfully using browser authentication for destination "${destination}"`);
183
- // Update session with new token
184
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
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.error(errorMessage);
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 authConfigWithRefresh = { ...authConfig, refreshToken: sessionAuthConfig?.refreshToken || authConfig.refreshToken };
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
- // Update session with new token
230
- await this.sessionStore.setConnectionConfig(destination, tokenResult.connectionConfig);
231
- // Update authorization config with new refresh token if available
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
- return await this.serviceKeyStore.getAuthorizationConfig(destination);
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
- return await this.serviceKeyStore.getConnectionConfig(destination);
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;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGpE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGjE"}
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
- return cfg.auth_broker?.paths?.service_keys_dir || null;
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
- return cfg.auth_broker?.paths?.sessions_dir || null;
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,6 @@
1
+ /**
2
+ * Test logger with environment variable control
3
+ */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
5
+ export declare function createTestLogger(prefix?: string): ILogger;
6
+ //# sourceMappingURL=testLogger.d.ts.map
@@ -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.11",
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.5",
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",