@mcp-abap-adt/auth-providers 3.0.0 → 4.1.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 +187 -0
- package/README.md +773 -77
- package/dist/auth/saml2Auth.d.ts +6 -2
- package/dist/auth/saml2Auth.d.ts.map +1 -1
- package/dist/auth/saml2Auth.js +9 -20
- package/dist/auth/samlBearerAssertion.d.ts.map +1 -1
- package/dist/auth/samlBearerAssertion.js +6 -2
- package/dist/auth/strictXml.d.ts +13 -0
- package/dist/auth/strictXml.d.ts.map +1 -0
- package/dist/auth/strictXml.js +21 -0
- package/dist/errors/AssertionValidationError.d.ts +15 -0
- package/dist/errors/AssertionValidationError.d.ts.map +1 -0
- package/dist/errors/AssertionValidationError.js +24 -0
- package/dist/errors/TokenProviderErrors.d.ts +2 -0
- package/dist/errors/TokenProviderErrors.d.ts.map +1 -1
- package/dist/errors/TokenProviderErrors.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/providers/Saml2BearerProvider.d.ts +1 -0
- package/dist/providers/Saml2BearerProvider.d.ts.map +1 -1
- package/dist/providers/Saml2BearerProvider.js +17 -2
- package/dist/providers/Saml2PureProvider.d.ts +1 -0
- package/dist/providers/Saml2PureProvider.d.ts.map +1 -1
- package/dist/providers/Saml2PureProvider.js +19 -5
- package/dist/providers/saml2Utils.d.ts +50 -2
- package/dist/providers/saml2Utils.d.ts.map +1 -1
- package/dist/providers/saml2Utils.js +102 -2
- package/dist/validation/assertionValidator.d.ts +28 -0
- package/dist/validation/assertionValidator.d.ts.map +1 -0
- package/dist/validation/assertionValidator.js +519 -0
- package/dist/validation/documentIds.d.ts +15 -0
- package/dist/validation/documentIds.d.ts.map +1 -0
- package/dist/validation/documentIds.js +32 -0
- package/dist/validation/inMemoryReplayStore.d.ts +22 -0
- package/dist/validation/inMemoryReplayStore.d.ts.map +1 -0
- package/dist/validation/inMemoryReplayStore.js +49 -0
- package/dist/validation/signedNode.d.ts +54 -0
- package/dist/validation/signedNode.d.ts.map +1 -0
- package/dist/validation/signedNode.js +182 -0
- package/dist/validation/xsdDateTime.d.ts +17 -0
- package/dist/validation/xsdDateTime.d.ts.map +1 -0
- package/dist/validation/xsdDateTime.js +67 -0
- package/package.json +5 -10
- package/bin/auth-authorization-code.ts +0 -147
- package/bin/auth-client-credentials.ts +0 -109
- package/bin/utils/parseConfig.ts +0 -270
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,193 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.1.0] - 2026-09-26
|
|
11
|
+
|
|
12
|
+
Three refusals get stricter; no export, configuration field or error class
|
|
13
|
+
changes. The README's *Upgrading from 4.0 to 4.1* lists what now fails.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Every refusal names its rule.** No two rules under one `check` share a
|
|
18
|
+
message. An element that must appear exactly once says which way it failed
|
|
19
|
+
— absent, or more than one — for the Response's direct-child `Assertion`,
|
|
20
|
+
each signature's `ds:Reference`, `Status`, `StatusCode`, the assertion's
|
|
21
|
+
`Issuer`, `Conditions`, `Subject` and each bearer candidate's
|
|
22
|
+
`SubjectConfirmationData`. An empty `Issuer`, a `StatusCode` without a
|
|
23
|
+
`Value`, an `AudienceRestriction` naming no audience, and an absent versus
|
|
24
|
+
an invalid `Conditions/@NotOnOrAfter` each get their own message. The
|
|
25
|
+
signed-node refusal names the element the validator requires
|
|
26
|
+
(`samlp:Response` or `saml:Assertion`). `check` is unchanged for every
|
|
27
|
+
refusal, and every document 4.0.0 refused is still refused; code matching
|
|
28
|
+
on message text must match on `check`. The README lists every message
|
|
29
|
+
the shipped validators produce.
|
|
30
|
+
- **`bearerConfirmation` says why every candidate failed.** Still
|
|
31
|
+
existential: one confirmation passing every sub-rule is enough, and every
|
|
32
|
+
candidate is evaluated. A refusal lists each candidate in document order
|
|
33
|
+
with the first sub-rule it failed, in a fixed order, at most five, then
|
|
34
|
+
`and N more`. A `Subject` absent or repeated, and a `Subject` holding no
|
|
35
|
+
`SubjectConfirmation`, have messages of their own.
|
|
36
|
+
- **Every document value a message interpolates is quoted and cut**
|
|
37
|
+
(`quoteUntrusted`: JSON-quoted, 64 characters at most), including the
|
|
38
|
+
post-signature `Status` code, issuer and `Destination`, the `Conditions`
|
|
39
|
+
dates, xml-crypto's own messages about a malformed `Signature`, which embed
|
|
40
|
+
the offending element, and the parser message in `Saml2BearerProvider`'s
|
|
41
|
+
bearer conversion.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- A SAML 1.x `Assertion` (`urn:oasis:names:tc:SAML:1.0:assertion`) outside
|
|
46
|
+
the signed assertion is refused at `signedNode`, as a SAML 2.0 one already
|
|
47
|
+
was. Before, one in `Extensions` passed either validator.
|
|
48
|
+
- An `Assertion` or `EncryptedAssertion` (SAML 2.0) or a SAML 1.x `Assertion`
|
|
49
|
+
inside a `ds:Signature` is refused at `signedNode`. An enveloped signature
|
|
50
|
+
leaves its own subtree out of the digest, so an element in `ds:Object`
|
|
51
|
+
there is unsigned however deep inside the signed assertion it sits; before,
|
|
52
|
+
such an element inside the signed assertion's own `ds:Signature` passed
|
|
53
|
+
either validator, `Saml2BearerProvider`'s default included.
|
|
54
|
+
- `idpInitiated: true` with `authnRequestId` is refused when the provider is
|
|
55
|
+
constructed — a `ValidationError` with `missingFields: ['idpInitiated']` —
|
|
56
|
+
not after `authorize()` returns, so before the user has been through the
|
|
57
|
+
browser.
|
|
58
|
+
- A malformed `Signature` whose `loadSignature` throws something other than
|
|
59
|
+
an `Error` is refused at `signature` quoting what was thrown. Before, the
|
|
60
|
+
refusal carried an empty message, and a thrown `null` or `undefined`
|
|
61
|
+
escaped as a `TypeError` instead of an `AssertionValidationError`.
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
|
|
65
|
+
- The `bin` commands `auth-authorization-code` and `auth-client-credentials`
|
|
66
|
+
(the `bin` field, and `bin` in `files`), and the devDependency `tsx`. They
|
|
67
|
+
never ran from an npm install: they were `.ts` files with a `tsx` shebang,
|
|
68
|
+
importing `src/`, which is not published. Not a breaking change for that
|
|
69
|
+
reason.
|
|
70
|
+
|
|
71
|
+
### Documentation
|
|
72
|
+
|
|
73
|
+
- `ValidatedAssertion.nameId` is `undefined` when the `Subject` carries no
|
|
74
|
+
`NameID` or more than one. `NameID` is surfaced, never refused.
|
|
75
|
+
|
|
76
|
+
### Development
|
|
77
|
+
|
|
78
|
+
- `@mcp-abap-adt/interfaces-auth` `^2.0.1`, whose
|
|
79
|
+
`IAssertionReplayStore.recordIfUnseen` JSDoc now describes the retention this
|
|
80
|
+
package implements: until the last instant a validator would still accept
|
|
81
|
+
the assertion, not its expiry.
|
|
82
|
+
- `@mcp-abap-adt/auth-stores` stays a devDependency: three test suites import
|
|
83
|
+
`AbapServiceKeyStore` from it.
|
|
84
|
+
- The end-to-end SAML suite (`samlValidation.test.ts`) runs about 6× faster,
|
|
85
|
+
about 41 s down to about 7 s: it starts one mock identity provider per
|
|
86
|
+
signed element in `beforeAll` and switches variants, instead of one per
|
|
87
|
+
test, each generating an RSA key. Every test still gets its own replay
|
|
88
|
+
store.
|
|
89
|
+
- The Keycloak suite trusts only the certificates under `KeyDescriptor
|
|
90
|
+
use="signing"` in Keycloak's metadata.
|
|
91
|
+
|
|
92
|
+
## [4.0.0] - 2026-09-25
|
|
93
|
+
|
|
94
|
+
### Breaking
|
|
95
|
+
|
|
96
|
+
- **Both SAML providers validate the assertion before trusting it** (#19).
|
|
97
|
+
Until now the only check was that the payload was a non-empty string, and
|
|
98
|
+
`Saml2PureProvider` took its session lifetime from a regular expression over
|
|
99
|
+
the unverified XML. `Saml2BearerProvider` now validates before the token
|
|
100
|
+
exchange, `Saml2PureProvider` before `cookieProvider`, through twelve checks:
|
|
101
|
+
document shape, unique IDs, signature, that the signed node is the node read,
|
|
102
|
+
`Status`, assertion ID, issuer, `Conditions`, `NotBefore`, `NotOnOrAfter`,
|
|
103
|
+
audience, one bearer subject confirmation (request ID, recipient, window) and
|
|
104
|
+
`Destination`, then replay. The README's *SAML assertion validation* lists
|
|
105
|
+
them with what refuses each.
|
|
106
|
+
|
|
107
|
+
**The configuration is required.** Supply `idpCertificates` (PEM or bare
|
|
108
|
+
base64 DER, a list for key rotation) and `idpEntityId`, or an
|
|
109
|
+
`assertionValidator` of your own; without them the provider's constructor
|
|
110
|
+
throws a `ValidationError` naming what is missing. A shipped validator
|
|
111
|
+
supplied as `assertionValidator` still needs `idpEntityId`, and its absence
|
|
112
|
+
fails at construction too; only a custom validator does without.
|
|
113
|
+
`spEntityId` becomes the `Audience` the assertion must name.
|
|
114
|
+
|
|
115
|
+
**Request IDs.** `InResponseTo` must answer the AuthnRequest the package
|
|
116
|
+
minted, or `authnRequestId` when the package did not build the request — a
|
|
117
|
+
pre-built `authorizationUrl`, or a strategy that returns a payload without
|
|
118
|
+
calling `buildAuthorizationUrl`. `idpInitiated: true` declares that no request
|
|
119
|
+
was sent, so the assertion must carry no `InResponseTo`; it gives up the
|
|
120
|
+
login-CSRF defence of a request ID, and is never inferred.
|
|
121
|
+
`Saml2BearerProvider` against UAA or XSUAA needs it, since both refuse an
|
|
122
|
+
assertion carrying `InResponseTo`. Having neither an ID nor the declaration,
|
|
123
|
+
or combining the declaration with an ID, raises a `ValidationError`. With
|
|
124
|
+
`idpInitiated` and no `authorizationUrl`, a strategy that calls
|
|
125
|
+
`buildAuthorizationUrl` is refused inside the builder, before any URL is
|
|
126
|
+
produced — so before a browser opens.
|
|
127
|
+
|
|
128
|
+
**Status, by validator.** Under `createSignedResponseValidator` —
|
|
129
|
+
`Saml2PureProvider`'s default — an identity provider returning a
|
|
130
|
+
non-`Success` status is now refused, where it was accepted before.
|
|
131
|
+
`Saml2BearerProvider`'s default, `createSignedAssertionValidator`, does not
|
|
132
|
+
read `Status`, so a bearer consumer sees no change there: a declining identity
|
|
133
|
+
provider mints no signed assertion, and the login fails for want of one.
|
|
134
|
+
|
|
135
|
+
**Migrating:** see *Migrating from 3.x to 4.0* in the README — add the trust
|
|
136
|
+
configuration, check `spEntityId`, and for `Saml2BearerProvider` against UAA
|
|
137
|
+
or XSUAA add `idpInitiated: true` with a strategy that never calls
|
|
138
|
+
`buildAuthorizationUrl`.
|
|
139
|
+
- **`parseSamlNotOnOrAfter` is removed.** `Saml2PureProvider`'s `expiresAt` is
|
|
140
|
+
now the validated assertion's expiry — the earlier of `Conditions/@NotOnOrAfter`
|
|
141
|
+
and the accepted bearer confirmation's — read from the verified document.
|
|
142
|
+
- **`buildSamlAuthorizationUrl` returns `{ url, requestId? }`**, since the
|
|
143
|
+
minted ID must survive to validation, and **`getSamlAssertion` returns
|
|
144
|
+
`Promise<SamlAssertionResult>`** (payload, request ID, ACS) instead of
|
|
145
|
+
`Promise<string>`. None of these, nor `parseSamlNotOnOrAfter`, was exported
|
|
146
|
+
from the package root; only a deep import of `dist/auth/saml2Auth` or
|
|
147
|
+
`dist/providers/saml2Utils` is affected.
|
|
148
|
+
- **`@mcp-abap-adt/interfaces-auth` `^2.0.0`** (was `^1.2.0`), where
|
|
149
|
+
`AssertionContext.expectedInResponseTo` is optional — a breaking change for
|
|
150
|
+
implementers of `IAssertionValidator`, which must refuse an assertion
|
|
151
|
+
carrying `InResponseTo` when it is absent.
|
|
152
|
+
|
|
153
|
+
### Added
|
|
154
|
+
|
|
155
|
+
- **`createSignedResponseValidator`** and **`createSignedAssertionValidator`**,
|
|
156
|
+
sharing `ShippedValidatorOptions` (`idpCertificates`, `clockSkewMs`,
|
|
157
|
+
`replayStore`). The first requires the signature to cover the `Response` and
|
|
158
|
+
performs all twelve checks; the second accepts a signature over the
|
|
159
|
+
`Assertion` — bare, or inside a Response — and does not read `Status`,
|
|
160
|
+
`Response/Issuer` or `Destination` at all. `Saml2PureProvider` defaults to the
|
|
161
|
+
first, `Saml2BearerProvider` to the second; `assertionValidator` replaces
|
|
162
|
+
either.
|
|
163
|
+
- **Replay detection**: `defaultReplayStore`, a process-wide in-memory store
|
|
164
|
+
shared by every default validator, keyed by `{issuer, assertionId}` and
|
|
165
|
+
retained until the earlier of `Conditions/@NotOnOrAfter` and the latest
|
|
166
|
+
`NotOnOrAfter` of a bearer confirmation that answers the request and names
|
|
167
|
+
the ACS, plus `clockSkewMs` — as long as the assertion could still be
|
|
168
|
+
accepted, which can outlast `expiresAt`; `createInMemoryReplayStore()` for
|
|
169
|
+
an isolated one; `assertionReplayStore` on the providers for a shared store
|
|
170
|
+
across processes.
|
|
171
|
+
- **`clockSkewMs`**, default `0`.
|
|
172
|
+
- Any XML parse fault — including one `@xmldom/xmldom` would otherwise repair,
|
|
173
|
+
such as an undeclared entity — is a refusal at `document`, and the parser
|
|
174
|
+
never writes to the console; the bearer conversion (`toBearerAssertion`)
|
|
175
|
+
parses the same way.
|
|
176
|
+
- The validators refuse a payload carrying a `<!DOCTYPE` declaration, never
|
|
177
|
+
take a signing certificate from the document's own `KeyInfo`, and accept
|
|
178
|
+
RSA-SHA1 signatures and SHA-1 digests, as `xml-crypto` does by default; a
|
|
179
|
+
consumer wanting to refuse SHA-1 wraps a shipped validator in its own.
|
|
180
|
+
- **`AssertionValidationError`**, with `check: AssertionCheck` naming the check
|
|
181
|
+
that refused the assertion, and code `ASSERTION_VALIDATION_ERROR`.
|
|
182
|
+
- `xml-crypto` becomes a runtime dependency, for signature verification.
|
|
183
|
+
|
|
184
|
+
### Development
|
|
185
|
+
|
|
186
|
+
- Both validators run end to end in `npm test`, through `Saml2PureProvider`
|
|
187
|
+
and a real callback, against responses from `@mcp-abap-adt/auth-mocks` (the
|
|
188
|
+
devDependency and its range are unchanged). Every corruption variant is refused at the
|
|
189
|
+
check it targets, except `statusFailure` and `wrongDestination`, which the
|
|
190
|
+
assertion-only validator accepts — both halves asserted.
|
|
191
|
+
- The provider stand's SAML suites run every login through the provider's
|
|
192
|
+
validation, including Keycloak's real responses, signed at both levels; the
|
|
193
|
+
live XSUAA suite does the same with its per-run identity provider, and now
|
|
194
|
+
refuses the `InResponseTo` case itself, before XSUAA sees it.
|
|
195
|
+
- `@mcp-abap-adt/interfaces-auth-sap` `^1.0.1`.
|
|
196
|
+
|
|
10
197
|
## [3.0.0] - 2026-09-24
|
|
11
198
|
|
|
12
199
|
### Added
|