samlify 2.12.0 → 2.13.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 (84) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +41 -3
  3. package/build/src/api.js.map +1 -1
  4. package/build/src/binding-post.js +236 -182
  5. package/build/src/binding-post.js.map +1 -1
  6. package/build/src/binding-redirect.js +303 -215
  7. package/build/src/binding-redirect.js.map +1 -1
  8. package/build/src/binding-simplesign.js +285 -137
  9. package/build/src/binding-simplesign.js.map +1 -1
  10. package/build/src/entity-idp.js +130 -47
  11. package/build/src/entity-idp.js.map +1 -1
  12. package/build/src/entity-sp.js +81 -39
  13. package/build/src/entity-sp.js.map +1 -1
  14. package/build/src/entity.js +100 -62
  15. package/build/src/entity.js.map +1 -1
  16. package/build/src/extractor.js +118 -151
  17. package/build/src/extractor.js.map +1 -1
  18. package/build/src/flow.js +100 -96
  19. package/build/src/flow.js.map +1 -1
  20. package/build/src/libsaml.js +315 -259
  21. package/build/src/libsaml.js.map +1 -1
  22. package/build/src/metadata-idp.js +60 -30
  23. package/build/src/metadata-idp.js.map +1 -1
  24. package/build/src/metadata-sp.js +51 -41
  25. package/build/src/metadata-sp.js.map +1 -1
  26. package/build/src/metadata.js +47 -43
  27. package/build/src/metadata.js.map +1 -1
  28. package/build/src/options.js +73 -0
  29. package/build/src/options.js.map +1 -0
  30. package/build/src/urn.js +28 -1
  31. package/build/src/urn.js.map +1 -1
  32. package/build/src/utility.js +140 -85
  33. package/build/src/utility.js.map +1 -1
  34. package/build/src/validator.js +27 -10
  35. package/build/src/validator.js.map +1 -1
  36. package/package.json +16 -5
  37. package/types/src/api.d.ts +33 -3
  38. package/types/src/binding-post.d.ts +67 -34
  39. package/types/src/binding-redirect.d.ts +58 -31
  40. package/types/src/binding-simplesign.d.ts +77 -21
  41. package/types/src/entity-idp.d.ts +40 -31
  42. package/types/src/entity-sp.d.ts +37 -27
  43. package/types/src/entity.d.ts +71 -77
  44. package/types/src/extractor.d.ts +31 -22
  45. package/types/src/flow.d.ts +24 -2
  46. package/types/src/libsaml.d.ts +172 -118
  47. package/types/src/metadata-idp.d.ts +27 -11
  48. package/types/src/metadata-sp.d.ts +29 -19
  49. package/types/src/metadata.d.ts +59 -34
  50. package/types/src/options.d.ts +37 -0
  51. package/types/src/types.d.ts +250 -24
  52. package/types/src/urn.d.ts +7 -0
  53. package/types/src/utility.d.ts +139 -90
  54. package/types/src/validator.d.ts +21 -0
  55. package/.circleci/config.yml +0 -98
  56. package/.editorconfig +0 -19
  57. package/.github/FUNDING.yml +0 -1
  58. package/.github/workflows/deploy-docs.yml +0 -56
  59. package/.pre-commit.sh +0 -15
  60. package/.snyk +0 -4
  61. package/Makefile +0 -25
  62. package/index.ts +0 -28
  63. package/samlify-2.11.0.tgz +0 -0
  64. package/src/api.ts +0 -48
  65. package/src/binding-post.ts +0 -336
  66. package/src/binding-redirect.ts +0 -335
  67. package/src/binding-simplesign.ts +0 -231
  68. package/src/entity-idp.ts +0 -145
  69. package/src/entity-sp.ts +0 -114
  70. package/src/entity.ts +0 -243
  71. package/src/extractor.ts +0 -399
  72. package/src/flow.ts +0 -469
  73. package/src/libsaml.ts +0 -779
  74. package/src/metadata-idp.ts +0 -146
  75. package/src/metadata-sp.ts +0 -203
  76. package/src/metadata.ts +0 -166
  77. package/src/types.ts +0 -127
  78. package/src/urn.ts +0 -210
  79. package/src/utility.ts +0 -259
  80. package/src/validator.ts +0 -44
  81. package/tsconfig.json +0 -41
  82. package/tslint.json +0 -35
  83. package/types.d.ts +0 -2
  84. package/vitest.config.ts +0 -12
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Build Status](https://img.shields.io/circleci/build/github/tngan/samlify?style=for-the-badge&logo=circleci)](https://app.circleci.com/pipelines/github/tngan/samlify)
4
4
  [![npm version](https://img.shields.io/npm/v/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify)
5
5
  [![NPM](https://img.shields.io/npm/dm/samlify.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/samlify)
6
- [![Coverage Status](https://img.shields.io/coveralls/tngan/samlify/master.svg?style=for-the-badge&logo=coveralls)](https://coveralls.io/github/tngan/samlify?branch=master)
6
+ [![Coverage](https://img.shields.io/badge/coverage-%E2%89%A590%25-brightgreen?style=for-the-badge&logo=vitest)](./vitest.config.ts)
7
7
 
8
8
  Highly configuarable Node.js SAML 2.0 library for Single Sign On
9
9
 
package/build/src/api.js CHANGED
@@ -1,8 +1,24 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.getContext = getContext;
4
15
  exports.setSchemaValidator = setSchemaValidator;
5
16
  exports.setDOMParserOptions = setDOMParserOptions;
17
+ /**
18
+ * @file api.ts
19
+ * @author tngan
20
+ * @desc Global module configuration: XML schema validator and DOM parser.
21
+ */
6
22
  var xmldom_1 = require("@xmldom/xmldom");
7
23
  var XXE_SAFE_OPTIONS = {
8
24
  /**
@@ -17,20 +33,42 @@ var XXE_SAFE_OPTIONS = {
17
33
  };
18
34
  var context = {
19
35
  validate: undefined,
20
- dom: new xmldom_1.DOMParser(XXE_SAFE_OPTIONS)
36
+ dom: new xmldom_1.DOMParser(XXE_SAFE_OPTIONS),
21
37
  };
38
+ /**
39
+ * Return the module-wide runtime context (DOM parser and validator).
40
+ *
41
+ * @returns shared context object
42
+ */
22
43
  function getContext() {
23
44
  return context;
24
45
  }
46
+ /**
47
+ * Register the caller-supplied SAML schema validator. Throws when the
48
+ * supplied value does not expose a `validate` callback.
49
+ *
50
+ * @param params object with a `validate(xml)` callback
51
+ */
25
52
  function setSchemaValidator(params) {
26
53
  if (typeof params.validate !== 'function') {
27
54
  throw new Error('validate must be a callback function having one argument as xml input');
28
55
  }
29
- // assign the validate function to the context
30
56
  context.validate = params.validate;
31
57
  }
58
+ /**
59
+ * Replace the module-wide DOM parser with one configured by the caller.
60
+ *
61
+ * The XXE-safe error handlers are merged into the supplied options as a
62
+ * baseline so callers can override unrelated settings without
63
+ * accidentally disabling XXE protection (`saml-core §6.4`,
64
+ * `saml-sec-consider §6.3.1`). A caller can still opt out by passing
65
+ * its own `errorHandler`, but it must do so explicitly.
66
+ *
67
+ * @param options xmldom parser options
68
+ */
32
69
  function setDOMParserOptions(options) {
70
+ var _a;
33
71
  if (options === void 0) { options = {}; }
34
- context.dom = new xmldom_1.DOMParser(options);
72
+ context.dom = new xmldom_1.DOMParser(__assign(__assign(__assign({}, XXE_SAFE_OPTIONS), options), { errorHandler: (_a = options.errorHandler) !== null && _a !== void 0 ? _a : XXE_SAFE_OPTIONS.errorHandler }));
35
73
  }
36
74
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":";;AA8BA,gCAEC;AAED,gDASC;AAED,kDAEC;AA/CD,yCAA+E;AAa/E,IAAM,gBAAgB,GAAqB;IACzC;;;;OAIG;IACH,YAAY,EAAE;QACZ,KAAK,EAAE,UAAC,GAAW,IAAO,MAAM,IAAI,KAAK,CAAC,6BAAsB,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC;QACzE,UAAU,EAAE,UAAC,GAAW,IAAO,MAAM,IAAI,KAAK,CAAC,2BAAoB,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC;KAC7E;CACF,CAAC;AAEF,IAAM,OAAO,GAAY;IACvB,QAAQ,EAAE,SAAS;IACnB,GAAG,EAAE,IAAI,kBAAG,CAAC,gBAAgB,CAAC;CAC/B,CAAC;AAEF,SAAgB,UAAU;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,kBAAkB,CAAC,MAAwB;IAEzD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IAC3F,CAAC;IAED,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AAErC,CAAC;AAED,SAAgB,mBAAmB,CAAC,OAA8B;IAA9B,wBAAA,EAAA,YAA8B;IAChE,OAAO,CAAC,GAAG,GAAG,IAAI,kBAAG,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":";;;;;;;;;;;;;AA0CA,gCAEC;AAQD,gDAKC;AAaD,kDAMC;AA5ED;;;;GAIG;AACH,yCAA+E;AAe/E,IAAM,gBAAgB,GAAqB;IACzC;;;;OAIG;IACH,YAAY,EAAE;QACZ,KAAK,EAAE,UAAC,GAAW,IAAO,MAAM,IAAI,KAAK,CAAC,6BAAsB,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC;QACzE,UAAU,EAAE,UAAC,GAAW,IAAO,MAAM,IAAI,KAAK,CAAC,2BAAoB,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC;KAC7E;CACF,CAAC;AAEF,IAAM,OAAO,GAAY;IACvB,QAAQ,EAAE,SAAS;IACnB,GAAG,EAAE,IAAI,kBAAG,CAAC,gBAAgB,CAAC;CAC/B,CAAC;AAEF;;;;GAIG;AACH,SAAgB,UAAU;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,MAAwB;IACzD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AACrC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAAC,OAA8B;;IAA9B,wBAAA,EAAA,YAA8B;IAChE,OAAO,CAAC,GAAG,GAAG,IAAI,kBAAG,gCAChB,gBAAgB,GAChB,OAAO,KACV,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,gBAAgB,CAAC,YAAY,IACnE,CAAC;AACL,CAAC"}
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  /**
3
- * @file binding-post.ts
4
- * @author tngan
5
- * @desc Binding-level API, declare the functions using POST binding
6
- */
3
+ * @file binding-post.ts
4
+ * @author tngan
5
+ * @desc Binding-level API for SAML HTTP-POST. Builds base64 login/logout
6
+ * request and response payloads that callers embed in an auto-submitting
7
+ * HTML form.
8
+ */
7
9
  var __assign = (this && this.__assign) || function () {
8
10
  __assign = Object.assign || function(t) {
9
11
  for (var s, i = 1, n = arguments.length; i < n; i++) {
@@ -93,81 +95,115 @@ var libsaml_1 = __importDefault(require("./libsaml"));
93
95
  var utility_1 = __importStar(require("./utility"));
94
96
  var binding = urn_1.wording.binding;
95
97
  /**
96
- * @desc Generate a base64 encoded login request
97
- * @param {string} referenceTagXPath reference uri
98
- * @param {object} entity object includes both idp and sp
99
- * @param {function} customTagReplacement used when developers have their own login response template
100
- */
101
- function base64LoginRequest(referenceTagXPath, entity, customTagReplacement) {
98
+ * Generate a base64-encoded AuthnRequest for the HTTP-POST binding.
99
+ *
100
+ * @param referenceTagXPath XPath used when signing the request
101
+ * @param entity `{ idp, sp }` handles
102
+ * @param customTagReplacement optional custom template transformer
103
+ * @param forceAuthn per-request `ForceAuthn` flag (saml-core §3.4.1)
104
+ * @param assertionConsumerServiceIndex per-request ACS index (saml-core §3.4.1).
105
+ * Mutually exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`;
106
+ * when supplied, both of those attributes are dropped from the rendered XML.
107
+ * @returns id / base64-XML pair
108
+ */
109
+ function base64LoginRequest(referenceTagXPath, entity, customTagReplacement, forceAuthn, assertionConsumerServiceIndex) {
110
+ var _a, _b;
102
111
  var metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
103
112
  var spSetting = entity.sp.entitySetting;
104
113
  var id = '';
105
- if (metadata && metadata.idp && metadata.sp) {
106
- var base = metadata.idp.getSingleSignOnService(binding.post);
107
- var rawSamlRequest = void 0;
108
- if (spSetting.loginRequestTemplate && customTagReplacement) {
109
- var info = customTagReplacement(spSetting.loginRequestTemplate.context);
110
- id = (0, utility_1.get)(info, 'id', null);
111
- rawSamlRequest = (0, utility_1.get)(info, 'context', null);
112
- }
113
- else {
114
- var nameIDFormat = spSetting.nameIDFormat;
115
- var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
116
- id = spSetting.generateID();
117
- rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginRequestTemplate.context, {
118
- ID: id,
119
- Destination: base,
120
- Issuer: metadata.sp.getEntityID(),
121
- IssueInstant: new Date().toISOString(),
122
- AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
123
- EntityID: metadata.sp.getEntityID(),
124
- AllowCreate: spSetting.allowCreate,
125
- NameIDFormat: selectedNameIDFormat
126
- });
127
- }
128
- if (metadata.idp.isWantAuthnRequestsSigned()) {
129
- var privateKey = spSetting.privateKey, privateKeyPass = spSetting.privateKeyPass, signatureAlgorithm = spSetting.requestSignatureAlgorithm, transformationAlgorithms = spSetting.transformationAlgorithms;
130
- return {
131
- id: id,
132
- context: libsaml_1.default.constructSAMLSignature({
133
- referenceTagXPath: referenceTagXPath,
134
- privateKey: privateKey,
135
- privateKeyPass: privateKeyPass,
136
- signatureAlgorithm: signatureAlgorithm,
137
- transformationAlgorithms: transformationAlgorithms,
138
- rawSamlMessage: rawSamlRequest,
139
- signingCert: metadata.sp.getX509Certificate('signing'),
140
- signatureConfig: spSetting.signatureConfig || {
141
- prefix: 'ds',
142
- location: { reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']", action: 'after' },
143
- }
144
- }),
145
- };
146
- }
147
- // No need to embeded XML signature
114
+ /* v8 ignore start */
115
+ if (!metadata.idp || !metadata.sp) {
116
+ throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
117
+ }
118
+ /* v8 ignore stop */
119
+ var base = metadata.idp.getSingleSignOnService(binding.post);
120
+ var rawSamlRequest;
121
+ if (customTagReplacement) {
122
+ // saml-bindings §3.5 — the AuthnRequest template is informative, not
123
+ // normative. Honour the callback regardless of whether the caller
124
+ // supplied a custom template (closes #549). Pass the user-supplied
125
+ // template when present; otherwise the library default.
126
+ var templateContext = (_b = (_a = spSetting.loginRequestTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : libsaml_1.default.defaultLoginRequestTemplate.context;
127
+ var info = customTagReplacement(templateContext);
128
+ id = (0, utility_1.get)(info, 'id');
129
+ rawSamlRequest = (0, utility_1.get)(info, 'context');
130
+ }
131
+ else {
132
+ var nameIDFormat = spSetting.nameIDFormat;
133
+ var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
134
+ id = spSetting.generateID();
135
+ // saml-core §3.4.1 — `AssertionConsumerServiceIndex` is mutually
136
+ // exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`.
137
+ // When the caller supplies the index we set the URL+ProtocolBinding
138
+ // tags to undefined so `replaceTagsByValue` drops both attributes
139
+ // from the rendered XML (closes #437).
140
+ var useAcsIndex = assertionConsumerServiceIndex !== undefined;
141
+ var tags = {
142
+ ID: id,
143
+ Destination: base,
144
+ Issuer: metadata.sp.getEntityID(),
145
+ IssueInstant: new Date().toISOString(),
146
+ ProtocolBinding: useAcsIndex
147
+ ? undefined
148
+ : 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
149
+ AssertionConsumerServiceURL: useAcsIndex
150
+ ? undefined
151
+ : metadata.sp.getAssertionConsumerService(binding.post),
152
+ AssertionConsumerServiceIndex: assertionConsumerServiceIndex,
153
+ EntityID: metadata.sp.getEntityID(),
154
+ AllowCreate: spSetting.allowCreate,
155
+ NameIDFormat: selectedNameIDFormat,
156
+ // saml-core §3.4.1 `replaceTagsByValue` drops the attribute when
157
+ // `forceAuthn` is undefined, matching `use="optional"`.
158
+ ForceAuthn: forceAuthn,
159
+ };
160
+ rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginRequestTemplate.context, tags);
161
+ }
162
+ if (metadata.idp.isWantAuthnRequestsSigned()) {
163
+ var privateKey = spSetting.privateKey, privateKeyPass = spSetting.privateKeyPass, signatureAlgorithm = spSetting.requestSignatureAlgorithm, transformationAlgorithms = spSetting.transformationAlgorithms;
148
164
  return {
149
165
  id: id,
150
- context: utility_1.default.base64Encode(rawSamlRequest),
166
+ context: libsaml_1.default.constructSAMLSignature({
167
+ referenceTagXPath: referenceTagXPath,
168
+ privateKey: privateKey,
169
+ privateKeyPass: privateKeyPass,
170
+ signatureAlgorithm: signatureAlgorithm,
171
+ transformationAlgorithms: transformationAlgorithms,
172
+ rawSamlMessage: rawSamlRequest,
173
+ signingCert: metadata.sp.getX509Certificate('signing'),
174
+ signatureConfig: spSetting.signatureConfig || {
175
+ prefix: 'ds',
176
+ location: { reference: "/*[local-name(.)='AuthnRequest']/*[local-name(.)='Issuer']", action: 'after' },
177
+ },
178
+ }),
151
179
  };
152
180
  }
153
- throw new Error('ERR_GENERATE_POST_LOGIN_REQUEST_MISSING_METADATA');
181
+ return {
182
+ id: id,
183
+ context: utility_1.default.base64Encode(rawSamlRequest),
184
+ };
154
185
  }
155
186
  /**
156
- * @desc Generate a base64 encoded login response
157
- * @param {object} requestInfo corresponding request, used to obtain the id
158
- * @param {object} entity object includes both idp and sp
159
- * @param {object} user current logged user (e.g. req.user)
160
- * @param {function} customTagReplacement used when developers have their own login response template
161
- * @param {boolean} encryptThenSign whether or not to encrypt then sign first (if signing). Defaults to sign-then-encrypt
162
- */
187
+ * Generate a base64-encoded login response for the HTTP-POST binding.
188
+ * Supports the sign-then-encrypt and encrypt-then-sign pipelines based on
189
+ * `encryptThenSign`.
190
+ *
191
+ * @param requestInfo parsed login request used to link `InResponseTo`
192
+ * @param entity `{ idp, sp }` handles
193
+ * @param user authenticated user
194
+ * @param customTagReplacement optional custom template transformer
195
+ * @param encryptThenSign when true, encrypt the assertion first then sign
196
+ * @returns id / base64-XML pair
197
+ */
163
198
  function base64LoginResponse() {
164
199
  return __awaiter(this, arguments, void 0, function (requestInfo, entity, user, customTagReplacement, encryptThenSign) {
165
- var idpSetting, spSetting, id, metadata, nameIDFormat, selectedNameIDFormat, base, rawSamlResponse, nowTime, spEntityID, fiveMinutesLaterTime, fiveMinutesLater, now, acl, tvalue, template, privateKey, privateKeyPass, signatureAlgorithm, config, context;
200
+ var idpSetting, spSetting, id, metadata, nameIDFormat, selectedNameIDFormat, base, rawSamlResponse, nowTime, spEntityID, fiveMinutesLaterTime, fiveMinutesLater, now, acl, tvalue, templateContext, template, baseTemplate, privateKey, privateKeyPass, signatureAlgorithm, config, context;
201
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
202
  if (requestInfo === void 0) { requestInfo = {}; }
167
203
  if (user === void 0) { user = {}; }
168
204
  if (encryptThenSign === void 0) { encryptThenSign = false; }
169
- return __generator(this, function (_a) {
170
- switch (_a.label) {
205
+ return __generator(this, function (_k) {
206
+ switch (_k.label) {
171
207
  case 0:
172
208
  idpSetting = entity.idp.entitySetting;
173
209
  spSetting = entity.sp.entitySetting;
@@ -178,9 +214,11 @@ function base64LoginResponse() {
178
214
  };
179
215
  nameIDFormat = idpSetting.nameIDFormat;
180
216
  selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
181
- if (!(metadata && metadata.idp && metadata.sp)) return [3 /*break*/, 3];
217
+ /* v8 ignore start */
218
+ if (!metadata.idp || !metadata.sp) {
219
+ throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
220
+ }
182
221
  base = metadata.sp.getAssertionConsumerService(binding.post);
183
- rawSamlResponse = void 0;
184
222
  nowTime = new Date();
185
223
  spEntityID = metadata.sp.getEntityID();
186
224
  fiveMinutesLaterTime = new Date(nowTime.getTime());
@@ -199,7 +237,6 @@ function base64LoginResponse() {
199
237
  IssueInstant: now,
200
238
  AssertionConsumerServiceURL: acl,
201
239
  StatusCode: urn_1.StatusCode.Success,
202
- // can be customized
203
240
  ConditionsNotBefore: now,
204
241
  ConditionsNotOnOrAfter: fiveMinutesLater,
205
242
  SubjectConfirmationDataNotOnOrAfter: fiveMinutesLater,
@@ -209,15 +246,17 @@ function base64LoginResponse() {
209
246
  AuthnStatement: '',
210
247
  AttributeStatement: '',
211
248
  };
212
- if (idpSetting.loginResponseTemplate && customTagReplacement) {
213
- template = customTagReplacement(idpSetting.loginResponseTemplate.context);
214
- rawSamlResponse = (0, utility_1.get)(template, 'context', null);
249
+ if (customTagReplacement) {
250
+ templateContext = (_e = (_b = (_a = idpSetting.loginResponseTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = idpSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.loginResponseTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLoginResponseTemplate.context;
251
+ template = customTagReplacement(templateContext);
252
+ rawSamlResponse = (0, utility_1.get)(template, 'context');
215
253
  }
216
254
  else {
217
- if (requestInfo !== null) {
255
+ if (requestInfo !== null && ((_f = requestInfo.extract) === null || _f === void 0 ? void 0 : _f.request)) {
218
256
  tvalue.InResponseTo = requestInfo.extract.request.id;
219
257
  }
220
- rawSamlResponse = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginResponseTemplate.context, tvalue);
258
+ baseTemplate = (_j = (_h = (_g = idpSetting.tagPrefixedDefaults) === null || _g === void 0 ? void 0 : _g.loginResponseTemplate) === null || _h === void 0 ? void 0 : _h.context) !== null && _j !== void 0 ? _j : libsaml_1.default.defaultLoginResponseTemplate.context;
259
+ rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
221
260
  }
222
261
  privateKey = idpSetting.privateKey, privateKeyPass = idpSetting.privateKeyPass, signatureAlgorithm = idpSetting.requestSignatureAlgorithm;
223
262
  config = {
@@ -227,18 +266,14 @@ function base64LoginResponse() {
227
266
  signingCert: metadata.idp.getX509Certificate('signing'),
228
267
  isBase64Output: false,
229
268
  };
230
- // step: sign assertion ? -> encrypted ? -> sign message ?
269
+ // Order: sign assertion (if SP wants) encrypt (if IdP wants) → sign message (if needed).
231
270
  if (metadata.sp.isWantAssertionsSigned()) {
232
- // console.debug('sp wants assertion signed');
233
271
  rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, transformationAlgorithms: spSetting.transformationAlgorithms, referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']", signatureConfig: {
234
272
  prefix: 'ds',
235
273
  location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
236
274
  } }));
237
275
  }
238
- // console.debug('after assertion signed', rawSamlResponse);
239
- // SAML response must be signed sign message first, then encrypt
240
276
  if (!encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
241
- // console.debug('sign then encrypt and sign entire message');
242
277
  rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, isMessageSigned: true, transformationAlgorithms: spSetting.transformationAlgorithms, signatureConfig: spSetting.signatureConfig || {
243
278
  prefix: 'ds',
244
279
  location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Issuer']", action: 'after' },
@@ -247,17 +282,15 @@ function base64LoginResponse() {
247
282
  if (!idpSetting.isAssertionEncrypted) return [3 /*break*/, 2];
248
283
  return [4 /*yield*/, libsaml_1.default.encryptAssertion(entity.idp, entity.sp, rawSamlResponse)];
249
284
  case 1:
250
- context = _a.sent();
285
+ context = _k.sent();
251
286
  if (encryptThenSign) {
252
- //need to decode it
253
287
  rawSamlResponse = utility_1.default.base64Decode(context);
254
288
  }
255
289
  else {
256
290
  return [2 /*return*/, Promise.resolve({ id: id, context: context })];
257
291
  }
258
- _a.label = 2;
292
+ _k.label = 2;
259
293
  case 2:
260
- //sign after encrypting
261
294
  if (encryptThenSign && (spSetting.wantMessageSigned || !metadata.sp.isWantAssertionsSigned())) {
262
295
  rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, isMessageSigned: true, transformationAlgorithms: spSetting.transformationAlgorithms, signatureConfig: spSetting.signatureConfig || {
263
296
  prefix: 'ds',
@@ -268,134 +301,155 @@ function base64LoginResponse() {
268
301
  id: id,
269
302
  context: utility_1.default.base64Encode(rawSamlResponse),
270
303
  })];
271
- case 3: throw new Error('ERR_GENERATE_POST_LOGIN_RESPONSE_MISSING_METADATA');
272
304
  }
273
305
  });
274
306
  });
275
307
  }
276
308
  /**
277
- * @desc Generate a base64 encoded logout request
278
- * @param {object} user current logged user (e.g. req.user)
279
- * @param {string} referenceTagXPath reference uri
280
- * @param {object} entity object includes both idp and sp
281
- * @param {function} customTagReplacement used when developers have their own login response template
282
- * @return {string} base64 encoded request
283
- */
309
+ * Generate a base64-encoded LogoutRequest for the HTTP-POST binding.
310
+ *
311
+ * @param user currently authenticated user
312
+ * @param referenceTagXPath XPath used when signing the request
313
+ * @param entity `{ init, target }` handles
314
+ * @param customTagReplacement optional custom template transformer
315
+ * @returns id / base64-XML pair
316
+ */
284
317
  function base64LogoutRequest(user, referenceTagXPath, entity, customTagReplacement) {
318
+ var _a, _b, _c, _d, _e, _f, _g, _h;
285
319
  var metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
286
320
  var initSetting = entity.init.entitySetting;
287
321
  var nameIDFormat = initSetting.nameIDFormat;
288
322
  var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
289
323
  var id = '';
290
- if (metadata && metadata.init && metadata.target) {
291
- var rawSamlRequest = void 0;
292
- if (initSetting.logoutRequestTemplate && customTagReplacement) {
293
- var template = customTagReplacement(initSetting.logoutRequestTemplate.context);
294
- id = (0, utility_1.get)(template, 'id', null);
295
- rawSamlRequest = (0, utility_1.get)(template, 'context', null);
296
- }
297
- else {
298
- id = initSetting.generateID();
299
- var tvalue = {
300
- ID: id,
301
- Destination: metadata.target.getSingleLogoutService(binding.post),
302
- Issuer: metadata.init.getEntityID(),
303
- IssueInstant: new Date().toISOString(),
304
- EntityID: metadata.init.getEntityID(),
305
- NameIDFormat: selectedNameIDFormat,
306
- NameID: user.logoutNameID,
307
- };
308
- rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLogoutRequestTemplate.context, tvalue);
309
- }
310
- if (entity.target.entitySetting.wantLogoutRequestSigned) {
311
- // Need to embeded XML signature
312
- var privateKey = initSetting.privateKey, privateKeyPass = initSetting.privateKeyPass, signatureAlgorithm = initSetting.requestSignatureAlgorithm, transformationAlgorithms = initSetting.transformationAlgorithms;
313
- return {
314
- id: id,
315
- context: libsaml_1.default.constructSAMLSignature({
316
- referenceTagXPath: referenceTagXPath,
317
- privateKey: privateKey,
318
- privateKeyPass: privateKeyPass,
319
- signatureAlgorithm: signatureAlgorithm,
320
- transformationAlgorithms: transformationAlgorithms,
321
- rawSamlMessage: rawSamlRequest,
322
- signingCert: metadata.init.getX509Certificate('signing'),
323
- signatureConfig: initSetting.signatureConfig || {
324
- prefix: 'ds',
325
- location: { reference: "/*[local-name(.)='LogoutRequest']/*[local-name(.)='Issuer']", action: 'after' },
326
- }
327
- }),
328
- };
329
- }
324
+ /* v8 ignore start */
325
+ if (!metadata.init || !metadata.target) {
326
+ throw new Error('ERR_GENERATE_POST_LOGOUT_REQUEST_MISSING_METADATA');
327
+ }
328
+ /* v8 ignore stop */
329
+ var rawSamlRequest;
330
+ if (customTagReplacement) {
331
+ // saml-bindings §3.5 — honour the callback even when the caller did
332
+ // not override `logoutRequestTemplate` (closes #549). Prefer the
333
+ // user-supplied template, then the tag-prefixed default (closes #388),
334
+ // and finally the library default.
335
+ var templateContext = (_e = (_b = (_a = initSetting.logoutRequestTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = initSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.logoutRequestTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLogoutRequestTemplate.context;
336
+ var template = customTagReplacement(templateContext);
337
+ id = (0, utility_1.get)(template, 'id');
338
+ rawSamlRequest = (0, utility_1.get)(template, 'context');
339
+ }
340
+ else {
341
+ id = initSetting.generateID();
342
+ var tvalue = {
343
+ ID: id,
344
+ Destination: metadata.target.getSingleLogoutService(binding.post),
345
+ Issuer: metadata.init.getEntityID(),
346
+ IssueInstant: new Date().toISOString(),
347
+ EntityID: metadata.init.getEntityID(),
348
+ NameIDFormat: selectedNameIDFormat,
349
+ NameID: user.logoutNameID,
350
+ // saml-core §3.7.1 — SessionIndex is optional; replaceTagsByValue
351
+ // drops the element when undefined (closes #470).
352
+ SessionIndex: user.sessionIndex,
353
+ };
354
+ var baseTemplate = (_h = (_g = (_f = initSetting.tagPrefixedDefaults) === null || _f === void 0 ? void 0 : _f.logoutRequestTemplate) === null || _g === void 0 ? void 0 : _g.context) !== null && _h !== void 0 ? _h : libsaml_1.default.defaultLogoutRequestTemplate.context;
355
+ rawSamlRequest = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
356
+ }
357
+ if (entity.target.entitySetting.wantLogoutRequestSigned) {
358
+ var privateKey = initSetting.privateKey, privateKeyPass = initSetting.privateKeyPass, signatureAlgorithm = initSetting.requestSignatureAlgorithm, transformationAlgorithms = initSetting.transformationAlgorithms;
330
359
  return {
331
360
  id: id,
332
- context: utility_1.default.base64Encode(rawSamlRequest),
361
+ context: libsaml_1.default.constructSAMLSignature({
362
+ referenceTagXPath: referenceTagXPath,
363
+ privateKey: privateKey,
364
+ privateKeyPass: privateKeyPass,
365
+ signatureAlgorithm: signatureAlgorithm,
366
+ transformationAlgorithms: transformationAlgorithms,
367
+ rawSamlMessage: rawSamlRequest,
368
+ signingCert: metadata.init.getX509Certificate('signing'),
369
+ signatureConfig: initSetting.signatureConfig || {
370
+ prefix: 'ds',
371
+ location: { reference: "/*[local-name(.)='LogoutRequest']/*[local-name(.)='Issuer']", action: 'after' },
372
+ },
373
+ }),
333
374
  };
334
375
  }
335
- throw new Error('ERR_GENERATE_POST_LOGOUT_REQUEST_MISSING_METADATA');
376
+ return {
377
+ id: id,
378
+ context: utility_1.default.base64Encode(rawSamlRequest),
379
+ };
336
380
  }
337
381
  /**
338
- * @desc Generate a base64 encoded logout response
339
- * @param {object} requestInfo corresponding request, used to obtain the id
340
- * @param {string} referenceTagXPath reference uri
341
- * @param {object} entity object includes both idp and sp
342
- * @param {function} customTagReplacement used when developers have their own login response template
343
- */
382
+ * Generate a base64-encoded LogoutResponse for the HTTP-POST binding.
383
+ *
384
+ * @param requestInfo parsed request used to link `InResponseTo`
385
+ * @param entity `{ init, target }` handles
386
+ * @param customTagReplacement optional custom template transformer
387
+ * @returns id / base64-XML pair
388
+ */
344
389
  function base64LogoutResponse(requestInfo, entity, customTagReplacement) {
390
+ var _a, _b, _c, _d, _e, _f, _g, _h;
345
391
  var metadata = {
346
392
  init: entity.init.entityMeta,
347
393
  target: entity.target.entityMeta,
348
394
  };
349
395
  var id = '';
350
396
  var initSetting = entity.init.entitySetting;
351
- if (metadata && metadata.init && metadata.target) {
352
- var rawSamlResponse = void 0;
353
- if (initSetting.logoutResponseTemplate) {
354
- var template = customTagReplacement(initSetting.logoutResponseTemplate.context);
355
- id = template.id;
356
- rawSamlResponse = template.context;
357
- }
358
- else {
359
- id = initSetting.generateID();
360
- var tvalue = {
361
- ID: id,
362
- Destination: metadata.target.getSingleLogoutService(binding.post),
363
- EntityID: metadata.init.getEntityID(),
364
- Issuer: metadata.init.getEntityID(),
365
- IssueInstant: new Date().toISOString(),
366
- StatusCode: urn_1.StatusCode.Success,
367
- InResponseTo: (0, utility_1.get)(requestInfo, 'extract.request.id', null)
368
- };
369
- rawSamlResponse = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLogoutResponseTemplate.context, tvalue);
370
- }
371
- if (entity.target.entitySetting.wantLogoutResponseSigned) {
372
- var privateKey = initSetting.privateKey, privateKeyPass = initSetting.privateKeyPass, signatureAlgorithm = initSetting.requestSignatureAlgorithm, transformationAlgorithms = initSetting.transformationAlgorithms;
373
- return {
374
- id: id,
375
- context: libsaml_1.default.constructSAMLSignature({
376
- isMessageSigned: true,
377
- transformationAlgorithms: transformationAlgorithms,
378
- privateKey: privateKey,
379
- privateKeyPass: privateKeyPass,
380
- signatureAlgorithm: signatureAlgorithm,
381
- rawSamlMessage: rawSamlResponse,
382
- signingCert: metadata.init.getX509Certificate('signing'),
383
- signatureConfig: {
384
- prefix: 'ds',
385
- location: {
386
- reference: "/*[local-name(.)='LogoutResponse']/*[local-name(.)='Issuer']",
387
- action: 'after'
388
- }
389
- }
390
- }),
391
- };
392
- }
397
+ /* v8 ignore start */
398
+ if (!metadata.init || !metadata.target) {
399
+ throw new Error('ERR_GENERATE_POST_LOGOUT_RESPONSE_MISSING_METADATA');
400
+ }
401
+ /* v8 ignore stop */
402
+ var rawSamlResponse;
403
+ if (customTagReplacement) {
404
+ // saml-bindings §3.5 — honour the callback even when the caller did
405
+ // not override `logoutResponseTemplate` (closes #549). Prefer the
406
+ // user-supplied template, then the tag-prefixed default (closes #388),
407
+ // and finally the library default.
408
+ var templateContext = (_e = (_b = (_a = initSetting.logoutResponseTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : (_d = (_c = initSetting.tagPrefixedDefaults) === null || _c === void 0 ? void 0 : _c.logoutResponseTemplate) === null || _d === void 0 ? void 0 : _d.context) !== null && _e !== void 0 ? _e : libsaml_1.default.defaultLogoutResponseTemplate.context;
409
+ var template = customTagReplacement(templateContext);
410
+ id = template.id;
411
+ rawSamlResponse = template.context;
412
+ }
413
+ else {
414
+ id = initSetting.generateID();
415
+ var tvalue = {
416
+ ID: id,
417
+ Destination: metadata.target.getSingleLogoutService(binding.post),
418
+ EntityID: metadata.init.getEntityID(),
419
+ Issuer: metadata.init.getEntityID(),
420
+ IssueInstant: new Date().toISOString(),
421
+ StatusCode: urn_1.StatusCode.Success,
422
+ InResponseTo: (0, utility_1.get)(requestInfo, 'extract.request.id'),
423
+ };
424
+ var baseTemplate = (_h = (_g = (_f = initSetting.tagPrefixedDefaults) === null || _f === void 0 ? void 0 : _f.logoutResponseTemplate) === null || _g === void 0 ? void 0 : _g.context) !== null && _h !== void 0 ? _h : libsaml_1.default.defaultLogoutResponseTemplate.context;
425
+ rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
426
+ }
427
+ if (entity.target.entitySetting.wantLogoutResponseSigned) {
428
+ var privateKey = initSetting.privateKey, privateKeyPass = initSetting.privateKeyPass, signatureAlgorithm = initSetting.requestSignatureAlgorithm, transformationAlgorithms = initSetting.transformationAlgorithms;
393
429
  return {
394
430
  id: id,
395
- context: utility_1.default.base64Encode(rawSamlResponse),
431
+ context: libsaml_1.default.constructSAMLSignature({
432
+ isMessageSigned: true,
433
+ transformationAlgorithms: transformationAlgorithms,
434
+ privateKey: privateKey,
435
+ privateKeyPass: privateKeyPass,
436
+ signatureAlgorithm: signatureAlgorithm,
437
+ rawSamlMessage: rawSamlResponse,
438
+ signingCert: metadata.init.getX509Certificate('signing'),
439
+ signatureConfig: {
440
+ prefix: 'ds',
441
+ location: {
442
+ reference: "/*[local-name(.)='LogoutResponse']/*[local-name(.)='Issuer']",
443
+ action: 'after',
444
+ },
445
+ },
446
+ }),
396
447
  };
397
448
  }
398
- throw new Error('ERR_GENERATE_POST_LOGOUT_RESPONSE_MISSING_METADATA');
449
+ return {
450
+ id: id,
451
+ context: utility_1.default.base64Encode(rawSamlResponse),
452
+ };
399
453
  }
400
454
  var postBinding = {
401
455
  base64LoginRequest: base64LoginRequest,