@mcp-abap-adt/auth-providers 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,75 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.2.0] - 2026-09-26
11
+
12
+ ### Added
13
+
14
+ - **`refreshTokens()`** on every provider, through `BaseTokenProvider`: a new
15
+ token, never the cached one — the refresh token when there is one, the login
16
+ flow when there is none or the refresh is refused — and it replaces the
17
+ cache. `getTokens()` answers the cache while the token looks valid, so a
18
+ caller holding a 401 had no way to ask for another; auth-broker's
19
+ `refreshToken()` got the refused token back. Every provider now implements
20
+ `IRefreshableTokenProvider` from `@mcp-abap-adt/interfaces-auth` 2.1.0
21
+ (decision 39 there). `getTokens()` is unchanged: it now calls
22
+ `refreshTokens()` once the cache is not valid, which is the same path it
23
+ took inline before.
24
+ - **`SsoProviderFactory.create()` answers `IRefreshableTokenProvider`**, and
25
+ `SsoProviderInstance` is that type, so a provider from the factory can be
26
+ handed to anything requiring the refreshable contract without a cast. Every
27
+ provider it builds already was one.
28
+
29
+ ### Fixed
30
+
31
+ - **A failed browser login is a `BrowserAuthError`.** The class was exported
32
+ and documented as "browser auth failed", and thrown nowhere: a timeout, the
33
+ identity provider's refusal (`OAuth2 authentication failed: …`), a busy
34
+ callback port, a browser that would not open and an abort all reached the
35
+ caller as a plain `Error`, so the one type a caller could catch for them
36
+ never arrived. `BrowserCallbackStrategy` — and so `browserCallbackStrategy`,
37
+ `oidcCallbackStrategy` and `samlCallbackStrategy` — now throws it, with the
38
+ original message and the original error as `cause`; an error that already
39
+ has a type (a `ValidationError` from building the URL) passes unchanged.
40
+ Found by auth-broker, whose migration note had nothing to point at.
41
+ - The README's error-handling example caught `RefreshError` as "browser auth
42
+ failed". No provider throws `RefreshError`, `SessionDataError` or
43
+ `ServiceKeyError`; the README now says so.
44
+
45
+ ### Changed
46
+
47
+ - `@mcp-abap-adt/interfaces-auth` `^2.1.0` (was `^2.0.1`), which declares
48
+ `IRefreshableTokenProvider`.
49
+
50
+ ### Documentation
51
+
52
+ - `docs/btp-setup.md`: what each provider needs on the SAP side — XSUAA
53
+ client, trust, user, ABAP mapping — and whether ADT accepts its token, with
54
+ every claim tagged by source (SAP, Community, Measured, Inference) and the
55
+ open questions still to be settled live.
56
+ - `docs/passwordless-sso.md`: SAP GUI's passwordless SNC login, why Eclipse
57
+ ADT's on-premise SSO runs over RFC, the HTTP equivalents (X.509 client
58
+ certificates, SPNego, IAS), what a Node.js client can reach, and options for
59
+ this package.
60
+
61
+ ## [4.1.3] - 2026-09-26
62
+
63
+ ### Changed
64
+
65
+ - A `bearerConfirmation` refusal naming several candidates joins them with
66
+ ` | ` instead of `; `, and ends ` | and N more`. The count reason
67
+ `carries N SubjectConfirmationData; exactly one is allowed` contains `; `
68
+ itself, so the old separator read ambiguously. `check` and every reason's
69
+ wording are unchanged.
70
+
71
+ ### Removed
72
+
73
+ - `mcp.json`, an XSUAA service key with a client secret, committed to the
74
+ repository on 2025-12-23 with the since-removed `bin/` CLIs. It was never in
75
+ the npm package. It stays in the git history, so that binding should be
76
+ rotated or deleted. `.gitignore` now keeps `mcp.json` and
77
+ `*service-key*.json` out.
78
+
10
79
  ## [4.1.2] - 2026-09-26
11
80
 
12
81
  ### Security
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install @mcp-abap-adt/auth-providers
13
13
 
14
14
  ## Overview
15
15
 
16
- This package implements the `ITokenProvider` interface from `@mcp-abap-adt/interfaces-auth`:
16
+ This package implements `IRefreshableTokenProvider` — `ITokenProvider` plus `refreshTokens()` — from `@mcp-abap-adt/interfaces-auth`:
17
17
 
18
18
  - **ClientCredentialsProvider** — `client_credentials`, no user interaction
19
19
  - **AuthorizationCodeProvider** — UAA/XSUAA authorization code, through a browser
@@ -27,7 +27,15 @@ This package implements the `ITokenProvider` interface from `@mcp-abap-adt/inter
27
27
  (RFC 7522)
28
28
  - **Saml2PureProvider** — a SAML assertion exchanged for session cookies
29
29
 
30
- Providers are configured via constructor; `getTokens()` takes no parameters and handles refresh/login internally.
30
+ Providers are configured via constructor; `getTokens()` takes no parameters and handles refresh/login internally. `refreshTokens()` obtains a new token even while the cached one looks valid — what a caller holding a 401 needs.
31
+
32
+ A token is only half of it: whether ADT accepts it depends on the XSUAA client,
33
+ the trust and the user configured on the SAP side. What each provider needs
34
+ there, and which are usable for ADT at all, is in
35
+ [docs/btp-setup.md](docs/btp-setup.md).
36
+ Logging on without a password — SAP GUI's SNC single sign-on and its HTTP
37
+ equivalents, X.509 client certificates and SPNego — is in
38
+ [docs/passwordless-sso.md](docs/passwordless-sso.md).
31
39
 
32
40
  Since 2.0.0 an interactive login is conducted by an **authorization strategy**
33
41
  (`IAuthorizationStrategy` from `@mcp-abap-adt/interfaces-auth`) passed as
@@ -672,7 +680,7 @@ What the table compresses:
672
680
  `xsd:dateTime`; `NotOnOrAfter` has not passed beyond `clockSkewMs`;
673
681
  `NotBefore` has arrived within it. When none qualifies, the refusal names
674
682
  every candidate in document order with the first sub-rule it failed —
675
- `no bearer confirmation qualifies: #1 Recipient is not the ACS; #2
683
+ `no bearer confirmation qualifies: #1 Recipient is not the ACS | #2
676
684
  NotOnOrAfter has passed` — listing at most five, then `and N more`.
677
685
  - **Check 9 is AND across restrictions, OR within one**, as SAML Core §2.5.1.4
678
686
  says: every `AudienceRestriction` must name you; the `Audience` elements
@@ -755,14 +763,16 @@ value.
755
763
  | `bearerConfirmation` | `the assertion carries no saml:Subject` |
756
764
  | `bearerConfirmation` | `the assertion carries <n> saml:Subject; exactly one is allowed` |
757
765
  | `bearerConfirmation` | `the saml:Subject holds no SubjectConfirmation` |
758
- | `bearerConfirmation` | `no bearer confirmation qualifies: #1 <reason>; #2 <reason>; …` |
766
+ | `bearerConfirmation` | `no bearer confirmation qualifies: #1 <reason> \| #2 <reason> \| …` |
759
767
  | `destination` | `the response carries no Destination` |
760
768
  | `destination` | `the response is addressed to "…", not to us` |
761
769
  | `replay` | `this assertion has been presented before` |
762
770
 
763
771
  A `bearerConfirmation` refusal naming candidates lists each one's first failed
764
- sub-rule, in document order, joined by `; `; past five candidates it ends
765
- `; and N more`, N being how many were not listed. The eleven reasons:
772
+ sub-rule, in document order, joined by ` | ` — not `; `, which a count reason
773
+ such as `carries 2 SubjectConfirmationData; exactly one is allowed` contains
774
+ itself; past five candidates it ends ` | and N more`, N being how many were
775
+ not listed. The eleven reasons:
766
776
 
767
777
  | # | `<reason>`, in the order a candidate is tested |
768
778
  |---|---|
@@ -1202,7 +1212,22 @@ This approach prevents unnecessary token refresh and browser authentication when
1202
1212
 
1203
1213
  ### Token Refresh
1204
1214
 
1205
- Providers handle refresh automatically inside `getTokens()`. No separate refresh methods are needed.
1215
+ Providers handle refresh automatically inside `getTokens()`: while the cached token is valid it
1216
+ is returned, once it expires the refresh token is used, and a login follows when there is none or
1217
+ the refresh is refused.
1218
+
1219
+ The clock is not the only judge, though. When the server refuses a token the cache still
1220
+ considers valid — a 401 — ask for a new one with `refreshTokens()`. It skips the cache, takes the
1221
+ same refresh-then-login path, and replaces the cache with what it obtains:
1222
+
1223
+ ```typescript
1224
+ let { authorizationToken } = await provider.getTokens();
1225
+ let response = await call(authorizationToken);
1226
+ if (response.status === 401) {
1227
+ ({ authorizationToken } = await provider.refreshTokens());
1228
+ response = await call(authorizationToken);
1229
+ }
1230
+ ```
1206
1231
 
1207
1232
  ```typescript
1208
1233
  try {
@@ -1211,8 +1236,9 @@ try {
1211
1236
  } catch (error) {
1212
1237
  if (error instanceof ValidationError) {
1213
1238
  console.error('Missing fields:', error.missingFields);
1214
- } else if (error instanceof RefreshError) {
1215
- console.error('Browser auth failed:', error.cause);
1239
+ } else if (error instanceof BrowserAuthError) {
1240
+ // the login timed out, the IdP refused, the port was taken, ...
1241
+ console.error('Browser auth failed:', error.message, error.cause);
1216
1242
  }
1217
1243
  }
1218
1244
  ```
@@ -1243,14 +1269,12 @@ try {
1243
1269
  // provider config validation failed
1244
1270
  console.error('Missing required fields:', error.missingFields);
1245
1271
  console.error('Error code:', error.code); // 'VALIDATION_ERROR'
1246
- } else if (error instanceof RefreshError) {
1247
- // Token refresh operation failed
1248
- console.error('Refresh failed:', error.message);
1249
- console.error('Original error:', error.cause);
1250
- console.error('Error code:', error.code); // 'REFRESH_ERROR'
1251
1272
  } else if (error instanceof BrowserAuthError) {
1252
- // Browser authentication failed
1253
- console.error('Browser auth failed:', error.cause);
1273
+ // A browser login failed: timeout, the IdP's refusal, a busy callback
1274
+ // port, a browser that would not open, an abort. The message is the
1275
+ // original's, and the original is `cause`.
1276
+ console.error('Browser auth failed:', error.message);
1277
+ console.error('Error code:', error.code); // 'BROWSER_AUTH_ERROR'
1254
1278
  }
1255
1279
  }
1256
1280
  ```
@@ -1258,10 +1282,8 @@ try {
1258
1282
  **Error Types**:
1259
1283
  - `TokenProviderError` - Base class with `code: string` property
1260
1284
  - `ValidationError` - provider config validation failed, includes `missingFields: string[]`
1261
- - `RefreshError` - Token refresh failed, includes `cause?: Error`
1262
- - `SessionDataError` - Session data invalid, includes `missingFields: string[]`
1263
- - `ServiceKeyError` - Service key data invalid, includes `missingFields: string[]`
1264
- - `BrowserAuthError` - Browser auth failed, includes `cause?: Error`
1285
+ - `BrowserAuthError` - a browser login failed (timeout, the identity provider's refusal, a busy callback port, a browser that would not open, an abort), includes `cause?: Error`; thrown by every browser strategy (`browserCallbackStrategy`, `oidcCallbackStrategy`, `samlCallbackStrategy`)
1286
+ - `RefreshError`, `SessionDataError`, `ServiceKeyError` - exported, but no provider throws them: a refused refresh falls back to a login inside `getTokens()`/`refreshTokens()`, and sessions and service keys are read by `@mcp-abap-adt/auth-stores`, not here
1265
1287
  - `AssertionValidationError` - a SAML assertion was refused, includes `check: AssertionCheck` naming the check that failed — see [SAML assertion validation](#errors)
1266
1288
 
1267
1289
  All error codes are defined in `@mcp-abap-adt/interfaces-auth` package as `TOKEN_PROVIDER_ERROR_CODES`, and `AssertionValidationError`'s as `ASSERTION_ERROR_CODES`.
@@ -7,7 +7,7 @@
7
7
  * - Expiration checking
8
8
  * - Automatic refresh/relogin
9
9
  */
10
- import type { ITokenProvider, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces-auth';
10
+ import type { IRefreshableTokenProvider, ITokenResult, OAuth2GrantType } from '@mcp-abap-adt/interfaces-auth';
11
11
  import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
12
12
  /**
13
13
  * Abstract base class for token providers
@@ -18,7 +18,7 @@ import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
18
18
  * - Automatically refreshes expired tokens
19
19
  * - Falls back to login if refresh fails
20
20
  */
21
- export declare abstract class BaseTokenProvider implements ITokenProvider {
21
+ export declare abstract class BaseTokenProvider implements IRefreshableTokenProvider {
22
22
  protected authorizationToken?: string;
23
23
  protected refreshToken?: string;
24
24
  protected expiresAt?: number;
@@ -66,6 +66,15 @@ export declare abstract class BaseTokenProvider implements ITokenProvider {
66
66
  * @returns Promise that resolves to token result
67
67
  */
68
68
  getTokens(): Promise<ITokenResult>;
69
+ /**
70
+ * A new token, never the cached one: the refresh token when there is one,
71
+ * the login flow when there is none or the refresh is refused.
72
+ *
73
+ * `getTokens()` answers the cache while the token looks valid, so a caller
74
+ * holding a 401 — the server refused a token the clock still accepts — has
75
+ * no other way to get a different one. What this obtains replaces the cache.
76
+ */
77
+ refreshTokens(): Promise<ITokenResult>;
69
78
  validateToken(_token: string, _serviceUrl?: string): Promise<boolean>;
70
79
  /**
71
80
  * Update internal token cache from result
@@ -1 +1 @@
1
- {"version":3,"file":"BaseTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BaseTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAE9D;;;;;;;;GAQG;AACH,8BAAsB,iBAAkB,YAAW,cAAc;IAC/D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAChD,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAWzD;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKzD;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO;IAyBjC;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;IAE1D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,eAAe;IAEjD;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAyElC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoC3E;;;OAGG;IACH,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IA0BlD;;;;OAIG;IACH,SAAS,CAAC,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA0BnE;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAShE"}
1
+ {"version":3,"file":"BaseTokenProvider.d.ts","sourceRoot":"","sources":["../../src/providers/BaseTokenProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,yBAAyB,EACzB,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAE9D;;;;;;;;GAQG;AACH,8BAAsB,iBAAkB,YAAW,yBAAyB;IAC1E,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAChD,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAWzD;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKzD;;;OAGG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO;IAyBjC;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;IAE1D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,eAAe;IAEjD;;;;;;;;OAQG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAmCxC;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,YAAY,CAAC;IAsCtC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoC3E;;;OAGG;IACH,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IA0BlD;;;;OAIG;IACH,SAAS,CAAC,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA0BnE;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAShE"}
@@ -115,9 +115,19 @@ class BaseTokenProvider {
115
115
  : undefined,
116
116
  };
117
117
  }
118
- // Try refresh if we have refresh token
118
+ return this.refreshTokens();
119
+ }
120
+ /**
121
+ * A new token, never the cached one: the refresh token when there is one,
122
+ * the login flow when there is none or the refresh is refused.
123
+ *
124
+ * `getTokens()` answers the cache while the token looks valid, so a caller
125
+ * holding a 401 — the server refused a token the clock still accepts — has
126
+ * no other way to get a different one. What this obtains replaces the cache.
127
+ */
128
+ async refreshTokens() {
119
129
  if (this.refreshToken) {
120
- this.logger?.info('[BaseTokenProvider] Token invalid, attempting refresh', {
130
+ this.logger?.info('[BaseTokenProvider] Obtaining a new token by refresh', {
121
131
  oldToken: this.formatToken(this.authorizationToken),
122
132
  refreshToken: this.formatToken(this.refreshToken),
123
133
  });
@@ -134,14 +144,11 @@ class BaseTokenProvider {
134
144
  this.logger?.warn('[BaseTokenProvider] Refresh failed', {
135
145
  error: error instanceof Error ? error.message : String(error),
136
146
  });
137
- // Refresh failed - need to login
138
- // Clear refresh token as it's invalid
147
+ // The refresh token was refused: it is spent, so a login follows.
139
148
  this.refreshToken = undefined;
140
- // Fall through to login
141
149
  }
142
150
  }
143
- // Perform login
144
- this.logger?.info('[BaseTokenProvider] Token invalid and no refresh token, performing login');
151
+ this.logger?.info('[BaseTokenProvider] No usable refresh token, performing login');
145
152
  const result = await this.performLogin();
146
153
  this.updateTokens(result);
147
154
  this.logger?.info('[BaseTokenProvider] Login completed', {
@@ -2,7 +2,7 @@
2
2
  * Token Providers
3
3
  *
4
4
  * Stateful token providers with automatic token lifecycle management.
5
- * All providers extend BaseTokenProvider and implement ITokenProvider.
5
+ * All providers extend BaseTokenProvider and implement IRefreshableTokenProvider.
6
6
  */
7
7
  export type { AuthorizationCodeProviderConfig } from './AuthorizationCodeProvider';
8
8
  export { AuthorizationCodeProvider } from './AuthorizationCodeProvider';
@@ -3,7 +3,7 @@
3
3
  * Token Providers
4
4
  *
5
5
  * Stateful token providers with automatic token lifecycle management.
6
- * All providers extend BaseTokenProvider and implement ITokenProvider.
6
+ * All providers extend BaseTokenProvider and implement IRefreshableTokenProvider.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.UaaPasscodeProvider = exports.Saml2PureProvider = exports.Saml2BearerProvider = exports.OidcTokenExchangeProvider = exports.OidcPasswordProvider = exports.OidcDeviceFlowProvider = exports.OidcBrowserProvider = exports.ClientCredentialsProvider = exports.BaseTokenProvider = exports.AuthorizationCodeProvider = void 0;
@@ -1,6 +1,6 @@
1
- import type { ITokenProvider } from '@mcp-abap-adt/interfaces-auth';
1
+ import type { IRefreshableTokenProvider } from '@mcp-abap-adt/interfaces-auth';
2
2
  import type { SsoProviderConfig } from './types';
3
3
  export declare class SsoProviderFactory {
4
- static create(config: SsoProviderConfig): ITokenProvider;
4
+ static create(config: SsoProviderConfig): IRefreshableTokenProvider;
5
5
  }
6
6
  //# sourceMappingURL=SsoProviderFactory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SsoProviderFactory.d.ts","sourceRoot":"","sources":["../../src/sso/SsoProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAOpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc;CA6BzD"}
1
+ {"version":3,"file":"SsoProviderFactory.d.ts","sourceRoot":"","sources":["../../src/sso/SsoProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAO/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,yBAAyB;CA6BpE"}
@@ -1,11 +1,11 @@
1
- import type { ITokenProvider } from '@mcp-abap-adt/interfaces-auth';
1
+ import type { IRefreshableTokenProvider } from '@mcp-abap-adt/interfaces-auth';
2
2
  import type { OidcBrowserProviderConfig } from '../providers/OidcBrowserProvider';
3
3
  import type { OidcDeviceFlowProviderConfig } from '../providers/OidcDeviceFlowProvider';
4
4
  import type { OidcPasswordProviderConfig } from '../providers/OidcPasswordProvider';
5
5
  import type { OidcTokenExchangeProviderConfig } from '../providers/OidcTokenExchangeProvider';
6
6
  import type { Saml2BearerProviderConfig } from '../providers/Saml2BearerProvider';
7
7
  import type { Saml2PureProviderConfig } from '../providers/Saml2PureProvider';
8
- export type SsoProviderInstance = ITokenProvider;
8
+ export type SsoProviderInstance = IRefreshableTokenProvider;
9
9
  export type SsoProviderConfig = {
10
10
  protocol: 'oidc';
11
11
  flow: 'browser';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sso/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAC9F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GACzB;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,yBAAyB,CAAC;CACnC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,4BAA4B,CAAC;CACtC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,0BAA0B,CAAC;CACpC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,+BAA+B,CAAC;CACzC,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,yBAAyB,CAAC;CACnC,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sso/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAC9F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GACzB;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,yBAAyB,CAAC;CACnC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,4BAA4B,CAAC;CACtC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,0BAA0B,CAAC;CACpC,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,+BAA+B,CAAC;CACzC,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,yBAAyB,CAAC;CACnC,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BrowserCallbackStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/BrowserCallbackStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIlE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,MAAM,WAAW,uBAAuB,CAAC,OAAO,GAAG,MAAM;IACvD,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAChD,wFAAwF;IACxF,OAAO,CAAC,EAAE,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,8BAA8B,CAAC,OAAO,CACrD,SAAQ,uBAAuB,CAAC,OAAO,CAAC;IACxC,cAAc,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;CAChD;AAwBD,qBAAa,uBAAuB,CAAC,OAAO,CAC1C,YAAW,sBAAsB,CAAC,OAAO,CAAC;IAOxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAuD;IACvE,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,QAAQ,CAAS;gBAGN,OAAO,EAAE,8BAA8B,CAAC,OAAO,CAAC;IAG7D,SAAS,CACb,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IA8FzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAM/B;AAyBD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAAuB,CAAC,MAAM,CAAM,GAC5C,sBAAsB,CAAC,MAAM,CAAC,CAWhC;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,uBAAuB,CAAC,kBAAkB,CAAM,GACxD,sBAAsB,CAAC,kBAAkB,CAAC,CAK5C;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,uBAAuB,CAAC,MAAM,CAAM,GAC5C,sBAAsB,CAAC,MAAM,CAAC,CAKhC"}
1
+ {"version":3,"file":"BrowserCallbackStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/BrowserCallbackStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAQlE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,MAAM,WAAW,uBAAuB,CAAC,OAAO,GAAG,MAAM;IACvD,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAChD,wFAAwF;IACxF,OAAO,CAAC,EAAE,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,8BAA8B,CAAC,OAAO,CACrD,SAAQ,uBAAuB,CAAC,OAAO,CAAC;IACxC,cAAc,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC;CAChD;AAwBD,qBAAa,uBAAuB,CAAC,OAAO,CAC1C,YAAW,sBAAsB,CAAC,OAAO,CAAC;IAOxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAuD;IACvE,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,QAAQ,CAAS;gBAGN,OAAO,EAAE,8BAA8B,CAAC,OAAO,CAAC;IAG7D,SAAS,CACb,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAyGzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAM/B;AAyBD,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,uBAAuB,CAAC,MAAM,CAAM,GAC5C,sBAAsB,CAAC,MAAM,CAAC,CAWhC;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,uBAAuB,CAAC,kBAAkB,CAAM,GACxD,sBAAsB,CAAC,kBAAkB,CAAC,CAK5C;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,uBAAuB,CAAC,MAAM,CAAM,GAC5C,sBAAsB,CAAC,MAAM,CAAC,CAKhC"}
@@ -51,6 +51,7 @@ const browserAuth_1 = require("../auth/browserAuth");
51
51
  const callbackServer_1 = require("../auth/callbackServer");
52
52
  const oidcBrowserAuth_1 = require("../auth/oidcBrowserAuth");
53
53
  const saml2Auth_1 = require("../auth/saml2Auth");
54
+ const TokenProviderErrors_1 = require("../errors/TokenProviderErrors");
54
55
  /**
55
56
  * Above Linux's `ip_local_port_range` (32768–60999), so an outbound connection
56
57
  * never squats on it, and far from the 3001/3333 range application servers use.
@@ -146,6 +147,19 @@ class BrowserCallbackStrategy {
146
147
  try {
147
148
  return await run;
148
149
  }
150
+ catch (error) {
151
+ // Everything that ends a browser login here — the timeout, the identity
152
+ // provider's own refusal, a port in use, a browser that would not open,
153
+ // an abort — is a browser authentication failure, and the one type a
154
+ // caller can catch for it. It was exported and thrown nowhere: each of
155
+ // these reached the caller as a plain Error. The text is kept, and the
156
+ // original is the cause. An error that already has a type (a
157
+ // ValidationError from building the URL) is not one of these.
158
+ if (error instanceof TokenProviderErrors_1.TokenProviderError)
159
+ throw error;
160
+ const cause = error instanceof Error ? error : new Error(String(error));
161
+ throw new TokenProviderErrors_1.BrowserAuthError(cause.message, cause);
162
+ }
149
163
  finally {
150
164
  this.options.signal?.removeEventListener('abort', relay);
151
165
  this.controller = null;
@@ -506,7 +506,7 @@ function readConfirmation(confirmation, context) {
506
506
  return { notOnOrAfter, notBefore };
507
507
  }
508
508
  /**
509
- * `no bearer confirmation qualifies: #1 …; #2 …`, naming at most
509
+ * `no bearer confirmation qualifies: #1 … | #2 …`, naming at most
510
510
  * LISTED_CANDIDATES candidates so the message stays bounded however many the
511
511
  * document carries.
512
512
  */
@@ -515,5 +515,5 @@ function describeRefusals(reasons) {
515
515
  .slice(0, LISTED_CANDIDATES)
516
516
  .map((reason, index) => `#${index + 1} ${reason}`);
517
517
  const more = reasons.length - listed.length;
518
- return `no bearer confirmation qualifies: ${listed.join('; ')}${more > 0 ? `; and ${more} more` : ''}`;
518
+ return `no bearer confirmation qualifies: ${listed.join(' | ')}${more > 0 ? ` | and ${more} more` : ''}`;
519
519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/auth-providers",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "description": "Token providers for MCP ABAP ADT auth-broker",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -57,7 +57,7 @@
57
57
  "node": "^22 || ^24"
58
58
  },
59
59
  "dependencies": {
60
- "@mcp-abap-adt/interfaces-auth": "^2.0.1",
60
+ "@mcp-abap-adt/interfaces-auth": "^2.1.0",
61
61
  "@mcp-abap-adt/interfaces-auth-sap": "^1.0.1",
62
62
  "@mcp-abap-adt/interfaces-utils": "^1.1.0",
63
63
  "@xmldom/xmldom": "^0.9.12",