@tachybase/plugin-auth-saml 0.23.8

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 (67) hide show
  1. package/.turbo/turbo-build.log +14 -0
  2. package/README.md +11 -0
  3. package/README.zh-CN.md +55 -0
  4. package/client.d.ts +2 -0
  5. package/client.js +1 -0
  6. package/dist/client/Options.d.ts +2 -0
  7. package/dist/client/SAMLButton.d.ts +5 -0
  8. package/dist/client/index.d.ts +5 -0
  9. package/dist/client/index.js +3 -0
  10. package/dist/client/locale/index.d.ts +3 -0
  11. package/dist/client/schemas/saml.d.ts +35 -0
  12. package/dist/constants.d.ts +2 -0
  13. package/dist/constants.js +31 -0
  14. package/dist/externalVersion.js +14 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +39 -0
  17. package/dist/locale/en-US.json +26 -0
  18. package/dist/locale/es-ES.json +22 -0
  19. package/dist/locale/fr-FR.json +22 -0
  20. package/dist/locale/ko_KR.json +29 -0
  21. package/dist/locale/pt-BR.json +22 -0
  22. package/dist/locale/zh-CN.json +29 -0
  23. package/dist/node_modules/@node-saml/node-saml/LICENSE +23 -0
  24. package/dist/node_modules/@node-saml/node-saml/lib/algorithms.d.ts +5 -0
  25. package/dist/node_modules/@node-saml/node-saml/lib/algorithms.js +41 -0
  26. package/dist/node_modules/@node-saml/node-saml/lib/crypto.d.ts +5 -0
  27. package/dist/node_modules/@node-saml/node-saml/lib/crypto.js +48 -0
  28. package/dist/node_modules/@node-saml/node-saml/lib/datetime.d.ts +13 -0
  29. package/dist/node_modules/@node-saml/node-saml/lib/datetime.js +27 -0
  30. package/dist/node_modules/@node-saml/node-saml/lib/index.d.ts +3 -0
  31. package/dist/node_modules/@node-saml/node-saml/lib/index.js +9 -0
  32. package/dist/node_modules/@node-saml/node-saml/lib/inmemory-cache-provider.d.ts +38 -0
  33. package/dist/node_modules/@node-saml/node-saml/lib/inmemory-cache-provider.js +100 -0
  34. package/dist/node_modules/@node-saml/node-saml/lib/metadata.d.ts +2 -0
  35. package/dist/node_modules/@node-saml/node-saml/lib/metadata.js +112 -0
  36. package/dist/node_modules/@node-saml/node-saml/lib/passport-saml-types.d.ts +8 -0
  37. package/dist/node_modules/@node-saml/node-saml/lib/passport-saml-types.js +3 -0
  38. package/dist/node_modules/@node-saml/node-saml/lib/saml-post-signing.d.ts +3 -0
  39. package/dist/node_modules/@node-saml/node-saml/lib/saml-post-signing.js +15 -0
  40. package/dist/node_modules/@node-saml/node-saml/lib/saml.d.ts +75 -0
  41. package/dist/node_modules/@node-saml/node-saml/lib/saml.js +1005 -0
  42. package/dist/node_modules/@node-saml/node-saml/lib/types.d.ts +219 -0
  43. package/dist/node_modules/@node-saml/node-saml/lib/types.js +21 -0
  44. package/dist/node_modules/@node-saml/node-saml/lib/utility.d.ts +5 -0
  45. package/dist/node_modules/@node-saml/node-saml/lib/utility.js +27 -0
  46. package/dist/node_modules/@node-saml/node-saml/lib/xml.d.ts +26 -0
  47. package/dist/node_modules/@node-saml/node-saml/lib/xml.js +234 -0
  48. package/dist/node_modules/@node-saml/node-saml/package.json +1 -0
  49. package/dist/server/actions/getAuthUrl.d.ts +2 -0
  50. package/dist/server/actions/getAuthUrl.js +35 -0
  51. package/dist/server/actions/metadata.d.ts +2 -0
  52. package/dist/server/actions/metadata.js +36 -0
  53. package/dist/server/actions/redirect.d.ts +2 -0
  54. package/dist/server/actions/redirect.js +49 -0
  55. package/dist/server/index.d.ts +1 -0
  56. package/dist/server/index.js +33 -0
  57. package/dist/server/migrations/20231008112900-update-autosignup.d.ts +6 -0
  58. package/dist/server/migrations/20231008112900-update-autosignup.js +52 -0
  59. package/dist/server/plugin.d.ts +11 -0
  60. package/dist/server/plugin.js +70 -0
  61. package/dist/server/saml-auth.d.ts +8 -0
  62. package/dist/server/saml-auth.js +110 -0
  63. package/dist/swagger/index.d.ts +137 -0
  64. package/dist/swagger/index.js +163 -0
  65. package/package.json +35 -0
  66. package/server.d.ts +2 -0
  67. package/server.js +1 -0
@@ -0,0 +1,1005 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SAML = void 0;
4
+ const debug_1 = require("debug");
5
+ const debug = (0, debug_1.default)("node-saml");
6
+ const zlib = require("zlib");
7
+ const crypto = require("crypto");
8
+ const url_1 = require("url");
9
+ const querystring = require("querystring");
10
+ const util = require("util");
11
+ const inmemory_cache_provider_1 = require("./inmemory-cache-provider");
12
+ const algorithms = require("./algorithms");
13
+ const types_1 = require("./types");
14
+ const utility_1 = require("./utility");
15
+ const xml_1 = require("./xml");
16
+ const crypto_1 = require("./crypto");
17
+ const datetime_1 = require("./datetime");
18
+ const saml_post_signing_1 = require("./saml-post-signing");
19
+ const metadata_1 = require("./metadata");
20
+ const inflateRawAsync = util.promisify(zlib.inflateRaw);
21
+ const deflateRawAsync = util.promisify(zlib.deflateRaw);
22
+ class SAML {
23
+ constructor(ctorOptions) {
24
+ this.options = this.initialize(ctorOptions);
25
+ this.cacheProvider = this.options.cacheProvider;
26
+ }
27
+ initialize(ctorOptions) {
28
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
29
+ if (!ctorOptions) {
30
+ throw new TypeError("SamlOptions required on construction");
31
+ }
32
+ (0, utility_1.assertRequired)(ctorOptions.issuer, "issuer is required");
33
+ (0, utility_1.assertRequired)(ctorOptions.cert, "cert is required");
34
+ // Prevent a JS user from passing in "false", which is truthy, and doing the wrong thing
35
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.passive);
36
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.disableRequestedAuthnContext);
37
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.forceAuthn);
38
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.skipRequestCompression);
39
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.disableRequestAcsUrl);
40
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.allowCreate);
41
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.wantAssertionsSigned);
42
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.wantAuthnResponseSigned);
43
+ (0, utility_1.assertBooleanIfPresent)(ctorOptions.signMetadata);
44
+ const options = {
45
+ ...ctorOptions,
46
+ passive: (_a = ctorOptions.passive) !== null && _a !== void 0 ? _a : false,
47
+ disableRequestedAuthnContext: (_b = ctorOptions.disableRequestedAuthnContext) !== null && _b !== void 0 ? _b : false,
48
+ additionalParams: (_c = ctorOptions.additionalParams) !== null && _c !== void 0 ? _c : {},
49
+ additionalAuthorizeParams: (_d = ctorOptions.additionalAuthorizeParams) !== null && _d !== void 0 ? _d : {},
50
+ additionalLogoutParams: (_e = ctorOptions.additionalLogoutParams) !== null && _e !== void 0 ? _e : {},
51
+ forceAuthn: (_f = ctorOptions.forceAuthn) !== null && _f !== void 0 ? _f : false,
52
+ skipRequestCompression: (_g = ctorOptions.skipRequestCompression) !== null && _g !== void 0 ? _g : false,
53
+ disableRequestAcsUrl: (_h = ctorOptions.disableRequestAcsUrl) !== null && _h !== void 0 ? _h : false,
54
+ acceptedClockSkewMs: (_j = ctorOptions.acceptedClockSkewMs) !== null && _j !== void 0 ? _j : 0,
55
+ maxAssertionAgeMs: (_k = ctorOptions.maxAssertionAgeMs) !== null && _k !== void 0 ? _k : 0,
56
+ path: (_l = ctorOptions.path) !== null && _l !== void 0 ? _l : "/saml/consume",
57
+ host: (_m = ctorOptions.host) !== null && _m !== void 0 ? _m : "localhost",
58
+ issuer: ctorOptions.issuer,
59
+ audience: (_p = (_o = ctorOptions.audience) !== null && _o !== void 0 ? _o : ctorOptions.issuer) !== null && _p !== void 0 ? _p : "unknown_audience",
60
+ identifierFormat: ctorOptions.identifierFormat === undefined
61
+ ? "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
62
+ : ctorOptions.identifierFormat,
63
+ allowCreate: (_q = ctorOptions.allowCreate) !== null && _q !== void 0 ? _q : true,
64
+ spNameQualifier: ctorOptions.spNameQualifier,
65
+ wantAssertionsSigned: (_r = ctorOptions.wantAssertionsSigned) !== null && _r !== void 0 ? _r : true,
66
+ wantAuthnResponseSigned: (_s = ctorOptions.wantAuthnResponseSigned) !== null && _s !== void 0 ? _s : true,
67
+ authnContext: (_t = ctorOptions.authnContext) !== null && _t !== void 0 ? _t : [
68
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
69
+ ],
70
+ validateInResponseTo: (_u = ctorOptions.validateInResponseTo) !== null && _u !== void 0 ? _u : types_1.ValidateInResponseTo.never,
71
+ cert: ctorOptions.cert,
72
+ requestIdExpirationPeriodMs: (_v = ctorOptions.requestIdExpirationPeriodMs) !== null && _v !== void 0 ? _v : 28800000,
73
+ cacheProvider: (_w = ctorOptions.cacheProvider) !== null && _w !== void 0 ? _w : new inmemory_cache_provider_1.InMemoryCacheProvider({
74
+ keyExpirationPeriodMs: ctorOptions.requestIdExpirationPeriodMs,
75
+ }),
76
+ logoutUrl: (_y = (_x = ctorOptions.logoutUrl) !== null && _x !== void 0 ? _x : ctorOptions.entryPoint) !== null && _y !== void 0 ? _y : "",
77
+ signatureAlgorithm: (_z = ctorOptions.signatureAlgorithm) !== null && _z !== void 0 ? _z : "sha1",
78
+ authnRequestBinding: (_0 = ctorOptions.authnRequestBinding) !== null && _0 !== void 0 ? _0 : "HTTP-Redirect",
79
+ generateUniqueId: (_1 = ctorOptions.generateUniqueId) !== null && _1 !== void 0 ? _1 : crypto_1.generateUniqueId,
80
+ signMetadata: (_2 = ctorOptions.signMetadata) !== null && _2 !== void 0 ? _2 : false,
81
+ racComparison: (_3 = ctorOptions.racComparison) !== null && _3 !== void 0 ? _3 : "exact",
82
+ };
83
+ /**
84
+ * List of possible values:
85
+ * - exact : Assertion context must exactly match a context in the list
86
+ * - minimum: Assertion context must be at least as strong as a context in the list
87
+ * - maximum: Assertion context must be no stronger than a context in the list
88
+ * - better: Assertion context must be stronger than all contexts in the list
89
+ */
90
+ if (!["exact", "minimum", "maximum", "better"].includes(options.racComparison)) {
91
+ throw new TypeError("racComparison must be one of ['exact', 'minimum', 'maximum', 'better']");
92
+ }
93
+ return options;
94
+ }
95
+ getCallbackUrl(host) {
96
+ // Post-auth destination
97
+ if (this.options.callbackUrl) {
98
+ return this.options.callbackUrl;
99
+ }
100
+ else {
101
+ const url = new url_1.URL("http://localhost");
102
+ if (host) {
103
+ url.host = host;
104
+ }
105
+ else {
106
+ url.host = this.options.host;
107
+ }
108
+ if (this.options.protocol) {
109
+ url.protocol = this.options.protocol;
110
+ }
111
+ url.pathname = this.options.path;
112
+ return url.toString();
113
+ }
114
+ }
115
+ signRequest(samlMessage) {
116
+ (0, utility_1.assertRequired)(this.options.privateKey, "privateKey is required");
117
+ const samlMessageToSign = {};
118
+ samlMessage.SigAlg = algorithms.getSigningAlgorithm(this.options.signatureAlgorithm);
119
+ const signer = algorithms.getSigner(this.options.signatureAlgorithm);
120
+ if (samlMessage.SAMLRequest) {
121
+ samlMessageToSign.SAMLRequest = samlMessage.SAMLRequest;
122
+ }
123
+ if (samlMessage.SAMLResponse) {
124
+ samlMessageToSign.SAMLResponse = samlMessage.SAMLResponse;
125
+ }
126
+ if (samlMessage.RelayState) {
127
+ samlMessageToSign.RelayState = samlMessage.RelayState;
128
+ }
129
+ if (samlMessage.SigAlg) {
130
+ samlMessageToSign.SigAlg = samlMessage.SigAlg;
131
+ }
132
+ signer.update(querystring.stringify(samlMessageToSign));
133
+ samlMessage.Signature = signer.sign((0, crypto_1.keyToPEM)(this.options.privateKey), "base64");
134
+ }
135
+ async generateAuthorizeRequestAsync(isPassive, isHttpPostBinding, host) {
136
+ (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
137
+ const id = this.options.generateUniqueId();
138
+ const instant = (0, datetime_1.generateInstant)();
139
+ if (this.mustValidateInResponseTo(true)) {
140
+ await this.cacheProvider.saveAsync(id, instant);
141
+ }
142
+ const request = {
143
+ "samlp:AuthnRequest": {
144
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
145
+ "@ID": id,
146
+ "@Version": "2.0",
147
+ "@IssueInstant": instant,
148
+ "@ProtocolBinding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
149
+ "@Destination": this.options.entryPoint,
150
+ "saml:Issuer": {
151
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
152
+ "#text": this.options.issuer,
153
+ },
154
+ },
155
+ };
156
+ if (isPassive)
157
+ request["samlp:AuthnRequest"]["@IsPassive"] = true;
158
+ if (this.options.forceAuthn === true) {
159
+ request["samlp:AuthnRequest"]["@ForceAuthn"] = true;
160
+ }
161
+ if (!this.options.disableRequestAcsUrl) {
162
+ request["samlp:AuthnRequest"]["@AssertionConsumerServiceURL"] = this.getCallbackUrl(host);
163
+ }
164
+ const samlAuthnRequestExtensions = this.options.samlAuthnRequestExtensions;
165
+ if (samlAuthnRequestExtensions != null) {
166
+ if (typeof samlAuthnRequestExtensions != "object") {
167
+ throw new TypeError("samlAuthnRequestExtensions should be Object");
168
+ }
169
+ request["samlp:AuthnRequest"]["samlp:Extensions"] = {
170
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
171
+ ...samlAuthnRequestExtensions,
172
+ };
173
+ }
174
+ const nameIDPolicy = {
175
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
176
+ "@AllowCreate": this.options.allowCreate,
177
+ };
178
+ if (this.options.identifierFormat != null) {
179
+ nameIDPolicy["@Format"] = this.options.identifierFormat;
180
+ }
181
+ if (this.options.spNameQualifier != null) {
182
+ nameIDPolicy["@SPNameQualifier"] = this.options.spNameQualifier;
183
+ }
184
+ request["samlp:AuthnRequest"]["samlp:NameIDPolicy"] = nameIDPolicy;
185
+ if (!this.options.disableRequestedAuthnContext) {
186
+ const authnContextClassRefs = [];
187
+ this.options.authnContext.forEach(function (value) {
188
+ authnContextClassRefs.push({
189
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
190
+ "#text": value,
191
+ });
192
+ });
193
+ request["samlp:AuthnRequest"]["samlp:RequestedAuthnContext"] = {
194
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
195
+ "@Comparison": this.options.racComparison,
196
+ "saml:AuthnContextClassRef": authnContextClassRefs,
197
+ };
198
+ }
199
+ if (this.options.attributeConsumingServiceIndex != null) {
200
+ request["samlp:AuthnRequest"]["@AttributeConsumingServiceIndex"] =
201
+ this.options.attributeConsumingServiceIndex;
202
+ }
203
+ if (this.options.providerName != null) {
204
+ request["samlp:AuthnRequest"]["@ProviderName"] = this.options.providerName;
205
+ }
206
+ if (this.options.scoping != null) {
207
+ const scoping = {
208
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
209
+ };
210
+ if (typeof this.options.scoping.proxyCount === "number") {
211
+ scoping["@ProxyCount"] = this.options.scoping.proxyCount;
212
+ }
213
+ if (this.options.scoping.idpList) {
214
+ scoping["samlp:IDPList"] = this.options.scoping.idpList.map((idpListItem) => {
215
+ const formattedIdpListItem = {
216
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
217
+ };
218
+ if (idpListItem.entries) {
219
+ formattedIdpListItem["samlp:IDPEntry"] = idpListItem.entries.map((entry) => {
220
+ const formattedEntry = {
221
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
222
+ };
223
+ formattedEntry["@ProviderID"] = entry.providerId;
224
+ if (entry.name) {
225
+ formattedEntry["@Name"] = entry.name;
226
+ }
227
+ if (entry.loc) {
228
+ formattedEntry["@Loc"] = entry.loc;
229
+ }
230
+ return formattedEntry;
231
+ });
232
+ }
233
+ if (idpListItem.getComplete) {
234
+ formattedIdpListItem["samlp:GetComplete"] = idpListItem.getComplete;
235
+ }
236
+ return formattedIdpListItem;
237
+ });
238
+ }
239
+ if (this.options.scoping.requesterId) {
240
+ scoping["samlp:RequesterID"] = this.options.scoping.requesterId;
241
+ }
242
+ request["samlp:AuthnRequest"]["samlp:Scoping"] = scoping;
243
+ }
244
+ let stringRequest = (0, xml_1.buildXmlBuilderObject)(request, false);
245
+ // TODO: maybe we should always sign here
246
+ if (isHttpPostBinding && (0, types_1.isValidSamlSigningOptions)(this.options)) {
247
+ stringRequest = (0, saml_post_signing_1.signAuthnRequestPost)(stringRequest, this.options);
248
+ }
249
+ return stringRequest;
250
+ }
251
+ async _generateLogoutRequest(user) {
252
+ const id = this.options.generateUniqueId();
253
+ const instant = (0, datetime_1.generateInstant)();
254
+ const request = {
255
+ "samlp:LogoutRequest": {
256
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
257
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
258
+ "@ID": id,
259
+ "@Version": "2.0",
260
+ "@IssueInstant": instant,
261
+ "@Destination": this.options.logoutUrl,
262
+ "saml:Issuer": {
263
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
264
+ "#text": this.options.issuer,
265
+ },
266
+ "samlp:Extensions": {},
267
+ "saml:NameID": {
268
+ "@Format": user.nameIDFormat,
269
+ "#text": user.nameID,
270
+ },
271
+ },
272
+ };
273
+ const samlLogoutRequestExtensions = this.options.samlLogoutRequestExtensions;
274
+ if (samlLogoutRequestExtensions != null) {
275
+ if (typeof samlLogoutRequestExtensions != "object") {
276
+ throw new TypeError("samlLogoutRequestExtensions should be Object");
277
+ }
278
+ request["samlp:LogoutRequest"]["samlp:Extensions"] = {
279
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
280
+ ...samlLogoutRequestExtensions,
281
+ };
282
+ }
283
+ else {
284
+ delete request["samlp:LogoutRequest"]["samlp:Extensions"];
285
+ }
286
+ if (user.nameQualifier != null) {
287
+ request["samlp:LogoutRequest"]["saml:NameID"]["@NameQualifier"] = user.nameQualifier;
288
+ }
289
+ if (user.spNameQualifier != null) {
290
+ request["samlp:LogoutRequest"]["saml:NameID"]["@SPNameQualifier"] = user.spNameQualifier;
291
+ }
292
+ if (user.sessionIndex) {
293
+ request["samlp:LogoutRequest"]["saml2p:SessionIndex"] = {
294
+ "@xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
295
+ "#text": user.sessionIndex,
296
+ };
297
+ }
298
+ await this.cacheProvider.saveAsync(id, instant);
299
+ return (0, xml_1.buildXmlBuilderObject)(request, false);
300
+ }
301
+ _generateLogoutResponse(logoutRequest, success) {
302
+ const id = this.options.generateUniqueId();
303
+ const instant = (0, datetime_1.generateInstant)();
304
+ const successStatus = {
305
+ "samlp:StatusCode": {
306
+ "@Value": "urn:oasis:names:tc:SAML:2.0:status:Success",
307
+ },
308
+ };
309
+ const failStatus = {
310
+ "samlp:StatusCode": {
311
+ "@Value": "urn:oasis:names:tc:SAML:2.0:status:Requester",
312
+ "samlp:StatusCode": {
313
+ "@Value": "urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal",
314
+ },
315
+ },
316
+ };
317
+ const request = {
318
+ "samlp:LogoutResponse": {
319
+ "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
320
+ "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
321
+ "@ID": id,
322
+ "@Version": "2.0",
323
+ "@IssueInstant": instant,
324
+ "@Destination": this.options.logoutUrl,
325
+ "@InResponseTo": logoutRequest.ID,
326
+ "saml:Issuer": {
327
+ "#text": this.options.issuer,
328
+ },
329
+ "samlp:Status": success ? successStatus : failStatus,
330
+ },
331
+ };
332
+ return (0, xml_1.buildXmlBuilderObject)(request, false);
333
+ }
334
+ async _requestToUrlAsync(request, response, operation, additionalParameters) {
335
+ (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
336
+ const requestOrResponse = request || response;
337
+ (0, utility_1.assertRequired)(requestOrResponse, "either request or response is required");
338
+ let buffer;
339
+ if (this.options.skipRequestCompression) {
340
+ buffer = Buffer.from(requestOrResponse, "utf8");
341
+ }
342
+ else {
343
+ buffer = await deflateRawAsync(requestOrResponse);
344
+ }
345
+ const base64 = buffer.toString("base64");
346
+ let target = new url_1.URL(this.options.entryPoint);
347
+ if (operation === "logout") {
348
+ if (this.options.logoutUrl) {
349
+ target = new url_1.URL(this.options.logoutUrl);
350
+ }
351
+ }
352
+ else if (operation !== "authorize") {
353
+ throw new Error("Unknown operation: " + operation);
354
+ }
355
+ const samlMessage = request
356
+ ? {
357
+ SAMLRequest: base64,
358
+ }
359
+ : {
360
+ SAMLResponse: base64,
361
+ };
362
+ Object.keys(additionalParameters).forEach((k) => {
363
+ samlMessage[k] = additionalParameters[k];
364
+ });
365
+ if ((0, types_1.isValidSamlSigningOptions)(this.options)) {
366
+ if (!this.options.entryPoint) {
367
+ throw new Error('"entryPoint" config parameter is required for signed messages');
368
+ }
369
+ // sets .SigAlg and .Signature
370
+ this.signRequest(samlMessage);
371
+ }
372
+ Object.keys(samlMessage).forEach((k) => {
373
+ target.searchParams.set(k, samlMessage[k]);
374
+ });
375
+ return target.toString();
376
+ }
377
+ _getAdditionalParams(relayState, operation, overrideParams) {
378
+ const additionalParams = {};
379
+ if (typeof relayState === "string" && relayState.length > 0) {
380
+ additionalParams.RelayState = relayState;
381
+ }
382
+ return Object.assign(additionalParams, this.options.additionalParams, operation === "logout"
383
+ ? this.options.additionalLogoutParams
384
+ : this.options.additionalAuthorizeParams, overrideParams !== null && overrideParams !== void 0 ? overrideParams : {});
385
+ }
386
+ async getAuthorizeUrlAsync(RelayState, host, options) {
387
+ const request = await this.generateAuthorizeRequestAsync(this.options.passive, false, host);
388
+ const operation = "authorize";
389
+ const overrideParams = options ? options.additionalParams || {} : {};
390
+ return await this._requestToUrlAsync(request, null, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
391
+ }
392
+ async getAuthorizeFormAsync(RelayState, host) {
393
+ (0, utility_1.assertRequired)(this.options.entryPoint, "entryPoint is required");
394
+ // The quoteattr() function is used in a context, where the result will not be evaluated by javascript
395
+ // but must be interpreted by an XML or HTML parser, and it must absolutely avoid breaking the syntax
396
+ // of an element attribute.
397
+ const quoteattr = function (s, preserveCR) {
398
+ const preserveCRChar = preserveCR ? "
" : "\n";
399
+ return (("" + s) // Forces the conversion to string.
400
+ .replace(/&/g, "&") // This MUST be the 1st replacement.
401
+ .replace(/'/g, "'") // The 4 other predefined entities, required.
402
+ .replace(/"/g, """)
403
+ .replace(/</g, "&lt;")
404
+ .replace(/>/g, "&gt;")
405
+ // Add other replacements here for HTML only
406
+ // Or for XML, only if the named entities are defined in its DTD.
407
+ .replace(/\r\n/g, preserveCRChar) // Must be before the next replacement.
408
+ .replace(/[\r\n]/g, preserveCRChar));
409
+ };
410
+ const request = await this.generateAuthorizeRequestAsync(this.options.passive, true, host);
411
+ let buffer;
412
+ if (this.options.skipRequestCompression) {
413
+ buffer = Buffer.from(request, "utf8");
414
+ }
415
+ else {
416
+ buffer = await deflateRawAsync(request);
417
+ }
418
+ const operation = "authorize";
419
+ const additionalParameters = this._getAdditionalParams(RelayState, operation);
420
+ const samlMessage = {
421
+ SAMLRequest: buffer.toString("base64"),
422
+ };
423
+ Object.keys(additionalParameters).forEach((k) => {
424
+ samlMessage[k] = additionalParameters[k] || "";
425
+ });
426
+ const formInputs = Object.keys(samlMessage)
427
+ .map((k) => {
428
+ return '<input type="hidden" name="' + k + '" value="' + quoteattr(samlMessage[k]) + '" />';
429
+ })
430
+ .join("\r\n");
431
+ return [
432
+ "<!DOCTYPE html>",
433
+ "<html>",
434
+ "<head>",
435
+ '<meta charset="utf-8">',
436
+ '<meta http-equiv="x-ua-compatible" content="ie=edge">',
437
+ "</head>",
438
+ '<body onload="document.forms[0].submit()">',
439
+ "<noscript>",
440
+ "<p><strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below once to proceed.</p>",
441
+ "</noscript>",
442
+ '<form method="post" action="' + encodeURI(this.options.entryPoint) + '">',
443
+ formInputs,
444
+ '<input type="submit" value="Submit" />',
445
+ "</form>",
446
+ '<script>document.forms[0].style.display="none";</script>',
447
+ "</body>",
448
+ "</html>",
449
+ ].join("\r\n");
450
+ }
451
+ async getLogoutUrlAsync(user, RelayState, options) {
452
+ const request = await this._generateLogoutRequest(user);
453
+ const operation = "logout";
454
+ const overrideParams = options ? options.additionalParams || {} : {};
455
+ return await this._requestToUrlAsync(request, null, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
456
+ }
457
+ getLogoutResponseUrl(samlLogoutRequest, RelayState, options, success, callback) {
458
+ util.callbackify(() => this.getLogoutResponseUrlAsync(samlLogoutRequest, RelayState, options, success))(callback);
459
+ }
460
+ async getLogoutResponseUrlAsync(samlLogoutRequest, RelayState, options, success) {
461
+ const response = this._generateLogoutResponse(samlLogoutRequest, success);
462
+ const operation = "logout";
463
+ const overrideParams = options ? options.additionalParams || {} : {};
464
+ return await this._requestToUrlAsync(null, response, operation, this._getAdditionalParams(RelayState, operation, overrideParams));
465
+ }
466
+ async certsToCheck() {
467
+ let checkedCerts;
468
+ if (typeof this.options.cert === "function") {
469
+ checkedCerts = await util
470
+ .promisify(this.options.cert)()
471
+ .then((certs) => {
472
+ (0, utility_1.assertRequired)(certs, "callback didn't return cert");
473
+ if (!Array.isArray(certs)) {
474
+ certs = [certs];
475
+ }
476
+ return certs;
477
+ });
478
+ }
479
+ else if (Array.isArray(this.options.cert)) {
480
+ checkedCerts = this.options.cert;
481
+ }
482
+ else {
483
+ checkedCerts = [this.options.cert];
484
+ }
485
+ checkedCerts.forEach((cert) => {
486
+ (0, utility_1.assertRequired)(cert, "unknown cert found");
487
+ });
488
+ return checkedCerts;
489
+ }
490
+ async validatePostResponseAsync(container) {
491
+ var _a, _b, _c, _d;
492
+ let xml;
493
+ let doc;
494
+ let inResponseTo = null;
495
+ try {
496
+ xml = Buffer.from(container.SAMLResponse, "base64").toString("utf8");
497
+ doc = await (0, xml_1.parseDomFromString)(xml);
498
+ const inResponseToNodes = xml_1.xpath.selectAttributes(doc, "/*[local-name()='Response']/@InResponseTo");
499
+ if (inResponseToNodes) {
500
+ inResponseTo = inResponseToNodes.length ? inResponseToNodes[0].nodeValue : null;
501
+ await this.validateInResponseTo(inResponseTo);
502
+ }
503
+ const certs = await this.certsToCheck();
504
+ // Check if this document has a valid top-level signature which applies to the entire XML document
505
+ let validSignature = false;
506
+ if ((0, xml_1.validateSignature)(xml, doc.documentElement, certs)) {
507
+ validSignature = true;
508
+ }
509
+ if (this.options.wantAuthnResponseSigned === true && validSignature === false) {
510
+ throw new Error("Invalid document signature");
511
+ }
512
+ const assertions = xml_1.xpath.selectElements(doc, "/*[local-name()='Response']/*[local-name()='Assertion']");
513
+ const encryptedAssertions = xml_1.xpath.selectElements(doc, "/*[local-name()='Response']/*[local-name()='EncryptedAssertion']");
514
+ if (assertions.length + encryptedAssertions.length > 1) {
515
+ // There's no reason I know of that we want to handle multiple assertions, and it seems like a
516
+ // potential risk vector for signature scope issues, so treat this as an invalid signature
517
+ throw new Error("Invalid signature: multiple assertions");
518
+ }
519
+ if (assertions.length == 1) {
520
+ if ((this.options.wantAssertionsSigned || !validSignature) &&
521
+ !(0, xml_1.validateSignature)(xml, assertions[0], certs)) {
522
+ throw new Error("Invalid signature");
523
+ }
524
+ return await this.processValidlySignedAssertionAsync(assertions[0].toString(), xml, inResponseTo);
525
+ }
526
+ if (encryptedAssertions.length == 1) {
527
+ (0, utility_1.assertRequired)(this.options.decryptionPvk, "No decryption key for encrypted SAML response");
528
+ const encryptedAssertionXml = encryptedAssertions[0].toString();
529
+ const decryptedXml = await (0, xml_1.decryptXml)(encryptedAssertionXml, this.options.decryptionPvk);
530
+ const decryptedDoc = await (0, xml_1.parseDomFromString)(decryptedXml);
531
+ const decryptedAssertions = xml_1.xpath.selectElements(decryptedDoc, "/*[local-name()='Assertion']");
532
+ if (decryptedAssertions.length != 1)
533
+ throw new Error("Invalid EncryptedAssertion content");
534
+ if ((this.options.wantAssertionsSigned || !validSignature) &&
535
+ !(0, xml_1.validateSignature)(decryptedXml, decryptedAssertions[0], certs)) {
536
+ throw new Error("Invalid signature from encrypted assertion");
537
+ }
538
+ return await this.processValidlySignedAssertionAsync(decryptedAssertions[0].toString(), xml, inResponseTo);
539
+ }
540
+ // If there's no assertion, fall back on xml2js response parsing for the status &
541
+ // LogoutResponse code.
542
+ const xmljsDoc = (await (0, xml_1.parseXml2JsFromString)(xml));
543
+ const response = xmljsDoc.Response;
544
+ if (response) {
545
+ if (!("Assertion" in response)) {
546
+ const status = response.Status;
547
+ if (status) {
548
+ const statusCode = status[0].StatusCode;
549
+ if (statusCode &&
550
+ ((_a = statusCode[0].$) === null || _a === void 0 ? void 0 : _a.Value) === "urn:oasis:names:tc:SAML:2.0:status:Responder") {
551
+ const nestedStatusCode = statusCode[0].StatusCode;
552
+ if (nestedStatusCode &&
553
+ ((_b = nestedStatusCode[0].$) === null || _b === void 0 ? void 0 : _b.Value) === "urn:oasis:names:tc:SAML:2.0:status:NoPassive") {
554
+ if (!validSignature) {
555
+ throw new Error("Invalid signature: NoPassive");
556
+ }
557
+ return { profile: null, loggedOut: false };
558
+ }
559
+ }
560
+ // Note that we're not requiring a valid signature before this logic -- since we are
561
+ // throwing an error in any case, and some providers don't sign error results,
562
+ // let's go ahead and give the potentially more helpful error.
563
+ if (statusCode && ((_c = statusCode[0].$) === null || _c === void 0 ? void 0 : _c.Value)) {
564
+ const msgType = statusCode[0].$.Value.match(/[^:]*$/);
565
+ if (msgType && msgType[0] != "Success") {
566
+ let msg = "unspecified";
567
+ if (status[0].StatusMessage) {
568
+ msg = status[0].StatusMessage[0]._ || msg;
569
+ }
570
+ else if (statusCode[0].StatusCode) {
571
+ const msgValues = (_d = statusCode[0].StatusCode[0].$) === null || _d === void 0 ? void 0 : _d.Value.match(/[^:]*$/);
572
+ msg = msgValues ? msgValues[0] : msg;
573
+ }
574
+ const statusXml = (0, xml_1.buildXml2JsObject)("Status", status[0]);
575
+ throw new types_1.ErrorWithXmlStatus("SAML provider returned " + msgType + " error: " + msg, statusXml);
576
+ }
577
+ }
578
+ }
579
+ }
580
+ throw new Error("Missing SAML assertion");
581
+ }
582
+ else {
583
+ if (!validSignature) {
584
+ throw new Error("Invalid signature: No response found");
585
+ }
586
+ const logoutResponse = xmljsDoc.LogoutResponse;
587
+ if (logoutResponse) {
588
+ return { profile: null, loggedOut: true };
589
+ }
590
+ else {
591
+ throw new Error("Unknown SAML response message");
592
+ }
593
+ }
594
+ }
595
+ catch (err) {
596
+ debug("validatePostResponse resulted in an error: %s", err);
597
+ if (this.mustValidateInResponseTo(Boolean(inResponseTo))) {
598
+ await this.cacheProvider.removeAsync(inResponseTo);
599
+ }
600
+ throw err;
601
+ }
602
+ }
603
+ async validateInResponseTo(inResponseTo) {
604
+ if (this.mustValidateInResponseTo(Boolean(inResponseTo))) {
605
+ if (inResponseTo) {
606
+ const result = await this.cacheProvider.getAsync(inResponseTo);
607
+ if (!result)
608
+ throw new Error("InResponseTo is not valid");
609
+ return;
610
+ }
611
+ else {
612
+ throw new Error("InResponseTo is missing from response");
613
+ }
614
+ }
615
+ }
616
+ async validateRedirectAsync(container, originalQuery) {
617
+ const samlMessageType = container.SAMLRequest ? "SAMLRequest" : "SAMLResponse";
618
+ const data = Buffer.from(container[samlMessageType], "base64");
619
+ const inflated = await inflateRawAsync(data);
620
+ const dom = await (0, xml_1.parseDomFromString)(inflated.toString());
621
+ const doc = await (0, xml_1.parseXml2JsFromString)(inflated);
622
+ samlMessageType === "SAMLResponse"
623
+ ? await this.verifyLogoutResponse(doc)
624
+ : this.verifyLogoutRequest(doc);
625
+ await this.hasValidSignatureForRedirect(container, originalQuery);
626
+ return await this.processValidlySignedSamlLogoutAsync(doc, dom);
627
+ }
628
+ async hasValidSignatureForRedirect(container, originalQuery) {
629
+ const tokens = originalQuery.split("&");
630
+ const getParam = (key) => {
631
+ const exists = tokens.filter((t) => {
632
+ return new RegExp(key).test(t);
633
+ });
634
+ return exists[0];
635
+ };
636
+ if (container.Signature) {
637
+ let urlString = getParam("SAMLRequest") || getParam("SAMLResponse");
638
+ if (getParam("RelayState")) {
639
+ urlString += "&" + getParam("RelayState");
640
+ }
641
+ urlString += "&" + getParam("SigAlg");
642
+ const certs = await this.certsToCheck();
643
+ const hasValidQuerySignature = certs.some((cert) => {
644
+ return this.validateSignatureForRedirect(urlString, container.Signature, container.SigAlg, cert);
645
+ });
646
+ if (!hasValidQuerySignature) {
647
+ throw new Error("Invalid query signature");
648
+ }
649
+ }
650
+ else {
651
+ return true;
652
+ }
653
+ }
654
+ validateSignatureForRedirect(urlString, signature, alg, cert) {
655
+ // See if we support a matching algorithm, case-insensitive. Otherwise, throw error.
656
+ function hasMatch(ourAlgo) {
657
+ // The incoming algorithm is forwarded as a URL.
658
+ // We trim everything before the last # get something we can compare to the Node.js list
659
+ const algFromURI = alg.toLowerCase().replace(/.*#(.*)$/, "$1");
660
+ return ourAlgo.toLowerCase() === algFromURI;
661
+ }
662
+ const i = crypto.getHashes().findIndex(hasMatch);
663
+ let matchingAlgo;
664
+ if (i > -1) {
665
+ matchingAlgo = crypto.getHashes()[i];
666
+ }
667
+ else {
668
+ throw new Error(alg + " is not supported");
669
+ }
670
+ const verifier = crypto.createVerify(matchingAlgo);
671
+ verifier.update(urlString);
672
+ return verifier.verify((0, crypto_1.certToPEM)(cert), signature, "base64");
673
+ }
674
+ verifyLogoutRequest(doc) {
675
+ this.verifyIssuer(doc.LogoutRequest);
676
+ const nowMs = new Date().getTime();
677
+ const conditions = doc.LogoutRequest.$;
678
+ const conErr = this.checkTimestampsValidityError(nowMs, conditions.NotBefore, conditions.NotOnOrAfter);
679
+ if (conErr) {
680
+ throw conErr;
681
+ }
682
+ }
683
+ async verifyLogoutResponse(doc) {
684
+ const statusCode = doc.LogoutResponse.Status[0].StatusCode[0].$.Value;
685
+ if (statusCode !== "urn:oasis:names:tc:SAML:2.0:status:Success")
686
+ throw new Error("Bad status code: " + statusCode);
687
+ this.verifyIssuer(doc.LogoutResponse);
688
+ const inResponseTo = doc.LogoutResponse.$.InResponseTo;
689
+ if (inResponseTo) {
690
+ return this.validateInResponseTo(inResponseTo);
691
+ }
692
+ return;
693
+ }
694
+ verifyIssuer(samlMessage) {
695
+ if (this.options.idpIssuer != null) {
696
+ const issuer = samlMessage.Issuer;
697
+ if (issuer) {
698
+ if (issuer[0]._ !== this.options.idpIssuer)
699
+ throw new Error("Unknown SAML issuer. Expected: " + this.options.idpIssuer + " Received: " + issuer[0]._);
700
+ }
701
+ else {
702
+ throw new Error("Missing SAML issuer");
703
+ }
704
+ }
705
+ }
706
+ async processValidlySignedAssertionAsync(xml, samlResponseXml, inResponseTo) {
707
+ let msg;
708
+ const nowMs = new Date().getTime();
709
+ const profile = {};
710
+ const doc = await (0, xml_1.parseXml2JsFromString)(xml);
711
+ const parsedAssertion = doc;
712
+ const assertion = doc.Assertion;
713
+ getInResponseTo: {
714
+ const issuer = assertion.Issuer;
715
+ if (issuer && issuer[0]._) {
716
+ profile.issuer = issuer[0]._;
717
+ }
718
+ if (inResponseTo != null) {
719
+ profile.inResponseTo = inResponseTo;
720
+ }
721
+ const authnStatement = assertion.AuthnStatement;
722
+ if (authnStatement) {
723
+ if (authnStatement[0].$ && authnStatement[0].$.SessionIndex) {
724
+ profile.sessionIndex = authnStatement[0].$.SessionIndex;
725
+ }
726
+ }
727
+ const subject = assertion.Subject;
728
+ let subjectConfirmation;
729
+ let confirmData = null;
730
+ let subjectConfirmations = null;
731
+ if (subject) {
732
+ const nameID = subject[0].NameID;
733
+ if (nameID && nameID[0]._) {
734
+ profile.nameID = nameID[0]._;
735
+ if (nameID[0].$ && nameID[0].$.Format) {
736
+ profile.nameIDFormat = nameID[0].$.Format;
737
+ profile.nameQualifier = nameID[0].$.NameQualifier;
738
+ profile.spNameQualifier = nameID[0].$.SPNameQualifier;
739
+ }
740
+ }
741
+ subjectConfirmations = subject[0].SubjectConfirmation;
742
+ subjectConfirmation = subjectConfirmations === null || subjectConfirmations === void 0 ? void 0 : subjectConfirmations.find((_subjectConfirmation) => {
743
+ var _a;
744
+ const _confirmData = (_a = _subjectConfirmation.SubjectConfirmationData) === null || _a === void 0 ? void 0 : _a[0];
745
+ if (_confirmData === null || _confirmData === void 0 ? void 0 : _confirmData.$) {
746
+ const subjectNotBefore = _confirmData.$.NotBefore;
747
+ const subjectNotOnOrAfter = _confirmData.$.NotOnOrAfter;
748
+ const maxTimeLimitMs = this.calcMaxAgeAssertionTime(this.options.maxAssertionAgeMs, subjectNotOnOrAfter, assertion.$.IssueInstant);
749
+ const subjErr = this.checkTimestampsValidityError(nowMs, subjectNotBefore, subjectNotOnOrAfter, maxTimeLimitMs);
750
+ if (subjErr === null)
751
+ return true;
752
+ }
753
+ return false;
754
+ });
755
+ if (subjectConfirmation != null) {
756
+ confirmData = subjectConfirmation.SubjectConfirmationData[0];
757
+ }
758
+ }
759
+ /**
760
+ * Test to see that if we have a SubjectConfirmation InResponseTo that it matches
761
+ * the 'InResponseTo' attribute set in the Response
762
+ */
763
+ if (this.mustValidateInResponseTo(Boolean(inResponseTo))) {
764
+ if (subjectConfirmation) {
765
+ if (confirmData === null || confirmData === void 0 ? void 0 : confirmData.$) {
766
+ const subjectInResponseTo = confirmData.$.InResponseTo;
767
+ if (inResponseTo && subjectInResponseTo && subjectInResponseTo != inResponseTo) {
768
+ await this.cacheProvider.removeAsync(inResponseTo);
769
+ throw new Error("InResponseTo does not match subjectInResponseTo");
770
+ }
771
+ else if (subjectInResponseTo) {
772
+ let foundValidInResponseTo = false;
773
+ const result = await this.cacheProvider.getAsync(subjectInResponseTo);
774
+ if (result) {
775
+ const createdAt = new Date(result);
776
+ if (nowMs < createdAt.getTime() + this.options.requestIdExpirationPeriodMs)
777
+ foundValidInResponseTo = true;
778
+ }
779
+ await this.cacheProvider.removeAsync(inResponseTo);
780
+ if (!foundValidInResponseTo) {
781
+ throw new Error("SubjectInResponseTo is not valid");
782
+ }
783
+ break getInResponseTo;
784
+ }
785
+ }
786
+ }
787
+ else {
788
+ if (subjectConfirmations != null && subjectConfirmation == null) {
789
+ msg = "No valid subject confirmation found among those available in the SAML assertion";
790
+ throw new Error(msg);
791
+ }
792
+ else {
793
+ await this.cacheProvider.removeAsync(inResponseTo);
794
+ break getInResponseTo;
795
+ }
796
+ }
797
+ }
798
+ else {
799
+ break getInResponseTo;
800
+ }
801
+ }
802
+ const conditions = assertion.Conditions ? assertion.Conditions[0] : null;
803
+ if (assertion.Conditions && assertion.Conditions.length > 1) {
804
+ msg = "Unable to process multiple conditions in SAML assertion";
805
+ throw new Error(msg);
806
+ }
807
+ if (conditions && conditions.$) {
808
+ const maxTimeLimitMs = this.calcMaxAgeAssertionTime(this.options.maxAssertionAgeMs, conditions.$.NotOnOrAfter, assertion.$.IssueInstant);
809
+ const conErr = this.checkTimestampsValidityError(nowMs, conditions.$.NotBefore, conditions.$.NotOnOrAfter, maxTimeLimitMs);
810
+ if (conErr)
811
+ throw conErr;
812
+ }
813
+ if (this.options.audience !== false) {
814
+ const audienceErr = this.checkAudienceValidityError(this.options.audience, conditions.AudienceRestriction);
815
+ if (audienceErr)
816
+ throw audienceErr;
817
+ }
818
+ const attributeStatement = assertion.AttributeStatement;
819
+ if (attributeStatement) {
820
+ const attributes = [].concat(...attributeStatement
821
+ .filter((attr) => Array.isArray(attr.Attribute))
822
+ .map((attr) => attr.Attribute));
823
+ const attrValueMapper = (value) => {
824
+ const hasChildren = Object.keys(value).some((cur) => {
825
+ return cur !== "_" && cur !== "$";
826
+ });
827
+ return hasChildren ? value : value._;
828
+ };
829
+ if (attributes.length > 0) {
830
+ const profileAttributes = {};
831
+ attributes.forEach((attribute) => {
832
+ if (!Object.prototype.hasOwnProperty.call(attribute, "AttributeValue")) {
833
+ // if attributes has no AttributeValue child, continue
834
+ return;
835
+ }
836
+ const name = attribute.$.Name;
837
+ const value = attribute.AttributeValue.length === 1
838
+ ? attrValueMapper(attribute.AttributeValue[0])
839
+ : attribute.AttributeValue.map(attrValueMapper);
840
+ profileAttributes[name] = value;
841
+ /**
842
+ * If any property is already present in profile and is also present
843
+ * in attributes, then skip the one from attributes. Handle this
844
+ * conflict gracefully without returning any error
845
+ */
846
+ if (Object.prototype.hasOwnProperty.call(profile, name)) {
847
+ return;
848
+ }
849
+ profile[name] = value;
850
+ });
851
+ profile.attributes = profileAttributes;
852
+ }
853
+ }
854
+ if (!profile.mail && profile["urn:oid:0.9.2342.19200300.100.1.3"]) {
855
+ /**
856
+ * See https://spaces.internet2.edu/display/InCFederation/Supported+Attribute+Summary
857
+ * for definition of attribute OIDs
858
+ */
859
+ profile.mail = profile["urn:oid:0.9.2342.19200300.100.1.3"];
860
+ }
861
+ if (!profile.email && profile.mail) {
862
+ profile.email = profile.mail;
863
+ }
864
+ profile.getAssertionXml = () => xml.toString();
865
+ profile.getAssertion = () => parsedAssertion;
866
+ profile.getSamlResponseXml = () => samlResponseXml;
867
+ return { profile, loggedOut: false };
868
+ }
869
+ checkTimestampsValidityError(nowMs, notBefore, notOnOrAfter, maxTimeLimitMs) {
870
+ if (this.options.acceptedClockSkewMs == -1)
871
+ return null;
872
+ if (notBefore) {
873
+ const notBeforeMs = (0, datetime_1.dateStringToTimestamp)(notBefore, "NotBefore");
874
+ if (nowMs + this.options.acceptedClockSkewMs < notBeforeMs)
875
+ return new Error("SAML assertion not yet valid");
876
+ }
877
+ if (notOnOrAfter) {
878
+ const notOnOrAfterMs = (0, datetime_1.dateStringToTimestamp)(notOnOrAfter, "NotOnOrAfter");
879
+ if (nowMs - this.options.acceptedClockSkewMs >= notOnOrAfterMs)
880
+ return new Error("SAML assertion expired: clocks skewed too much");
881
+ }
882
+ if (maxTimeLimitMs) {
883
+ if (nowMs - this.options.acceptedClockSkewMs >= maxTimeLimitMs)
884
+ return new Error("SAML assertion expired: assertion too old");
885
+ }
886
+ return null;
887
+ }
888
+ checkAudienceValidityError(expectedAudience, audienceRestrictions) {
889
+ if (!audienceRestrictions || audienceRestrictions.length < 1) {
890
+ return new Error("SAML assertion has no AudienceRestriction");
891
+ }
892
+ const errors = audienceRestrictions
893
+ .map((restriction) => {
894
+ if (!restriction.Audience || !restriction.Audience[0] || !restriction.Audience[0]._) {
895
+ return new Error("SAML assertion AudienceRestriction has no Audience value");
896
+ }
897
+ if (restriction.Audience[0]._ !== expectedAudience) {
898
+ return new Error("SAML assertion audience mismatch");
899
+ }
900
+ return null;
901
+ })
902
+ .filter((result) => {
903
+ return result !== null;
904
+ });
905
+ if (errors.length > 0) {
906
+ return errors[0];
907
+ }
908
+ return null;
909
+ }
910
+ async validatePostRequestAsync(container) {
911
+ const xml = Buffer.from(container.SAMLRequest, "base64").toString("utf8");
912
+ const dom = await (0, xml_1.parseDomFromString)(xml);
913
+ const doc = await (0, xml_1.parseXml2JsFromString)(xml);
914
+ const certs = await this.certsToCheck();
915
+ if (!(0, xml_1.validateSignature)(xml, dom.documentElement, certs)) {
916
+ throw new Error("Invalid signature on documentElement");
917
+ }
918
+ return await this.processValidlySignedPostRequestAsync(doc, dom);
919
+ }
920
+ async processValidlySignedPostRequestAsync(doc, dom) {
921
+ var _a;
922
+ const request = doc.LogoutRequest;
923
+ this.verifyLogoutRequest(doc);
924
+ if (request) {
925
+ const profile = {};
926
+ if (request.$.ID) {
927
+ profile.ID = request.$.ID;
928
+ }
929
+ else {
930
+ throw new Error("Missing SAML LogoutRequest ID");
931
+ }
932
+ const issuer = request.Issuer;
933
+ if (issuer && issuer[0]._) {
934
+ profile.issuer = issuer[0]._;
935
+ }
936
+ else {
937
+ throw new Error("Missing SAML issuer");
938
+ }
939
+ const nameID = await (0, xml_1.getNameIdAsync)(dom, (_a = this.options.decryptionPvk) !== null && _a !== void 0 ? _a : null);
940
+ if (nameID.value) {
941
+ profile.nameID = nameID.value;
942
+ if (nameID.format) {
943
+ profile.nameIDFormat = nameID.format;
944
+ }
945
+ }
946
+ else {
947
+ throw new Error("Missing SAML NameID");
948
+ }
949
+ const sessionIndex = request.SessionIndex;
950
+ if (sessionIndex) {
951
+ profile.sessionIndex = sessionIndex[0]._;
952
+ }
953
+ return { profile, loggedOut: true };
954
+ }
955
+ else {
956
+ throw new Error("Unknown SAML request message");
957
+ }
958
+ }
959
+ async processValidlySignedSamlLogoutAsync(doc, dom) {
960
+ const response = doc.LogoutResponse;
961
+ const request = doc.LogoutRequest;
962
+ if (response) {
963
+ return { profile: null, loggedOut: true };
964
+ }
965
+ else if (request) {
966
+ return await this.processValidlySignedPostRequestAsync(doc, dom);
967
+ }
968
+ else {
969
+ throw new Error("Unknown SAML response message");
970
+ }
971
+ }
972
+ generateServiceProviderMetadata(decryptionCert, signingCerts) {
973
+ const callbackUrl = this.getCallbackUrl(); // TODO it would probably be useful to have a host parameter here
974
+ return (0, metadata_1.generateServiceProviderMetadata)({
975
+ ...this.options,
976
+ callbackUrl,
977
+ decryptionCert,
978
+ signingCerts,
979
+ });
980
+ }
981
+ /**
982
+ * Process max age assertion and use it if it is more restrictive than the NotOnOrAfter age
983
+ * assertion received in the SAMLResponse.
984
+ *
985
+ * @param maxAssertionAgeMs Max time after IssueInstant that we will accept assertion, in Ms.
986
+ * @param notOnOrAfter Expiration provided in response.
987
+ * @param issueInstant Time when response was issued.
988
+ * @returns {*} The expiration time to be used, in Ms.
989
+ */
990
+ calcMaxAgeAssertionTime(maxAssertionAgeMs, notOnOrAfter, issueInstant) {
991
+ const notOnOrAfterMs = (0, datetime_1.dateStringToTimestamp)(notOnOrAfter, "NotOnOrAfter");
992
+ const issueInstantMs = (0, datetime_1.dateStringToTimestamp)(issueInstant, "IssueInstant");
993
+ if (maxAssertionAgeMs === 0) {
994
+ return notOnOrAfterMs;
995
+ }
996
+ const maxAssertionTimeMs = issueInstantMs + maxAssertionAgeMs;
997
+ return maxAssertionTimeMs < notOnOrAfterMs ? maxAssertionTimeMs : notOnOrAfterMs;
998
+ }
999
+ mustValidateInResponseTo(hasInResponseTo) {
1000
+ return (this.options.validateInResponseTo === types_1.ValidateInResponseTo.always ||
1001
+ (this.options.validateInResponseTo === types_1.ValidateInResponseTo.ifPresent && hasInResponseTo));
1002
+ }
1003
+ }
1004
+ exports.SAML = SAML;
1005
+ //# sourceMappingURL=saml.js.map