samlify 2.11.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 (83) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +52 -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 +119 -155
  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 +318 -261
  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 +165 -83
  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 +17 -7
  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 +144 -89
  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/src/api.ts +0 -36
  64. package/src/binding-post.ts +0 -336
  65. package/src/binding-redirect.ts +0 -335
  66. package/src/binding-simplesign.ts +0 -231
  67. package/src/entity-idp.ts +0 -145
  68. package/src/entity-sp.ts +0 -114
  69. package/src/entity.ts +0 -243
  70. package/src/extractor.ts +0 -399
  71. package/src/flow.ts +0 -469
  72. package/src/libsaml.ts +0 -777
  73. package/src/metadata-idp.ts +0 -146
  74. package/src/metadata-sp.ts +0 -203
  75. package/src/metadata.ts +0 -166
  76. package/src/types.ts +0 -127
  77. package/src/urn.ts +0 -210
  78. package/src/utility.ts +0 -231
  79. package/src/validator.ts +0 -44
  80. package/tsconfig.json +0 -41
  81. package/tslint.json +0 -35
  82. package/types.d.ts +0 -2
  83. package/vitest.config.ts +0 -12
@@ -48,10 +48,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  };
49
49
  Object.defineProperty(exports, "__esModule", { value: true });
50
50
  /**
51
- * @file binding-redirect.ts
52
- * @author tngan
53
- * @desc Binding-level API, declare the functions using Redirect binding
54
- */
51
+ * @file binding-redirect.ts
52
+ * @author tngan
53
+ * @desc Binding-level API for SAML HTTP-Redirect. Builds signed/unsigned
54
+ * redirect URLs for login/logout requests and responses.
55
+ */
55
56
  var utility_1 = __importStar(require("./utility"));
56
57
  var libsaml_1 = __importDefault(require("./libsaml"));
57
58
  var url = __importStar(require("url"));
@@ -59,31 +60,28 @@ var urn_1 = require("./urn");
59
60
  var binding = urn_1.wording.binding;
60
61
  var urlParams = urn_1.wording.urlParams;
61
62
  /**
62
- * @private
63
- * @desc Helper of generating URL param/value pair
64
- * @param {string} param key
65
- * @param {string} value value of key
66
- * @param {boolean} first determine whether the param is the starting one in order to add query header '?'
67
- * @return {string}
68
- */
63
+ * Build a `key=value` URL fragment prefixed with the correct separator.
64
+ *
65
+ * @param param key name
66
+ * @param value key value
67
+ * @param first when true, use `?` instead of `&`
68
+ */
69
69
  function pvPair(param, value, first) {
70
70
  return (first === true ? '?' : '&') + param + '=' + value;
71
71
  }
72
72
  /**
73
- * @private
74
- * @desc Refractored part of URL generation for login/logout request
75
- * @param {string} type
76
- * @param {boolean} isSigned
77
- * @param {string} rawSamlRequest
78
- * @param {object} entitySetting
79
- * @return {string}
80
- */
73
+ * Compose the final redirect URL, deflate/base64/urlencode the SAML message,
74
+ * optionally append the detached signature.
75
+ *
76
+ * @param opts redirect configuration
77
+ * @returns absolute redirect URL
78
+ */
81
79
  function buildRedirectURL(opts) {
82
80
  var baseUrl = opts.baseUrl, type = opts.type, isSigned = opts.isSigned, context = opts.context, entitySetting = opts.entitySetting;
83
81
  var _a = opts.relayState, relayState = _a === void 0 ? '' : _a;
84
82
  var noParams = (url.parse(baseUrl).query || []).length === 0;
85
83
  var queryParam = libsaml_1.default.getQueryParamByType(type);
86
- // In general, this xmlstring is required to do deflate -> base64 -> urlencode
84
+ // SAML redirect binding: deflate base64 URL-encode.
87
85
  var samlRequest = encodeURIComponent(utility_1.default.base64Encode(utility_1.default.deflateString(context)));
88
86
  if (relayState !== '') {
89
87
  relayState = pvPair(urlParams.relayState, encodeURIComponent(relayState));
@@ -98,65 +96,106 @@ function buildRedirectURL(opts) {
98
96
  return baseUrl + pvPair(queryParam, samlRequest + relayState, noParams);
99
97
  }
100
98
  /**
101
- * @desc Redirect URL for login request
102
- * @param {object} entity object includes both idp and sp
103
- * @param {function} customTagReplacement used when developers have their own login response template
104
- * @return {string} redirect URL
105
- */
106
- function loginRequestRedirectURL(entity, customTagReplacement) {
99
+ * Build a redirect URL carrying a SAML AuthnRequest.
100
+ *
101
+ * @param entity `{ idp, sp }` handles
102
+ * @param customTagReplacement optional custom template transformer
103
+ * @param relayState per-request RelayState; falls back to `entitySetting.relayState`
104
+ * @param forceAuthn per-request `ForceAuthn` flag (saml-core §3.4.1)
105
+ * @param assertionConsumerServiceIndex per-request ACS index (saml-core §3.4.1).
106
+ * Mutually exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`;
107
+ * when supplied, both of those attributes are dropped from the rendered XML.
108
+ * @returns id + redirect URL wrapped in a {@link BindingContext}
109
+ */
110
+ function loginRequestRedirectURL(entity, customTagReplacement, relayState, forceAuthn, assertionConsumerServiceIndex) {
111
+ var _a, _b;
107
112
  var metadata = { idp: entity.idp.entityMeta, sp: entity.sp.entityMeta };
108
113
  var spSetting = entity.sp.entitySetting;
109
114
  var id = '';
110
- if (metadata && metadata.idp && metadata.sp) {
111
- var base = metadata.idp.getSingleSignOnService(binding.redirect);
112
- var rawSamlRequest = void 0;
113
- if (spSetting.loginRequestTemplate && customTagReplacement) {
114
- var info = customTagReplacement(spSetting.loginRequestTemplate);
115
- id = (0, utility_1.get)(info, 'id', null);
116
- rawSamlRequest = (0, utility_1.get)(info, 'context', null);
117
- // Support callback returning { context: string } or { context: { context: string } }
118
- if (typeof rawSamlRequest === 'object' && rawSamlRequest !== null && 'context' in rawSamlRequest) {
119
- rawSamlRequest = rawSamlRequest.context;
120
- }
121
- }
122
- else {
123
- var nameIDFormat = spSetting.nameIDFormat;
124
- var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
125
- id = spSetting.generateID();
126
- rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginRequestTemplate.context, {
127
- ID: id,
128
- Destination: base,
129
- Issuer: metadata.sp.getEntityID(),
130
- IssueInstant: new Date().toISOString(),
131
- NameIDFormat: selectedNameIDFormat,
132
- AssertionConsumerServiceURL: metadata.sp.getAssertionConsumerService(binding.post),
133
- EntityID: metadata.sp.getEntityID(),
134
- AllowCreate: spSetting.allowCreate,
135
- });
115
+ /* v8 ignore start */
116
+ if (!metadata.idp || !metadata.sp) {
117
+ throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
118
+ }
119
+ /* v8 ignore stop */
120
+ var base = metadata.idp.getSingleSignOnService(binding.redirect);
121
+ // saml-bindings §3.4 / saml-metadata §2.4.3: the IdP must declare a
122
+ // <SingleSignOnService> entry with the HTTP-Redirect Binding URI.
123
+ // When that endpoint is absent, getSingleSignOnService returns the raw
124
+ // service map (an object) rather than a URL string — surface a clear
125
+ // error instead of letting it crash inside url.parse downstream.
126
+ if (typeof base !== 'string') {
127
+ throw new Error('ERR_NO_REDIRECT_SSO_ENDPOINT');
128
+ }
129
+ var rawSamlRequest;
130
+ if (customTagReplacement) {
131
+ // saml-bindings §3.4 — the AuthnRequest template is informative, not
132
+ // normative. Honour the callback regardless of whether the caller
133
+ // supplied a custom template (closes #549). Pass the user-supplied
134
+ // template when present; otherwise the library default.
135
+ var templateContext = (_b = (_a = spSetting.loginRequestTemplate) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : libsaml_1.default.defaultLoginRequestTemplate.context;
136
+ var info = customTagReplacement(templateContext);
137
+ id = (0, utility_1.get)(info, 'id');
138
+ rawSamlRequest = (0, utility_1.get)(info, 'context');
139
+ // Support callback returning { context: string } or { context: { context: string } }.
140
+ if (typeof rawSamlRequest === 'object' && rawSamlRequest !== null && 'context' in rawSamlRequest) {
141
+ rawSamlRequest = rawSamlRequest.context;
136
142
  }
137
- return {
138
- id: id,
139
- context: buildRedirectURL({
140
- context: rawSamlRequest,
141
- type: urlParams.samlRequest,
142
- isSigned: metadata.sp.isAuthnRequestSigned(),
143
- entitySetting: spSetting,
144
- baseUrl: base,
145
- relayState: spSetting.relayState,
146
- }),
143
+ }
144
+ else {
145
+ var nameIDFormat = spSetting.nameIDFormat;
146
+ var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
147
+ id = spSetting.generateID();
148
+ // saml-core §3.4.1 — `AssertionConsumerServiceIndex` is mutually
149
+ // exclusive with `AssertionConsumerServiceURL` / `ProtocolBinding`.
150
+ // When the caller supplies the index we set the URL+ProtocolBinding
151
+ // tags to undefined so `replaceTagsByValue` drops both attributes
152
+ // from the rendered XML (closes #437).
153
+ var useAcsIndex = assertionConsumerServiceIndex !== undefined;
154
+ var tags = {
155
+ ID: id,
156
+ Destination: base,
157
+ Issuer: metadata.sp.getEntityID(),
158
+ IssueInstant: new Date().toISOString(),
159
+ NameIDFormat: selectedNameIDFormat,
160
+ ProtocolBinding: useAcsIndex
161
+ ? undefined
162
+ : 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
163
+ AssertionConsumerServiceURL: useAcsIndex
164
+ ? undefined
165
+ : metadata.sp.getAssertionConsumerService(binding.post),
166
+ AssertionConsumerServiceIndex: assertionConsumerServiceIndex,
167
+ EntityID: metadata.sp.getEntityID(),
168
+ AllowCreate: spSetting.allowCreate,
169
+ // saml-core §3.4.1 — `replaceTagsByValue` drops the attribute when
170
+ // `forceAuthn` is undefined, matching `use="optional"`.
171
+ ForceAuthn: forceAuthn,
147
172
  };
173
+ rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginRequestTemplate.context, tags);
148
174
  }
149
- throw new Error('ERR_GENERATE_REDIRECT_LOGIN_REQUEST_MISSING_METADATA');
175
+ return {
176
+ id: id,
177
+ context: buildRedirectURL({
178
+ context: rawSamlRequest,
179
+ type: urlParams.samlRequest,
180
+ isSigned: metadata.sp.isAuthnRequestSigned(),
181
+ entitySetting: spSetting,
182
+ baseUrl: base,
183
+ relayState: relayState !== null && relayState !== void 0 ? relayState : spSetting.relayState,
184
+ }),
185
+ };
150
186
  }
151
187
  /**
152
- * @desc Redirect URL for login response
153
- * @param {object} requestInfo corresponding request, used to obtain the id
154
- * @param {object} entity object includes both idp and sp
155
- * @param {object} user current logged user (e.g. req.user)
156
- * @param {String} relayState the relaystate sent by sp corresponding request
157
- * @param {function} customTagReplacement used when developers have their own login response template
158
- */
188
+ * Build a redirect URL carrying a SAML login Response.
189
+ *
190
+ * @param requestInfo parsed request used to link `InResponseTo`
191
+ * @param entity `{ idp, sp }` handles
192
+ * @param user authenticated user
193
+ * @param relayState caller-supplied redirect URL
194
+ * @param customTagReplacement optional custom template transformer
195
+ * @returns id + redirect URL wrapped in a {@link BindingContext}
196
+ */
159
197
  function loginResponseRedirectURL(requestInfo, entity, user, relayState, customTagReplacement) {
198
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
160
199
  if (user === void 0) { user = {}; }
161
200
  var idpSetting = entity.idp.entitySetting;
162
201
  var spSetting = entity.sp.entitySetting;
@@ -165,173 +204,222 @@ function loginResponseRedirectURL(requestInfo, entity, user, relayState, customT
165
204
  sp: entity.sp.entityMeta,
166
205
  };
167
206
  var id = idpSetting.generateID();
168
- if (metadata && metadata.idp && metadata.sp) {
169
- var base = metadata.sp.getAssertionConsumerService(binding.redirect);
170
- var rawSamlResponse = void 0;
171
- //
172
- var nameIDFormat = idpSetting.nameIDFormat;
173
- var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
174
- var nowTime = new Date();
175
- // Five minutes later : nowtime + 5 * 60 * 1000 (in milliseconds)
176
- var fiveMinutesLaterTime = new Date(nowTime.getTime() + 300000);
177
- var tvalue = {
178
- ID: id,
179
- AssertionID: idpSetting.generateID(),
180
- Destination: base,
181
- SubjectRecipient: base,
182
- Issuer: metadata.idp.getEntityID(),
183
- Audience: metadata.sp.getEntityID(),
184
- EntityID: metadata.sp.getEntityID(),
185
- IssueInstant: nowTime.toISOString(),
186
- AssertionConsumerServiceURL: base,
187
- StatusCode: urn_1.namespace.statusCode.success,
188
- // can be customized
189
- ConditionsNotBefore: nowTime.toISOString(),
190
- ConditionsNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
191
- SubjectConfirmationDataNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
192
- NameIDFormat: selectedNameIDFormat,
193
- NameID: user.email || '',
194
- InResponseTo: (0, utility_1.get)(requestInfo, 'extract.request.id', ''),
195
- AuthnStatement: '',
196
- AttributeStatement: '',
197
- };
198
- if (idpSetting.loginResponseTemplate && customTagReplacement) {
199
- var template = customTagReplacement(idpSetting.loginResponseTemplate.context);
200
- id = (0, utility_1.get)(template, 'id', null);
201
- rawSamlResponse = (0, utility_1.get)(template, 'context', null);
202
- }
203
- else {
204
- if (requestInfo !== null) {
205
- tvalue.InResponseTo = requestInfo.extract.request.id;
206
- }
207
- rawSamlResponse = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLoginResponseTemplate.context, tvalue);
208
- }
209
- var privateKey = idpSetting.privateKey, privateKeyPass = idpSetting.privateKeyPass, signatureAlgorithm = idpSetting.requestSignatureAlgorithm;
210
- var config = {
211
- privateKey: privateKey,
212
- privateKeyPass: privateKeyPass,
213
- signatureAlgorithm: signatureAlgorithm,
214
- signingCert: metadata.idp.getX509Certificate('signing'),
215
- isBase64Output: false,
216
- };
217
- // step: sign assertion ? -> encrypted ? -> sign message ?
218
- if (metadata.sp.isWantAssertionsSigned()) {
219
- rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, transformationAlgorithms: spSetting.transformationAlgorithms, referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']", signatureConfig: {
220
- prefix: 'ds',
221
- location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
222
- } }));
207
+ /* v8 ignore start */
208
+ if (!metadata.idp || !metadata.sp) {
209
+ throw new Error('ERR_GENERATE_REDIRECT_LOGIN_RESPONSE_MISSING_METADATA');
210
+ }
211
+ /* v8 ignore stop */
212
+ var base = metadata.sp.getAssertionConsumerService(binding.redirect);
213
+ // saml-bindings §3.4 / saml-metadata §2.4.3: the SP must declare an
214
+ // <AssertionConsumerService> entry with the HTTP-Redirect Binding URI.
215
+ // When that endpoint is absent, getAssertionConsumerService returns
216
+ // undefined or the raw service list — reject with a clear error rather
217
+ // than crashing in url.parse.
218
+ if (typeof base !== 'string') {
219
+ throw new Error('ERR_NO_REDIRECT_SSO_ENDPOINT');
220
+ }
221
+ var rawSamlResponse;
222
+ var nameIDFormat = idpSetting.nameIDFormat;
223
+ var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
224
+ var nowTime = new Date();
225
+ var fiveMinutesLaterTime = new Date(nowTime.getTime() + 300000);
226
+ var tvalue = {
227
+ ID: id,
228
+ AssertionID: idpSetting.generateID(),
229
+ Destination: base,
230
+ SubjectRecipient: base,
231
+ Issuer: metadata.idp.getEntityID(),
232
+ Audience: metadata.sp.getEntityID(),
233
+ EntityID: metadata.sp.getEntityID(),
234
+ IssueInstant: nowTime.toISOString(),
235
+ AssertionConsumerServiceURL: base,
236
+ StatusCode: urn_1.namespace.statusCode.success,
237
+ ConditionsNotBefore: nowTime.toISOString(),
238
+ ConditionsNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
239
+ SubjectConfirmationDataNotOnOrAfter: fiveMinutesLaterTime.toISOString(),
240
+ NameIDFormat: selectedNameIDFormat,
241
+ NameID: user.email || '',
242
+ InResponseTo: (0, utility_1.get)(requestInfo, 'extract.request.id', ''),
243
+ AuthnStatement: '',
244
+ AttributeStatement: '',
245
+ };
246
+ if (customTagReplacement) {
247
+ // saml-bindings §3.4 — honour the callback even when the caller did
248
+ // not override `loginResponseTemplate` (closes #549). Prefer the
249
+ // user-supplied template, then the tag-prefixed default (closes #388),
250
+ // and finally the library default.
251
+ var 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;
252
+ var template = customTagReplacement(templateContext);
253
+ id = (0, utility_1.get)(template, 'id');
254
+ rawSamlResponse = (0, utility_1.get)(template, 'context');
255
+ }
256
+ else {
257
+ if (requestInfo !== null && ((_f = requestInfo.extract) === null || _f === void 0 ? void 0 : _f.request)) {
258
+ tvalue.InResponseTo = requestInfo.extract.request.id;
223
259
  }
224
- // Like in post binding, SAML response is always signed
225
- return {
226
- id: id,
227
- context: buildRedirectURL({
228
- baseUrl: base,
229
- type: urlParams.samlResponse,
230
- isSigned: true,
231
- context: rawSamlResponse,
232
- entitySetting: idpSetting,
233
- relayState: relayState,
234
- }),
235
- };
260
+ // saml-core §1.4: prefer the IdP-rewritten default when tagPrefix is
261
+ // overridden (closes #388); otherwise fall back to the library default.
262
+ var 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;
263
+ rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
264
+ }
265
+ var privateKey = idpSetting.privateKey, privateKeyPass = idpSetting.privateKeyPass, signatureAlgorithm = idpSetting.requestSignatureAlgorithm;
266
+ var config = {
267
+ privateKey: privateKey,
268
+ privateKeyPass: privateKeyPass,
269
+ signatureAlgorithm: signatureAlgorithm,
270
+ signingCert: metadata.idp.getX509Certificate('signing'),
271
+ isBase64Output: false,
272
+ };
273
+ if (metadata.sp.isWantAssertionsSigned()) {
274
+ rawSamlResponse = libsaml_1.default.constructSAMLSignature(__assign(__assign({}, config), { rawSamlMessage: rawSamlResponse, transformationAlgorithms: spSetting.transformationAlgorithms, referenceTagXPath: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']", signatureConfig: {
275
+ prefix: 'ds',
276
+ location: { reference: "/*[local-name(.)='Response']/*[local-name(.)='Assertion']/*[local-name(.)='Issuer']", action: 'after' },
277
+ } }));
236
278
  }
237
- throw new Error('ERR_GENERATE_REDIRECT_LOGIN_RESPONSE_MISSING_METADATA');
279
+ // SAML response over redirect binding is always signed (see SAML core 3.4.4).
280
+ return {
281
+ id: id,
282
+ context: buildRedirectURL({
283
+ baseUrl: base,
284
+ type: urlParams.samlResponse,
285
+ isSigned: true,
286
+ context: rawSamlResponse,
287
+ entitySetting: idpSetting,
288
+ relayState: relayState,
289
+ }),
290
+ };
238
291
  }
239
292
  /**
240
- * @desc Redirect URL for logout request
241
- * @param {object} user current logged user (e.g. req.user)
242
- * @param {object} entity object includes both idp and sp
243
- * @param {function} customTagReplacement used when developers have their own login response template
244
- * @return {string} redirect URL
245
- */
293
+ * Build a redirect URL carrying a SAML LogoutRequest.
294
+ *
295
+ * @param user currently authenticated user
296
+ * @param entity `{ init, target }` handles
297
+ * @param relayState caller-supplied redirect URL
298
+ * @param customTagReplacement optional custom template transformer
299
+ * @returns id + redirect URL wrapped in a {@link BindingContext}
300
+ */
246
301
  function logoutRequestRedirectURL(user, entity, relayState, customTagReplacement) {
302
+ var _a, _b, _c, _d, _e, _f, _g, _h;
247
303
  var metadata = { init: entity.init.entityMeta, target: entity.target.entityMeta };
248
304
  var initSetting = entity.init.entitySetting;
249
305
  var id = initSetting.generateID();
250
306
  var nameIDFormat = initSetting.nameIDFormat;
251
307
  var selectedNameIDFormat = Array.isArray(nameIDFormat) ? nameIDFormat[0] : nameIDFormat;
252
- if (metadata && metadata.init && metadata.target) {
253
- var base = metadata.target.getSingleLogoutService(binding.redirect);
254
- var rawSamlRequest = '';
255
- var requiredTags = {
256
- ID: id,
257
- Destination: base,
258
- EntityID: metadata.init.getEntityID(),
259
- Issuer: metadata.init.getEntityID(),
260
- IssueInstant: new Date().toISOString(),
261
- NameIDFormat: selectedNameIDFormat,
262
- NameID: user.logoutNameID,
263
- SessionIndex: user.sessionIndex,
264
- };
265
- if (initSetting.logoutRequestTemplate && customTagReplacement) {
266
- var info = customTagReplacement(initSetting.logoutRequestTemplate, requiredTags);
267
- id = (0, utility_1.get)(info, 'id', null);
268
- rawSamlRequest = (0, utility_1.get)(info, 'context', null);
269
- }
270
- else {
271
- rawSamlRequest = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLogoutRequestTemplate.context, requiredTags);
272
- }
273
- return {
274
- id: id,
275
- context: buildRedirectURL({
276
- context: rawSamlRequest,
277
- relayState: relayState,
278
- type: urlParams.logoutRequest,
279
- isSigned: entity.target.entitySetting.wantLogoutRequestSigned,
280
- entitySetting: initSetting,
281
- baseUrl: base,
282
- }),
283
- };
308
+ /* v8 ignore start */
309
+ if (!metadata.init || !metadata.target) {
310
+ throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_REQUEST_MISSING_METADATA');
311
+ }
312
+ /* v8 ignore stop */
313
+ var base = metadata.target.getSingleLogoutService(binding.redirect);
314
+ // saml-bindings §3.4 / saml-metadata §2.4.3: the target entity must declare
315
+ // a <SingleLogoutService> with the HTTP-Redirect Binding URI. Otherwise the
316
+ // service map leaks through and crashes inside url.parse downstream.
317
+ if (typeof base !== 'string') {
318
+ throw new Error('ERR_NO_REDIRECT_SLO_ENDPOINT');
319
+ }
320
+ var rawSamlRequest = '';
321
+ var requiredTags = {
322
+ ID: id,
323
+ Destination: base,
324
+ EntityID: metadata.init.getEntityID(),
325
+ Issuer: metadata.init.getEntityID(),
326
+ IssueInstant: new Date().toISOString(),
327
+ NameIDFormat: selectedNameIDFormat,
328
+ NameID: user.logoutNameID,
329
+ SessionIndex: user.sessionIndex,
330
+ };
331
+ if (customTagReplacement) {
332
+ // saml-bindings §3.4 — honour the callback even when the caller did
333
+ // not override `logoutRequestTemplate` (closes #549). Prefer the
334
+ // user-supplied template, then the tag-prefixed default (closes #388),
335
+ // and finally the library default.
336
+ 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;
337
+ var info = customTagReplacement(templateContext, requiredTags);
338
+ id = (0, utility_1.get)(info, 'id');
339
+ rawSamlRequest = (0, utility_1.get)(info, 'context');
340
+ }
341
+ else {
342
+ 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;
343
+ rawSamlRequest = libsaml_1.default.replaceTagsByValue(baseTemplate, requiredTags);
284
344
  }
285
- throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_REQUEST_MISSING_METADATA');
345
+ return {
346
+ id: id,
347
+ context: buildRedirectURL({
348
+ context: rawSamlRequest,
349
+ relayState: relayState,
350
+ type: urlParams.logoutRequest,
351
+ isSigned: entity.target.entitySetting.wantLogoutRequestSigned,
352
+ entitySetting: initSetting,
353
+ baseUrl: base,
354
+ }),
355
+ };
286
356
  }
287
357
  /**
288
- * @desc Redirect URL for logout response
289
- * @param {object} requescorresponding request, used to obtain the id
290
- * @param {object} entity object includes both idp and sp
291
- * @param {function} customTagReplacement used when developers have their own login response template
292
- */
358
+ * Build a redirect URL carrying a SAML LogoutResponse.
359
+ *
360
+ * @param requestInfo parsed request used to link `InResponseTo`
361
+ * @param entity `{ init, target }` handles
362
+ * @param relayState caller-supplied redirect URL
363
+ * @param customTagReplacement optional custom template transformer
364
+ * @returns id + redirect URL wrapped in a {@link BindingContext}
365
+ */
293
366
  function logoutResponseRedirectURL(requestInfo, entity, relayState, customTagReplacement) {
367
+ var _a, _b, _c, _d, _e, _f, _g, _h;
294
368
  var metadata = {
295
369
  init: entity.init.entityMeta,
296
370
  target: entity.target.entityMeta,
297
371
  };
298
372
  var initSetting = entity.init.entitySetting;
299
373
  var id = initSetting.generateID();
300
- if (metadata && metadata.init && metadata.target) {
301
- var base = metadata.target.getSingleLogoutService(binding.redirect);
302
- var rawSamlResponse = void 0;
303
- if (initSetting.logoutResponseTemplate && customTagReplacement) {
304
- var template = customTagReplacement(initSetting.logoutResponseTemplate);
305
- id = (0, utility_1.get)(template, 'id', null);
306
- rawSamlResponse = (0, utility_1.get)(template, 'context', null);
307
- }
308
- else {
309
- var tvalue = {
310
- ID: id,
311
- Destination: base,
312
- Issuer: metadata.init.getEntityID(),
313
- EntityID: metadata.init.getEntityID(),
314
- IssueInstant: new Date().toISOString(),
315
- StatusCode: urn_1.namespace.statusCode.success,
316
- };
317
- if (requestInfo && requestInfo.extract && requestInfo.extract.request) {
318
- tvalue.InResponseTo = requestInfo.extract.request.id;
319
- }
320
- rawSamlResponse = libsaml_1.default.replaceTagsByValue(libsaml_1.default.defaultLogoutResponseTemplate.context, tvalue);
321
- }
322
- return {
323
- id: id,
324
- context: buildRedirectURL({
325
- baseUrl: base,
326
- type: urlParams.logoutResponse,
327
- isSigned: entity.target.entitySetting.wantLogoutResponseSigned,
328
- context: rawSamlResponse,
329
- entitySetting: initSetting,
330
- relayState: relayState,
331
- }),
374
+ /* v8 ignore start */
375
+ if (!metadata.init || !metadata.target) {
376
+ throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_RESPONSE_MISSING_METADATA');
377
+ }
378
+ /* v8 ignore stop */
379
+ var base = metadata.target.getSingleLogoutService(binding.redirect);
380
+ // saml-bindings §3.4 / saml-metadata §2.4.3: same constraint as the
381
+ // logout request path — the target must advertise a HTTP-Redirect SLO
382
+ // endpoint before we can build a URL.
383
+ if (typeof base !== 'string') {
384
+ throw new Error('ERR_NO_REDIRECT_SLO_ENDPOINT');
385
+ }
386
+ var rawSamlResponse;
387
+ if (customTagReplacement) {
388
+ // saml-bindings §3.4 — honour the callback even when the caller did
389
+ // not override `logoutResponseTemplate` (closes #549). Prefer the
390
+ // user-supplied template, then the tag-prefixed default (closes #388),
391
+ // and finally the library default.
392
+ 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;
393
+ var template = customTagReplacement(templateContext);
394
+ id = (0, utility_1.get)(template, 'id');
395
+ rawSamlResponse = (0, utility_1.get)(template, 'context');
396
+ }
397
+ else {
398
+ var tvalue = {
399
+ ID: id,
400
+ Destination: base,
401
+ Issuer: metadata.init.getEntityID(),
402
+ EntityID: metadata.init.getEntityID(),
403
+ IssueInstant: new Date().toISOString(),
404
+ StatusCode: urn_1.namespace.statusCode.success,
332
405
  };
406
+ if (requestInfo && requestInfo.extract && requestInfo.extract.request) {
407
+ tvalue.InResponseTo = requestInfo.extract.request.id;
408
+ }
409
+ 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;
410
+ rawSamlResponse = libsaml_1.default.replaceTagsByValue(baseTemplate, tvalue);
333
411
  }
334
- throw new Error('ERR_GENERATE_REDIRECT_LOGOUT_RESPONSE_MISSING_METADATA');
412
+ return {
413
+ id: id,
414
+ context: buildRedirectURL({
415
+ baseUrl: base,
416
+ type: urlParams.logoutResponse,
417
+ isSigned: entity.target.entitySetting.wantLogoutResponseSigned,
418
+ context: rawSamlResponse,
419
+ entitySetting: initSetting,
420
+ relayState: relayState,
421
+ }),
422
+ };
335
423
  }
336
424
  var redirectBinding = {
337
425
  loginRequestRedirectURL: loginRequestRedirectURL,