@mcp-abap-adt/auth-providers 2.2.0 → 2.2.2
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 +23 -0
- package/README.md +8 -0
- package/dist/auth/saml2TokenExchange.d.ts +8 -0
- package/dist/auth/saml2TokenExchange.d.ts.map +1 -1
- package/dist/auth/saml2TokenExchange.js +46 -0
- package/dist/providers/Saml2BearerProvider.d.ts +5 -0
- package/dist/providers/Saml2BearerProvider.d.ts.map +1 -1
- package/dist/providers/Saml2BearerProvider.js +14 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.2] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Dependencies
|
|
13
|
+
|
|
14
|
+
- The lockfile is refreshed within the declared ranges (#33), replacing
|
|
15
|
+
thirteen Dependabot bumps. `npm audit` on the development tree goes from 15
|
|
16
|
+
findings (1 critical, 8 high) to none. No dependency range in `package.json`
|
|
17
|
+
changed, and the lockfile is not part of the published package, so an
|
|
18
|
+
installation of 2.2.2 resolves exactly what one of 2.2.1 already could.
|
|
19
|
+
|
|
20
|
+
## [2.2.1] - 2026-09-24
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`Saml2BearerProvider` spends its refresh token** (#23). `performRefresh()`
|
|
25
|
+
used to run a full interactive login on both of its branches, so a session
|
|
26
|
+
holding a valid refresh token still went through the IdP and a browser — and
|
|
27
|
+
a headless consumer got `BROWSER_AUTH_REQUIRED` from `auth-broker`. It now
|
|
28
|
+
sends a `refresh_token` grant to the same token endpoint and with the same
|
|
29
|
+
client credentials as the assertion exchange, keeps the refresh token it spent
|
|
30
|
+
when the response carries no new one, and falls back to a full login when the
|
|
31
|
+
grant is refused.
|
|
32
|
+
|
|
10
33
|
## [2.2.0] - 2026-09-24
|
|
11
34
|
|
|
12
35
|
### Dependencies
|
package/README.md
CHANGED
|
@@ -378,6 +378,14 @@ const provider = new Saml2BearerProvider({
|
|
|
378
378
|
const broker = new AuthBroker({ tokenProvider: provider }, 'none');
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
+
**Refresh.** When the token endpoint returns a `refresh_token` with the SAML
|
|
382
|
+
bearer exchange, `Saml2BearerProvider` spends it once the access token expires:
|
|
383
|
+
a `refresh_token` grant to the same endpoint (`tokenUrl`, or `uaaUrl` +
|
|
384
|
+
`/oauth/token`) with the same client credentials, and no assertion, strategy or
|
|
385
|
+
browser involved. Pass a stored one back as `refreshToken` in the config and the
|
|
386
|
+
next `getTokens()` uses it. If the grant is refused, or no refresh token was
|
|
387
|
+
ever issued, the provider falls back to a full login through `authorization`.
|
|
388
|
+
|
|
381
389
|
Pure SAML example (cookie-based):
|
|
382
390
|
|
|
383
391
|
```typescript
|
|
@@ -9,4 +9,12 @@ export interface Saml2TokenExchangeResponse {
|
|
|
9
9
|
tokenType?: string;
|
|
10
10
|
}
|
|
11
11
|
export declare function exchangeSamlAssertion(samlResponse: string, tokenUrl: string, clientId: string | undefined, clientSecret: string | undefined, logger?: ILogger): Promise<Saml2TokenExchangeResponse>;
|
|
12
|
+
/**
|
|
13
|
+
* Spends a refresh token obtained from a SAML bearer exchange.
|
|
14
|
+
*
|
|
15
|
+
* Sent to the same token endpoint, with the same client authentication, as the
|
|
16
|
+
* exchange that issued it — so an explicit `tokenUrl` is honoured rather than
|
|
17
|
+
* rebuilt from a UAA base URL.
|
|
18
|
+
*/
|
|
19
|
+
export declare function refreshSamlBearerToken(refreshToken: string, tokenUrl: string, clientId: string | undefined, clientSecret?: string, logger?: ILogger): Promise<Saml2TokenExchangeResponse>;
|
|
12
20
|
//# sourceMappingURL=saml2TokenExchange.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saml2TokenExchange.d.ts","sourceRoot":"","sources":["../../src/auth/saml2TokenExchange.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,0BAA0B,CAAC,CAwCrC"}
|
|
1
|
+
{"version":3,"file":"saml2TokenExchange.d.ts","sourceRoot":"","sources":["../../src/auth/saml2TokenExchange.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,0BAA0B,CAAC,CAwCrC;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,CAAC,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,0BAA0B,CAAC,CAwCrC"}
|
|
@@ -7,6 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.exchangeSamlAssertion = exchangeSamlAssertion;
|
|
10
|
+
exports.refreshSamlBearerToken = refreshSamlBearerToken;
|
|
10
11
|
const axios_1 = __importDefault(require("axios"));
|
|
11
12
|
function toBasicAuth(clientId, clientSecret) {
|
|
12
13
|
return Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
@@ -49,3 +50,48 @@ async function exchangeSamlAssertion(samlResponse, tokenUrl, clientId, clientSec
|
|
|
49
50
|
tokenType: data.token_type,
|
|
50
51
|
};
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Spends a refresh token obtained from a SAML bearer exchange.
|
|
55
|
+
*
|
|
56
|
+
* Sent to the same token endpoint, with the same client authentication, as the
|
|
57
|
+
* exchange that issued it — so an explicit `tokenUrl` is honoured rather than
|
|
58
|
+
* rebuilt from a UAA base URL.
|
|
59
|
+
*/
|
|
60
|
+
async function refreshSamlBearerToken(refreshToken, tokenUrl, clientId, clientSecret, logger) {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
params.append('grant_type', 'refresh_token');
|
|
63
|
+
params.append('refresh_token', refreshToken);
|
|
64
|
+
if (clientId) {
|
|
65
|
+
params.append('client_id', clientId);
|
|
66
|
+
}
|
|
67
|
+
logger?.info('[SAML] Refreshing token', { tokenUrl });
|
|
68
|
+
const headers = {
|
|
69
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
70
|
+
};
|
|
71
|
+
if (clientId && clientSecret) {
|
|
72
|
+
headers.Authorization = `Basic ${toBasicAuth(clientId, clientSecret)}`;
|
|
73
|
+
}
|
|
74
|
+
let response;
|
|
75
|
+
try {
|
|
76
|
+
response = await axios_1.default.post(tokenUrl, params.toString(), { headers });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
80
|
+
logger?.error('[SAML] Token refresh failed', {
|
|
81
|
+
status: error.response?.status,
|
|
82
|
+
data: error.response?.data,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
const data = response.data;
|
|
88
|
+
if (!data?.access_token) {
|
|
89
|
+
throw new Error('Refresh response missing access_token');
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
accessToken: data.access_token,
|
|
93
|
+
refreshToken: data.refresh_token,
|
|
94
|
+
expiresIn: data.expires_in,
|
|
95
|
+
tokenType: data.token_type,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -17,6 +17,11 @@ export declare class Saml2BearerProvider extends BaseTokenProvider {
|
|
|
17
17
|
constructor(config: Saml2BearerProviderConfig);
|
|
18
18
|
protected getAuthType(): OAuth2GrantType;
|
|
19
19
|
protected performLogin(): Promise<ITokenResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Spends the refresh token at the endpoint that issued it. A failure is
|
|
22
|
+
* thrown rather than handled: `BaseTokenProvider.getTokens()` drops the
|
|
23
|
+
* refresh token and falls back to `performLogin()`.
|
|
24
|
+
*/
|
|
20
25
|
protected performRefresh(): Promise<ITokenResult>;
|
|
21
26
|
}
|
|
22
27
|
//# sourceMappingURL=Saml2BearerProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Saml2BearerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/Saml2BearerProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"Saml2BearerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/Saml2BearerProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAK9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAOtB,MAAM,WAAW,yBACf,SAAQ,iBAAiB,EACvB,yBAAyB;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,mBAAoB,SAAQ,iBAAiB;IACxD,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,yBAAyB;IAiB7C,SAAS,CAAC,WAAW,IAAI,eAAe;cAIxB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBrD;;;;OAIG;cACa,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;CAqBxD"}
|
|
@@ -42,11 +42,23 @@ class Saml2BearerProvider extends BaseTokenProvider_1.BaseTokenProvider {
|
|
|
42
42
|
tokenType: 'jwt',
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Spends the refresh token at the endpoint that issued it. A failure is
|
|
47
|
+
* thrown rather than handled: `BaseTokenProvider.getTokens()` drops the
|
|
48
|
+
* refresh token and falls back to `performLogin()`.
|
|
49
|
+
*/
|
|
45
50
|
async performRefresh() {
|
|
46
51
|
if (!this.refreshToken) {
|
|
47
|
-
|
|
52
|
+
throw new Error('Refresh token is required for refresh');
|
|
48
53
|
}
|
|
49
|
-
|
|
54
|
+
const tokens = await (0, saml2TokenExchange_1.refreshSamlBearerToken)(this.refreshToken, (0, saml2Utils_1.resolveTokenUrl)(this.config), this.config.clientId, this.config.clientSecret, this.logger);
|
|
55
|
+
return {
|
|
56
|
+
authorizationToken: tokens.accessToken,
|
|
57
|
+
refreshToken: tokens.refreshToken || this.refreshToken,
|
|
58
|
+
authType: interfaces_auth_1.AUTH_TYPE_SAML2_BEARER,
|
|
59
|
+
expiresIn: tokens.expiresIn,
|
|
60
|
+
tokenType: 'jwt',
|
|
61
|
+
};
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
64
|
exports.Saml2BearerProvider = Saml2BearerProvider;
|