@mcp-abap-adt/auth-broker 3.1.0 → 4.0.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 +396 -0
- package/README.md +694 -226
- package/dist/AuthBroker.d.ts +178 -29
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +494 -101
- package/dist/DestinationConfigError.d.ts +27 -0
- package/dist/DestinationConfigError.d.ts.map +1 -0
- package/dist/DestinationConfigError.js +36 -0
- package/dist/SessionWriter.d.ts +54 -0
- package/dist/SessionWriter.d.ts.map +1 -0
- package/dist/SessionWriter.js +135 -0
- package/dist/binding.d.ts +95 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +207 -0
- package/dist/bindingOf.d.ts +36 -0
- package/dist/bindingOf.d.ts.map +1 -0
- package/dist/bindingOf.js +53 -0
- package/dist/destinations.d.ts +139 -0
- package/dist/destinations.d.ts.map +1 -0
- package/dist/destinations.js +429 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/providers/ITokenProvider.d.ts +2 -1
- package/dist/providers/ITokenProvider.d.ts.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +2 -2
- package/dist/stores/index.d.ts +2 -2
- package/dist/stores/index.js +2 -2
- package/dist/stores/interfaces.d.ts +4 -3
- package/dist/stores/interfaces.d.ts.map +1 -1
- package/dist/stores/interfaces.js +2 -2
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/package.json +15 -8
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,402 @@ Thank you to all contributors! See [CONTRIBUTORS.md](../../CONTRIBUTORS.md) for
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [4.0.0] - 2026-10-02
|
|
15
|
+
|
|
16
|
+
**What 4.0.0 is.** The broker hands a process **the credential for a
|
|
17
|
+
destination, ready to use**: `getProvider(destination)` returns the
|
|
18
|
+
`IAuthProvider` (`@mcp-abap-adt/interfaces-auth` 3) the destination states —
|
|
19
|
+
basic, SNC, a UAA, OIDC or SAML grant, or a credential handed over — which a
|
|
20
|
+
`@mcp-abap-adt/connection` 10 connector takes as it is, and whatever that
|
|
21
|
+
provider obtains or renews is stored in the session store, whoever triggered
|
|
22
|
+
it. The token API (`getToken`, `refreshToken`, `createTokenRefresher`) stays.
|
|
23
|
+
Released with `@mcp-abap-adt/auth-broker-cli` 2.0.0, the commands on this
|
|
24
|
+
version.
|
|
25
|
+
|
|
26
|
+
### Breaking
|
|
27
|
+
|
|
28
|
+
1. **The two stores have one role each.** The service key store holds the
|
|
29
|
+
*means* — `authType`, `grantType`, the client, basic's user and password,
|
|
30
|
+
the SNC, OIDC and SAML settings, `serviceUrl`; the session store holds the
|
|
31
|
+
*secret* — the token or cookies, `expiresAt`, the refresh token, and what
|
|
32
|
+
the secret is bound to (`issuedFor`, `issuedBy`). The broker writes the
|
|
33
|
+
secret alone, in one `saveSession`, and never a means field: no
|
|
34
|
+
`serviceUrl`, `authType` or client is written to the session any more
|
|
35
|
+
(3.x called `setConnectionConfig` and `setAuthorizationConfig`).
|
|
36
|
+
`getProvider` reads the means from the key store only: a destination
|
|
37
|
+
whose means only a session answers is a `DestinationConfigError`.
|
|
38
|
+
2. **The session store must take a write of the secret alone:
|
|
39
|
+
`@mcp-abap-adt/auth-stores` 3 or later**, or a store of your own that does.
|
|
40
|
+
auth-stores 1.x and 2.x are not supported: their `AbapSessionStore`
|
|
41
|
+
throws on a `saveSession` without `serviceUrl` and their
|
|
42
|
+
`SafeAbapSessionStore` refuses it (measured on 1.2.3, 1.2.4 and 2.0.0), so
|
|
43
|
+
a token would be obtained and then never stored. auth-stores 3.1.0 or
|
|
44
|
+
later for the binding (item 4).
|
|
45
|
+
3. **`getConnectionConfig` / `getAuthorizationConfig` compose the two
|
|
46
|
+
stores** — the key store's means with the session's secret laid over them
|
|
47
|
+
— instead of answering the session's configuration whole.
|
|
48
|
+
4. **A stored secret is used only for the resource and issuer it was
|
|
49
|
+
obtained for** (`issuedFor`, `issuedBy`): one bound elsewhere, or not
|
|
50
|
+
bound at all, is not reused — the provider logs in afresh, and a `none`
|
|
51
|
+
destination is refused.
|
|
52
|
+
5. **The token API refuses a destination the key store states as `basic` or
|
|
53
|
+
`snc`** (`DestinationConfigError`, `authType`), instead of writing a token
|
|
54
|
+
over its session.
|
|
55
|
+
6. **Contracts and dependencies.** `@mcp-abap-adt/interfaces-auth` `^3.0.0`,
|
|
56
|
+
`@mcp-abap-adt/interfaces-auth-sap` `^2.0.0`,
|
|
57
|
+
`@mcp-abap-adt/interfaces-auth-broker` `^1.1.0` (the store contracts, new),
|
|
58
|
+
`@mcp-abap-adt/auth-providers` `^5.2.1` (now a runtime dependency). Types
|
|
59
|
+
of `interfaces-auth` 2.x and providers of auth-providers 4.x no longer mix
|
|
60
|
+
with this version.
|
|
61
|
+
|
|
62
|
+
### Migrating from 3.x
|
|
63
|
+
|
|
64
|
+
- **Stores.** Use `@mcp-abap-adt/auth-stores` `^3.1.0` (this version is
|
|
65
|
+
tested against 3.2.0), or stores of your own on the
|
|
66
|
+
`@mcp-abap-adt/interfaces-auth-broker` contracts. State the means in a key
|
|
67
|
+
store: an `EnvDestinationStore(dir, { fallback })` over
|
|
68
|
+
`<destination>.env`, or `AbapServiceKeyStore(dir, { grantType })` /
|
|
69
|
+
`XsuaaServiceKeyStore(dir, { grantType })` over a SAP service key — a key
|
|
70
|
+
cannot state its grant, so whoever builds the store states it. The session
|
|
71
|
+
store keeps the secret: `AbapSessionStore`, `XsuaaSessionStore`,
|
|
72
|
+
`EnvFileSessionStore`, or the in-memory `Safe*` ones. A custom
|
|
73
|
+
`ISessionStore` must persist `issuedFor` and `issuedBy` beside the secret,
|
|
74
|
+
or the broker never reuses its sessions (a login every start).
|
|
75
|
+
- **3.x session files** hold means and secret together. Point an
|
|
76
|
+
`EnvDestinationStore` and a session store at the same directory: each reads
|
|
77
|
+
its own keys where they are. A `basic` or `snc` file works once it states
|
|
78
|
+
`SAP_AUTH_TYPE`; a `jwt` or `saml` file needs `SAP_GRANT_TYPE` (3.x never
|
|
79
|
+
wrote one) — run the `auth-broker-cli` command that produced it again, or
|
|
80
|
+
add the line. Its stored token is reused when its `SAP_URL` (+
|
|
81
|
+
`SAP_CLIENT`) and `SAP_UAA_URL` + `SAP_UAA_CLIENT_ID` are the destination's
|
|
82
|
+
(auth-stores 3.1.0 reads them as the binding); otherwise the first start
|
|
83
|
+
logs in once. XSUAA sessions written with an empty URL log in once.
|
|
84
|
+
- **A `@mcp-abap-adt/connection` 10 connector:** build it from
|
|
85
|
+
`await broker.getProvider(destination)` instead of a token and a token
|
|
86
|
+
refresher. Construct the broker with a `serviceKeyStore` and without a
|
|
87
|
+
`provider`, and give it the collaborators its destinations' grants use —
|
|
88
|
+
`authorization`, `oidcAuthorization`, `deviceCodePresenter`, `samlCookies`,
|
|
89
|
+
`assertionReplayStore`; there is no default. A headless process gives ones
|
|
90
|
+
that refuse. The connector's URL comes from your key store
|
|
91
|
+
(`getConnectionConfig`); `getProvider` does not need it.
|
|
92
|
+
- **The token API with a provider of your own** (calm-server's way): see
|
|
93
|
+
*The token API* below — the same calls, reads and answers; what it writes
|
|
94
|
+
is the secret alone.
|
|
95
|
+
- **`DestinationConfigError`** (new, exported): catch it where the process
|
|
96
|
+
builds its connector or asks for a token. `missingFields` names what the
|
|
97
|
+
destination lacks (a field, or a collaborator option) — never a value.
|
|
98
|
+
- **`flush()`** (new): a session write that fails is retried by the broker
|
|
99
|
+
instead of failing the authentication. Call `await broker.flush()` on
|
|
100
|
+
shutdown (`SIGTERM`, before a stdio transport closes) to learn whether
|
|
101
|
+
every secret is stored; it rejects naming the destinations still failing.
|
|
102
|
+
- **`bindingOf(means, client?)`** (new): a consumer that writes a handed-over
|
|
103
|
+
token or cookies (a `none` destination) to the session store itself writes
|
|
104
|
+
`bindingOf(means)` beside them, or `getProvider` refuses the destination.
|
|
105
|
+
- **Imports.** The re-exported types keep their names. If you import
|
|
106
|
+
`ISessionStore`, `IServiceKeyStore`, `IConnectionConfig` or `IConfig` from
|
|
107
|
+
`@mcp-abap-adt/interfaces-auth-sap` yourself, take them from
|
|
108
|
+
`@mcp-abap-adt/interfaces-auth-broker`; `IAuthorizationConfig` and
|
|
109
|
+
`AuthType` stay in `interfaces-auth-sap`. Providers are auth-providers 5:
|
|
110
|
+
the interactive ones take their `authorization` strategy explicitly.
|
|
111
|
+
- **The commands** are `@mcp-abap-adt/auth-broker-cli` (since 3.1.0); its
|
|
112
|
+
2.0.0 is the version on this library — what changed for its flags, files
|
|
113
|
+
and exit codes is in its CHANGELOG, *Migrating from 1.0.0*.
|
|
114
|
+
|
|
115
|
+
#### The token API (`getToken`, `refreshToken`, `createTokenRefresher`)
|
|
116
|
+
|
|
117
|
+
**What stays.** A consumer that passes its own `provider` (an instance or a
|
|
118
|
+
`TokenProviderFactory`) calls the same methods with the same signatures and
|
|
119
|
+
gets the same answers: the provider is asked as before, seeded from the same
|
|
120
|
+
reads in the same order (the session first, then the key store), and its
|
|
121
|
+
errors — and a store's — reach the caller unchanged. `createTokenRefresher`
|
|
122
|
+
is unchanged and stays supported (calm-server injects it into its own
|
|
123
|
+
connection).
|
|
124
|
+
|
|
125
|
+
**What changes for it.**
|
|
126
|
+
|
|
127
|
+
1. **The session gets the secret alone** — the token or cookies, `expiresAt`,
|
|
128
|
+
the refresh token, and what the secret is bound to (`issuedFor`,
|
|
129
|
+
`issuedBy`) — in one `saveSession`. No `serviceUrl`, `authType` or client
|
|
130
|
+
is written back any more, so a wrapper that only kept the written-back
|
|
131
|
+
`serviceUrl` out of the session (calm-server's `TargetUrlSessionStore`)
|
|
132
|
+
has nothing left to filter. A store that merges a write into what it holds
|
|
133
|
+
— auth-stores 3, and auth-stores 1.x/2.x's `XsuaaSessionStore` and
|
|
134
|
+
`EnvFileSessionStore` (measured on 1.2.4 and 2.0.0) — keeps what it holds.
|
|
135
|
+
2. **The session store must accept a write of the secret alone.**
|
|
136
|
+
Measured: auth-stores **1.2.3, 1.2.4 and 2.0.0**'s `AbapSessionStore`
|
|
137
|
+
throws a `TypeError` on a `saveSession` without `serviceUrl`, and their
|
|
138
|
+
`SafeAbapSessionStore` refuses it (`missing required field: serviceUrl`) —
|
|
139
|
+
even when the session already holds its URL. On those stores `getToken`
|
|
140
|
+
obtains a token and then throws the store's error, and the broker's retry
|
|
141
|
+
cannot succeed. Use auth-stores **3** (`^3.1.0` for the binding) — or a
|
|
142
|
+
store of your own that takes the secret alone. (1.x/2.x's
|
|
143
|
+
`SafeXsuaaSessionStore` replaces the session with what is written: the
|
|
144
|
+
URL or client it held in memory is gone after the first token, so state
|
|
145
|
+
them in the key store.)
|
|
146
|
+
3. **A destination the key store states as `basic` or `snc` is refused**
|
|
147
|
+
(`DestinationConfigError`, `missingFields: ['authType']`) instead of
|
|
148
|
+
getting a token written over its credential.
|
|
149
|
+
4. **A failed write is retried.** It still reaches `getToken`'s caller; the
|
|
150
|
+
broker then retries it on its own, and `flush()` reports what is still
|
|
151
|
+
pending — call it on shutdown.
|
|
152
|
+
|
|
153
|
+
**Without a `provider`,** the token API serves what the destination states,
|
|
154
|
+
on the provider `getProvider` builds for it (see *Added*): the consumer
|
|
155
|
+
composes the key store (the means, `grantType` included) and the
|
|
156
|
+
collaborators instead of a provider.
|
|
157
|
+
|
|
158
|
+
### Added
|
|
159
|
+
|
|
160
|
+
- **`bindingOf(means, client?)`** — the binding (`issuedFor`, `issuedBy`) the
|
|
161
|
+
broker writes beside a secret for a destination with those means, for a
|
|
162
|
+
consumer that hands over a credential (a `none` destination's token or
|
|
163
|
+
cookies) and writes it to the session store itself. It is the same function
|
|
164
|
+
`getProvider` checks a stored secret against and `persist` writes
|
|
165
|
+
(`destinationBinding`, `src/bindingOf.ts`), so what a consumer writes and
|
|
166
|
+
what the broker expects cannot diverge, and no consumer canonicalises a URI.
|
|
167
|
+
`@mcp-abap-adt/auth-broker-cli`'s `--cookie` uses it.
|
|
168
|
+
|
|
169
|
+
- **`getProvider(destination): Promise<IAuthProvider>`** — the credential the
|
|
170
|
+
destination states, for a `@mcp-abap-adt/connection` 10 connector. The
|
|
171
|
+
destination's `authType` (and `grantType`, for `jwt` and `saml`) chooses the
|
|
172
|
+
provider and nothing else does; the means are read from the service key
|
|
173
|
+
store only, the secret (token or cookies) from the session store only. Built
|
|
174
|
+
in this version: `basic` → `BasicAuthProvider`, `snc` →
|
|
175
|
+
`SncLogonProvider.forSecureLoginClient` (from `sncPartnerName`, `sncQop`,
|
|
176
|
+
`sncLib`, `sncMyName`), `jwt` / `none` → `TokenAuthProvider.fixed` with the
|
|
177
|
+
session's token, `saml` / `none` → `SamlAuthProvider` with the session's
|
|
178
|
+
cookies, the UAA grants, the OIDC grants and the SAML grants (below) —
|
|
179
|
+
every pair of the table. One provider per destination: the promise of the build is cached before the
|
|
180
|
+
first read, so concurrent first calls build once, and dropped when the build
|
|
181
|
+
throws, so the next call retries.
|
|
182
|
+
- **The UAA grants in `getProvider`**: `jwt` / `authorization_code` →
|
|
183
|
+
`AuthorizationCodeProvider`, `jwt` / `client_credentials` →
|
|
184
|
+
`ClientCredentialsProvider`, `jwt` / `passcode` → `UaaPasscodeProvider`. The
|
|
185
|
+
client (`uaaUrl`, `uaaClientId`, `uaaClientSecret`) comes from the key
|
|
186
|
+
store's `getAuthorizationConfig`; `authorization_code` and `passcode` are
|
|
187
|
+
seeded from the session — the token, the refresh token and `expiresAt` —
|
|
188
|
+
and without a session log in at `prepare()`; `client_credentials` takes the
|
|
189
|
+
client alone. `uaaClientSecret: ''` is a public client, which `passcode`
|
|
190
|
+
takes as no secret. No row needs `serviceUrl`: the resource URL is not
|
|
191
|
+
authorization data — no token provider reads it — so the connector takes it
|
|
192
|
+
from the key store, and `getProvider` neither requires it nor passes it to a
|
|
193
|
+
provider; it reads it only to bind a stored secret (below).
|
|
194
|
+
- **The OIDC grants in `getProvider`**: `jwt` / `oidc_authorization_code` →
|
|
195
|
+
`OidcBrowserProvider` (PKCE), `device_code` → `OidcDeviceFlowProvider`,
|
|
196
|
+
`password` → `OidcPasswordProvider`, `token_exchange` →
|
|
197
|
+
`OidcTokenExchangeProvider`. The client id and secret come from the key
|
|
198
|
+
store's client (`uaaClientSecret: ''` is a public client, sent with no
|
|
199
|
+
secret); the endpoints from `oidcIssuerUrl` (discovery) or, without it,
|
|
200
|
+
every explicit endpoint the row reads (`oidcAuthorizationEndpoint`,
|
|
201
|
+
`oidcDeviceAuthorizationEndpoint`, `oidcTokenEndpoint`); `oidcScopes` as
|
|
202
|
+
given, joined by one space into `token_exchange`'s `scope`; `password`'s
|
|
203
|
+
user and `token_exchange`'s subject, audience and actor token from the
|
|
204
|
+
means — sent, never written. Seeded from the session like the UAA rows;
|
|
205
|
+
`issuedBy` is `oidcIssuerUrl`, else `uaaUrl`, with `client_id`.
|
|
206
|
+
- **The SAML grants in `getProvider`**: `saml` / `saml2_pure` →
|
|
207
|
+
`Saml2PureProvider`, `saml2_bearer` → `Saml2BearerProvider`. The broker
|
|
208
|
+
composes the assertion validator from the destination's trust —
|
|
209
|
+
`createSignedResponseValidator` for `saml2_pure`,
|
|
210
|
+
`createSignedAssertionValidator` for `saml2_bearer`, from
|
|
211
|
+
`samlIdpCertificates`, `samlClockSkewMs` and the consumer's
|
|
212
|
+
`assertionReplayStore` — and passes `samlIdpEntityId` as the expected
|
|
213
|
+
issuer, with `samlIdpSsoUrl`, `samlSpEntityId`, `samlAcsUrl`,
|
|
214
|
+
`samlRelayState` and `samlIdpInitiated`. `saml2_pure` is seeded with the
|
|
215
|
+
stored cookies and `expiresAt` (auth-providers 5.1.0's seed) and bound to
|
|
216
|
+
`samlAcsUrl` (origin and path); `saml2_bearer` takes the client and
|
|
217
|
+
`samlTokenUrl`, is seeded with the token, refresh token and `expiresAt`, and
|
|
218
|
+
is bound to `uaaUrl` with `client_id`.
|
|
219
|
+
- **The `authorization` option is used**: `(destination, grant) =>
|
|
220
|
+
IAuthorizationStrategy<string>`, the interactive half of
|
|
221
|
+
`authorization_code` and `passcode` (for `passcode` the strategy is handed
|
|
222
|
+
`<uaaUrl>/passcode`). Called once per destination's build, never disposed
|
|
223
|
+
by the broker; required only by those two grants and the two SAML grants
|
|
224
|
+
(which it conducts with the grant passed), with no default.
|
|
225
|
+
- **The other collaborator options are used**: `oidcAuthorization` by
|
|
226
|
+
`oidc_authorization_code`, `deviceCodePresenter` by `device_code`,
|
|
227
|
+
`samlCookies` (the validated SAMLResponse → the system's session cookies)
|
|
228
|
+
by `saml2_pure`, `assertionReplayStore` by both SAML grants — each a
|
|
229
|
+
function of the destination, called once per build, never disposed, and
|
|
230
|
+
required only by its rows: missing, a `DestinationConfigError` naming it.
|
|
231
|
+
- **What a `getProvider` provider obtains is stored.** Every token provider
|
|
232
|
+
the broker builds gets `onTokens`, so a token obtained at `prepare()`, on
|
|
233
|
+
expiry, or in `rejected()` after a 401 is in the session store before the
|
|
234
|
+
provider answers. The write is the session secret alone — one
|
|
235
|
+
`saveSession(destination, { authorizationToken, expiresAt, refreshToken,
|
|
236
|
+
issuedFor, issuedBy })`, the stored refresh token carried forward when the
|
|
237
|
+
result has none and the stored session is bound where the new secret is —
|
|
238
|
+
and never a means field (no `serviceUrl`, no `authType`, no client secret,
|
|
239
|
+
no subject token, no password). A `saml2_pure` result (`tokenType:
|
|
240
|
+
'saml'`) is written as cookies — `{ sessionCookies, expiresAt, issuedFor,
|
|
241
|
+
issuedBy }`, no refresh token; a `saml2_bearer` result as a token.
|
|
242
|
+
A destination the key store states as `basic` or `snc` is not written.
|
|
243
|
+
- **A stored secret is used only where it is bound** (`issuedFor` /
|
|
244
|
+
`issuedBy`, `@mcp-abap-adt/interfaces-auth-broker` 1.1.0). The broker
|
|
245
|
+
computes from the means the resource a secret is for (`serviceUrl` with
|
|
246
|
+
`sap-client`) and who issued it to which client (`uaaUrl` with
|
|
247
|
+
`client_id` for the UAA grants and `saml2_bearer`; the OIDC issuer, else
|
|
248
|
+
`uaaUrl`, with `client_id` for the OIDC grants; the ACS for `saml2_pure`),
|
|
249
|
+
canonicalises them and the session's values with one function
|
|
250
|
+
— scheme and host lower-cased, the port explicit (`443` / `80`), the path
|
|
251
|
+
without a trailing `/`, only `sap-client` (the means' `sapClient` first) or
|
|
252
|
+
`client_id` kept and re-encoded, so percent-encoding compares equal — and
|
|
253
|
+
writes both with every secret, each left out when the means lack its source.
|
|
254
|
+
The grants that obtain a secret (UAA, OIDC, SAML) are seeded only when both
|
|
255
|
+
stored values match; otherwise the
|
|
256
|
+
stored secret, refresh token included, is not used: the provider logs in
|
|
257
|
+
afresh and the log says only `<destination>: secret bound to another
|
|
258
|
+
resource, discarded`. A `none` destination is refused instead: a
|
|
259
|
+
`DestinationConfigError` naming `issuedFor` when the stored resource is not
|
|
260
|
+
the destination's or is absent, and naming `issuedBy` when the means state
|
|
261
|
+
an issuer (`oidcIssuerUrl` or the client for `jwt`, `samlAcsUrl` for `saml`)
|
|
262
|
+
and the stored one is not it. **What a consumer meets:** a custom
|
|
263
|
+
`ISessionStore` must persist both fields beside the secret, else every
|
|
264
|
+
process start is a fresh login; a headless process whose strategy refuses
|
|
265
|
+
logins answers Oops on a mismatch instead of presenting a foreign secret; a
|
|
266
|
+
changed URL, SAP client, UAA or client costs one login. Session files
|
|
267
|
+
written before `@mcp-abap-adt/auth-stores` 3.1.0 keep working: its stores
|
|
268
|
+
answer the binding from the file's `SAP_URL` (+ `SAP_CLIENT`) and
|
|
269
|
+
`SAP_UAA_URL` + `SAP_UAA_CLIENT_ID`; XSUAA sessions written with an empty
|
|
270
|
+
URL have none and log in once.
|
|
271
|
+
- **A failed session write does not fail the authentication**: it stays
|
|
272
|
+
pending for its destination and the broker retries it on its own — one
|
|
273
|
+
second, doubling, capped at one minute, on a timer that does not keep the
|
|
274
|
+
process alive. A newer result replaces the pending one; writes for one
|
|
275
|
+
destination never overlap; failures are logged by class name, never their
|
|
276
|
+
message.
|
|
277
|
+
- **`flush(): Promise<void>`** — one more attempt for every pending session
|
|
278
|
+
write; resolves when all have landed, rejects with an `AggregateError`
|
|
279
|
+
naming the destinations whose store still refuses (the retries go on). Call
|
|
280
|
+
it on shutdown.
|
|
281
|
+
- **`DestinationConfigError`** (`code: 'DESTINATION_CONFIG'`, `destination`,
|
|
282
|
+
`missingFields`) — a destination that lacks what its type needs:
|
|
283
|
+
no `serviceKeyStore` option, no means, no or an unknown `authType`, a
|
|
284
|
+
`jwt`/`saml` destination without a `grantType` or with a pair outside the
|
|
285
|
+
table, a missing field of its row (`''` counts as missing; for the UAA
|
|
286
|
+
grants `uaaUrl`, `uaaClientId`, and `uaaClientSecret` for
|
|
287
|
+
`authorization_code` / `client_credentials`), a missing collaborator option
|
|
288
|
+
(`authorization`; for the OIDC and SAML grants `oidcAuthorization`,
|
|
289
|
+
`deviceCodePresenter`, `samlCookies`, `assertionReplayStore`), an OIDC
|
|
290
|
+
grant with neither `oidcIssuerUrl` nor its endpoints (`oidcIssuerUrl` and
|
|
291
|
+
each endpoint missing), a SAML grant without its trust (`samlIdpSsoUrl`,
|
|
292
|
+
`samlSpEntityId`, `samlIdpEntityId`, `samlIdpCertificates` — an empty list
|
|
293
|
+
counts as missing), a certificate the validator cannot read
|
|
294
|
+
(`samlIdpCertificates`) or a `samlClockSkewMs` that is not a whole,
|
|
295
|
+
non-negative number, a `none` credential not bound to the destination
|
|
296
|
+
(`issuedFor`, `issuedBy`), or a provider
|
|
297
|
+
constructor's own `ValidationError` (an `sncQop` outside `1|2|3|8|9`, named
|
|
298
|
+
as the store field; the provider's error, which quotes the value, is not
|
|
299
|
+
kept). It names fields and options, never a stored value.
|
|
300
|
+
- **The token API shares `getProvider`'s provider.** Without a consumer
|
|
301
|
+
`provider`, `getToken`, `refreshToken` and `createTokenRefresher` ask the
|
|
302
|
+
very provider `getProvider` hands out for the destination — one per
|
|
303
|
+
destination, so one token, one refresh token and one renewal in flight
|
|
304
|
+
serve the connector and the token API alike (a `refreshToken()` during a
|
|
305
|
+
renewal in `rejected()` joins it). They write nothing themselves: the
|
|
306
|
+
provider's `onTokens` does, as for any `getProvider` provider. They serve the
|
|
307
|
+
destinations whose provider obtains a token — every grant but `none` — and
|
|
308
|
+
throw `DestinationConfigError` naming `provider` for a `none` destination,
|
|
309
|
+
before any provider is built. A failed write still reaches the caller:
|
|
310
|
+
`getToken` / `refreshToken` throw the store's error for the token they
|
|
311
|
+
received — as 3.x did — while the broker keeps retrying the write.
|
|
312
|
+
- The collaborator options `authorization`, `oidcAuthorization`,
|
|
313
|
+
`deviceCodePresenter`, `samlCookies`, `assertionReplayStore` on
|
|
314
|
+
`AuthBrokerConfig`, and the `StrategyGrant` type.
|
|
315
|
+
- **The broker's own test stand** (`tests/stand/`, `npm run test:stand`):
|
|
316
|
+
Cloud Foundry UAA and Keycloak in Docker, copied from auth-providers' stand,
|
|
317
|
+
with suites running the UAA grants end to end through auth-stores 3.1.0's
|
|
318
|
+
file stores — the binding asserted in the session file, and a session bound
|
|
319
|
+
to another URL not reused — the OIDC grants against Keycloak, and the SAML
|
|
320
|
+
grants with Keycloak as identity provider and UAA as service provider
|
|
321
|
+
(`saml2_pure`'s cookies a real UAA session from its web SSO ACS;
|
|
322
|
+
`saml2_bearer` exchanged at UAA's bearer grant); a CI workflow runs the build-and-test gate and the stand on every
|
|
323
|
+
push and pull request. Not shipped in the package.
|
|
324
|
+
|
|
325
|
+
### Changed
|
|
326
|
+
|
|
327
|
+
- **`provider` is optional.** `getProvider` does not use it. With it, the
|
|
328
|
+
token API asks it, as in 3.x; without it, the token API asks
|
|
329
|
+
`getProvider`'s provider (above). With neither a `provider` nor a
|
|
330
|
+
`serviceKeyStore`, `getToken` and `refreshToken` throw
|
|
331
|
+
`DestinationConfigError` naming both.
|
|
332
|
+
- **Breaking: the token API refuses a destination stated `basic` or `snc`.**
|
|
333
|
+
It reads the destination's `authType` from the key store first and, for
|
|
334
|
+
`basic` or `snc`, throws `DestinationConfigError` naming `authType` before
|
|
335
|
+
any provider is asked — 3.x asked the provider and wrote `authType: 'jwt'`
|
|
336
|
+
and a token over the session of a destination that holds no token. A key
|
|
337
|
+
store that states no `authType` (a 3.x setup with a consumer `provider`),
|
|
338
|
+
or no key store at all, is served as before.
|
|
339
|
+
- **Breaking: the token API writes the session secret alone.** With a
|
|
340
|
+
consumer `provider` it still writes after every `getTokens()` /
|
|
341
|
+
`refreshTokens()`, cache hits included, but through the same write path as
|
|
342
|
+
`getProvider`'s providers: one `saveSession(destination, {
|
|
343
|
+
authorizationToken | sessionCookies, expiresAt, refreshToken, issuedFor,
|
|
344
|
+
issuedBy })`, retried when the store fails and awaited by `flush()`. 3.x
|
|
345
|
+
wrote the session's connection config back with `serviceUrl` and a derived
|
|
346
|
+
`authType` (`setConnectionConfig`), and a client the session held with the
|
|
347
|
+
new refresh token (`setAuthorizationConfig`); neither method is called any
|
|
348
|
+
more. A result without a refresh token carries the stored one forward.
|
|
349
|
+
`issuedFor` is the `serviceUrl` the token API resolved, with the SAP client
|
|
350
|
+
(the session's, else the key store's); `issuedBy` is the client a factory
|
|
351
|
+
was handed (`uaaUrl` with `client_id`) — an instance is handed no client,
|
|
352
|
+
so its tokens are bound to no issuer and `getProvider` never seeds from
|
|
353
|
+
them. Both are fixed when the provider is built for the destination (an
|
|
354
|
+
instance: at its first call for it) and kept with it, so a URL or client
|
|
355
|
+
changed later never re-labels a token obtained before the change; a new
|
|
356
|
+
broker picks the change up. A failed write is thrown to the caller as the store raised it, as in
|
|
357
|
+
3.x, and retried.
|
|
358
|
+
- The token API's reads are 3.x's, and so are its answers: the session first,
|
|
359
|
+
then the key store, for `serviceUrl` and the client a factory is seeded
|
|
360
|
+
with; the factory called once per destination — now cached as the promise
|
|
361
|
+
of its build, so concurrent first calls build once — and again after one
|
|
362
|
+
that threw; an instance used for every destination; the provider's errors
|
|
363
|
+
and a store's failures propagate unchanged; a result without a token is an
|
|
364
|
+
error. `createTokenRefresher(destination)` is unchanged and not
|
|
365
|
+
deprecated.
|
|
366
|
+
- **Breaking: `getConnectionConfig` / `getAuthorizationConfig` compose the two
|
|
367
|
+
stores** instead of answering the session's configuration whole (and the
|
|
368
|
+
key's only when the session had none). `getConnectionConfig` is the key
|
|
369
|
+
store's means with the session's `authorizationToken`, `sessionCookies` and
|
|
370
|
+
`expiresAt` laid over them; `getAuthorizationConfig` is the key store's
|
|
371
|
+
client with the session's refresh token, `null` without a client in the key
|
|
372
|
+
store. Means a session store answers are not read, nor a secret a key store
|
|
373
|
+
answers. A consumer that kept the URL or the client only in its session
|
|
374
|
+
store gets them from a key store now.
|
|
375
|
+
- **Contracts from their 2026-10 packages.** `@mcp-abap-adt/interfaces-auth`
|
|
376
|
+
`^3.0.0` (was `^2.1.0`), `@mcp-abap-adt/interfaces-auth-sap` `^2.0.0` (was
|
|
377
|
+
`^1.0.1`), and `@mcp-abap-adt/interfaces-auth-broker` `^1.1.0` (new; 1.1.0
|
|
378
|
+
for `issuedFor` / `issuedBy`). The
|
|
379
|
+
store contracts — `IConfig`, `IConnectionConfig`, `ISessionStore`,
|
|
380
|
+
`IServiceKeyStore` — moved out of `interfaces-auth-sap` into
|
|
381
|
+
`interfaces-auth-broker`; this package now takes them from there.
|
|
382
|
+
`IAuthorizationConfig` and `AuthType` stay in `interfaces-auth-sap`. The
|
|
383
|
+
re-exported names are unchanged; only where they come from. A consumer that
|
|
384
|
+
imports them from this package changes nothing; one that imports
|
|
385
|
+
`ISessionStore` and friends from `interfaces-auth-sap` itself takes them
|
|
386
|
+
from `interfaces-auth-broker` from its 2.0.0 on.
|
|
387
|
+
- `@mcp-abap-adt/auth-providers` `^5.2.1` (was `^4.2.0`, a dev dependency) is
|
|
388
|
+
a runtime dependency: `getProvider` constructs its classes. None is
|
|
389
|
+
re-exported. 5.2.0 because `ClientCredentialsProvider` takes a `logger`
|
|
390
|
+
from it: the broker passes its own, so a `client_credentials` destination's
|
|
391
|
+
token lifecycle is logged like the other grants' (tokens redacted by the
|
|
392
|
+
provider); 5.2.1 because 5.2.0 skipped OIDC discovery unless every
|
|
393
|
+
device-flow endpoint was missing, so a `device_code` destination stating
|
|
394
|
+
its issuer and one explicit endpoint was refused at `prepare()`. The library's tests run against `@mcp-abap-adt/auth-stores`
|
|
395
|
+
`^3.2.0` (a dev dependency, was `^1.2.3`; the `auth-stores-3` alias the
|
|
396
|
+
stand, live and binding suites used is gone): its session stores hold the
|
|
397
|
+
secret alone, which is all the broker writes now. The carried-over on-disk
|
|
398
|
+
case reads the means from an `EnvDestinationStore` and asserts the session
|
|
399
|
+
file holds the secret and its binding and nothing else. Dev dependency
|
|
400
|
+
`@mcp-abap-adt/connection` `^10.0.3` (was `^10.0.2`), for the end-to-end
|
|
401
|
+
test: a connector built from `getProvider`, a 401 renewed in `rejected()`,
|
|
402
|
+
the new token in the session file, and the token API answering it.
|
|
403
|
+
- Comments no longer name `@mcp-abap-adt/auth-stores-btp` / `-xsuaa` (which do
|
|
404
|
+
not exist) or the deleted `@mcp-abap-adt/interfaces` facade.
|
|
405
|
+
- Dev dependency `@mcp-abap-adt/auth-mocks` `^0.3.0` (new): the SAML grants'
|
|
406
|
+
unit tests validate assertions its identity provider signs — one signing
|
|
407
|
+
the Response, one the Assertion alone — rather than documents the suite
|
|
408
|
+
signs itself. Not shipped.
|
|
409
|
+
|
|
14
410
|
## [3.1.0] - 2026-10-01
|
|
15
411
|
|
|
16
412
|
**Why a minor.** The library's API is unchanged: every export, signature and
|