From 3c547e314b79a31fb3f15412a47707a22dc3eefd Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Thu, 30 Dec 2021 14:41:18 -0800 Subject: [PATCH] refactor of authentication and html-templates into separate modules --- config/default.js | 18 +- package-lock.json | 745 ++++++++++++------ package.json | 14 +- src/authenticator.js | 276 ------- src/service.js | 21 +- src/session-manager.js | 279 ------- src/template/admin-ia-html.js | 39 - src/template/admin-login-html.js | 83 -- src/template/admin-overview-html.js | 17 +- src/template/admin-topic-details-html.js | 17 +- src/template/index.js | 2 - src/template/root-html.js | 19 +- src/template/template-helper.js | 206 +---- static/custom.css | 0 static/theme.css | 4 + test/lint-html.js | 13 + test/src/authenticator.js | 344 -------- test/src/service.js | 10 +- test/src/session-manager.js | 240 ------ test/src/template/admin-ia-html.js | 25 - test/src/template/admin-login-html.js | 26 - test/src/template/admin-overview-html.js | 8 +- test/src/template/admin-topic-details-html.js | 9 +- test/src/template/root-html.js | 4 + test/src/template/template-helper.js | 170 ---- 25 files changed, 632 insertions(+), 1957 deletions(-) delete mode 100644 src/authenticator.js delete mode 100644 src/session-manager.js delete mode 100644 src/template/admin-ia-html.js delete mode 100644 src/template/admin-login-html.js create mode 100644 static/custom.css create mode 100644 test/lint-html.js delete mode 100644 test/src/authenticator.js delete mode 100644 test/src/session-manager.js delete mode 100644 test/src/template/admin-ia-html.js delete mode 100644 test/src/template/admin-login-html.js diff --git a/config/default.js b/config/default.js index bbc66fc..9781ba6 100644 --- a/config/default.js +++ b/config/default.js @@ -2,8 +2,9 @@ // Provide default values for all configuration. -const packageName = require('../package.json').name; +const { name: packageName, version: packageVersion } = require('../package.json'); const common = require('../src/common'); +const Enum = require('../src/enum'); const defaultOptions = { // Uniquely identify this instance, used to tag work-in-progress. @@ -47,6 +48,7 @@ const defaultOptions = { manager: { pageTitle: packageName, // title on html pages + logoUrl: '/static/logo.svg', // image to go with title footerEntries: [ // common footers on all html pages 'Development Repository / GitHub mirror', '©', @@ -62,17 +64,19 @@ const defaultOptions = { claimTimeoutSeconds: 600, // how long until an in-progress task is deemed abandoned }, - // Outgoing request UA header. Comments are defaults in code. + // Outgoing request UA header. + // These values are the same as the defaults in the code, but we are setting + // them here so they also apply to UA of other modules, e.g. @squeep/indieauth-helper userAgent: { - // product: packageName, - // version: packageVersion, - // implementation: Enum.Specification, + product: packageName, + version: packageVersion, + implementation: Enum.Specification, }, authenticator: { basicRealm: packageName, // Realm prompt for login on administration pages secureAuthOnly: true, // Require secure transport for authentication. - authnEnabled: ['argon2', 'pam'], + authnEnabled: ['indieAuth', 'argon2', 'pam'], forbiddenPAMIdentifiers: ['root'], }, @@ -84,4 +88,4 @@ const defaultOptions = { }; -module.exports = defaultOptions; \ No newline at end of file +module.exports = defaultOptions; diff --git a/package-lock.json b/package-lock.json index d63e97d..bb90f2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -436,20 +436,20 @@ "dev": true }, "@mapbox/node-pre-gyp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", - "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", + "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", "optional": true, "requires": { "detect-libc": "^1.0.3", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.5", "nopt": "^5.0.0", - "npmlog": "^4.1.2", + "npmlog": "^5.0.1", "rimraf": "^3.0.2", - "semver": "^7.3.4", - "tar": "^6.1.0" + "semver": "^7.3.5", + "tar": "^6.1.11" } }, "@phc/format": { @@ -494,10 +494,10 @@ "dev": true }, "@squeep/api-dingus": { - "version": "git+https://git.squeep.com/squeep-api-dingus/#47f38ca4c67e902ccef0b7114a0d144f476258bd", - "from": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.2", + "version": "git+https://git.squeep.com/squeep-api-dingus/#a1b8b75e64c862276563e874c11a0228ac681946", + "from": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.3", "requires": { - "mime-db": "^1.50.0", + "mime-db": "^1.51.0", "uuid": "^8.3.2" }, "dependencies": { @@ -508,37 +508,39 @@ } } }, + "@squeep/authentication-module": { + "version": "git+https://git.squeep.com/squeep-authentication-module/#ecf9c52f00dae3b9eee8316a1cffbc496f0fbd67", + "from": "git+https://git.squeep.com/squeep-authentication-module/#v1.1.0", + "requires": { + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.3", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.0.1", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.1", + "@squeep/mystery-box": "git+https://git.squeep.com/squeep-mystery-box/#v1.0.2", + "argon2": "^0.28.3", + "node-linux-pam": "^0.2.1" + } + }, + "@squeep/html-template-helper": { + "version": "git+https://git.squeep.com/squeep-html-template-helper#d3f76b9e76b8f133e8158c1087bb01b32c38d9bb", + "from": "git+https://git.squeep.com/squeep-html-template-helper#v1.0.1" + }, "@squeep/indieauth-helper": { - "version": "git+https://git.squeep.com/squeep-indieauth-helper/#ea59a4bf7a596b502aacf06baee26518d86fd312", - "from": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.0", + "version": "git+https://git.squeep.com/squeep-indieauth-helper/#f8f7fc8165426471f3d290f10cda2ea2cdb7f643", + "from": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.1", "requires": { - "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.0", - "axios": "^0.23.0", + "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.2", + "axios": "^0.24.0", "iconv": "^3.0.1", "microformats-parser": "^1.4.0" - }, - "dependencies": { - "@squeep/web-linking": { - "version": "git+https://git.squeep.com/squeep-web-linking/#3436c07c25324507228f3d538d345ea35751c623", - "from": "git+https://git.squeep.com/squeep-web-linking/#v1.0.0" - }, - "axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", - "requires": { - "follow-redirects": "^1.14.4" - } - } } }, "@squeep/mystery-box": { - "version": "git+https://git.squeep.com/squeep-mystery-box/#8df7723f7bbd9ad239bd5a3f66d6e9a8cd3c8100", - "from": "git+https://git.squeep.com/squeep-mystery-box/#v1.0.1" + "version": "git+https://git.squeep.com/squeep-mystery-box/#c6559eefb2fa776d6e4eedb876470790a53339c1", + "from": "git+https://git.squeep.com/squeep-mystery-box/#v1.0.2" }, "@squeep/web-linking": { - "version": "git+https://git.squeep.com/squeep-web-linking/#0728e2b9347826ed0be7f9a7939bf5d61e181204", - "from": "git+https://git.squeep.com/squeep-web-linking/#v1.0.1" + "version": "git+https://git.squeep.com/squeep-web-linking/#e0d9ba95109a2bbb61dbdce7a582f5579aa77c5c", + "from": "git+https://git.squeep.com/squeep-web-linking/#v1.0.2" }, "@ungap/promise-all-settled": { "version": "1.1.2", @@ -639,9 +641,9 @@ } }, "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "optional": true }, "archy": { @@ -651,92 +653,15 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "optional": true, "requires": { "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argon2": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.28.3.tgz", - "integrity": "sha512-NkEJOImg+T7nnkx6/Fy8EbjZsF20hbBBKdVP/YUxujuLTAjIODmrFeY4vVpekKwGAGDm6roXxluFQ+CIaoVrbg==", - "optional": true, - "requires": { - "@mapbox/node-pre-gyp": "^1.0.7", - "@phc/format": "^1.0.0", - "node-addon-api": "^4.2.0", - "opencollective-postinstall": "^2.0.3" + "readable-stream": "^3.6.0" }, "dependencies": { - "@mapbox/node-pre-gyp": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", - "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", - "optional": true, - "requires": { - "detect-libc": "^1.0.3", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.5", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - } - }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", - "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", - "optional": true, - "requires": { - "ansi-regex": "^5.0.1", - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, - "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "npmlog": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", - "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", - "optional": true, - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.0", - "set-blocking": "^2.0.0" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -747,29 +672,21 @@ "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "optional": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "optional": true, - "requires": { - "ansi-regex": "^5.0.1" - } } } }, + "argon2": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.28.3.tgz", + "integrity": "sha512-NkEJOImg+T7nnkx6/Fy8EbjZsF20hbBBKdVP/YUxujuLTAjIODmrFeY4vVpekKwGAGDm6roXxluFQ+CIaoVrbg==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.7", + "@phc/format": "^1.0.0", + "node-addon-api": "^4.2.0", + "opencollective-postinstall": "^2.0.3" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -810,9 +727,9 @@ "optional": true }, "better-sqlite3": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.4.5.tgz", - "integrity": "sha512-mybC3dgrtJeHkIRGP36tST7wjBlIMgTRAXhhO4bMpPZ17EG23FZxZeFcwKWy6o8mV1SKQFnQNyeAZlQpGrgheQ==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.4.6.tgz", + "integrity": "sha512-LB/UxnMhcJY12bRCDXl2jTk0lsbXHCHOLn3cPjGhy3GCcVPGq45sCGJPUdfBZnfXGN14tYTJyq0ztUI3lGng8A==", "optional": true, "requires": { "bindings": "^1.5.0", @@ -936,6 +853,16 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", @@ -1026,11 +953,28 @@ } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "optional": true }, + "clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1079,6 +1023,12 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", "dev": true }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1692,54 +1642,40 @@ "dev": true }, "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "optional": true, "requires": { - "aproba": "^1.0.3", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "optional": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.1" } } } @@ -1860,6 +1796,30 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + } + }, + "html-minifier-lint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-minifier-lint/-/html-minifier-lint-2.0.0.tgz", + "integrity": "sha1-i0vuyTHaiNsEyHc+5WgYjfO111g=", + "dev": true, + "requires": { + "html-minifier": "3.x" + } + }, "htmlparser2": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", @@ -2242,6 +2202,12 @@ "is-unicode-supported": "^0.1.0" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2274,9 +2240,9 @@ } }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mimic-response": { "version": "3.1.0", @@ -2298,9 +2264,9 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", "optional": true, "requires": { "yallist": "^4.0.0" @@ -2462,6 +2428,15 @@ } } }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, "node-abi": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.5.0.tgz", @@ -2478,10 +2453,13 @@ "optional": true }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "optional": true + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + } }, "node-linux-pam": { "version": "0.2.1", @@ -2496,6 +2474,29 @@ "yargs": "15.4.1" }, "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2505,6 +2506,22 @@ "color-convert": "^2.0.1" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -2520,6 +2537,40 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "color-convert": { @@ -2553,6 +2604,31 @@ "path-exists": "^4.0.0" } }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -2568,6 +2644,18 @@ "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==", "optional": true }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2586,6 +2674,26 @@ "p-limit": "^2.2.0" } }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -2595,6 +2703,40 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "y18n": { @@ -2620,6 +2762,40 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "yargs-parser": { @@ -2665,15 +2841,15 @@ "dev": true }, "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" } }, "number-is-nan": { @@ -2945,6 +3121,15 @@ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", "optional": true }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3241,6 +3426,87 @@ "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "prelude-ls": { @@ -3364,6 +3630,12 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -3651,14 +3923,6 @@ "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" - }, - "dependencies": { - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true - } } }, "tar-fs": { @@ -3671,6 +3935,14 @@ "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "optional": true + } } }, "tar-stream": { @@ -3782,6 +4054,36 @@ "is-typedarray": "^1.0.0" } }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3839,45 +4141,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "optional": true, "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "optional": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "optional": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "word-wrap": { diff --git a/package.json b/package.json index 4ae8aac..99af66e 100644 --- a/package.json +++ b/package.json @@ -32,19 +32,18 @@ "coverage-check" ], "dependencies": { - "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.2", - "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.0", - "@squeep/mystery-box": "git+https://git.squeep.com/squeep-mystery-box/#v1.0.1", - "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.1", + "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.3", + "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.1.0", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.0.1", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.1", + "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.2", "axios": "^0.24.0", "feedparser": "^2.2.10", "htmlparser2": "^7.2.0", "iconv": "^3.0.1" }, "optionalDependencies": { - "argon2": "^0.28.3", - "better-sqlite3": "^7.4.5", - "node-linux-pam": "^0.2.1", + "better-sqlite3": "^7.4.6", "pg-promise": "^10.11.1" }, "devDependencies": { @@ -52,6 +51,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-security": "^1.4.0", "eslint-plugin-sonarjs": "^0.11.0", + "html-minifier-lint": "^2.0.0", "mocha": "^9.1.3", "mocha-steps": "^1.3.0", "nyc": "^15.1.0", diff --git a/src/authenticator.js b/src/authenticator.js deleted file mode 100644 index 3a196fc..0000000 --- a/src/authenticator.js +++ /dev/null @@ -1,276 +0,0 @@ -'use strict'; - -const common = require('./common'); -const Enum = require('./enum'); -const Errors = require('./errors'); -const { MysteryBox } = require('@squeep/mystery-box'); - -const _fileScope = common.fileScope(__filename); - -class Authenticator { - /** - * @param {Console} logger - * @param {*} db - * @param {Object} options - * @param {Object} options.authenticator - * @param {String} options.authenticator.basicRealm - * @param {Boolean} options.authenticator.secureAuthOnly - * @param {String[]} options.authenticator.forbiddenPAMIdentifiers - * @param {String[]} options.authenticator.authnEnabled - */ - constructor(logger, db, options) { - this.logger = logger; - this.db = db; - this.basicRealm = options.authenticator.basicRealm; - this.secureAuthOnly = options.authenticator.secureAuthOnly; - - this.authn = { - DEBUG_ANY: {}, - }; - try { - this.authn.argon2 = require('argon2'); - } catch (e) { /**/ } - try { - this.authn.pam = require('node-linux-pam'); - this.forbiddenPAMIdentifiers = options.authenticator.forbiddenPAMIdentifiers; - } catch (e) { /**/ } - - this.authnEnabled = Object.keys(this.authn).filter((auth) => options.authenticator.authnEnabled.includes(auth)); - this.logger.debug(_fileScope('constructor'), 'available mechanisms', { authn: this.authnEnabled }); - - if (this.authnEnabled.length === 0) { - throw new Error('no authentication mechanisms available'); - } - - this.mysteryBox = new MysteryBox(logger, options); - } - - - /** - * Check for valid Basic auth, updates ctx with identifier if valid. - * @param {String} credentials - * @param {Object} ctx - * @returns {Boolean} - */ - async isValidBasic(credentials, ctx) { - const _scope = _fileScope('isValidBasic'); - this.logger.debug(_scope, 'called', { ctx }); - - const [identifier, credential] = common.splitFirst(credentials, ':', ''); - - return this.isValidIdentifierCredential(identifier, credential, ctx); - } - - - /** - * Check local auth entries. - * @param {String} identifier - * @param {String} credential - * @param {Object} ctx - */ - async isValidIdentifierCredential(identifier, credential, ctx) { - const _scope = _fileScope('isValidIdentifierCredential'); - this.logger.debug(_scope, 'called', { identifier, credential: '*'.repeat(credential.length), ctx }); - - let isValid = false; - - await this.db.context(async (dbCtx) => { - const authData = await this.db.authenticationGet(dbCtx, identifier); - if (!authData) { - this.logger.debug(_scope, 'failed, invalid identifier', { ctx, identifier }); - } else { - if (authData.credential.startsWith('$argon2') - && this.authnEnabled.includes('argon2')) { - isValid = await this.authn.argon2.verify(authData.credential, credential); - } else if (authData.credential.startsWith('$PAM$') - && this.authnEnabled.includes('pam')) { - isValid = this._isValidPAMIdentifier(identifier, credential); - } else { - this.logger.error(_scope, 'failed, unknown type of stored credential', { identifier, ctx }); - } - } - - if (this.authnEnabled.includes('DEBUG_ANY')) { - isValid = true; - } - - if (isValid) { - ctx.authenticationId = identifier; - await this.db.authenticationSuccess(dbCtx, identifier); - } - }); // dbCtx - - return isValid; - } - - - /** - * Check system PAM. - * @param {String} identifier - * @param {String} credential - * @returns {Boolean} - */ - async _isValidPAMIdentifier(identifier, credential) { - const _scope = _fileScope('_isValidPAMIdentifier'); - let isValid = false; - if (this.forbiddenPAMIdentifiers.includes(identifier)) { - return false; - } - try { - await this.authn.pam.pamAuthenticatePromise({ username: identifier, password: credential }); - isValid = true; - } catch (e) { - this.logger.debug(_scope, 'failed', { error: e }); - if (!(e instanceof this.authn.pam.PamError)) { - throw e; - } - } - return isValid; - } - - - /** - * Determine which Authorization header is available, and if it is valid. - * @param {String} authorizationHeader - * @param {Object} ctx - */ - async isValidAuthorization(authorizationHeader, ctx) { - const _scope = _fileScope('isValidAuthorization'); - this.logger.debug(_scope, 'called', { authorizationHeader, ctx }); - - const [authMethod, authString] = common.splitFirst(authorizationHeader, ' ', '').map((x) => x.trim()); - // eslint-disable-next-line sonarjs/no-small-switch - switch (authMethod.toLowerCase()) { - case 'basic': { - const credentials = Buffer.from(authString, 'base64').toString('utf-8'); - return this.isValidBasic(credentials, ctx); - } - - default: - this.logger.debug(_scope, 'unknown authorization scheme', { ctx }); - return false; - } - } - - - /** - * Send a response requesting basic auth. - * @param {http.ServerResponse} res - */ - requestBasic(res) { - res.setHeader(Enum.Header.WWWAuthenticate, `Basic realm="${this.basicRealm}", charset="UTF-8"`); - throw new Errors.ResponseError(Enum.ErrorResponse.Unauthorized); - } - - - /** - * Attempt to parse a session cookie, and determine if it - * contains authenticated user. - * Restores ctx.session from cookie data. - * @param {Object} ctx - * @param {String} cookieHeader - * @returns {Boolean} - */ - async isValidCookieAuth(ctx, cookieHeader) { - const _scope = _fileScope('isValidCookieAuth'); - this.logger.debug(_scope, 'called', { ctx, cookieHeader }); - - const [ cookieName, cookieValue ] = common.splitFirst(cookieHeader, '=', ''); - if (cookieName !== 'WSHas') { - return false; - } - try { - ctx.session = await this.mysteryBox.unpack(cookieValue); - this.logger.debug(_scope, 'unpacked cookie', { ctx }); - return !!ctx.session.authenticatedProfile || !! ctx.session.authenticatedIdentifier; - } catch (e) { - this.logger.debug(_scope, 'could not unpack cookie', { error:e, ctx }); - return false; - } - } - - - /** - * Require that a request has valid auth over secure channel, requests if missing. - * @param {http.ClientRequest} req - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {String} loginPath - */ - async required(req, res, ctx, loginPath) { - const _scope = _fileScope('required'); - this.logger.debug(_scope, 'called', { ctx }); - - if (this.secureAuthOnly && ctx.clientProtocol.toLowerCase() !== 'https') { - this.logger.debug(_scope, 'rejecting insecure auth', ctx); - throw new Errors.ResponseError(Enum.ErrorResponse.Forbidden, 'authentication required, but connection is insecure; cannot continue'); - } - - const sessionCookie = req.getHeader(Enum.Header.Cookie); - if (sessionCookie && await this.isValidCookieAuth(ctx, sessionCookie)) { - return true; - } - - const authData = req.getHeader(Enum.Header.Authorization); - if (authData) { - if (await this.isValidAuthorization(authData, ctx)) { - return true; - } - // If they came in trying header auth, let them try again. - return this.requestBasic(res); - } - - // Otherwise redirect to login. - res.statusCode = 302; - res.setHeader(Enum.Header.Location, loginPath); - res.end(); - - return false; - } - - - /** - * Require that a request has valid local auth over secure channel, requests if missing. - * @param {http.ClientRequest} req - * @param {http.ServerResponse} res - * @param {Object} ctx - * @param {String} loginPath - */ - async requiredLocal(req, res, ctx, loginPath) { - const _scope = _fileScope('requiredLocal'); - this.logger.debug(_scope, 'called', { ctx }); - - if (this.secureAuthOnly && ctx.clientProtocol.toLowerCase() !== 'https') { - this.logger.debug(_scope, 'rejecting insecure auth', ctx); - throw new Errors.ResponseError(Enum.ErrorResponse.Forbidden, 'authentication required, but connection is insecure; cannot continue'); - } - - // Only accept identifier sessions. - const sessionCookie = req.getHeader(Enum.Header.Cookie); - if (sessionCookie - && await this.isValidCookieAuth(ctx, sessionCookie) - && ctx.session.authenticatedIdentifier) { - return true; - } - - // Allow header auth - const authData = req.getHeader(Enum.Header.Authorization); - if (authData) { - if (await this.isValidAuthorization(authData, ctx)) { - return true; - } - // If they came in trying header auth, let them try again. - return this.requestBasic(res); - } - - // Otherwise redirect to login. - res.statusCode = 302; - res.setHeader(Enum.Header.Location, loginPath); - res.end(); - - return false; - } - -} - -module.exports = Authenticator; \ No newline at end of file diff --git a/src/service.js b/src/service.js index a0043f7..6dcde1e 100644 --- a/src/service.js +++ b/src/service.js @@ -9,8 +9,7 @@ const { Dingus } = require('@squeep/api-dingus'); const common = require('./common'); const Enum = require('./enum'); const Manager = require('./manager'); -const SessionManager = require('./session-manager'); -const Authenticator = require('./authenticator'); +const { Authenticator, SessionManager } = require('@squeep/authentication-module'); const path = require('path'); const _fileScope = common.fileScope(__filename); @@ -155,7 +154,7 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); - await this.authenticator.required(req, res, ctx, this.loginPath); + await this.authenticator.sessionRequired(req, res, ctx, this.loginPath); await this.manager.getAdminOverview(res, ctx); } @@ -174,7 +173,7 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); - await this.authenticator.required(req, res, ctx, this.loginPath); + await this.authenticator.sessionRequired(req, res, ctx, this.loginPath); await this.manager.getTopicDetails(res, ctx); } @@ -209,7 +208,7 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); - await this.authenticator.requiredLocal(req, res, ctx, this.loginPath); + await this.authenticator.apiRequiredLocal(req, res, ctx); await this.maybeIngestBody(req, res, ctx); ctx.method = req.method; @@ -228,7 +227,7 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); - await this.authenticator.requiredLocal(req, res, ctx, this.loginPath); + await this.authenticator.apiRequiredLocal(req, res, ctx); await this.maybeIngestBody(req, res, ctx); ctx.method = req.method; @@ -247,13 +246,14 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); - await this.authenticator.requiredLocal(req, res, ctx, this.loginPath); + await this.authenticator.apiRequiredLocal(req, res, ctx); await this.manager.processTasks(res, ctx); } /** + * Delegate login to authentication module. * @param {http.ClientRequest} req * @param {http.ServerResponse} res * @param {Object} ctx @@ -271,6 +271,7 @@ class Service extends Dingus { /** + * Delegate login to authentication module. * @param {http.ClientRequest} req * @param {http.ServerResponse} res * @param {Object} ctx @@ -281,6 +282,8 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); + await this.authenticator.sessionOptionalLocal(req, res, ctx); + await this.maybeIngestBody(req, res, ctx); await this.sessionManager.postAdminLogin(res, ctx); @@ -288,6 +291,7 @@ class Service extends Dingus { /** + * Delegate login to authentication module. * @param {http.ClientRequest} req * @param {http.ServerResponse} res * @param {Object} ctx @@ -298,11 +302,14 @@ class Service extends Dingus { this.setResponseType(this.responseTypes, req, res, ctx); + await this.authenticator.sessionOptionalLocal(req, res, ctx); + await this.sessionManager.getAdminLogout(res, ctx); } /** + * Delegate login to authentication module. * @param {http.ClientRequest} req * @param {http.ServerResponse} res * @param {Object} ctx diff --git a/src/session-manager.js b/src/session-manager.js deleted file mode 100644 index 5caff34..0000000 --- a/src/session-manager.js +++ /dev/null @@ -1,279 +0,0 @@ -'use strict'; - -/** - * Here we process activities which support login sessions. - */ - -const { Communication: IndieAuthCommunication } = require('@squeep/indieauth-helper'); -const { MysteryBox } = require('@squeep/mystery-box'); -const common = require('./common'); -const Enum = require('./enum'); -const Template = require('./template'); - -const _fileScope = common.fileScope(__filename); - -class SessionManager { - constructor(logger, authenticator, options) { - this.logger = logger; - this.authenticator = authenticator; - this.options = options; - this.indieAuthCommunication = new IndieAuthCommunication(logger, options); - this.mysteryBox = new MysteryBox(logger, options); - - this.secureCookie = options.authenticator.secureAuthOnly ? ' Secure;' : ''; - this.cookieLifespan = 60 * 60 * 24 * 32; - } - - - /** - * Set or update our session cookie. - * @param {http.ServerResponse} res - * @param {Object} session - * @param {Number} maxAge - */ - async _sessionCookieSet(res, session, maxAge) { - const cookieName = 'WSHas'; - const secureSession = session && await this.mysteryBox.pack(session) || ''; - const cookie = [ - `${cookieName}=${secureSession}`, - 'HttpOnly', - this.secureCookie, - `Max-Age: ${maxAge}`, - ].join('; '); - res.setHeader(Enum.Header.SetCookie, cookie); - } - - - /** - * GET request for establishing admin session. - * @param {http.ServerResponse} res - * @param {Object} ctx - */ - async getAdminLogin(res, ctx) { - const _scope = _fileScope('getAdminLogin'); - this.logger.debug(_scope, 'called', { ctx }); - - res.end(Template.adminLoginHTML(ctx, this.options)); - this.logger.info(_scope, 'finished', { ctx }) - } - - - /** - * POST request for taking form data to establish admin session. - * @param {http.ServerResponse} res - * @param {Object} ctx - */ - async postAdminLogin(res, ctx) { - const _scope = _fileScope('postAdminLogin'); - this.logger.debug(_scope, 'called', { ctx }); - - ctx.errors = []; - - // Only attempt user login if no IndieAuth profile is set - if (!ctx.parsedBody['me']) { - this.logger.debug(_scope, 'no indieauth profile, trying identifier', { ctx }); - - const identifier = ctx.parsedBody['identifier']; - const credential = ctx.parsedBody['credential']; - - const isValidLocalIdentifier = await this.authenticator.isValidIdentifierCredential(identifier, credential, ctx); - if (!isValidLocalIdentifier) { - ctx.errors.push('Invalid username or password'); - } - - if (ctx.errors.length) { - res.end(Template.adminLoginHTML(ctx, this.options)); - return; - } - - // Valid auth, persist the authenticated session - ctx.session = { - authenticatedIdentifier: ctx.authenticationId, - }; - await this._sessionCookieSet(res, ctx.session, this.cookieLifespan); - res.statusCode = 302; - res.setHeader(Enum.Header.Location, './'); - res.end(); - this.logger.info(_scope, 'finished local', { ctx }); - return; - } - - let me, session, authorizationEndpoint; - try { - me = new URL(ctx.parsedBody['me']); - } catch (e) { - this.logger.debug(_scope, 'failed to parse supplied profile url', { ctx }); - ctx.errors.push(`Unable to understand '${ctx.parsedBody['me']}' as a profile URL.`); - } - - if (me) { - const profile = await this.indieAuthCommunication.fetchProfile(me); - if (!profile || !profile.authorizationEndpoint) { - this.logger.debug(_scope, 'failed to find any profile information at url', { ctx }); - ctx.errors.push(`No profile information was found at '${me}'.`); - } else { - // fetch and parse me for 'authorization_endpoint' relation links - try { - authorizationEndpoint = new URL(profile.authorizationEndpoint); - } catch (e) { - ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`); - } - } - - if (authorizationEndpoint) { - const pkce = await IndieAuthCommunication.generatePKCE(); - session = { - authorizationEndpoint: authorizationEndpoint.href, - state: ctx.requestId, - codeVerifier: pkce.codeVerifier, - me, - }; - - Object.entries({ - 'response_type': 'code', - 'client_id': this.options.dingus.selfBaseUrl, - 'redirect_uri': `${this.options.dingus.selfBaseUrl}admin/_ia`, - 'state': session.state, - 'code_challenge': pkce.codeChallenge, - 'code_challenge_method': pkce.codeChallengeMethod, - 'me': me, - }).forEach(([name, value]) => authorizationEndpoint.searchParams.set(name, value)); - } - } - - if (ctx.errors.length) { - res.end(Template.adminLoginHTML(ctx, this.options)); - return; - } - - await this._sessionCookieSet(res, session, this.cookieLifespan); - res.setHeader(Enum.Header.Location, authorizationEndpoint.href); - res.statusCode = 302; // Found - res.end(); - - this.logger.info(_scope, 'finished indieauth', { ctx }) - } - - - /** - * GET request to remove current credentials. - * @param {http.ServerResponse} res - * @param {Object} ctx - */ - async getAdminLogout(res, ctx) { - const _scope = _fileScope('getAdminLogout'); - this.logger.debug(_scope, 'called', { ctx }); - - this._sessionCookieSet(res, '', 0); - res.statusCode = 302; - res.setHeader(Enum.Header.Location, './'); - res.end(); - - this.logger.info(_scope, 'finished', { ctx }); - } - - - /** - * GET request for returning IndieAuth redirect. - * @param {http.ServerResponse} res - * @param {Object} ctx - */ - async getAdminIA(res, ctx) { - const _scope = _fileScope('getAdminIA'); - this.logger.debug(_scope, 'called', { ctx }); - - ctx.errors = []; - ctx.session = {}; - - // Unpack cookie to restore session data - - const [ cookieName, cookieValue ] = common.splitFirst((ctx.cookie || ''), '=', ''); - if (cookieName !== 'WSHas') { - this.logger.debug(_scope, 'no cookie', { ctx }); - ctx.errors.push('missing required cookie'); - } else { - try { - ctx.session = await this.mysteryBox.unpack(cookieValue); - this.logger.debug(_scope, 'restored session from cookie', { ctx }); - } catch (e) { - this.logger.debug(_scope, 'could not unpack cookie'); - ctx.errors.push('invalid cookie'); - } - } - - // Validate unpacked session values - - // Add any auth errors - if (ctx.queryParams['error']) { - ctx.errors.push(ctx.queryParams['error']); - if (ctx.queryParams['error_description']) { - ctx.errors.push(ctx.queryParams['error_description']); - } - } - - // check stuff - if (ctx.queryParams['state'] !== ctx.session.state) { - this.logger.debug(_scope, 'state mismatch', { ctx }); - ctx.errors.push('invalid state'); - } - - const code = ctx.queryParams['code']; - if (!code) { - this.logger.debug(_scope, 'missing code', { ctx }); - ctx.errors.push('invalid code'); - } - - let redeemProfileUrl; - try { - redeemProfileUrl = new URL(ctx.session.authorizationEndpoint); - } catch (e) { - this.logger.debug(_scope, 'failed to parse restored session authorization endpoint as url', { ctx }); - ctx.errors.push('invalid cookie'); - } - let profile; - if (redeemProfileUrl) { - profile = await this.indieAuthCommunication.redeemProfileCode(redeemProfileUrl, code, ctx.session.codeVerifier, this.options.dingus.selfBaseUrl, `${this.options.dingus.selfBaseUrl}admin/_ia`); - if (!profile) { - this.logger.debug(_scope, 'no profile from code redemption', { ctx }); - ctx.errors.push('did not get a profile response from authorization endpoint code redemption'); - } else if (!profile.me) { - this.logger.debug(_scope, 'no profile me identifier from code redemption', { ctx }); - ctx.errors.push('did not get \'me\' value from authorization endpoint code redemption'); - } else if (profile.me !== ctx.session.me) { - this.logger.debug(_scope, 'mis-matched canonical me from redeemed profile', { ctx, profile }); - const newProfileUrl = new URL(profile.me); - // Rediscover auth endpoint for the new returned profile. - const newProfile = await this.indieAuthCommunication.fetchProfile(newProfileUrl); - if (newProfile.authorizationEndpoint !== ctx.session.authorizationEndpoint) { - this.logger.debug(_scope, 'mis-matched auth endpoints between provided me and canonical me', { ctx, profile, newProfile }); - ctx.errors.push('canonical profile url provided by authorization endpoint is not handled by that endpoint, cannot continue'); - } else { - // The endpoints match, all is okay, update our records. - ctx.session.me = profile.me; - } - } - } - - if (ctx.errors.length) { - await this._sessionCookieSet(res, '', 0); - res.end(Template.adminIAHTML(ctx, this.options)); - return; - } - - // set cookie as auth valid, redirect to admin - ctx.session = { - authenticatedProfile: ctx.session.me, - }; - - await this._sessionCookieSet(res, ctx.session, this.cookieLifespan); - res.statusCode = 302; - res.setHeader(Enum.Header.Location, './'); - res.end(); - - this.logger.info(_scope, 'finished', { ctx }) - } - - -} - -module.exports = SessionManager; \ No newline at end of file diff --git a/src/template/admin-ia-html.js b/src/template/admin-ia-html.js deleted file mode 100644 index 103582d..0000000 --- a/src/template/admin-ia-html.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const th = require('./template-helper'); - -function errorsSection(ctx) { - return (ctx.errors && ctx.errors.length) ? `
-

Troubles

-

Problems were encountered while trying to authenticate your profile URL.

- -
-
- Try Again? -
` - : ''; -} - -/** - * Render any errors from attempting IndieAuth. - * @param {Object} ctx - * @param {String[]} ctx.errors - * @param {Object} options - * @param {Object} options.manager - * @param {String} options.manager.pageTitle - * @param {Object} options.dingus - * @param {String} options.dingus.selfBaseUrl - * @returns {String} - */ -module.exports = (ctx, options) => { - const pageTitle = options.manager.pageTitle; - const footerEntries = options.manager.footerEntries; - const headElements = []; - const navLinks = []; - const mainContent = [ - errorsSection(ctx), - ]; - return th.htmlTemplate(ctx, 2, pageTitle, headElements, navLinks, mainContent, footerEntries); -}; \ No newline at end of file diff --git a/src/template/admin-login-html.js b/src/template/admin-login-html.js deleted file mode 100644 index a87f930..0000000 --- a/src/template/admin-login-html.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const th = require('./template-helper'); - - -/** - * Login form. - */ -function indieAuthSection() { - return `
-

Login

-
-
- IndieAuth - - - -
-
- Logging in with an IndieAuth profile will allow you to view details of any topics on this hub which are related to that profile's domain. -
-
-
-
`; -} - - -function userSection(ctx, options) { - const secure = (ctx.clientProtocol || '').toLowerCase() === 'https'; - const showUserForm = secure || !options.authenticator.secureAuthOnly; - return showUserForm ? `
-
-
- User Account - - -
- - -
- -
-
-
-
` - : ''; -} - - -function errorsSection(ctx) { - return (ctx.errors && ctx.errors.length) ? `
-

Troubles

-

Problems were encountered while trying to authenticate you.

- -
` - : ''; -} - - -/** - * Render login form for both local and profile authentication. - * @param {Object} ctx - * @param {Object} options - * @param {Object} options.manager - * @param {String} options.manager.pageTitle - * @param {Object} options.dingus - * @param {String} options.dingus.selfBaseUrl - * @returns {String} - */ -module.exports = (ctx, options) => { - const pageTitle = options.manager.pageTitle; - const footerEntries = options.manager.footerEntries; - const headElements = []; - const navLinks = []; - const mainContent = [ - errorsSection(ctx), - indieAuthSection(), - userSection(ctx, options), - ]; - return th.htmlTemplate(ctx, 2, pageTitle, headElements, navLinks, mainContent, footerEntries); -}; \ No newline at end of file diff --git a/src/template/admin-overview-html.js b/src/template/admin-overview-html.js index a8efef3..525337d 100644 --- a/src/template/admin-overview-html.js +++ b/src/template/admin-overview-html.js @@ -13,13 +13,20 @@ const th = require('./template-helper'); */ module.exports = (ctx, options) => { const pageTitle = `${options.manager.pageTitle} - Topics`; - const headElements = []; - const navLinks = []; + const logoUrl = options.manager.logoUrl; const footerEntries = options.manager.footerEntries; if (!ctx.topics) { ctx.topics = []; } - return th.htmlTemplate(ctx, 1, pageTitle, headElements, navLinks, [ + + const htmlOptions = { + pageTitle, + logoUrl, + footerEntries, + }; + + const content = [ + '', // This fixes a layout rendering flash on load in FF; do not know why this works but it does. `

${ctx.topics.length ? ctx.topics.length : 'no'} topic${(ctx.topics.length === 1) ? '' : 's'}

@@ -31,5 +38,7 @@ module.exports = (ctx, options) => { `
`, - ], footerEntries); + ]; + + return th.htmlPage(1, ctx, htmlOptions, content); }; \ No newline at end of file diff --git a/src/template/admin-topic-details-html.js b/src/template/admin-topic-details-html.js index e039189..0c281e4 100644 --- a/src/template/admin-topic-details-html.js +++ b/src/template/admin-topic-details-html.js @@ -14,7 +14,7 @@ const th = require('./template-helper'); */ module.exports = (ctx, options) => { const pageTitle = `${options.manager.pageTitle} - Topic Details`; - const headElements = []; + const logoUrl = options.manager.logoUrl; const navLinks = [ { href: '..', @@ -25,7 +25,16 @@ module.exports = (ctx, options) => { if (!ctx.subscriptions) { ctx.subscriptions = []; } - return th.htmlTemplate(ctx, 2, pageTitle, headElements, navLinks, [ + + const htmlOptions = { + pageTitle, + logoUrl, + navLinks, + footerEntries, + }; + + const content = [ + '', // This fixes a layout rendering flash on load in FF; do not know why this works but it does. `
`, @@ -47,5 +56,7 @@ module.exports = (ctx, options) => { `
`, - ], footerEntries); + ]; + + return th.htmlPage(2, htmlOptions, content); }; \ No newline at end of file diff --git a/src/template/index.js b/src/template/index.js index ced94bf..e177788 100644 --- a/src/template/index.js +++ b/src/template/index.js @@ -1,8 +1,6 @@ 'use strict'; module.exports = { - adminLoginHTML: require('./admin-login-html'), - adminIAHTML: require('./admin-ia-html'), adminOverviewHTML: require('./admin-overview-html'), adminTopicDetailsHTML: require('./admin-topic-details-html'), badgeSVG: require('./badge-svg'), diff --git a/src/template/root-html.js b/src/template/root-html.js index fc655c0..14b9d47 100644 --- a/src/template/root-html.js +++ b/src/template/root-html.js @@ -2,10 +2,10 @@ const th = require('./template-helper'); -function hAppSection(pageTitle) { +function hAppSection(pageTitle, logoUrl) { return `