@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/README.md +773 -77
  3. package/dist/auth/saml2Auth.d.ts +6 -2
  4. package/dist/auth/saml2Auth.d.ts.map +1 -1
  5. package/dist/auth/saml2Auth.js +9 -20
  6. package/dist/auth/samlBearerAssertion.d.ts.map +1 -1
  7. package/dist/auth/samlBearerAssertion.js +6 -2
  8. package/dist/auth/strictXml.d.ts +13 -0
  9. package/dist/auth/strictXml.d.ts.map +1 -0
  10. package/dist/auth/strictXml.js +21 -0
  11. package/dist/errors/AssertionValidationError.d.ts +15 -0
  12. package/dist/errors/AssertionValidationError.d.ts.map +1 -0
  13. package/dist/errors/AssertionValidationError.js +24 -0
  14. package/dist/errors/TokenProviderErrors.d.ts +2 -0
  15. package/dist/errors/TokenProviderErrors.d.ts.map +1 -1
  16. package/dist/errors/TokenProviderErrors.js +3 -1
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +11 -1
  20. package/dist/providers/Saml2BearerProvider.d.ts +1 -0
  21. package/dist/providers/Saml2BearerProvider.d.ts.map +1 -1
  22. package/dist/providers/Saml2BearerProvider.js +17 -2
  23. package/dist/providers/Saml2PureProvider.d.ts +1 -0
  24. package/dist/providers/Saml2PureProvider.d.ts.map +1 -1
  25. package/dist/providers/Saml2PureProvider.js +19 -5
  26. package/dist/providers/saml2Utils.d.ts +50 -2
  27. package/dist/providers/saml2Utils.d.ts.map +1 -1
  28. package/dist/providers/saml2Utils.js +102 -2
  29. package/dist/validation/assertionValidator.d.ts +28 -0
  30. package/dist/validation/assertionValidator.d.ts.map +1 -0
  31. package/dist/validation/assertionValidator.js +519 -0
  32. package/dist/validation/documentIds.d.ts +15 -0
  33. package/dist/validation/documentIds.d.ts.map +1 -0
  34. package/dist/validation/documentIds.js +32 -0
  35. package/dist/validation/inMemoryReplayStore.d.ts +22 -0
  36. package/dist/validation/inMemoryReplayStore.d.ts.map +1 -0
  37. package/dist/validation/inMemoryReplayStore.js +49 -0
  38. package/dist/validation/signedNode.d.ts +54 -0
  39. package/dist/validation/signedNode.d.ts.map +1 -0
  40. package/dist/validation/signedNode.js +182 -0
  41. package/dist/validation/xsdDateTime.d.ts +17 -0
  42. package/dist/validation/xsdDateTime.d.ts.map +1 -0
  43. package/dist/validation/xsdDateTime.js +67 -0
  44. package/package.json +5 -10
  45. package/bin/auth-authorization-code.ts +0 -147
  46. package/bin/auth-client-credentials.ts +0 -109
  47. 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
- return (0, saml2Auth_1.buildSamlAuthorizationUrl)({
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
- return outcome.payload;
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"}