@mcp-abap-adt/auth-providers 0.1.5 → 0.2.1

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
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2025-01-XX
11
+
12
+ ### Added
13
+ - **Automatic Port Selection**: Browser auth server now automatically finds an available port if the requested port is in use
14
+ - When `startBrowserAuth()` is called with a port, it checks if the port is available
15
+ - If the port is busy, it automatically tries the next ports (up to 10 attempts)
16
+ - This prevents `EADDRINUSE` errors when multiple stdio servers run simultaneously
17
+ - Port selection happens before server startup, ensuring no conflicts
18
+
19
+ ### Fixed
20
+ - **Server Port Cleanup**: Improved server shutdown to ensure ports are properly freed after authentication completes
21
+ - Added `keepAliveTimeout = 0` and `headersTimeout = 0` to prevent connections from staying open
22
+ - Added `closeAllConnections()` calls before `server.close()` to ensure all connections are closed
23
+ - Server now waits for HTTP response to finish before closing to prevent connection leaks
24
+ - Added proper error handling for browser open failures to ensure server is closed
25
+ - Server now properly closes in all error scenarios (timeout, browser open failure, callback errors)
26
+ - This prevents ports from remaining occupied after authentication completes or server shutdown
27
+
28
+ ### Changed
29
+ - **Port Selection Logic**: `startBrowserAuth()` now uses `findAvailablePort()` to automatically select a free port
30
+ - Default behavior: tries requested port first, then tries next ports if busy
31
+ - Port range: tries up to 10 consecutive ports starting from the requested port
32
+ - Logs when a different port is used (for debugging)
33
+
34
+ ## [0.2.0] - 2025-12-19
35
+
36
+ ### Added
37
+ - **Typed Error Classes**: Added specialized error classes for better error handling and debugging
38
+ - `TokenProviderError` - Base class for all token provider errors with error code
39
+ - `ValidationError` - Thrown when authConfig validation fails (includes `missingFields: string[]` array)
40
+ - `RefreshError` - Thrown when token refresh operation fails (includes `cause?: Error` with original error)
41
+ - `SessionDataError` - Thrown when session data is invalid or incomplete (includes `missingFields` array)
42
+ - `ServiceKeyError` - Thrown when service key data is invalid or incomplete (includes `missingFields` array)
43
+ - `BrowserAuthError` - Thrown when browser authentication fails or is cancelled (includes `cause` error)
44
+ - All error codes use constants from `@mcp-abap-adt/interfaces` package (`TOKEN_PROVIDER_ERROR_CODES`)
45
+ - Errors are exported from package root for easy import
46
+
47
+ ### Changed
48
+ - **Enhanced Validation Error Messages**: Validation errors now list specific missing field names instead of generic messages
49
+ - Example: `XSUAA refreshTokenFromSession: authConfig missing required fields: uaaUrl, uaaClientId`
50
+ - `ValidationError` includes `missingFields: string[]` property for programmatic access to missing fields
51
+ - Each missing field is checked individually and added to the list
52
+ - **Improved Error Handling in Refresh Methods**: All refresh operations now wrap errors with typed error classes
53
+ - `refreshTokenFromSession` throws `RefreshError` when client_credentials or browser auth fails
54
+ - `refreshTokenFromServiceKey` throws `RefreshError` when browser authentication fails
55
+ - Original error is preserved in `RefreshError.cause` property for debugging
56
+ - Error messages include provider type (XSUAA/BTP) and operation name for clarity
57
+ - **Dependency Update**: Updated `@mcp-abap-adt/interfaces` to `^0.2.2` for `TOKEN_PROVIDER_ERROR_CODES` constants
58
+ - **Test Coverage**: Added tests for error handling edge cases
59
+ - Tests verify `RefreshError` is thrown when authentication fails
60
+ - Tests verify `ValidationError` includes correct missing field names
61
+ - Tests verify error messages contain expected substrings
62
+
10
63
  ## [0.1.5] - 2025-12-13
11
64
 
12
65
  ### Changed
package/README.md CHANGED
@@ -173,10 +173,12 @@ import { BtpTokenProvider } from '@mcp-abap-adt/auth-providers';
173
173
  import type { IAuthorizationConfig } from '@mcp-abap-adt/auth-broker';
174
174
 
175
175
  // Create provider with default port (3001)
176
+ // The provider will automatically find an available port if 3001 is busy
176
177
  const provider = new BtpTokenProvider();
177
178
 
178
- // Or specify custom port for OAuth callback server (useful to avoid port conflicts)
179
- const providerWithCustomPort = new BtpTokenProvider(4001);
179
+ // Or specify custom port for OAuth callback server
180
+ // If the port is busy, the provider will automatically try the next ports
181
+ const providerWithCustomPort = new BtpTokenProvider(4002);
180
182
 
181
183
  const authConfig: IAuthorizationConfig = {
182
184
  uaaUrl: 'https://...authentication...hana.ondemand.com',
@@ -196,7 +198,7 @@ const result = await provider.getConnectionConfig(authConfig, {
196
198
  // result.refreshToken contains refresh token (if browser flow was used)
197
199
  ```
198
200
 
199
- **Note**: The `browserAuthPort` parameter (default: 3001) configures the OAuth callback server port. This is useful when running the provider in environments where port 3001 is already in use (e.g., when running alongside a proxy server).
201
+ **Note**: The `browserAuthPort` parameter (default: 3001) configures the OAuth callback server port. The provider automatically finds an available port if the requested port is in use, preventing `EADDRINUSE` errors when multiple instances run simultaneously. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation. This is useful when running the provider in environments where ports may be occupied (e.g., when running alongside a proxy server or multiple stdio servers).
200
202
 
201
203
  ### Token Validation
202
204
 
@@ -207,6 +209,104 @@ const isValid = await provider.validateToken(token, serviceUrl);
207
209
  // Returns true if token is valid (200-299 status), false otherwise
208
210
  ```
209
211
 
212
+ ### Token Refresh Methods
213
+
214
+ Both providers implement two refresh methods from `ITokenProvider` interface:
215
+
216
+ #### refreshTokenFromSession
217
+
218
+ Refreshes token using existing session data (with refreshToken):
219
+
220
+ ```typescript
221
+ import { XsuaaTokenProvider } from '@mcp-abap-adt/auth-providers';
222
+ import { ValidationError, RefreshError } from '@mcp-abap-adt/auth-providers';
223
+
224
+ const provider = new XsuaaTokenProvider();
225
+
226
+ const authConfig: IAuthorizationConfig = {
227
+ uaaUrl: 'https://...authentication...hana.ondemand.com',
228
+ uaaClientId: '...',
229
+ uaaClientSecret: '...',
230
+ refreshToken: '...', // From existing session
231
+ };
232
+
233
+ try {
234
+ const result = await provider.refreshTokenFromSession(authConfig);
235
+ // XSUAA uses client_credentials - no refresh token in response
236
+ // BTP uses browser auth - returns new refresh token
237
+ } catch (error) {
238
+ if (error instanceof ValidationError) {
239
+ // authConfig missing required fields
240
+ console.error('Missing fields:', error.missingFields); // ['uaaUrl', 'uaaClientId', ...]
241
+ } else if (error instanceof RefreshError) {
242
+ // Token refresh failed
243
+ console.error('Refresh failed:', error.message);
244
+ console.error('Original error:', error.cause);
245
+ }
246
+ }
247
+ ```
248
+
249
+ #### refreshTokenFromServiceKey
250
+
251
+ Refreshes token using service key credentials (without refreshToken):
252
+
253
+ ```typescript
254
+ try {
255
+ const result = await provider.refreshTokenFromServiceKey(authConfig);
256
+ // Both XSUAA and BTP use browser authentication for service key refresh
257
+ // Returns new access token and refresh token
258
+ } catch (error) {
259
+ if (error instanceof ValidationError) {
260
+ console.error('Missing fields:', error.missingFields);
261
+ } else if (error instanceof RefreshError) {
262
+ console.error('Browser auth failed:', error.cause);
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Error Handling
268
+
269
+ The package provides typed error classes for better error handling:
270
+
271
+ ```typescript
272
+ import {
273
+ TokenProviderError,
274
+ ValidationError,
275
+ RefreshError,
276
+ SessionDataError,
277
+ ServiceKeyError,
278
+ BrowserAuthError,
279
+ } from '@mcp-abap-adt/auth-providers';
280
+
281
+ try {
282
+ const result = await provider.refreshTokenFromSession(authConfig);
283
+ } catch (error) {
284
+ if (error instanceof ValidationError) {
285
+ // authConfig validation failed
286
+ console.error('Missing required fields:', error.missingFields);
287
+ console.error('Error code:', error.code); // 'VALIDATION_ERROR'
288
+ } else if (error instanceof RefreshError) {
289
+ // Token refresh operation failed
290
+ console.error('Refresh failed:', error.message);
291
+ console.error('Original error:', error.cause);
292
+ console.error('Error code:', error.code); // 'REFRESH_ERROR'
293
+ } else if (error instanceof BrowserAuthError) {
294
+ // Browser authentication failed
295
+ console.error('Browser auth failed:', error.cause);
296
+ }
297
+ }
298
+ ```
299
+
300
+ **Error Types**:
301
+ - `TokenProviderError` - Base class with `code: string` property
302
+ - `ValidationError` - authConfig validation failed, includes `missingFields: string[]`
303
+ - `RefreshError` - Token refresh failed, includes `cause?: Error`
304
+ - `SessionDataError` - Session data invalid, includes `missingFields: string[]`
305
+ - `ServiceKeyError` - Service key data invalid, includes `missingFields: string[]`
306
+ - `BrowserAuthError` - Browser auth failed, includes `cause?: Error`
307
+
308
+ All error codes are defined in `@mcp-abap-adt/interfaces` package as `TOKEN_PROVIDER_ERROR_CODES`.
309
+
210
310
  ## Testing
211
311
 
212
312
  The package includes both unit tests (with mocks) and integration tests (with real files and services).
@@ -272,10 +372,7 @@ Example output:
272
372
 
273
373
  ## Dependencies
274
374
 
275
- - `@mcp-abap-adt/auth-broker` (^0.1.6) - Interface definitions
276
- - `@mcp-abap-adt/auth-stores` (^0.1.2) - Store implementations
277
- - `@mcp-abap-adt/connection` (^0.1.13) - Connection utilities
278
- - `@mcp-abap-adt/logger` (^0.1.0) - Logging utilities
375
+ - `@mcp-abap-adt/interfaces` (^0.2.2) - Interface definitions and error code constants
279
376
  - `axios` - HTTP client
280
377
  - `express` - OAuth2 callback server
281
378
  - `open` - Browser opening utility
@@ -1 +1 @@
1
- {"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAyB9E;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,oBAAoB,EAChC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAa,EACnB,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,GACnB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCzD;AAaD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,OAAO,EAChB,IAAI,GAAE,MAAa,GAClB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAuSzD"}
1
+ {"version":3,"file":"browserAuth.d.ts","sourceRoot":"","sources":["../../src/auth/browserAuth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAyB9E;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,oBAAoB,EAChC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAa,EACnB,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,GACnB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCzD;AAyCD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,oBAAoB,EAChC,OAAO,GAAE,MAAiB,EAC1B,MAAM,CAAC,EAAE,OAAO,EAChB,IAAI,GAAE,MAAa,GAClB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwXzD"}
@@ -42,6 +42,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.exchangeCodeForToken = exchangeCodeForToken;
43
43
  exports.startBrowserAuth = startBrowserAuth;
44
44
  const http = __importStar(require("http"));
45
+ const net = __importStar(require("net"));
45
46
  const child_process = __importStar(require("child_process"));
46
47
  const express_1 = __importDefault(require("express"));
47
48
  const axios_1 = __importDefault(require("axios"));
@@ -111,6 +112,32 @@ function isDebugEnabled() {
111
112
  process.env.DEBUG?.includes('auth-providers') === true ||
112
113
  process.env.DEBUG?.includes('browser-auth') === true;
113
114
  }
115
+ /**
116
+ * Check if a port is available
117
+ */
118
+ function isPortAvailable(port) {
119
+ return new Promise((resolve) => {
120
+ const server = net.createServer();
121
+ server.listen(port, () => {
122
+ server.once('close', () => resolve(true));
123
+ server.close();
124
+ });
125
+ server.on('error', () => resolve(false));
126
+ });
127
+ }
128
+ /**
129
+ * Find an available port starting from the given port
130
+ * Tries ports in range [startPort, startPort + maxAttempts)
131
+ */
132
+ async function findAvailablePort(startPort, maxAttempts = 10) {
133
+ for (let i = 0; i < maxAttempts; i++) {
134
+ const port = startPort + i;
135
+ if (await isPortAvailable(port)) {
136
+ return port;
137
+ }
138
+ }
139
+ throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
140
+ }
114
141
  /**
115
142
  * Start browser authentication flow
116
143
  * @param authConfig Authorization configuration with UAA credentials
@@ -123,6 +150,17 @@ function isDebugEnabled() {
123
150
  async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3001) {
124
151
  // Use logger if provided, otherwise null (no logging)
125
152
  const log = logger || null;
153
+ // Find available port (try starting from requested port, then try next ports)
154
+ let actualPort;
155
+ try {
156
+ actualPort = await findAvailablePort(port, 10);
157
+ if (actualPort !== port) {
158
+ log?.debug(`Port ${port} is in use, using port ${actualPort} instead`);
159
+ }
160
+ }
161
+ catch (error) {
162
+ throw new Error(`Failed to find available port starting from ${port}: ${error instanceof Error ? error.message : String(error)}`);
163
+ }
126
164
  return new Promise((originalResolve, originalReject) => {
127
165
  let timeoutId = null;
128
166
  const resolve = (value) => {
@@ -137,7 +175,10 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
137
175
  };
138
176
  const app = (0, express_1.default)();
139
177
  const server = http.createServer(app);
140
- const PORT = port;
178
+ // Disable keep-alive to ensure connections close immediately
179
+ server.keepAliveTimeout = 0;
180
+ server.headersTimeout = 0;
181
+ const PORT = actualPort;
141
182
  let serverInstance = null;
142
183
  const authorizationUrl = getJwtAuthorizationUrl(authConfig, PORT);
143
184
  // OAuth2 callback handler
@@ -284,36 +325,75 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
284
325
  </div>
285
326
  </body>
286
327
  </html>`;
287
- // Send success page first
328
+ // Send success page first and ensure response is finished
288
329
  res.send(html);
330
+ // Wait for response to finish before closing server
331
+ res.on('finish', () => {
332
+ // Response finished, now we can safely close server
333
+ });
289
334
  // Exchange code for tokens and close server
290
335
  try {
291
336
  const tokens = await exchangeCodeForToken(authConfig, code, PORT, log);
292
337
  log?.info(`Tokens received: accessToken(${tokens.accessToken?.length || 0} chars), refreshToken(${tokens.refreshToken?.length || 0} chars)`);
293
- // Close all connections and server immediately after getting tokens
338
+ // Close all connections first to ensure port is freed
294
339
  if (typeof server.closeAllConnections === 'function') {
295
340
  server.closeAllConnections();
296
341
  }
297
- server.close(() => {
298
- // Server closed
299
- });
342
+ // Close server after response is finished
343
+ // This ensures the response connection is closed before server.close()
344
+ const closeServer = () => {
345
+ server.close(() => {
346
+ // Server closed - port should be freed
347
+ log?.debug(`Server closed, port ${PORT} should be freed`);
348
+ });
349
+ };
350
+ if (res.finished) {
351
+ // Response already finished, close immediately
352
+ closeServer();
353
+ }
354
+ else {
355
+ // Wait for response to finish
356
+ res.once('finish', closeServer);
357
+ }
300
358
  resolve(tokens);
301
359
  }
302
360
  catch (error) {
303
361
  if (typeof server.closeAllConnections === 'function') {
304
362
  server.closeAllConnections();
305
363
  }
306
- server.close(() => {
307
- // Server closed on error
308
- });
364
+ // Use setTimeout to ensure connections are closed before server.close()
365
+ setTimeout(() => {
366
+ server.close(() => {
367
+ // Server closed on error - port should be freed
368
+ log?.debug(`Server closed on error, port ${PORT} should be freed`);
369
+ });
370
+ }, 100);
309
371
  reject(error);
310
372
  }
311
373
  }
312
374
  catch (error) {
313
375
  res.status(500).send('Error processing authentication');
314
- server.close(() => {
315
- // Server closed on error
316
- });
376
+ if (typeof server.closeAllConnections === 'function') {
377
+ server.closeAllConnections();
378
+ }
379
+ // Use setTimeout to ensure connections are closed before server.close()
380
+ setTimeout(() => {
381
+ server.close(() => {
382
+ // Server closed on error - port should be freed
383
+ log?.debug(`Server closed on error, port ${PORT} should be freed`);
384
+ });
385
+ }, 100);
386
+ reject(error);
387
+ }
388
+ });
389
+ // Handle server errors (e.g., EADDRINUSE)
390
+ server.on('error', (error) => {
391
+ if (error.code === 'EADDRINUSE') {
392
+ log?.error(`Port ${PORT} is already in use. This should not happen after port check.`);
393
+ reject(new Error(`Port ${PORT} is already in use. Please try again or specify a different port.`));
394
+ }
395
+ else {
396
+ log?.error(`Server error: ${error.message}`);
317
397
  reject(error);
318
398
  }
319
399
  });
@@ -324,9 +404,16 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
324
404
  // For 'none' browser, don't wait for callback - throw error immediately
325
405
  // User must open browser manually and we can't wait for callback in automated tests
326
406
  if (serverInstance) {
327
- server.close(() => {
328
- // Server closed
329
- });
407
+ if (typeof server.closeAllConnections === 'function') {
408
+ server.closeAllConnections();
409
+ }
410
+ // Use setTimeout to ensure connections are closed before server.close()
411
+ setTimeout(() => {
412
+ server.close(() => {
413
+ // Server closed - port should be freed
414
+ log?.debug(`Server closed (browser=none), port ${PORT} should be freed`);
415
+ });
416
+ }, 100);
330
417
  }
331
418
  reject(new Error(`Browser authentication required. Please open this URL manually: ${authorizationUrl}`));
332
419
  return;
@@ -391,7 +478,17 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
391
478
  }
392
479
  }
393
480
  catch (error) {
394
- // If browser cannot be opened, show URL and throw error for consumer to catch
481
+ // If browser cannot be opened, close server and show URL
482
+ if (typeof server.closeAllConnections === 'function') {
483
+ server.closeAllConnections();
484
+ }
485
+ // Use setTimeout to ensure connections are closed before server.close()
486
+ setTimeout(() => {
487
+ server.close(() => {
488
+ // Server closed on browser open error - port should be freed
489
+ log?.debug(`Server closed on browser open error, port ${PORT} should be freed`);
490
+ });
491
+ }, 100);
395
492
  log?.error(`❌ Failed to open browser: ${error?.message || String(error)}. Please open manually: ${authorizationUrl}`, { error: error?.message || String(error), url: authorizationUrl });
396
493
  log?.info(`🔗 Open in browser: ${authorizationUrl}`, { url: authorizationUrl });
397
494
  // Throw error so consumer can distinguish this from "service key missing" error
@@ -402,9 +499,16 @@ async function startBrowserAuth(authConfig, browser = 'system', logger, port = 3
402
499
  // Timeout after 5 minutes
403
500
  timeoutId = setTimeout(() => {
404
501
  if (serverInstance) {
405
- server.close(() => {
406
- // Server closed on timeout
407
- });
502
+ if (typeof server.closeAllConnections === 'function') {
503
+ server.closeAllConnections();
504
+ }
505
+ // Use setTimeout to ensure connections are closed before server.close()
506
+ setTimeout(() => {
507
+ server.close(() => {
508
+ // Server closed on timeout - port should be freed
509
+ log?.debug(`Server closed on timeout, port ${PORT} should be freed`);
510
+ });
511
+ }, 100);
408
512
  reject(new Error('Authentication timeout. Process aborted.'));
409
513
  }
410
514
  }, 5 * 60 * 1000);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Token Provider Error Types
3
+ *
4
+ * Defines specific error types that token providers can throw
5
+ * to enable better error handling and debugging.
6
+ */
7
+ /**
8
+ * Base class for all token provider errors
9
+ */
10
+ export declare class TokenProviderError extends Error {
11
+ readonly code: string;
12
+ constructor(message: string, code: string);
13
+ }
14
+ /**
15
+ * Thrown when authentication configuration is invalid or incomplete
16
+ */
17
+ export declare class ValidationError extends TokenProviderError {
18
+ readonly missingFields?: string[] | undefined;
19
+ constructor(message: string, missingFields?: string[] | undefined);
20
+ }
21
+ /**
22
+ * Thrown when token refresh operation fails
23
+ */
24
+ export declare class RefreshError extends TokenProviderError {
25
+ readonly cause?: Error | undefined;
26
+ constructor(message: string, cause?: Error | undefined);
27
+ }
28
+ /**
29
+ * Thrown when session data is invalid or incomplete
30
+ */
31
+ export declare class SessionDataError extends TokenProviderError {
32
+ readonly missingFields?: string[] | undefined;
33
+ constructor(message: string, missingFields?: string[] | undefined);
34
+ }
35
+ /**
36
+ * Thrown when service key data is invalid or incomplete
37
+ */
38
+ export declare class ServiceKeyError extends TokenProviderError {
39
+ readonly missingFields?: string[] | undefined;
40
+ constructor(message: string, missingFields?: string[] | undefined);
41
+ }
42
+ /**
43
+ * Thrown when browser authentication fails or is cancelled
44
+ */
45
+ export declare class BrowserAuthError extends TokenProviderError {
46
+ readonly cause?: Error | undefined;
47
+ constructor(message: string, cause?: Error | undefined);
48
+ }
49
+ //# sourceMappingURL=TokenProviderErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenProviderErrors.d.ts","sourceRoot":"","sources":["../../src/errors/TokenProviderErrors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;aACE,IAAI,EAAE,MAAM;gBAA7C,OAAO,EAAE,MAAM,EAAkB,IAAI,EAAE,MAAM;CAK1D;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,kBAAkB;aACR,aAAa,CAAC,EAAE,MAAM,EAAE;gBAAzD,OAAO,EAAE,MAAM,EAAkB,aAAa,CAAC,EAAE,MAAM,EAAE,YAAA;CAKtE;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,kBAAkB;aACL,KAAK,CAAC,EAAE,KAAK;gBAA9C,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,KAAK,YAAA;CAK3D;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;aACT,aAAa,CAAC,EAAE,MAAM,EAAE;gBAAzD,OAAO,EAAE,MAAM,EAAkB,aAAa,CAAC,EAAE,MAAM,EAAE,YAAA;CAKtE;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,kBAAkB;aACR,aAAa,CAAC,EAAE,MAAM,EAAE;gBAAzD,OAAO,EAAE,MAAM,EAAkB,aAAa,CAAC,EAAE,MAAM,EAAE,YAAA;CAKtE;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,kBAAkB;aACT,KAAK,CAAC,EAAE,KAAK;gBAA9C,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,KAAK,YAAA;CAK3D"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * Token Provider Error Types
4
+ *
5
+ * Defines specific error types that token providers can throw
6
+ * to enable better error handling and debugging.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.BrowserAuthError = exports.ServiceKeyError = exports.SessionDataError = exports.RefreshError = exports.ValidationError = exports.TokenProviderError = void 0;
10
+ const interfaces_1 = require("@mcp-abap-adt/interfaces");
11
+ /**
12
+ * Base class for all token provider errors
13
+ */
14
+ class TokenProviderError extends Error {
15
+ code;
16
+ constructor(message, code) {
17
+ super(message);
18
+ this.code = code;
19
+ this.name = 'TokenProviderError';
20
+ Object.setPrototypeOf(this, TokenProviderError.prototype);
21
+ }
22
+ }
23
+ exports.TokenProviderError = TokenProviderError;
24
+ /**
25
+ * Thrown when authentication configuration is invalid or incomplete
26
+ */
27
+ class ValidationError extends TokenProviderError {
28
+ missingFields;
29
+ constructor(message, missingFields) {
30
+ super(message, interfaces_1.TOKEN_PROVIDER_ERROR_CODES.VALIDATION_ERROR);
31
+ this.missingFields = missingFields;
32
+ this.name = 'ValidationError';
33
+ Object.setPrototypeOf(this, ValidationError.prototype);
34
+ }
35
+ }
36
+ exports.ValidationError = ValidationError;
37
+ /**
38
+ * Thrown when token refresh operation fails
39
+ */
40
+ class RefreshError extends TokenProviderError {
41
+ cause;
42
+ constructor(message, cause) {
43
+ super(message, interfaces_1.TOKEN_PROVIDER_ERROR_CODES.REFRESH_ERROR);
44
+ this.cause = cause;
45
+ this.name = 'RefreshError';
46
+ Object.setPrototypeOf(this, RefreshError.prototype);
47
+ }
48
+ }
49
+ exports.RefreshError = RefreshError;
50
+ /**
51
+ * Thrown when session data is invalid or incomplete
52
+ */
53
+ class SessionDataError extends TokenProviderError {
54
+ missingFields;
55
+ constructor(message, missingFields) {
56
+ super(message, interfaces_1.TOKEN_PROVIDER_ERROR_CODES.SESSION_DATA_ERROR);
57
+ this.missingFields = missingFields;
58
+ this.name = 'SessionDataError';
59
+ Object.setPrototypeOf(this, SessionDataError.prototype);
60
+ }
61
+ }
62
+ exports.SessionDataError = SessionDataError;
63
+ /**
64
+ * Thrown when service key data is invalid or incomplete
65
+ */
66
+ class ServiceKeyError extends TokenProviderError {
67
+ missingFields;
68
+ constructor(message, missingFields) {
69
+ super(message, interfaces_1.TOKEN_PROVIDER_ERROR_CODES.SERVICE_KEY_ERROR);
70
+ this.missingFields = missingFields;
71
+ this.name = 'ServiceKeyError';
72
+ Object.setPrototypeOf(this, ServiceKeyError.prototype);
73
+ }
74
+ }
75
+ exports.ServiceKeyError = ServiceKeyError;
76
+ /**
77
+ * Thrown when browser authentication fails or is cancelled
78
+ */
79
+ class BrowserAuthError extends TokenProviderError {
80
+ cause;
81
+ constructor(message, cause) {
82
+ super(message, interfaces_1.TOKEN_PROVIDER_ERROR_CODES.BROWSER_AUTH_ERROR);
83
+ this.cause = cause;
84
+ this.name = 'BrowserAuthError';
85
+ Object.setPrototypeOf(this, BrowserAuthError.prototype);
86
+ }
87
+ }
88
+ exports.BrowserAuthError = BrowserAuthError;
package/dist/index.d.ts CHANGED
@@ -6,4 +6,5 @@
6
6
  */
7
7
  export { XsuaaTokenProvider } from './providers/XsuaaTokenProvider';
8
8
  export { BtpTokenProvider } from './providers/BtpTokenProvider';
9
+ export { TokenProviderError, ValidationError, RefreshError, SessionDataError, ServiceKeyError, BrowserAuthError, } from './errors/TokenProviderErrors';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGhE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -6,9 +6,17 @@
6
6
  * Provides XSUAA and BTP token providers
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.BtpTokenProvider = exports.XsuaaTokenProvider = void 0;
9
+ exports.BrowserAuthError = exports.ServiceKeyError = exports.SessionDataError = exports.RefreshError = exports.ValidationError = exports.TokenProviderError = exports.BtpTokenProvider = exports.XsuaaTokenProvider = void 0;
10
10
  // Token providers
11
11
  var XsuaaTokenProvider_1 = require("./providers/XsuaaTokenProvider");
12
12
  Object.defineProperty(exports, "XsuaaTokenProvider", { enumerable: true, get: function () { return XsuaaTokenProvider_1.XsuaaTokenProvider; } });
13
13
  var BtpTokenProvider_1 = require("./providers/BtpTokenProvider");
14
14
  Object.defineProperty(exports, "BtpTokenProvider", { enumerable: true, get: function () { return BtpTokenProvider_1.BtpTokenProvider; } });
15
+ // Errors
16
+ var TokenProviderErrors_1 = require("./errors/TokenProviderErrors");
17
+ Object.defineProperty(exports, "TokenProviderError", { enumerable: true, get: function () { return TokenProviderErrors_1.TokenProviderError; } });
18
+ Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return TokenProviderErrors_1.ValidationError; } });
19
+ Object.defineProperty(exports, "RefreshError", { enumerable: true, get: function () { return TokenProviderErrors_1.RefreshError; } });
20
+ Object.defineProperty(exports, "SessionDataError", { enumerable: true, get: function () { return TokenProviderErrors_1.SessionDataError; } });
21
+ Object.defineProperty(exports, "ServiceKeyError", { enumerable: true, get: function () { return TokenProviderErrors_1.ServiceKeyError; } });
22
+ Object.defineProperty(exports, "BrowserAuthError", { enumerable: true, get: function () { return TokenProviderErrors_1.BrowserAuthError; } });
@@ -23,6 +23,8 @@ export declare class BtpTokenProvider implements ITokenProvider {
23
23
  */
24
24
  private refreshJwtToken;
25
25
  getConnectionConfig(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
26
+ refreshTokenFromSession(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
27
+ refreshTokenFromServiceKey(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
26
28
  validateToken(token: string, serviceUrl?: string): Promise<boolean>;
27
29
  }
28
30
  //# sourceMappingURL=BtpTokenProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BtpTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BtpTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAW,MAAM,0BAA0B,CAAC;AAO3I;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,cAAc;IACrD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,eAAe,CAAC,EAAE,MAAM;IAKpC;;;OAGG;YACW,gBAAgB;IAQ9B;;OAEG;YACW,eAAe;IASvB,mBAAmB,CACvB,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAmC1B,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CA0C1E"}
1
+ {"version":3,"file":"BtpTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BtpTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAW,MAAM,0BAA0B,CAAC;AAQ3I;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,cAAc;IACrD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,eAAe,CAAC,EAAE,MAAM;IAKpC;;;OAGG;YACW,gBAAgB;IAQ9B;;OAEG;YACW,eAAe;IASvB,mBAAmB,CACvB,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAmC1B,uBAAuB,CAC3B,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAuC1B,0BAA0B,CAC9B,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAuC1B,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CA0C1E"}
@@ -10,6 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.BtpTokenProvider = void 0;
13
+ const TokenProviderErrors_1 = require("../errors/TokenProviderErrors");
13
14
  const axios_1 = __importDefault(require("axios"));
14
15
  // Import internal functions (not exported)
15
16
  const browserAuth_1 = require("../auth/browserAuth");
@@ -65,6 +66,68 @@ class BtpTokenProvider {
65
66
  refreshToken: result.refreshToken,
66
67
  };
67
68
  }
69
+ async refreshTokenFromSession(authConfig, options) {
70
+ const logger = options?.logger;
71
+ const browser = options?.browser || 'system';
72
+ // Validate authConfig
73
+ const missingFields = [];
74
+ if (!authConfig.uaaUrl)
75
+ missingFields.push('uaaUrl');
76
+ if (!authConfig.uaaClientId)
77
+ missingFields.push('uaaClientId');
78
+ if (!authConfig.uaaClientSecret)
79
+ missingFields.push('uaaClientSecret');
80
+ if (missingFields.length > 0) {
81
+ throw new TokenProviderErrors_1.ValidationError(`BTP refreshTokenFromSession: authConfig missing required fields: ${missingFields.join(', ')}`, missingFields);
82
+ }
83
+ if (logger) {
84
+ logger.debug('BTP: Refreshing token from session using browser authentication (UAA_URL)...');
85
+ }
86
+ // BTP refresh from session uses browser authentication through UAA_URL
87
+ try {
88
+ const result = await this.startBrowserAuth(authConfig, browser, logger);
89
+ return {
90
+ connectionConfig: {
91
+ authorizationToken: result.accessToken,
92
+ },
93
+ refreshToken: result.refreshToken,
94
+ };
95
+ }
96
+ catch (error) {
97
+ throw new TokenProviderErrors_1.RefreshError(`BTP refreshTokenFromSession failed: ${error.message}`, error);
98
+ }
99
+ }
100
+ async refreshTokenFromServiceKey(authConfig, options) {
101
+ const logger = options?.logger;
102
+ const browser = options?.browser || 'system';
103
+ // Validate authConfig
104
+ const missingFields = [];
105
+ if (!authConfig.uaaUrl)
106
+ missingFields.push('uaaUrl');
107
+ if (!authConfig.uaaClientId)
108
+ missingFields.push('uaaClientId');
109
+ if (!authConfig.uaaClientSecret)
110
+ missingFields.push('uaaClientSecret');
111
+ if (missingFields.length > 0) {
112
+ throw new TokenProviderErrors_1.ValidationError(`BTP refreshTokenFromServiceKey: authConfig missing required fields: ${missingFields.join(', ')}`, missingFields);
113
+ }
114
+ if (logger) {
115
+ logger.debug('BTP: Refreshing token from service key using browser authentication...');
116
+ }
117
+ // BTP refresh from service key uses browser authentication
118
+ try {
119
+ const result = await this.startBrowserAuth(authConfig, browser, logger);
120
+ return {
121
+ connectionConfig: {
122
+ authorizationToken: result.accessToken,
123
+ },
124
+ refreshToken: result.refreshToken,
125
+ };
126
+ }
127
+ catch (error) {
128
+ throw new TokenProviderErrors_1.RefreshError(`BTP refreshTokenFromServiceKey failed: ${error.message}`, error);
129
+ }
130
+ }
68
131
  async validateToken(token, serviceUrl) {
69
132
  if (!token || !serviceUrl) {
70
133
  return false;
@@ -16,6 +16,8 @@ export declare class XsuaaTokenProvider implements ITokenProvider {
16
16
  */
17
17
  private getTokenWithClientCredentials;
18
18
  getConnectionConfig(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
19
+ refreshTokenFromSession(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
20
+ refreshTokenFromServiceKey(authConfig: IAuthorizationConfig, options?: ITokenProviderOptions): Promise<ITokenProviderResult>;
19
21
  validateToken(token: string, serviceUrl?: string): Promise<boolean>;
20
22
  }
21
23
  //# sourceMappingURL=XsuaaTokenProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"XsuaaTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/XsuaaTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAMlI;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACvD;;OAEG;YACW,6BAA6B;IAQrC,mBAAmB,CACvB,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAyB1B,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAgD1E"}
1
+ {"version":3,"file":"XsuaaTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/XsuaaTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAOlI;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACvD;;OAEG;YACW,6BAA6B;IAQrC,mBAAmB,CACvB,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAyB1B,uBAAuB,CAC3B,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IA2C1B,0BAA0B,CAC9B,UAAU,EAAE,oBAAoB,EAChC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAwC1B,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAgD1E"}
@@ -5,11 +5,45 @@
5
5
  * Uses client_credentials grant type to obtain tokens (no browser required).
6
6
  * For XSUAA service keys with reduced scope access.
7
7
  */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
8
41
  var __importDefault = (this && this.__importDefault) || function (mod) {
9
42
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
43
  };
11
44
  Object.defineProperty(exports, "__esModule", { value: true });
12
45
  exports.XsuaaTokenProvider = void 0;
46
+ const TokenProviderErrors_1 = require("../errors/TokenProviderErrors");
13
47
  const axios_1 = __importDefault(require("axios"));
14
48
  // Import internal function (not exported)
15
49
  const clientCredentialsAuth_1 = require("../auth/clientCredentialsAuth");
@@ -42,6 +76,68 @@ class XsuaaTokenProvider {
42
76
  // XSUAA client_credentials doesn't provide refresh token
43
77
  };
44
78
  }
79
+ async refreshTokenFromSession(authConfig, options) {
80
+ const logger = options?.logger;
81
+ // Validate authConfig
82
+ const missingFields = [];
83
+ if (!authConfig.uaaUrl)
84
+ missingFields.push('uaaUrl');
85
+ if (!authConfig.uaaClientId)
86
+ missingFields.push('uaaClientId');
87
+ if (!authConfig.uaaClientSecret)
88
+ missingFields.push('uaaClientSecret');
89
+ if (missingFields.length > 0) {
90
+ throw new TokenProviderErrors_1.ValidationError(`XSUAA refreshTokenFromSession: authConfig missing required fields: ${missingFields.join(', ')}`, missingFields);
91
+ }
92
+ if (logger) {
93
+ logger.debug('XSUAA: Refreshing token from session using client_credentials...');
94
+ }
95
+ // XSUAA refresh from session uses client_credentials (clientId/clientSecret)
96
+ try {
97
+ const result = await this.getTokenWithClientCredentials(authConfig.uaaUrl, authConfig.uaaClientId, authConfig.uaaClientSecret);
98
+ return {
99
+ connectionConfig: {
100
+ authorizationToken: result.accessToken,
101
+ },
102
+ // XSUAA client_credentials doesn't provide refresh token
103
+ };
104
+ }
105
+ catch (error) {
106
+ throw new TokenProviderErrors_1.RefreshError(`XSUAA refreshTokenFromSession failed: ${error.message}`, error);
107
+ }
108
+ }
109
+ async refreshTokenFromServiceKey(authConfig, options) {
110
+ const logger = options?.logger;
111
+ const browser = options?.browser || 'system';
112
+ // Validate authConfig
113
+ const missingFields = [];
114
+ if (!authConfig.uaaUrl)
115
+ missingFields.push('uaaUrl');
116
+ if (!authConfig.uaaClientId)
117
+ missingFields.push('uaaClientId');
118
+ if (!authConfig.uaaClientSecret)
119
+ missingFields.push('uaaClientSecret');
120
+ if (missingFields.length > 0) {
121
+ throw new TokenProviderErrors_1.ValidationError(`XSUAA refreshTokenFromServiceKey: authConfig missing required fields: ${missingFields.join(', ')}`, missingFields);
122
+ }
123
+ if (logger) {
124
+ logger.debug('XSUAA: Refreshing token from service key using browser authentication...');
125
+ }
126
+ // XSUAA refresh from service key uses browser authentication
127
+ try {
128
+ const { startBrowserAuth } = await Promise.resolve().then(() => __importStar(require('../auth/browserAuth')));
129
+ const result = await startBrowserAuth(authConfig, browser, logger);
130
+ return {
131
+ connectionConfig: {
132
+ authorizationToken: result.accessToken,
133
+ },
134
+ refreshToken: result.refreshToken,
135
+ };
136
+ }
137
+ catch (error) {
138
+ throw new TokenProviderErrors_1.RefreshError(`XSUAA refreshTokenFromServiceKey failed: ${error.message}`, error);
139
+ }
140
+ }
45
141
  async validateToken(token, serviceUrl) {
46
142
  // XSUAA tokens are validated by the service itself when making requests
47
143
  // If serviceUrl is provided, we can test the connection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-providers",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "Token providers for MCP ABAP ADT auth-broker - XSUAA and BTP token providers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  "node": ">=18.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@mcp-abap-adt/interfaces": "^0.1.16",
51
+ "@mcp-abap-adt/interfaces": "^0.2.2",
52
52
  "axios": "^1.11.0",
53
53
  "express": "^5.1.0",
54
54
  "open": "^11.0.0"