@mcp-abap-adt/auth-broker 0.2.11 → 0.2.13
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 +20 -0
- package/README.md +113 -110
- package/bin/generate-env-from-service-key.ts +26 -5
- package/bin/mcp-auth.ts +25 -94
- package/dist/AuthBroker.d.ts +3 -17
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +78 -268
- package/dist/__tests__/helpers/configHelpers.d.ts +10 -0
- package/dist/__tests__/helpers/configHelpers.d.ts.map +1 -1
- package/dist/__tests__/helpers/configHelpers.js +16 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/providers/ITokenProvider.d.ts +3 -7
- package/dist/providers/ITokenProvider.d.ts.map +1 -1
- package/dist/providers/ITokenProvider.js +1 -4
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,26 @@ Thank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the co
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.2.13] - 2025-12-26
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Token lifecycle management**: Broker now always calls `provider.getTokens()` instead of validating tokens itself. Provider handles all token lifecycle operations internally (validation, refresh, login). Consumer doesn't need to know about token issues - provider manages everything automatically.
|
|
18
|
+
- **Removed token validation step**: Removed Step 1 (Token Validation) from broker flow. Broker no longer checks token validity before calling provider - provider decides what to do based on token state.
|
|
19
|
+
- **Simplified broker logic**: Broker is now a thin wrapper that always delegates to provider. Provider is responsible for token lifecycle management.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
- **`validateExistingToken()` method**: Removed internal token validation method. Broker no longer validates tokens - provider handles this internally via `getTokens()`.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **Compatibility with auth-providers 0.2.8**: Removed dependency on deprecated `refreshTokenFromServiceKey()` method from token providers. Broker now exclusively uses `getTokens()` method which handles all token lifecycle operations internally. This ensures compatibility with stateful token providers that manage refresh/re-auth internally.
|
|
26
|
+
|
|
27
|
+
## [0.2.12] - 2025-12-25
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- **Auth flow**: Broker now relies on `ITokenProvider.getTokens()` with no parameters and expects providers to manage refresh/re-auth internally.
|
|
31
|
+
- **Constructor**: `tokenProvider` is required and `allowBrowserAuth` is supported for non-interactive flows.
|
|
32
|
+
- **Docs**: Updated usage/architecture/export docs to reflect provider injection, new flow, and CLI usage.
|
|
33
|
+
|
|
14
34
|
## [0.2.11] - 2025-12-23
|
|
15
35
|
|
|
16
36
|
### Changed
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ JWT authentication broker for MCP ABAP ADT server. Manages authentication tokens
|
|
|
7
7
|
- 🔐 **Destination-based Authentication**: Load tokens based on `x-mcp-destination` header
|
|
8
8
|
- 📁 **Environment File Support**: Automatically loads tokens from `{destination}.env` files
|
|
9
9
|
- 🔄 **Automatic Token Refresh**: Refreshes expired tokens using service keys from `{destination}.json` files
|
|
10
|
-
- ✅ **Token Validation**: Validates tokens
|
|
10
|
+
- ✅ **Token Validation**: Validates tokens via provider (if `validateToken` is implemented)
|
|
11
11
|
- 💾 **Token Caching**: In-memory caching for improved performance
|
|
12
12
|
- 🔧 **Configurable Base Path**: Customize where `.env` and `.json` files are stored
|
|
13
13
|
|
|
@@ -19,19 +19,26 @@ npm install @mcp-abap-adt/auth-broker
|
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
22
|
-
### Basic Usage (
|
|
22
|
+
### Basic Usage (Provider Required)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
AuthBroker requires a token provider configured for the destination:
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
27
|
import { AuthBroker, AbapSessionStore } from '@mcp-abap-adt/auth-broker';
|
|
28
|
+
import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';
|
|
29
|
+
|
|
30
|
+
const tokenProvider = new AuthorizationCodeProvider({
|
|
31
|
+
uaaUrl: 'https://...authentication...hana.ondemand.com',
|
|
32
|
+
clientId: '...',
|
|
33
|
+
clientSecret: '...',
|
|
34
|
+
browser: 'system',
|
|
35
|
+
});
|
|
28
36
|
|
|
29
|
-
// Session-only mode - works if session has UAA credentials
|
|
30
37
|
const broker = new AuthBroker({
|
|
31
38
|
sessionStore: new AbapSessionStore('/path/to/destinations'),
|
|
39
|
+
tokenProvider,
|
|
32
40
|
});
|
|
33
41
|
|
|
34
|
-
// Get token - uses direct UAA HTTP requests automatically
|
|
35
42
|
const token = await broker.getToken('TRIAL');
|
|
36
43
|
```
|
|
37
44
|
|
|
@@ -40,37 +47,52 @@ const token = await broker.getToken('TRIAL');
|
|
|
40
47
|
For maximum flexibility, provide all three dependencies:
|
|
41
48
|
|
|
42
49
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
AuthBroker,
|
|
45
|
-
AbapServiceKeyStore,
|
|
46
|
-
AbapSessionStore,
|
|
47
|
-
BtpTokenProvider
|
|
50
|
+
import {
|
|
51
|
+
AuthBroker,
|
|
52
|
+
AbapServiceKeyStore,
|
|
53
|
+
AbapSessionStore,
|
|
48
54
|
} from '@mcp-abap-adt/auth-broker';
|
|
55
|
+
import { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';
|
|
49
56
|
|
|
50
57
|
const broker = new AuthBroker({
|
|
51
58
|
sessionStore: new AbapSessionStore('/path/to/destinations'),
|
|
52
59
|
serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'), // optional
|
|
53
|
-
tokenProvider: new
|
|
60
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
61
|
+
uaaUrl: 'https://...authentication...hana.ondemand.com',
|
|
62
|
+
clientId: '...',
|
|
63
|
+
clientSecret: '...',
|
|
64
|
+
browser: 'system',
|
|
65
|
+
}),
|
|
54
66
|
}, 'chrome', logger);
|
|
55
67
|
|
|
56
68
|
// Disable browser authentication for headless/stdio environments (e.g., MCP with Cline)
|
|
57
69
|
const brokerNoBrowser = new AuthBroker({
|
|
58
70
|
sessionStore: new AbapSessionStore('/path/to/destinations'),
|
|
59
71
|
serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),
|
|
60
|
-
tokenProvider: new
|
|
72
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
73
|
+
uaaUrl: 'https://...authentication...hana.ondemand.com',
|
|
74
|
+
clientId: '...',
|
|
75
|
+
clientSecret: '...',
|
|
76
|
+
browser: 'none',
|
|
77
|
+
}),
|
|
61
78
|
allowBrowserAuth: false, // Throws BROWSER_AUTH_REQUIRED if browser auth needed
|
|
62
79
|
}, 'chrome', logger);
|
|
63
80
|
```
|
|
64
81
|
|
|
65
82
|
### Session + Service Key (For Initialization)
|
|
66
83
|
|
|
67
|
-
If you need to initialize sessions from service keys:
|
|
84
|
+
If you need to initialize sessions from service keys, create the provider from service key auth config:
|
|
68
85
|
|
|
69
86
|
```typescript
|
|
70
87
|
const broker = new AuthBroker({
|
|
71
88
|
sessionStore: new AbapSessionStore('/path/to/destinations'),
|
|
72
89
|
serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),
|
|
73
|
-
|
|
90
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
91
|
+
uaaUrl: 'https://...authentication...hana.ondemand.com',
|
|
92
|
+
clientId: '...',
|
|
93
|
+
clientSecret: '...',
|
|
94
|
+
browser: 'system',
|
|
95
|
+
}),
|
|
74
96
|
});
|
|
75
97
|
```
|
|
76
98
|
|
|
@@ -94,16 +116,19 @@ To avoid port conflicts with browser authentication:
|
|
|
94
116
|
const broker = new AuthBroker({
|
|
95
117
|
sessionStore: new AbapSessionStore('/path/to/destinations'),
|
|
96
118
|
serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),
|
|
97
|
-
tokenProvider: new
|
|
119
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
120
|
+
uaaUrl: 'https://...authentication...hana.ondemand.com',
|
|
121
|
+
clientId: '...',
|
|
122
|
+
clientSecret: '...',
|
|
123
|
+
browser: 'system',
|
|
124
|
+
redirectPort: 4001,
|
|
125
|
+
}),
|
|
98
126
|
}, 'chrome');
|
|
99
127
|
```
|
|
100
128
|
|
|
101
|
-
**Note**: The `BtpTokenProvider` automatically finds an available port if the requested port is in use. This prevents `EADDRINUSE` errors when multiple stdio servers run simultaneously. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation.
|
|
102
|
-
|
|
103
129
|
### Getting Tokens
|
|
104
130
|
|
|
105
131
|
```typescript
|
|
106
|
-
// Get token - automatically uses direct UAA requests if UAA credentials available
|
|
107
132
|
const token = await broker.getToken('TRIAL');
|
|
108
133
|
|
|
109
134
|
// Force refresh token
|
|
@@ -282,7 +307,7 @@ This package supports two types of BTP authentication:
|
|
|
282
307
|
**Interface-Only Communication**: This package follows a fundamental development principle: **all interactions with external dependencies happen ONLY through interfaces**. The code knows **NOTHING beyond what is defined in the interfaces**.
|
|
283
308
|
|
|
284
309
|
This means:
|
|
285
|
-
- Does not know about concrete implementation classes (e.g., `AbapSessionStore`, `
|
|
310
|
+
- Does not know about concrete implementation classes (e.g., `AbapSessionStore`, `AuthorizationCodeProvider`)
|
|
286
311
|
- Does not know about internal data structures or methods not defined in interfaces
|
|
287
312
|
- Does not make assumptions about implementation behavior beyond interface contracts
|
|
288
313
|
- Does not access properties or methods not explicitly defined in interfaces
|
|
@@ -302,13 +327,11 @@ The `@mcp-abap-adt/auth-broker` package defines **interfaces** and provides **or
|
|
|
302
327
|
- **Orchestrates authentication flows**: Coordinates token retrieval, validation, and refresh using provided stores and providers
|
|
303
328
|
- **Manages token lifecycle**: Handles token caching, validation, and automatic refresh
|
|
304
329
|
- **Works with interfaces only**: Uses `IServiceKeyStore`, `ISessionStore`, and `ITokenProvider` interfaces without knowing concrete implementations
|
|
305
|
-
- **Delegates to providers**: Calls `tokenProvider.
|
|
306
|
-
- **Delegates to stores**:
|
|
330
|
+
- **Delegates to providers**: Calls `tokenProvider.getTokens()` to obtain tokens
|
|
331
|
+
- **Delegates to stores**: Saves tokens and connection configuration to `sessionStore`
|
|
307
332
|
|
|
308
333
|
#### What AuthBroker Does NOT Do
|
|
309
334
|
|
|
310
|
-
- **Does NOT know about `serviceUrl`**: `AuthBroker` does not know whether a specific `ISessionStore` implementation requires `serviceUrl` or not. It simply passes the `IConnectionConfig` returned by `tokenProvider` to `sessionStore.setConnectionConfig()`
|
|
311
|
-
- **Does NOT merge configurations**: `AuthBroker` does not merge `serviceUrl` from service keys with connection config from token providers. This is the responsibility of the consumer or the session store implementation
|
|
312
335
|
- **Does NOT implement storage**: File I/O, parsing, and storage logic are handled by concrete store implementations from `@mcp-abap-adt/auth-stores`
|
|
313
336
|
- **Does NOT implement token acquisition**: OAuth2 flows, refresh token logic, and client credentials are handled by concrete provider implementations from `@mcp-abap-adt/auth-providers`
|
|
314
337
|
|
|
@@ -317,9 +340,9 @@ The `@mcp-abap-adt/auth-broker` package defines **interfaces** and provides **or
|
|
|
317
340
|
The **consumer** (application using `AuthBroker`) is responsible for:
|
|
318
341
|
|
|
319
342
|
1. **Selecting appropriate implementations**: Choose the correct `IServiceKeyStore`, `ISessionStore`, and `ITokenProvider` implementations based on the use case:
|
|
320
|
-
- **ABAP systems**: Use `AbapServiceKeyStore`, `AbapSessionStore` (or `SafeAbapSessionStore`), and `
|
|
321
|
-
- **BTP systems**: Use `AbapServiceKeyStore`, `BtpSessionStore` (or `SafeBtpSessionStore`), and `
|
|
322
|
-
- **XSUAA services**: Use `XsuaaServiceKeyStore`, `XsuaaSessionStore` (or `SafeXsuaaSessionStore`), and `
|
|
343
|
+
- **ABAP systems**: Use `AbapServiceKeyStore`, `AbapSessionStore` (or `SafeAbapSessionStore`), and `AuthorizationCodeProvider`
|
|
344
|
+
- **BTP systems**: Use `AbapServiceKeyStore`, `BtpSessionStore` (or `SafeBtpSessionStore`), and `AuthorizationCodeProvider`
|
|
345
|
+
- **XSUAA services**: Use `XsuaaServiceKeyStore`, `XsuaaSessionStore` (or `SafeXsuaaSessionStore`), and `ClientCredentialsProvider`
|
|
323
346
|
|
|
324
347
|
2. **Ensuring complete configuration**: If a session store requires `serviceUrl` (e.g., `AbapSessionStore` requires `sapUrl`), the consumer must ensure that:
|
|
325
348
|
- The session is created with `serviceUrl` before calling `AuthBroker.getToken()`, OR
|
|
@@ -346,8 +369,7 @@ Concrete `ISessionStore` implementations are responsible for:
|
|
|
346
369
|
Concrete `ITokenProvider` implementations are responsible for:
|
|
347
370
|
|
|
348
371
|
- **Obtaining tokens**: Using OAuth2 flows, refresh tokens, or client credentials to obtain JWT tokens
|
|
349
|
-
- **
|
|
350
|
-
- **Not returning `serviceUrl` if unknown**: Providers like `BtpTokenProvider` may not return `serviceUrl` because they only handle token acquisition, not connection configuration
|
|
372
|
+
- **Managing token lifecycle**: Caching, validating, refreshing, and re-authenticating as needed
|
|
351
373
|
|
|
352
374
|
### Design Principles
|
|
353
375
|
|
|
@@ -361,22 +383,6 @@ Concrete `ITokenProvider` implementations are responsible for:
|
|
|
361
383
|
4. **Interface Segregation**: Interfaces are focused and minimal, containing only what's necessary for their specific purpose
|
|
362
384
|
5. **Open/Closed Principle**: New store and provider implementations can be added without modifying `AuthBroker`
|
|
363
385
|
|
|
364
|
-
### Example: Why AuthBroker Doesn't Handle `serviceUrl`
|
|
365
|
-
|
|
366
|
-
Consider this scenario:
|
|
367
|
-
- `BtpTokenProvider.getConnectionConfig()` returns `IConnectionConfig` with `authorizationToken` but **without** `serviceUrl` (because it only handles token acquisition)
|
|
368
|
-
- `AbapSessionStore.setConnectionConfig()` requires `sapUrl` (which maps to `serviceUrl`)
|
|
369
|
-
|
|
370
|
-
If `AuthBroker` tried to merge `serviceUrl` from `serviceKeyStore`, it would:
|
|
371
|
-
1. Violate the DIP by knowing about specific store requirements
|
|
372
|
-
2. Break the abstraction - `AuthBroker` shouldn't know that `AbapSessionStore` needs `serviceUrl`
|
|
373
|
-
3. Create coupling between `AuthBroker` and concrete implementations
|
|
374
|
-
|
|
375
|
-
Instead, the consumer or `AbapSessionStore` itself should handle this:
|
|
376
|
-
- **Option 1**: Consumer retrieves `serviceUrl` from `serviceKeyStore` and ensures it's in the session before calling `AuthBroker.getToken()`
|
|
377
|
-
- **Option 2**: `AbapSessionStore.setConnectionConfig()` retrieves `serviceUrl` from `serviceKeyStore` internally if not provided
|
|
378
|
-
- **Option 3**: `AbapSessionStore.setConnectionConfig()` uses existing `sapUrl` from current session if available
|
|
379
|
-
|
|
380
386
|
## API
|
|
381
387
|
|
|
382
388
|
### `AuthBroker`
|
|
@@ -388,7 +394,8 @@ new AuthBroker(
|
|
|
388
394
|
config: {
|
|
389
395
|
sessionStore: ISessionStore; // required
|
|
390
396
|
serviceKeyStore?: IServiceKeyStore; // optional
|
|
391
|
-
tokenProvider
|
|
397
|
+
tokenProvider: ITokenProvider; // required
|
|
398
|
+
allowBrowserAuth?: boolean; // optional
|
|
392
399
|
},
|
|
393
400
|
browser?: string,
|
|
394
401
|
logger?: ILogger
|
|
@@ -399,7 +406,8 @@ new AuthBroker(
|
|
|
399
406
|
- `config` - Configuration object:
|
|
400
407
|
- `sessionStore` - **Required** - Store for session data. Must contain initial session with `serviceUrl`
|
|
401
408
|
- `serviceKeyStore` - **Optional** - Store for service keys. Only needed for initializing sessions from service keys
|
|
402
|
-
- `tokenProvider` - **
|
|
409
|
+
- `tokenProvider` - **Required** - Token provider for token acquisition and refresh
|
|
410
|
+
- `allowBrowserAuth` - **Optional** - When `false`, throws `BROWSER_AUTH_REQUIRED` instead of launching browser auth
|
|
403
411
|
- `browser` - Optional browser name for authentication (`chrome`, `edge`, `firefox`, `system`, `headless`, `none`). Default: `system`
|
|
404
412
|
- Use `'headless'` for SSH/remote sessions - logs URL and waits for manual callback
|
|
405
413
|
- Use `'none'` for automated tests - logs URL and rejects immediately
|
|
@@ -411,16 +419,15 @@ new AuthBroker(
|
|
|
411
419
|
- **`sessionStore` (required)**: Always required. Must contain initial session with `serviceUrl`
|
|
412
420
|
- **`serviceKeyStore` (optional)**:
|
|
413
421
|
- Required if you need to initialize sessions from service keys (Step 0)
|
|
414
|
-
- Not needed if session already contains
|
|
415
|
-
- **`tokenProvider` (
|
|
416
|
-
-
|
|
417
|
-
-
|
|
418
|
-
- Not needed if session contains valid UAA credentials (direct UAA HTTP requests will be used)
|
|
422
|
+
- Not needed if session already contains authorization config and tokens
|
|
423
|
+
- **`tokenProvider` (required)**:
|
|
424
|
+
- Used for all token acquisition and refresh flows
|
|
425
|
+
- Must be configured with the destination's auth parameters (e.g., UAA credentials)
|
|
419
426
|
|
|
420
427
|
**Available Implementations:**
|
|
421
|
-
- **ABAP**: `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `AbapSessionStore(directory, defaultServiceUrl?, logger?)`, `SafeAbapSessionStore(defaultServiceUrl?, logger?)`, `
|
|
422
|
-
- **XSUAA** (reduced scope): `XsuaaServiceKeyStore(directory, logger?)`, `XsuaaSessionStore(directory, defaultServiceUrl, logger?)`, `SafeXsuaaSessionStore(defaultServiceUrl, logger?)`, `
|
|
423
|
-
- **BTP** (full scope for ABAP): `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `BtpSessionStore(directory, defaultServiceUrl, logger?)`, `SafeBtpSessionStore(defaultServiceUrl, logger?)`, `
|
|
428
|
+
- **ABAP**: `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `AbapSessionStore(directory, defaultServiceUrl?, logger?)`, `SafeAbapSessionStore(defaultServiceUrl?, logger?)`, `AuthorizationCodeProvider(...)`
|
|
429
|
+
- **XSUAA** (reduced scope): `XsuaaServiceKeyStore(directory, logger?)`, `XsuaaSessionStore(directory, defaultServiceUrl, logger?)`, `SafeXsuaaSessionStore(defaultServiceUrl, logger?)`, `ClientCredentialsProvider(...)`
|
|
430
|
+
- **BTP** (full scope for ABAP): `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `BtpSessionStore(directory, defaultServiceUrl, logger?)`, `SafeBtpSessionStore(defaultServiceUrl, logger?)`, `AuthorizationCodeProvider(...)`
|
|
424
431
|
|
|
425
432
|
#### Methods
|
|
426
433
|
|
|
@@ -429,31 +436,29 @@ new AuthBroker(
|
|
|
429
436
|
Gets authentication token for destination. Implements a three-step flow:
|
|
430
437
|
|
|
431
438
|
**Step 0: Initialize Session with Token (if needed)**
|
|
432
|
-
- Checks if session has `authorizationToken` and
|
|
433
|
-
- If both are
|
|
434
|
-
-
|
|
435
|
-
-
|
|
436
|
-
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
- If
|
|
441
|
-
-
|
|
442
|
-
-
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
-
|
|
448
|
-
- Tries direct UAA client_credentials request (if UAA credentials available)
|
|
449
|
-
- If failed and `tokenProvider` available → uses provider
|
|
450
|
-
- If successful → returns new token
|
|
439
|
+
- Checks if session has `authorizationToken` and authorization config
|
|
440
|
+
- If both are missing and `serviceKeyStore` is available:
|
|
441
|
+
- Loads authorization config from service key
|
|
442
|
+
- Uses `tokenProvider.getTokens()` to obtain tokens
|
|
443
|
+
- Persists tokens to session
|
|
444
|
+
- Otherwise → proceeds to Step 1
|
|
445
|
+
|
|
446
|
+
**Step 1: Token Refresh / Re-Auth**
|
|
447
|
+
- If session has authorization config:
|
|
448
|
+
- Uses `tokenProvider.getTokens()` to refresh or re-authenticate
|
|
449
|
+
- Persists tokens to session
|
|
450
|
+
- Returns new token
|
|
451
|
+
- If that fails (or no session auth config) and `serviceKeyStore` is available:
|
|
452
|
+
- Loads authorization config from service key
|
|
453
|
+
- Uses `tokenProvider.getTokens()` to obtain tokens
|
|
454
|
+
- Persists tokens to session
|
|
451
455
|
- If all failed → throws error
|
|
452
456
|
|
|
453
457
|
**Important Notes:**
|
|
454
|
-
-
|
|
455
|
-
- `tokenProvider` is
|
|
456
|
-
-
|
|
458
|
+
- All authentication is handled by the injected provider (authorization_code or client_credentials).
|
|
459
|
+
- `tokenProvider` is required for all token acquisition and refresh flows.
|
|
460
|
+
- **Broker always calls `provider.getTokens()`** - provider handles token lifecycle internally (validation, refresh, login). Consumer doesn't need to know about token issues.
|
|
461
|
+
- Provider decides whether to return cached token, refresh, or perform login based on token state.
|
|
457
462
|
- **Store errors are handled gracefully**: If service key files are missing or malformed, the broker logs the error and continues with fallback mechanisms (session store data or provider-based auth)
|
|
458
463
|
|
|
459
464
|
##### Error Handling
|
|
@@ -512,13 +517,7 @@ Example error scenarios handled:
|
|
|
512
517
|
|
|
513
518
|
##### `refreshToken(destination: string): Promise<string>`
|
|
514
519
|
|
|
515
|
-
Force refresh token for destination.
|
|
516
|
-
|
|
517
|
-
**Flow:**
|
|
518
|
-
- If refresh token exists and UAA credentials available → tries direct UAA refresh
|
|
519
|
-
- If direct UAA fails and `tokenProvider` available → uses provider
|
|
520
|
-
- If no refresh token but UAA credentials available → tries direct UAA client_credentials
|
|
521
|
-
- If all failed → throws error
|
|
520
|
+
Force refresh token for destination. Calls `getToken()` to run the full refresh flow and persist updated tokens.
|
|
522
521
|
|
|
523
522
|
##### `clearCache(destination: string): void`
|
|
524
523
|
|
|
@@ -530,14 +529,14 @@ Clear all cached tokens.
|
|
|
530
529
|
|
|
531
530
|
### Token Providers
|
|
532
531
|
|
|
533
|
-
The package uses `ITokenProvider` interface for token acquisition.
|
|
532
|
+
The package uses the `ITokenProvider` interface for token acquisition. Provider implementations live in `@mcp-abap-adt/auth-providers`:
|
|
534
533
|
|
|
535
|
-
- **`
|
|
534
|
+
- **`ClientCredentialsProvider`** - For XSUAA authentication (reduced scope)
|
|
536
535
|
- Uses client_credentials grant type
|
|
537
536
|
- No browser interaction required
|
|
538
537
|
- No refresh token provided
|
|
539
538
|
|
|
540
|
-
- **`
|
|
539
|
+
- **`AuthorizationCodeProvider`** - For BTP/ABAP authentication (full scope)
|
|
541
540
|
- Constructor accepts optional `browserAuthPort?: number` parameter (default: 3001)
|
|
542
541
|
- Automatically finds an available port if the requested port is in use (prevents `EADDRINUSE` errors)
|
|
543
542
|
- Server properly closes all connections and frees the port after authentication completes
|
|
@@ -553,55 +552,59 @@ import {
|
|
|
553
552
|
AuthBroker,
|
|
554
553
|
XsuaaServiceKeyStore,
|
|
555
554
|
XsuaaSessionStore,
|
|
556
|
-
XsuaaTokenProvider,
|
|
557
|
-
BtpTokenProvider,
|
|
558
555
|
AbapServiceKeyStore,
|
|
559
556
|
BtpSessionStore
|
|
560
557
|
} from '@mcp-abap-adt/auth-broker';
|
|
558
|
+
import {
|
|
559
|
+
ClientCredentialsProvider,
|
|
560
|
+
AuthorizationCodeProvider,
|
|
561
|
+
} from '@mcp-abap-adt/auth-providers';
|
|
561
562
|
|
|
562
|
-
// XSUAA authentication
|
|
563
|
+
// XSUAA authentication
|
|
563
564
|
const xsuaaBroker = new AuthBroker({
|
|
564
565
|
sessionStore: new XsuaaSessionStore('/path/to/sessions', 'https://mcp.example.com'),
|
|
565
|
-
|
|
566
|
+
tokenProvider: new ClientCredentialsProvider({
|
|
567
|
+
uaaUrl: 'https://auth.example.com',
|
|
568
|
+
clientId: '...',
|
|
569
|
+
clientSecret: '...',
|
|
570
|
+
}),
|
|
566
571
|
});
|
|
567
572
|
|
|
568
573
|
// XSUAA authentication - with service key initialization
|
|
569
574
|
const xsuaaBrokerWithServiceKey = new AuthBroker({
|
|
570
575
|
sessionStore: new XsuaaSessionStore('/path/to/sessions', 'https://mcp.example.com'),
|
|
571
576
|
serviceKeyStore: new XsuaaServiceKeyStore('/path/to/keys'),
|
|
572
|
-
|
|
577
|
+
tokenProvider: new ClientCredentialsProvider({
|
|
578
|
+
uaaUrl: 'https://auth.example.com',
|
|
579
|
+
clientId: '...',
|
|
580
|
+
clientSecret: '...',
|
|
581
|
+
}),
|
|
573
582
|
}, 'none');
|
|
574
583
|
|
|
575
|
-
// BTP authentication
|
|
584
|
+
// BTP authentication
|
|
576
585
|
const btpBroker = new AuthBroker({
|
|
577
586
|
sessionStore: new BtpSessionStore('/path/to/sessions', 'https://abap.example.com'),
|
|
578
|
-
|
|
587
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
588
|
+
uaaUrl: 'https://auth.example.com',
|
|
589
|
+
clientId: '...',
|
|
590
|
+
clientSecret: '...',
|
|
591
|
+
browser: 'system',
|
|
592
|
+
}),
|
|
579
593
|
});
|
|
580
594
|
|
|
581
595
|
// BTP authentication - with service key and provider (for browser auth)
|
|
582
596
|
const btpBrokerFull = new AuthBroker({
|
|
583
597
|
sessionStore: new BtpSessionStore('/path/to/sessions', 'https://abap.example.com'),
|
|
584
598
|
serviceKeyStore: new AbapServiceKeyStore('/path/to/keys'),
|
|
585
|
-
tokenProvider: new
|
|
599
|
+
tokenProvider: new AuthorizationCodeProvider({
|
|
600
|
+
uaaUrl: 'https://auth.example.com',
|
|
601
|
+
clientId: '...',
|
|
602
|
+
clientSecret: '...',
|
|
603
|
+
browser: 'system',
|
|
604
|
+
}),
|
|
586
605
|
});
|
|
587
606
|
```
|
|
588
607
|
|
|
589
|
-
### Direct UAA HTTP Requests
|
|
590
|
-
|
|
591
|
-
When UAA credentials are available in session, `AuthBroker` automatically uses direct HTTP requests to UAA without requiring `tokenProvider`:
|
|
592
|
-
|
|
593
|
-
- **Refresh Token Grant**: Direct HTTP POST to `{uaaUrl}/oauth/token` with `grant_type=refresh_token`
|
|
594
|
-
- **Client Credentials Grant**: Direct HTTP POST to `{uaaUrl}/oauth/token` with `grant_type=client_credentials`
|
|
595
|
-
|
|
596
|
-
**Benefits:**
|
|
597
|
-
- No dependency on `tokenProvider` when session has UAA credentials
|
|
598
|
-
- Faster token refresh (no provider overhead)
|
|
599
|
-
- Simpler configuration (only `sessionStore` needed)
|
|
600
|
-
|
|
601
|
-
**Fallback to Provider:**
|
|
602
|
-
- If direct UAA request fails and `tokenProvider` is available, broker automatically falls back to provider
|
|
603
|
-
- Provider is useful for browser authentication or alternative authentication flows
|
|
604
|
-
|
|
605
608
|
### CLI: mcp-auth
|
|
606
609
|
|
|
607
610
|
Generate or refresh `.env`/JSON output using AuthBroker + stores:
|
|
@@ -16,8 +16,16 @@
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import { AuthBroker } from '../src/AuthBroker';
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
AbapServiceKeyStore,
|
|
21
|
+
AbapSessionStore,
|
|
22
|
+
XsuaaServiceKeyStore,
|
|
23
|
+
XsuaaSessionStore,
|
|
24
|
+
} from '@mcp-abap-adt/auth-stores';
|
|
25
|
+
import {
|
|
26
|
+
AuthorizationCodeProvider,
|
|
27
|
+
ClientCredentialsProvider,
|
|
28
|
+
} from '@mcp-abap-adt/auth-providers';
|
|
21
29
|
|
|
22
30
|
async function main() {
|
|
23
31
|
const args = process.argv.slice(2);
|
|
@@ -65,10 +73,24 @@ async function main() {
|
|
|
65
73
|
? new XsuaaSessionStore(sessionDir)
|
|
66
74
|
: new AbapSessionStore(sessionDir);
|
|
67
75
|
|
|
76
|
+
const authConfig = await serviceKeyStore.getAuthorizationConfig(destination);
|
|
77
|
+
if (!authConfig) {
|
|
78
|
+
throw new Error(`Missing authorization config for ${destination}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
// Create token provider
|
|
69
82
|
const tokenProvider = isXsuaa
|
|
70
|
-
? new
|
|
71
|
-
|
|
83
|
+
? new ClientCredentialsProvider({
|
|
84
|
+
uaaUrl: authConfig.uaaUrl,
|
|
85
|
+
clientId: authConfig.uaaClientId,
|
|
86
|
+
clientSecret: authConfig.uaaClientSecret,
|
|
87
|
+
})
|
|
88
|
+
: new AuthorizationCodeProvider({
|
|
89
|
+
uaaUrl: authConfig.uaaUrl,
|
|
90
|
+
clientId: authConfig.uaaClientId,
|
|
91
|
+
clientSecret: authConfig.uaaClientSecret,
|
|
92
|
+
browser: 'system',
|
|
93
|
+
});
|
|
72
94
|
|
|
73
95
|
// Create AuthBroker
|
|
74
96
|
// For ABAP, use 'system' browser (will open browser for auth)
|
|
@@ -124,4 +146,3 @@ main().catch((error) => {
|
|
|
124
146
|
console.error('Fatal error:', error);
|
|
125
147
|
process.exit(1);
|
|
126
148
|
});
|
|
127
|
-
|
package/bin/mcp-auth.ts
CHANGED
|
@@ -32,12 +32,6 @@ import {
|
|
|
32
32
|
AuthorizationCodeProvider,
|
|
33
33
|
ClientCredentialsProvider,
|
|
34
34
|
} from '@mcp-abap-adt/auth-providers';
|
|
35
|
-
import type {
|
|
36
|
-
IAuthorizationConfig,
|
|
37
|
-
ITokenProvider,
|
|
38
|
-
ITokenProviderOptions,
|
|
39
|
-
ITokenProviderResult,
|
|
40
|
-
} from '@mcp-abap-adt/interfaces';
|
|
41
35
|
import {
|
|
42
36
|
ABAP_CONNECTION_VARS,
|
|
43
37
|
ABAP_AUTHORIZATION_VARS,
|
|
@@ -241,86 +235,6 @@ function parseArgs(): McpAuthOptions | null {
|
|
|
241
235
|
};
|
|
242
236
|
}
|
|
243
237
|
|
|
244
|
-
type ProviderMode = 'authorization_code' | 'client_credentials';
|
|
245
|
-
|
|
246
|
-
class BrokerTokenProvider implements ITokenProvider {
|
|
247
|
-
private mode: ProviderMode;
|
|
248
|
-
private browser?: string;
|
|
249
|
-
private redirectPort?: number;
|
|
250
|
-
|
|
251
|
-
constructor(mode: ProviderMode, browser?: string, redirectPort?: number) {
|
|
252
|
-
this.mode = mode;
|
|
253
|
-
this.browser = browser;
|
|
254
|
-
this.redirectPort = redirectPort;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async getConnectionConfig(
|
|
258
|
-
authConfig: IAuthorizationConfig,
|
|
259
|
-
options?: ITokenProviderOptions,
|
|
260
|
-
): Promise<ITokenProviderResult> {
|
|
261
|
-
return this.getTokenResult(authConfig, options);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async refreshTokenFromSession(
|
|
265
|
-
authConfig: IAuthorizationConfig,
|
|
266
|
-
options?: ITokenProviderOptions,
|
|
267
|
-
): Promise<ITokenProviderResult> {
|
|
268
|
-
return this.getTokenResult(authConfig, options);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async refreshTokenFromServiceKey(
|
|
272
|
-
authConfig: IAuthorizationConfig,
|
|
273
|
-
options?: ITokenProviderOptions,
|
|
274
|
-
): Promise<ITokenProviderResult> {
|
|
275
|
-
return this.getTokenResult(authConfig, options);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private async getTokenResult(
|
|
279
|
-
authConfig: IAuthorizationConfig,
|
|
280
|
-
options?: ITokenProviderOptions,
|
|
281
|
-
): Promise<ITokenProviderResult> {
|
|
282
|
-
const uaaUrl = authConfig.uaaUrl;
|
|
283
|
-
const uaaClientId = authConfig.uaaClientId;
|
|
284
|
-
const uaaClientSecret = authConfig.uaaClientSecret;
|
|
285
|
-
|
|
286
|
-
if (!uaaUrl || !uaaClientId || !uaaClientSecret) {
|
|
287
|
-
throw new Error('Auth config missing required UAA credentials');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (this.mode === 'client_credentials') {
|
|
291
|
-
const provider = new ClientCredentialsProvider({
|
|
292
|
-
uaaUrl,
|
|
293
|
-
clientId: uaaClientId,
|
|
294
|
-
clientSecret: uaaClientSecret,
|
|
295
|
-
});
|
|
296
|
-
const result = await provider.getTokens();
|
|
297
|
-
return {
|
|
298
|
-
connectionConfig: {
|
|
299
|
-
authorizationToken: result.authorizationToken,
|
|
300
|
-
},
|
|
301
|
-
refreshToken: result.refreshToken,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const browserValue = options?.browser ?? this.browser ?? 'system';
|
|
306
|
-
const provider = new AuthorizationCodeProvider({
|
|
307
|
-
uaaUrl,
|
|
308
|
-
clientId: uaaClientId,
|
|
309
|
-
clientSecret: uaaClientSecret,
|
|
310
|
-
refreshToken: authConfig.refreshToken,
|
|
311
|
-
browser: browserValue,
|
|
312
|
-
redirectPort: this.redirectPort,
|
|
313
|
-
});
|
|
314
|
-
const result = await provider.getTokens();
|
|
315
|
-
return {
|
|
316
|
-
connectionConfig: {
|
|
317
|
-
authorizationToken: result.authorizationToken,
|
|
318
|
-
},
|
|
319
|
-
refreshToken: result.refreshToken,
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
238
|
function writeEnvFile(
|
|
325
239
|
outputPath: string,
|
|
326
240
|
authType: 'abap' | 'xsuaa',
|
|
@@ -531,15 +445,32 @@ async function main() {
|
|
|
531
445
|
? new XsuaaSessionStore(tempSessionDir, brokerServiceUrl)
|
|
532
446
|
: new AbapSessionStore(tempSessionDir);
|
|
533
447
|
|
|
448
|
+
const sessionAuthConfig =
|
|
449
|
+
await sessionStore.getAuthorizationConfig(destination);
|
|
450
|
+
const serviceKeyAuthConfig =
|
|
451
|
+
serviceKeyStore?.getAuthorizationConfig
|
|
452
|
+
? await serviceKeyStore.getAuthorizationConfig(destination)
|
|
453
|
+
: null;
|
|
454
|
+
const authConfig = sessionAuthConfig || serviceKeyAuthConfig;
|
|
455
|
+
if (!authConfig) {
|
|
456
|
+
throw new Error(`Authorization config not found for ${destination}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
534
459
|
const useBrowserAuth = options.browser !== undefined;
|
|
535
|
-
const
|
|
536
|
-
?
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
460
|
+
const tokenProvider = useBrowserAuth
|
|
461
|
+
? new AuthorizationCodeProvider({
|
|
462
|
+
uaaUrl: authConfig.uaaUrl,
|
|
463
|
+
clientId: authConfig.uaaClientId,
|
|
464
|
+
clientSecret: authConfig.uaaClientSecret,
|
|
465
|
+
refreshToken: authConfig.refreshToken,
|
|
466
|
+
browser: options.browser,
|
|
467
|
+
redirectPort: options.redirectPort,
|
|
468
|
+
})
|
|
469
|
+
: new ClientCredentialsProvider({
|
|
470
|
+
uaaUrl: authConfig.uaaUrl,
|
|
471
|
+
clientId: authConfig.uaaClientId,
|
|
472
|
+
clientSecret: authConfig.uaaClientSecret,
|
|
473
|
+
});
|
|
543
474
|
|
|
544
475
|
const broker = new AuthBroker(
|
|
545
476
|
{
|
package/dist/AuthBroker.d.ts
CHANGED
|
@@ -55,27 +55,13 @@ export declare class AuthBroker {
|
|
|
55
55
|
/**
|
|
56
56
|
* Get UAA credentials from session or service key
|
|
57
57
|
*/
|
|
58
|
-
private
|
|
58
|
+
private getAuthorizationConfigFromServiceKey;
|
|
59
59
|
/**
|
|
60
60
|
* Save token and config to session
|
|
61
61
|
*/
|
|
62
62
|
private saveTokenToSession;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*/
|
|
66
|
-
private initializeSessionFromServiceKey;
|
|
67
|
-
/**
|
|
68
|
-
* Validate existing token (Step 1)
|
|
69
|
-
*/
|
|
70
|
-
private validateExistingToken;
|
|
71
|
-
/**
|
|
72
|
-
* Refresh token from session (Step 2a)
|
|
73
|
-
*/
|
|
74
|
-
private refreshTokenFromSession;
|
|
75
|
-
/**
|
|
76
|
-
* Refresh token from service key (Step 2b)
|
|
77
|
-
*/
|
|
78
|
-
private refreshTokenFromServiceKey;
|
|
63
|
+
private requestTokens;
|
|
64
|
+
private persistTokenResult;
|
|
79
65
|
/**
|
|
80
66
|
* Get authentication token for destination.
|
|
81
67
|
* Uses tokenProvider for all authentication operations (browser-based authorization).
|
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,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"AuthBroker.d.ts","sourceRoot":"","sources":["../src/AuthBroker.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AA4C7B;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,YAAY,EAAE,aAAa,CAAC;IAC5B,uEAAuE;IACvE,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4IAA4I;IAC5I,aAAa,EAAE,cAAc,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAU;IAElC;;;;;;;;;;;OAWG;gBACS,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAkFxE;;OAEG;YACW,eAAe;IA0D7B;;OAEG;YACW,aAAa;IAoD3B;;OAEG;YACW,oCAAoC;IA4ClD;;OAEG;YACW,kBAAkB;YAkClB,aAAa;YA8Cb,kBAAkB;IAiChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsHpD;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASxD;;;;OAIG;IACG,sBAAsB,CAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAoEvC;;;;OAIG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA8DpC;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe;CAqB3D"}
|
package/dist/AuthBroker.js
CHANGED
|
@@ -84,8 +84,8 @@ class AuthBroker {
|
|
|
84
84
|
throw new Error('AuthBroker: sessionStore.setConnectionConfig must be a function');
|
|
85
85
|
}
|
|
86
86
|
// Check tokenProvider methods (required)
|
|
87
|
-
if (typeof tokenProvider.
|
|
88
|
-
throw new Error('AuthBroker: tokenProvider.
|
|
87
|
+
if (typeof tokenProvider.getTokens !== 'function') {
|
|
88
|
+
throw new Error('AuthBroker: tokenProvider.getTokens must be a function');
|
|
89
89
|
}
|
|
90
90
|
// validateToken is optional, so we don't check it
|
|
91
91
|
// Check serviceKeyStore methods (if provided)
|
|
@@ -196,14 +196,9 @@ class AuthBroker {
|
|
|
196
196
|
/**
|
|
197
197
|
* Get UAA credentials from session or service key
|
|
198
198
|
*/
|
|
199
|
-
async
|
|
200
|
-
if (authConfig?.uaaUrl &&
|
|
201
|
-
authConfig?.uaaClientId &&
|
|
202
|
-
authConfig?.uaaClientSecret) {
|
|
203
|
-
return authConfig;
|
|
204
|
-
}
|
|
199
|
+
async getAuthorizationConfigFromServiceKey(destination) {
|
|
205
200
|
if (!this.serviceKeyStore) {
|
|
206
|
-
throw new Error(`
|
|
201
|
+
throw new Error(`Authorization config not found for ${destination}. Session has no auth config and serviceKeyStore is not available.`);
|
|
207
202
|
}
|
|
208
203
|
let serviceKeyAuthConfig = null;
|
|
209
204
|
try {
|
|
@@ -219,21 +214,17 @@ class AuthBroker {
|
|
|
219
214
|
this.logger?.warn(`Failed to parse service key for ${destination}: ${error.filePath || 'unknown path'} - ${getErrorMessage(error)}`);
|
|
220
215
|
}
|
|
221
216
|
else {
|
|
222
|
-
this.logger?.warn(`Failed to get
|
|
217
|
+
this.logger?.warn(`Failed to get authorization config from service key store for ${destination}: ${getErrorMessage(error)}`);
|
|
223
218
|
}
|
|
224
219
|
}
|
|
225
220
|
else {
|
|
226
|
-
this.logger?.warn(`Failed to get
|
|
221
|
+
this.logger?.warn(`Failed to get authorization config from service key store for ${destination}: ${getErrorMessage(error)}`);
|
|
227
222
|
}
|
|
228
223
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
!uaaCredentials.uaaUrl ||
|
|
232
|
-
!uaaCredentials.uaaClientId ||
|
|
233
|
-
!uaaCredentials.uaaClientSecret) {
|
|
234
|
-
throw new Error(`UAA credentials not found for ${destination}. Session has no UAA credentials${this.serviceKeyStore ? ' and serviceKeyStore has no UAA credentials' : ' and serviceKeyStore is not available'}.`);
|
|
224
|
+
if (!serviceKeyAuthConfig) {
|
|
225
|
+
throw new Error(`Authorization config not found for ${destination}. Session has no auth config${this.serviceKeyStore ? ' and serviceKeyStore has no auth config' : ' and serviceKeyStore is not available'}.`);
|
|
235
226
|
}
|
|
236
|
-
return
|
|
227
|
+
return serviceKeyAuthConfig;
|
|
237
228
|
}
|
|
238
229
|
/**
|
|
239
230
|
* Save token and config to session
|
|
@@ -254,228 +245,51 @@ class AuthBroker {
|
|
|
254
245
|
throw new Error(`Failed to save authorization config for destination "${destination}": ${getErrorMessage(error)}`);
|
|
255
246
|
}
|
|
256
247
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
*/
|
|
260
|
-
async initializeSessionFromServiceKey(destination, serviceUrl) {
|
|
261
|
-
if (!this.serviceKeyStore) {
|
|
262
|
-
throw new Error(`Cannot initialize session for destination "${destination}": authorizationToken is empty, UAA credentials are empty, and serviceKeyStore is not available. Provide serviceKeyStore to initialize from service key.`);
|
|
263
|
-
}
|
|
264
|
-
const serviceKeyAuthConfig = await this.serviceKeyStore.getAuthorizationConfig(destination);
|
|
265
|
-
if (!serviceKeyAuthConfig ||
|
|
266
|
-
!serviceKeyAuthConfig.uaaUrl ||
|
|
267
|
-
!serviceKeyAuthConfig.uaaClientId ||
|
|
268
|
-
!serviceKeyAuthConfig.uaaClientSecret) {
|
|
269
|
-
throw new Error(`Service key for destination "${destination}" does not contain UAA credentials`);
|
|
270
|
-
}
|
|
271
|
-
if (!this.allowBrowserAuth) {
|
|
272
|
-
const error = new Error(`Browser authentication required for destination "${destination}" but allowBrowserAuth is disabled. Either enable browser auth or provide a valid session with token.`);
|
|
273
|
-
error.code = 'BROWSER_AUTH_REQUIRED';
|
|
274
|
-
error.destination = destination;
|
|
275
|
-
this.logger?.error(`Step 0: Browser auth required but disabled for ${destination}`);
|
|
276
|
-
throw error;
|
|
277
|
-
}
|
|
278
|
-
this.logger?.debug(`Step 0: Authenticating via provider (browser) for ${destination} using service key UAA credentials`);
|
|
279
|
-
const getConnectionConfig = this.tokenProvider.getConnectionConfig;
|
|
280
|
-
if (!getConnectionConfig) {
|
|
281
|
-
throw new Error('AuthBroker: tokenProvider.getConnectionConfig is required');
|
|
282
|
-
}
|
|
283
|
-
let tokenResult;
|
|
284
|
-
try {
|
|
285
|
-
tokenResult = await getConnectionConfig(serviceKeyAuthConfig, {
|
|
286
|
-
browser: this.browser,
|
|
287
|
-
logger: this.logger,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
if (hasErrorCode(error)) {
|
|
292
|
-
if (error.code === 'VALIDATION_ERROR') {
|
|
293
|
-
throw new Error(`Cannot initialize session for destination "${destination}": provider validation failed - missing ${error.missingFields?.join(', ') || 'required fields'}`);
|
|
294
|
-
}
|
|
295
|
-
else if (error.code === 'BROWSER_AUTH_ERROR') {
|
|
296
|
-
throw new Error(`Cannot initialize session for destination "${destination}": browser authentication failed - ${getErrorMessage(error)}`);
|
|
297
|
-
}
|
|
298
|
-
else if (error.code === 'ECONNREFUSED' ||
|
|
299
|
-
error.code === 'ETIMEDOUT' ||
|
|
300
|
-
error.code === 'ENOTFOUND') {
|
|
301
|
-
throw new Error(`Cannot initialize session for destination "${destination}": network error - cannot reach authentication server (${error.code})`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
throw new Error(`Cannot initialize session for destination "${destination}": provider error - ${getErrorMessage(error)}`);
|
|
305
|
-
}
|
|
306
|
-
const token = tokenResult.connectionConfig.authorizationToken;
|
|
307
|
-
if (!token) {
|
|
308
|
-
throw new Error(`Token provider did not return authorization token for destination "${destination}"`);
|
|
309
|
-
}
|
|
310
|
-
const tokenLength = token.length;
|
|
311
|
-
this.logger?.info(`Step 0: Token initialized for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
312
|
-
// Get serviceUrl from service key store if not in connectionConfig
|
|
313
|
-
const serviceKeyConnConfig = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
314
|
-
const connectionConfigWithServiceUrl = {
|
|
315
|
-
...tokenResult.connectionConfig,
|
|
316
|
-
serviceUrl: tokenResult.connectionConfig.serviceUrl ||
|
|
317
|
-
serviceKeyConnConfig?.serviceUrl ||
|
|
318
|
-
serviceUrl,
|
|
319
|
-
};
|
|
320
|
-
await this.saveTokenToSession(destination, connectionConfigWithServiceUrl, {
|
|
321
|
-
...serviceKeyAuthConfig,
|
|
322
|
-
refreshToken: tokenResult.refreshToken || serviceKeyAuthConfig.refreshToken,
|
|
323
|
-
});
|
|
324
|
-
return token;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Validate existing token (Step 1)
|
|
328
|
-
*/
|
|
329
|
-
async validateExistingToken(destination, token, serviceUrl) {
|
|
330
|
-
if (!this.tokenProvider?.validateToken) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
248
|
+
async requestTokens(destination, sourceLabel) {
|
|
249
|
+
this.logger?.debug(`Requesting tokens for ${destination} via ${sourceLabel}`);
|
|
333
250
|
try {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
this.logger?.debug(`Step 1: Token invalid for ${destination}, continuing to refresh`);
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
// Validation failed due to network/server error - log and continue to refresh
|
|
344
|
-
this.logger?.warn(`Step 1: Token validation failed for ${destination} (network error): ${getErrorMessage(error)}. Continuing to refresh.`);
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Refresh token from session (Step 2a)
|
|
350
|
-
*/
|
|
351
|
-
async refreshTokenFromSession(destination, uaaCredentials, refreshToken, serviceUrl) {
|
|
352
|
-
this.logger?.debug(`Step 2a: Trying refreshTokenFromSession for ${destination}`);
|
|
353
|
-
const authConfigWithRefresh = { ...uaaCredentials, refreshToken };
|
|
354
|
-
const refreshTokenFromSession = this.tokenProvider.refreshTokenFromSession;
|
|
355
|
-
if (!refreshTokenFromSession) {
|
|
356
|
-
throw new Error('AuthBroker: tokenProvider.refreshTokenFromSession is required');
|
|
357
|
-
}
|
|
358
|
-
let tokenResult;
|
|
359
|
-
try {
|
|
360
|
-
tokenResult = await refreshTokenFromSession(authConfigWithRefresh, {
|
|
361
|
-
browser: this.browser,
|
|
362
|
-
logger: this.logger,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
if (hasErrorCode(error)) {
|
|
367
|
-
if (error.code === 'ECONNREFUSED' ||
|
|
368
|
-
error.code === 'ETIMEDOUT' ||
|
|
369
|
-
error.code === 'ENOTFOUND') {
|
|
370
|
-
this.logger?.debug(`Step 2a: Network error during refreshTokenFromSession for ${destination}: ${error.code}. Trying refreshTokenFromServiceKey`);
|
|
371
|
-
throw error; // Re-throw to trigger fallback to Step 2b
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
throw error; // Re-throw other errors
|
|
375
|
-
}
|
|
376
|
-
const token = tokenResult.connectionConfig.authorizationToken;
|
|
377
|
-
if (!token) {
|
|
378
|
-
throw new Error(`Token provider did not return authorization token for destination "${destination}"`);
|
|
379
|
-
}
|
|
380
|
-
const tokenLength = token.length;
|
|
381
|
-
this.logger?.info(`Step 2a: Token refreshed from session for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
382
|
-
// Get serviceUrl from session or service key
|
|
383
|
-
let serviceKeyServiceUrl;
|
|
384
|
-
if (this.serviceKeyStore) {
|
|
385
|
-
try {
|
|
386
|
-
const serviceKeyConn = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
387
|
-
serviceKeyServiceUrl = serviceKeyConn?.serviceUrl;
|
|
388
|
-
}
|
|
389
|
-
catch (error) {
|
|
390
|
-
this.logger?.debug(`Could not get serviceUrl from service key store: ${getErrorMessage(error)}`);
|
|
251
|
+
const getTokens = this.tokenProvider.getTokens;
|
|
252
|
+
if (!getTokens) {
|
|
253
|
+
throw new Error('AuthBroker: tokenProvider.getTokens is required');
|
|
391
254
|
}
|
|
392
|
-
|
|
393
|
-
const finalServiceUrl = tokenResult.connectionConfig.serviceUrl ||
|
|
394
|
-
serviceUrl ||
|
|
395
|
-
serviceKeyServiceUrl;
|
|
396
|
-
const connectionConfigWithServiceUrl = {
|
|
397
|
-
...tokenResult.connectionConfig,
|
|
398
|
-
serviceUrl: finalServiceUrl,
|
|
399
|
-
};
|
|
400
|
-
const authorizationConfig = {
|
|
401
|
-
...uaaCredentials,
|
|
402
|
-
refreshToken: tokenResult.refreshToken || refreshToken,
|
|
403
|
-
};
|
|
404
|
-
await this.saveTokenToSession(destination, connectionConfigWithServiceUrl, authorizationConfig);
|
|
405
|
-
return token;
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Refresh token from service key (Step 2b)
|
|
409
|
-
*/
|
|
410
|
-
async refreshTokenFromServiceKey(destination, uaaCredentials, serviceUrl) {
|
|
411
|
-
if (!this.allowBrowserAuth) {
|
|
412
|
-
const error = new Error(`Browser authentication required for destination "${destination}" but allowBrowserAuth is disabled. Token refresh via session failed and browser auth is not allowed. Either enable browser auth or ensure a valid refresh token exists in session.`);
|
|
413
|
-
error.code = 'BROWSER_AUTH_REQUIRED';
|
|
414
|
-
error.destination = destination;
|
|
415
|
-
this.logger?.error(`Step 2b: Browser auth required but disabled for ${destination}`);
|
|
416
|
-
throw error;
|
|
417
|
-
}
|
|
418
|
-
this.logger?.debug(`Step 2b: Trying refreshTokenFromServiceKey for ${destination}`);
|
|
419
|
-
const refreshTokenFromServiceKey = this.tokenProvider.refreshTokenFromServiceKey;
|
|
420
|
-
if (!refreshTokenFromServiceKey) {
|
|
421
|
-
throw new Error('AuthBroker: tokenProvider.refreshTokenFromServiceKey is required');
|
|
422
|
-
}
|
|
423
|
-
let tokenResult;
|
|
424
|
-
try {
|
|
425
|
-
tokenResult = await refreshTokenFromServiceKey(uaaCredentials, {
|
|
426
|
-
browser: this.browser,
|
|
427
|
-
logger: this.logger,
|
|
428
|
-
});
|
|
255
|
+
return await getTokens.call(this.tokenProvider);
|
|
429
256
|
}
|
|
430
257
|
catch (error) {
|
|
431
258
|
if (hasErrorCode(error)) {
|
|
432
259
|
if (error.code === 'VALIDATION_ERROR') {
|
|
433
|
-
throw new Error(`Token
|
|
260
|
+
throw new Error(`Token provider validation failed for ${destination}: missing ${error.missingFields?.join(', ') || 'required fields'}`);
|
|
434
261
|
}
|
|
435
|
-
|
|
436
|
-
throw new Error(`Token
|
|
262
|
+
if (error.code === 'BROWSER_AUTH_ERROR') {
|
|
263
|
+
throw new Error(`Token provider browser authentication failed for ${destination}: ${getErrorMessage(error)}`);
|
|
437
264
|
}
|
|
438
|
-
|
|
265
|
+
if (error.code === 'ECONNREFUSED' ||
|
|
439
266
|
error.code === 'ETIMEDOUT' ||
|
|
440
267
|
error.code === 'ENOTFOUND') {
|
|
441
|
-
throw new Error(`Token
|
|
268
|
+
throw new Error(`Token provider network error for ${destination}: ${error.code}`);
|
|
442
269
|
}
|
|
443
|
-
|
|
444
|
-
throw new Error(`Token
|
|
270
|
+
if (error.code === 'SERVICE_KEY_ERROR') {
|
|
271
|
+
throw new Error(`Token provider service key error for ${destination}: ${getErrorMessage(error)}`);
|
|
445
272
|
}
|
|
446
273
|
}
|
|
447
|
-
throw new Error(`Token
|
|
274
|
+
throw new Error(`Token provider error for ${destination}: ${getErrorMessage(error)}`);
|
|
448
275
|
}
|
|
449
|
-
|
|
276
|
+
}
|
|
277
|
+
async persistTokenResult(destination, serviceUrl, baseConnConfig, authConfig, tokenResult) {
|
|
278
|
+
const token = tokenResult.authorizationToken;
|
|
450
279
|
if (!token) {
|
|
451
280
|
throw new Error(`Token provider did not return authorization token for destination "${destination}"`);
|
|
452
281
|
}
|
|
453
|
-
const tokenLength = token.length;
|
|
454
|
-
this.logger?.info(`Step 2b: Token refreshed from service key for ${destination}: token(${tokenLength} chars), hasRefreshToken(${!!tokenResult.refreshToken})`);
|
|
455
|
-
// Get serviceUrl from session or service key
|
|
456
|
-
let serviceKeyServiceUrl;
|
|
457
|
-
if (this.serviceKeyStore) {
|
|
458
|
-
try {
|
|
459
|
-
const serviceKeyConn = await this.serviceKeyStore.getConnectionConfig(destination);
|
|
460
|
-
serviceKeyServiceUrl = serviceKeyConn?.serviceUrl;
|
|
461
|
-
}
|
|
462
|
-
catch (error) {
|
|
463
|
-
this.logger?.debug(`Could not get serviceUrl from service key store: ${getErrorMessage(error)}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const finalServiceUrl = tokenResult.connectionConfig.serviceUrl ||
|
|
467
|
-
serviceUrl ||
|
|
468
|
-
serviceKeyServiceUrl;
|
|
469
282
|
const connectionConfigWithServiceUrl = {
|
|
470
|
-
...
|
|
471
|
-
serviceUrl
|
|
283
|
+
...baseConnConfig,
|
|
284
|
+
serviceUrl,
|
|
285
|
+
authorizationToken: token,
|
|
286
|
+
authType: 'jwt',
|
|
472
287
|
};
|
|
473
288
|
const authorizationConfig = {
|
|
474
|
-
...
|
|
475
|
-
refreshToken: tokenResult.refreshToken
|
|
289
|
+
...authConfig,
|
|
290
|
+
refreshToken: tokenResult.refreshToken ?? authConfig.refreshToken,
|
|
476
291
|
};
|
|
477
292
|
await this.saveTokenToSession(destination, connectionConfigWithServiceUrl, authorizationConfig);
|
|
478
|
-
return token;
|
|
479
293
|
}
|
|
480
294
|
/**
|
|
481
295
|
* Get authentication token for destination.
|
|
@@ -525,66 +339,62 @@ class AuthBroker {
|
|
|
525
339
|
const serviceUrl = await this.getServiceUrl(destination, connConfig);
|
|
526
340
|
// Check if we have token or UAA credentials
|
|
527
341
|
const hasToken = !!connConfig?.authorizationToken;
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
authConfig?.uaaClientSecret);
|
|
531
|
-
this.logger?.debug(`Session check for ${destination}: hasToken(${hasToken}), hasUaaCredentials(${hasUaaCredentials}), serviceUrl(${serviceUrl ? 'yes' : 'no'})`);
|
|
342
|
+
const hasAuthConfig = !!authConfig;
|
|
343
|
+
this.logger?.debug(`Session check for ${destination}: hasToken(${hasToken}), hasAuthConfig(${hasAuthConfig}), serviceUrl(${serviceUrl ? 'yes' : 'no'})`);
|
|
532
344
|
// Step 0: Initialize Session with Token (if needed)
|
|
533
|
-
if (!hasToken && !
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (hasErrorCode(error) && error.code === 'BROWSER_AUTH_REQUIRED') {
|
|
540
|
-
throw error;
|
|
541
|
-
}
|
|
542
|
-
// Handle typed store errors
|
|
543
|
-
if (hasErrorCode(error)) {
|
|
544
|
-
if (error.code === interfaces_1.STORE_ERROR_CODES.FILE_NOT_FOUND) {
|
|
545
|
-
throw new Error(`Cannot initialize session for destination "${destination}": service key file not found`);
|
|
546
|
-
}
|
|
547
|
-
else if (error.code === interfaces_1.STORE_ERROR_CODES.PARSE_ERROR) {
|
|
548
|
-
throw new Error(`Cannot initialize session for destination "${destination}": service key parsing failed - ${getErrorMessage(error)}`);
|
|
549
|
-
}
|
|
550
|
-
else if (error.code === interfaces_1.STORE_ERROR_CODES.INVALID_CONFIG) {
|
|
551
|
-
throw new Error(`Cannot initialize session for destination "${destination}": invalid service key - missing ${error.missingFields?.join(', ') || 'required fields'}`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
345
|
+
if (!hasToken && !hasAuthConfig) {
|
|
346
|
+
if (!this.allowBrowserAuth) {
|
|
347
|
+
const error = new Error(`Browser authentication required for destination "${destination}" but allowBrowserAuth is disabled. Either enable browser auth or provide a valid session with token.`);
|
|
348
|
+
error.code = 'BROWSER_AUTH_REQUIRED';
|
|
349
|
+
error.destination = destination;
|
|
350
|
+
this.logger?.error(`Step 0: Browser auth required but disabled for ${destination}`);
|
|
554
351
|
throw error;
|
|
555
352
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
353
|
+
const serviceKeyAuthConfig = await this.getAuthorizationConfigFromServiceKey(destination);
|
|
354
|
+
const tokenResult = await this.requestTokens(destination, 'serviceKey');
|
|
355
|
+
await this.persistTokenResult(destination, serviceUrl, connConfig, serviceKeyAuthConfig, tokenResult);
|
|
356
|
+
return tokenResult.authorizationToken;
|
|
357
|
+
}
|
|
358
|
+
// Step 1: Request tokens via provider (provider handles token lifecycle internally)
|
|
359
|
+
// Broker always calls provider.getTokens() - provider decides whether to return cached token,
|
|
360
|
+
// refresh, or perform login. Consumer doesn't need to know about token issues.
|
|
361
|
+
this.logger?.debug(`Step 1: Requesting tokens via provider for ${destination}`);
|
|
362
|
+
let lastError = null;
|
|
363
|
+
if (authConfig) {
|
|
364
|
+
if (!this.allowBrowserAuth && !authConfig.refreshToken) {
|
|
365
|
+
const error = new Error(`Browser authentication required for destination "${destination}" but allowBrowserAuth is disabled. Session has no refresh token.`);
|
|
366
|
+
error.code = 'BROWSER_AUTH_REQUIRED';
|
|
367
|
+
error.destination = destination;
|
|
368
|
+
this.logger?.error(`Step 2: Browser auth required but disabled for ${destination}`);
|
|
369
|
+
throw error;
|
|
567
370
|
}
|
|
568
|
-
}
|
|
569
|
-
// Step 2: Refresh Token Flow
|
|
570
|
-
this.logger?.debug(`Step 2: Attempting token refresh for ${destination}`);
|
|
571
|
-
const uaaCredentials = await this.getUaaCredentials(destination, authConfig);
|
|
572
|
-
// Step 2a: Try refresh from session (if refresh token exists)
|
|
573
|
-
const refreshToken = authConfig?.refreshToken;
|
|
574
|
-
if (refreshToken) {
|
|
575
371
|
try {
|
|
576
|
-
|
|
372
|
+
const tokenResult = await this.requestTokens(destination, 'session');
|
|
373
|
+
await this.persistTokenResult(destination, serviceUrl, connConfig, authConfig, tokenResult);
|
|
374
|
+
return tokenResult.authorizationToken;
|
|
577
375
|
}
|
|
578
376
|
catch (error) {
|
|
579
|
-
|
|
580
|
-
|
|
377
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
378
|
+
this.logger?.debug(`Step 2: Token request via session failed for ${destination}: ${getErrorMessage(error)}, trying service key`);
|
|
581
379
|
}
|
|
582
380
|
}
|
|
583
|
-
|
|
584
|
-
|
|
381
|
+
if (!this.allowBrowserAuth) {
|
|
382
|
+
const error = new Error(`Browser authentication required for destination "${destination}" but allowBrowserAuth is disabled. Token refresh via session failed and browser auth is not allowed. Either enable browser auth or ensure a valid refresh token exists in session.`);
|
|
383
|
+
error.code = 'BROWSER_AUTH_REQUIRED';
|
|
384
|
+
error.destination = destination;
|
|
385
|
+
this.logger?.error(`Step 2: Browser auth required but disabled for ${destination}`);
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
if (!this.serviceKeyStore) {
|
|
389
|
+
if (lastError) {
|
|
390
|
+
throw lastError;
|
|
391
|
+
}
|
|
392
|
+
throw new Error(`Authorization config not found for ${destination}. Session has no auth config and serviceKeyStore is not available.`);
|
|
585
393
|
}
|
|
586
|
-
|
|
587
|
-
|
|
394
|
+
const serviceKeyAuthConfig = await this.getAuthorizationConfigFromServiceKey(destination);
|
|
395
|
+
const tokenResult = await this.requestTokens(destination, 'serviceKey');
|
|
396
|
+
await this.persistTokenResult(destination, serviceUrl, connConfig, serviceKeyAuthConfig, tokenResult);
|
|
397
|
+
return tokenResult.authorizationToken;
|
|
588
398
|
}
|
|
589
399
|
/**
|
|
590
400
|
* Force refresh token for destination.
|
|
@@ -10,6 +10,8 @@ export interface TestConfig {
|
|
|
10
10
|
};
|
|
11
11
|
abap?: {
|
|
12
12
|
destination?: string;
|
|
13
|
+
expired_token?: string;
|
|
14
|
+
refresh_token?: string;
|
|
13
15
|
};
|
|
14
16
|
xsuaa?: {
|
|
15
17
|
btp_destination?: string;
|
|
@@ -46,4 +48,12 @@ export declare function getServiceKeysDir(config?: TestConfig): string | null;
|
|
|
46
48
|
* Get sessions directory from config
|
|
47
49
|
*/
|
|
48
50
|
export declare function getSessionsDir(config?: TestConfig): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Get expired token from config (real expired token from YAML)
|
|
53
|
+
*/
|
|
54
|
+
export declare function getExpiredToken(config?: TestConfig): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Get refresh token from config (real refresh token from YAML)
|
|
57
|
+
*/
|
|
58
|
+
export declare function getRefreshToken(config?: TestConfig): string | null;
|
|
49
59
|
//# sourceMappingURL=configHelpers.d.ts.map
|
|
@@ -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;
|
|
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;YACrB,aAAa,CAAC,EAAE,MAAM,CAAC;YACvB,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,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,CAyD3C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,GAAG,OAAO,GACxB,OAAO,CAwBT;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;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAGlE"}
|
|
@@ -43,6 +43,8 @@ exports.getAbapDestination = getAbapDestination;
|
|
|
43
43
|
exports.getXsuaaDestinations = getXsuaaDestinations;
|
|
44
44
|
exports.getServiceKeysDir = getServiceKeysDir;
|
|
45
45
|
exports.getSessionsDir = getSessionsDir;
|
|
46
|
+
exports.getExpiredToken = getExpiredToken;
|
|
47
|
+
exports.getRefreshToken = getRefreshToken;
|
|
46
48
|
const fs = __importStar(require("node:fs"));
|
|
47
49
|
const path = __importStar(require("node:path"));
|
|
48
50
|
const yaml = __importStar(require("js-yaml"));
|
|
@@ -188,3 +190,17 @@ function getSessionsDir(config) {
|
|
|
188
190
|
const expanded = expandTilde(dir);
|
|
189
191
|
return path.resolve(expanded);
|
|
190
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Get expired token from config (real expired token from YAML)
|
|
195
|
+
*/
|
|
196
|
+
function getExpiredToken(config) {
|
|
197
|
+
const cfg = config || loadTestConfig();
|
|
198
|
+
return cfg.auth_broker?.abap?.expired_token || null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get refresh token from config (real refresh token from YAML)
|
|
202
|
+
*/
|
|
203
|
+
function getRefreshToken(config) {
|
|
204
|
+
const cfg = config || loadTestConfig();
|
|
205
|
+
return cfg.auth_broker?.abap?.refresh_token || null;
|
|
206
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export type { AuthType, ILogger, ITokenRefresher, } from '@mcp-abap-adt/interfaces';
|
|
6
6
|
export { AuthBroker, type AuthBrokerConfig } from './AuthBroker';
|
|
7
|
-
export type { ITokenProvider,
|
|
7
|
+
export type { ITokenProvider, ITokenResult, TokenProviderOptions, } from './providers';
|
|
8
8
|
export type { IAuthorizationConfig, IConnectionConfig, IServiceKeyStore, ISessionStore, } from './stores/interfaces';
|
|
9
9
|
export type { IConfig } from './types';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,YAAY,EACV,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEjE,YAAY,EACV,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,YAAY,EACV,QAAQ,EACR,OAAO,EACP,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEjE,YAAY,EACV,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token Provider interface
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Different implementations handle different authentication flows:
|
|
6
|
-
* - XSUAA: client_credentials grant type (no browser)
|
|
7
|
-
* - BTP/ABAP: browser-based OAuth2 or refresh token
|
|
4
|
+
* Stateful providers handle token lifecycle internally (refresh/relogin).
|
|
8
5
|
*/
|
|
9
|
-
import type { IAuthorizationConfig, IConnectionConfig, ITokenProvider, ITokenProviderOptions,
|
|
10
|
-
export type { ITokenProvider, IAuthorizationConfig, IConnectionConfig };
|
|
11
|
-
export type TokenProviderResult = ITokenProviderResult;
|
|
6
|
+
import type { IAuthorizationConfig, IConnectionConfig, ITokenProvider, ITokenProviderOptions, ITokenResult } from '@mcp-abap-adt/interfaces';
|
|
7
|
+
export type { ITokenProvider, IAuthorizationConfig, IConnectionConfig, ITokenResult, };
|
|
12
8
|
export type TokenProviderOptions = ITokenProviderOptions;
|
|
13
9
|
//# sourceMappingURL=ITokenProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ITokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ITokenProvider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ITokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ITokenProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,GACb,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,CAAC"}
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Token Provider interface
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* Different implementations handle different authentication flows:
|
|
7
|
-
* - XSUAA: client_credentials grant type (no browser)
|
|
8
|
-
* - BTP/ABAP: browser-based OAuth2 or refresh token
|
|
5
|
+
* Stateful providers handle token lifecycle internally (refresh/relogin).
|
|
9
6
|
*/
|
|
10
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Provider implementations are in separate packages:
|
|
5
5
|
* - @mcp-abap-adt/auth-providers - XSUAA and BTP providers
|
|
6
6
|
*/
|
|
7
|
-
export type { ITokenProvider,
|
|
7
|
+
export type { ITokenProvider, ITokenResult, TokenProviderOptions, } from './ITokenProvider';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/auth-broker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"node": ">=18.0.0"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@mcp-abap-adt/auth-providers": "^0.2.
|
|
60
|
-
"@mcp-abap-adt/auth-stores": "^0.2.
|
|
61
|
-
"@mcp-abap-adt/interfaces": "^0.2.
|
|
59
|
+
"@mcp-abap-adt/auth-providers": "^0.2.8",
|
|
60
|
+
"@mcp-abap-adt/auth-stores": "^0.2.9",
|
|
61
|
+
"@mcp-abap-adt/interfaces": "^0.2.14",
|
|
62
62
|
"axios": "^1.13.2",
|
|
63
63
|
"tsx": "^4.21.0"
|
|
64
64
|
},
|