@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 +69 -0
- package/README.md +42 -20
- package/dist/providers/BaseTokenProvider.d.ts +11 -2
- package/dist/providers/BaseTokenProvider.d.ts.map +1 -1
- package/dist/providers/BaseTokenProvider.js +14 -7
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/sso/SsoProviderFactory.d.ts +2 -2
- package/dist/sso/SsoProviderFactory.d.ts.map +1 -1
- package/dist/sso/types.d.ts +2 -2
- package/dist/sso/types.d.ts.map +1 -1
- package/dist/strategies/BrowserCallbackStrategy.d.ts.map +1 -1
- package/dist/strategies/BrowserCallbackStrategy.js +14 -0
- package/dist/validation/assertionValidator.js +2 -2
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
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
|
|
765
|
-
|
|
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()
|
|
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
|
|
1215
|
-
|
|
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
|
-
//
|
|
1253
|
-
|
|
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
|
-
- `
|
|
1262
|
-
- `SessionDataError` -
|
|
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 {
|
|
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
|
|
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,
|
|
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
|
-
|
|
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]
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
5
|
+
* All providers extend BaseTokenProvider and implement IRefreshableTokenProvider.
|
|
6
6
|
*/
|
|
7
7
|
export type { AuthorizationCodeProviderConfig } from './AuthorizationCodeProvider';
|
|
8
8
|
export { AuthorizationCodeProvider } from './AuthorizationCodeProvider';
|
package/dist/providers/index.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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):
|
|
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,
|
|
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"}
|
package/dist/sso/types.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
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 =
|
|
8
|
+
export type SsoProviderInstance = IRefreshableTokenProvider;
|
|
9
9
|
export type SsoProviderConfig = {
|
|
10
10
|
protocol: 'oidc';
|
|
11
11
|
flow: 'browser';
|
package/dist/sso/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sso/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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;
|
|
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
|
|
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('
|
|
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.
|
|
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
|
|
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",
|