@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
|
@@ -4,16 +4,69 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.validateSamlConfig = validateSamlConfig;
|
|
7
|
+
exports.resolveAssertionValidator = resolveAssertionValidator;
|
|
7
8
|
exports.resolveTokenUrl = resolveTokenUrl;
|
|
8
9
|
exports.getSamlAssertion = getSamlAssertion;
|
|
9
10
|
const saml2Auth_1 = require("../auth/saml2Auth");
|
|
11
|
+
const TokenProviderErrors_1 = require("../errors/TokenProviderErrors");
|
|
10
12
|
const strategies_1 = require("../strategies");
|
|
13
|
+
const assertionValidator_1 = require("../validation/assertionValidator");
|
|
11
14
|
/** Throw at construction rather than half-verify at runtime. */
|
|
12
15
|
function validateSamlConfig(config) {
|
|
13
16
|
if (config.authorizationUrl && !config.acsUrl) {
|
|
14
17
|
throw new Error('acsUrl is required when authorizationUrl is set: the ACS inside a ' +
|
|
15
18
|
'pre-built SAML request cannot be read, so it must be declared.');
|
|
16
19
|
}
|
|
20
|
+
// The runtime check in resolveExpectedRequestId stays for a direct caller
|
|
21
|
+
// of getSamlAssertion; through a provider this refuses first, before any
|
|
22
|
+
// browser opens.
|
|
23
|
+
if (config.idpInitiated && config.authnRequestId) {
|
|
24
|
+
throw new TokenProviderErrors_1.ValidationError('SAML idpInitiated is true and authnRequestId is set: an IdP-initiated ' +
|
|
25
|
+
'login sends no request, so the two describe different logins. ' +
|
|
26
|
+
'Remove one of them.', ['idpInitiated']);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The consumer's validator when supplied, otherwise the provider's default —
|
|
31
|
+
* `createSignedAssertionValidator` for `"bearer"`, since the token endpoint
|
|
32
|
+
* receives the Assertion alone (#40) and its own signature is what that
|
|
33
|
+
* endpoint verifies; `createSignedResponseValidator` for `"pure"`, since the
|
|
34
|
+
* whole response is handed on and `Status`/`Destination` must be inside a
|
|
35
|
+
* signature. See the spec's "A bare Assertion, and which validator each
|
|
36
|
+
* provider defaults to".
|
|
37
|
+
*/
|
|
38
|
+
function resolveAssertionValidator(config, provider) {
|
|
39
|
+
if (config.assertionValidator) {
|
|
40
|
+
// A shipped validator fails closed without expectedIssuer, so supplying
|
|
41
|
+
// one without idpEntityId would construct fine and then refuse every
|
|
42
|
+
// login at `issuer` — after the browser step. A custom validator needs
|
|
43
|
+
// no idpEntityId: it may establish trust some other way.
|
|
44
|
+
if ((0, assertionValidator_1.isShippedValidator)(config.assertionValidator) && !config.idpEntityId) {
|
|
45
|
+
throw new TokenProviderErrors_1.ValidationError('The supplied assertionValidator is a shipped one ' +
|
|
46
|
+
'(createSignedResponseValidator or createSignedAssertionValidator), ' +
|
|
47
|
+
'which refuses every assertion without an expected issuer: missing ' +
|
|
48
|
+
'idpEntityId.', ['idpEntityId']);
|
|
49
|
+
}
|
|
50
|
+
return config.assertionValidator;
|
|
51
|
+
}
|
|
52
|
+
const missing = [];
|
|
53
|
+
if (!config.idpCertificates?.length)
|
|
54
|
+
missing.push('idpCertificates');
|
|
55
|
+
if (!config.idpEntityId)
|
|
56
|
+
missing.push('idpEntityId');
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
throw new TokenProviderErrors_1.ValidationError(`The default assertion validator needs the identity provider it should ` +
|
|
59
|
+
`trust: missing ${missing.join(', ')}. Supply these, or supply an ` +
|
|
60
|
+
`assertionValidator of your own.`, missing);
|
|
61
|
+
}
|
|
62
|
+
const options = {
|
|
63
|
+
idpCertificates: config.idpCertificates,
|
|
64
|
+
clockSkewMs: config.clockSkewMs,
|
|
65
|
+
replayStore: config.assertionReplayStore,
|
|
66
|
+
};
|
|
67
|
+
return provider === 'bearer'
|
|
68
|
+
? (0, assertionValidator_1.createSignedAssertionValidator)(options)
|
|
69
|
+
: (0, assertionValidator_1.createSignedResponseValidator)(options);
|
|
17
70
|
}
|
|
18
71
|
function resolveTokenUrl(config) {
|
|
19
72
|
if (config.tokenUrl) {
|
|
@@ -26,9 +79,21 @@ function resolveTokenUrl(config) {
|
|
|
26
79
|
}
|
|
27
80
|
async function getSamlAssertion(config) {
|
|
28
81
|
const declaredAcs = config.acsUrl;
|
|
82
|
+
let mintedRequestId;
|
|
29
83
|
const request = {
|
|
30
84
|
logger: config.logger,
|
|
31
85
|
buildAuthorizationUrl: async (redirectUri) => {
|
|
86
|
+
// An IdP-initiated login sends no AuthnRequest, and without a pre-built
|
|
87
|
+
// authorizationUrl the only URL this could produce is one carrying a
|
|
88
|
+
// freshly minted request. Refused here, before any URL exists, so the
|
|
89
|
+
// mistake surfaces before a browser opens rather than after a login.
|
|
90
|
+
if (config.idpInitiated && !config.authorizationUrl) {
|
|
91
|
+
throw new TokenProviderErrors_1.ValidationError('SAML idpInitiated is true and no authorizationUrl is configured, ' +
|
|
92
|
+
'but the authorization strategy asked for an authorization URL: ' +
|
|
93
|
+
'the only one this package can build carries an AuthnRequest. ' +
|
|
94
|
+
'Configure the IdP-initiated SSO URL as authorizationUrl, or use a ' +
|
|
95
|
+
'strategy that does not call buildAuthorizationUrl.', ['authorizationUrl']);
|
|
96
|
+
}
|
|
32
97
|
// A declared ACS is registered with the IdP; the strategy must be
|
|
33
98
|
// listening exactly there, and an ephemeral port cannot be.
|
|
34
99
|
const acsUrl = declaredAcs ?? redirectUri;
|
|
@@ -36,13 +101,15 @@ async function getSamlAssertion(config) {
|
|
|
36
101
|
throw new Error(`SAML acsUrl is ${declaredAcs}, but the authorization strategy is ` +
|
|
37
102
|
`listening on ${redirectUri}. They must match.`);
|
|
38
103
|
}
|
|
39
|
-
|
|
104
|
+
const built = (0, saml2Auth_1.buildSamlAuthorizationUrl)({
|
|
40
105
|
idpSsoUrl: config.idpSsoUrl,
|
|
41
106
|
spEntityId: config.spEntityId,
|
|
42
107
|
acsUrl,
|
|
43
108
|
relayState: config.relayState,
|
|
44
109
|
authorizationUrl: config.authorizationUrl,
|
|
45
110
|
});
|
|
111
|
+
mintedRequestId = built.requestId;
|
|
112
|
+
return built.url;
|
|
46
113
|
},
|
|
47
114
|
};
|
|
48
115
|
const supplied = config.authorization;
|
|
@@ -55,7 +122,12 @@ async function getSamlAssertion(config) {
|
|
|
55
122
|
throw new Error(`SAML acsUrl is ${declaredAcs}, but the authorization strategy used ` +
|
|
56
123
|
`${outcome.redirectUri}. They must match.`);
|
|
57
124
|
}
|
|
58
|
-
|
|
125
|
+
const requestId = resolveExpectedRequestId(config, mintedRequestId);
|
|
126
|
+
return {
|
|
127
|
+
payload: outcome.payload,
|
|
128
|
+
requestId,
|
|
129
|
+
acsUrl: outcome.redirectUri,
|
|
130
|
+
};
|
|
59
131
|
}
|
|
60
132
|
finally {
|
|
61
133
|
if (!supplied) {
|
|
@@ -67,3 +139,31 @@ async function getSamlAssertion(config) {
|
|
|
67
139
|
}
|
|
68
140
|
}
|
|
69
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* The ID `InResponseTo` must answer, from the three sources the spec allows:
|
|
144
|
+
* minted, declared, or none by explicit `idpInitiated: true`. Anything else —
|
|
145
|
+
* no ID and no declaration, or `idpInitiated` combined with an ID from either
|
|
146
|
+
* of the other two sources — is a configuration error, not a validation
|
|
147
|
+
* failure blamed on the assertion.
|
|
148
|
+
*/
|
|
149
|
+
function resolveExpectedRequestId(config, mintedRequestId) {
|
|
150
|
+
const declaredRequestId = config.authnRequestId;
|
|
151
|
+
if (config.idpInitiated) {
|
|
152
|
+
if (mintedRequestId || declaredRequestId) {
|
|
153
|
+
throw new TokenProviderErrors_1.ValidationError('SAML idpInitiated is true, but a request ID was also minted or ' +
|
|
154
|
+
'configured (an authorization strategy called buildAuthorizationUrl, ' +
|
|
155
|
+
'or authnRequestId is set). An IdP-initiated login sends no request, ' +
|
|
156
|
+
'so an ID means the configuration describes two different logins.', ['idpInitiated']);
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const requestId = mintedRequestId ?? declaredRequestId;
|
|
161
|
+
if (!requestId) {
|
|
162
|
+
throw new TokenProviderErrors_1.ValidationError('Cannot validate InResponseTo: this login did not build its own AuthnRequest, ' +
|
|
163
|
+
'so authnRequestId must be configured — or, if the identity provider ' +
|
|
164
|
+
'starts this login itself, idpInitiated: true. This happens with a ' +
|
|
165
|
+
'pre-built authorizationUrl, or an authorization strategy that supplies an ' +
|
|
166
|
+
'assertion without asking for a URL.', ['authnRequestId']);
|
|
167
|
+
}
|
|
168
|
+
return requestId;
|
|
169
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipped assertion validator: the spec's check table, in order.
|
|
3
|
+
*
|
|
4
|
+
* Two properties matter more than any individual check. First, the signature
|
|
5
|
+
* is resolved to an element and every assertion-level field is read *from that
|
|
6
|
+
* element* — a document holding a validly signed fragment beside a forged one
|
|
7
|
+
* is the wrapping attack, and reading the wrong node is how it succeeds.
|
|
8
|
+
* Second, no two refusals share a distinguishing phrase, so a test cannot pass
|
|
9
|
+
* for a neighbouring check's reason.
|
|
10
|
+
*
|
|
11
|
+
* Three fields live on the Response rather than the assertion: Status,
|
|
12
|
+
* Response/Issuer and Destination. The signed-Response validator reads them,
|
|
13
|
+
* because there they are inside the signature. The assertion-only validator
|
|
14
|
+
* does not read them at all — not weakly. That is safe because a declined
|
|
15
|
+
* login carries no assertion, so flipping Status buys an attacker nothing they
|
|
16
|
+
* can sign, and addressing rests on Recipient inside the signed assertion.
|
|
17
|
+
*/
|
|
18
|
+
import type { IAssertionReplayStore, IAssertionValidator } from '@mcp-abap-adt/interfaces-auth';
|
|
19
|
+
export interface ShippedValidatorOptions {
|
|
20
|
+
readonly idpCertificates: readonly string[];
|
|
21
|
+
readonly clockSkewMs?: number;
|
|
22
|
+
readonly replayStore?: IAssertionReplayStore;
|
|
23
|
+
}
|
|
24
|
+
/** Whether this validator came from one of the two shipped factories. */
|
|
25
|
+
export declare function isShippedValidator(validator: IAssertionValidator): boolean;
|
|
26
|
+
export declare const createSignedResponseValidator: (options: ShippedValidatorOptions) => IAssertionValidator;
|
|
27
|
+
export declare const createSignedAssertionValidator: (options: ShippedValidatorOptions) => IAssertionValidator;
|
|
28
|
+
//# sourceMappingURL=assertionValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertionValidator.d.ts","sourceRoot":"","sources":["../../src/validation/assertionValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AA8BvC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CAC9C;AAsCD,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAE1E;AAED,eAAO,MAAM,6BAA6B,GACxC,SAAS,uBAAuB,KAC/B,mBAAkE,CAAC;AAEtE,eAAO,MAAM,8BAA8B,GACzC,SAAS,uBAAuB,KAC/B,mBAAmE,CAAC"}
|