From 2c3ddf0a6f40b9d0a4e54fa12b84b8af33eaaadc Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Sat, 23 Mar 2024 13:54:17 -0700 Subject: [PATCH] support interaction between module and apps for updating templates before rendering --- CHANGELOG.md | 4 +- README.md | 8 + index.js | 2 + lib/session-manager.js | 49 ++-- lib/template/helpers.js | 46 ++++ lib/template/ia-html.js | 11 +- lib/template/index.js | 2 + lib/template/login-html.js | 10 +- lib/template/otp-html.js | 10 +- lib/template/settings-html.js | 9 +- package-lock.json | 408 +++++++++++--------------------- package.json | 2 +- test/lib/template/helpers.js | 54 +++++ test/lib/template/login-html.js | 7 + 14 files changed, 327 insertions(+), 295 deletions(-) create mode 100644 lib/template/helpers.js create mode 100644 test/lib/template/helpers.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7f4e7..893b4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ Releases and notable changes to this project are documented here. ### Added -- TOTP 2FA support +- TOTP 2FA support. +- Add appCb parameter to session manager page methods to allow mogrifying template data before rendering, exempli gratia adding app navLinks to account management pages. +- Add sessinNavLinks helper function for populating navLinks in app templates. - Settings page for updating credentials and OTP. - Helper function for securely reading credential from stdin. - Replaced debug authn with plaintext. diff --git a/README.md b/README.md index c67283e..6b4400e 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,17 @@ Class providing service handler functions for rendering and processing session l for local users, or redirecting to IndieAuth server and persisting transient state in session cookie. - `getAdminIA` interprets the returning redirect from the IndieAuth server. +- `getAdminSettings` renders the HTML account settings form +- `postAdminSettings` ingests and acts on account updates + +### Helpers + +- `sessionNavLinks` call from app templates to populate navLinks for account settings and logout ### Other Notes +For the moment, this imposes a web structure of /admin/* for authentication management paths. + The logger used should be able to mask these context fields: - `ctx.parsedBody.credential` diff --git a/index.js b/index.js index 53b799e..5d9da0c 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,11 @@ const Authenticator = require('./lib/authenticator'); const SessionManager = require('./lib/session-manager'); const stdioCredential = require('./lib/stdio-credential'); +const templateHelpers = require('./lib/template/helpers'); module.exports = { Authenticator, SessionManager, stdioCredential, + ...templateHelpers, }; diff --git a/lib/session-manager.js b/lib/session-manager.js index d1045ff..2d3d96c 100644 --- a/lib/session-manager.js +++ b/lib/session-manager.js @@ -73,12 +73,17 @@ class SessionManager { await this._sessionCookieSet(res, undefined, 0, path); } + /** + * @typedef {(pagePathLevel: Number, ctx: Object, htmlOptions: Object) => void} AppTemplateCallback + */ + /** * GET request for establishing admin session. * @param {http.ServerResponse} res * @param {Object} ctx + * @param {AppTemplateCallback} appCb */ - async getAdminLogin(res, ctx) { + async getAdminLogin(res, ctx, appCb) { const _scope = _fileScope('getAdminLogin'); this.logger.debug(_scope, 'called', { ctx }); @@ -94,7 +99,7 @@ class SessionManager { res.setHeader(Enum.Header.Location, redirect); res.end(); } else { - res.end(Template.LoginHTML(ctx, this.options)); + res.end(Template.LoginHTML(ctx, this.options, appCb)); } this.logger.info(_scope, 'finished', { ctx }); @@ -105,20 +110,21 @@ class SessionManager { * POST request for taking form data to establish admin session. * @param {http.ServerResponse} res * @param {Object} ctx + * @param {AppTemplateCallback} appCb */ - async postAdminLogin(res, ctx) { + async postAdminLogin(res, ctx, appCb) { const _scope = _fileScope('postAdminLogin'); this.logger.debug(_scope, 'called', { ctx }); ctx.errors = []; // Check if this was an OTP entry attempt. - if (await this._otpSubmission(res, ctx)) { + if (await this._otpSubmission(res, ctx, appCb)) { // OTP path was taken, either successful entry and session creation, or re-prompting for otp. return; } - if (await this._localUserAuth(res, ctx)) { + if (await this._localUserAuth(res, ctx, appCb)) { // Local auth path was taken. return; } @@ -136,7 +142,7 @@ class SessionManager { } if (ctx.errors.length) { - res.end(Template.LoginHTML(ctx, this.options)); + res.end(Template.LoginHTML(ctx, this.options, appCb)); return; } @@ -207,7 +213,7 @@ class SessionManager { } if (ctx.errors.length) { - res.end(Template.LoginHTML(ctx, this.options)); + res.end(Template.LoginHTML(ctx, this.options, appCb)); return; } @@ -261,7 +267,7 @@ class SessionManager { * @param {String} ctx.parsedBody.otp * @returns {Promise} true if otp was handled, otherwise false indicates further login processing needed */ - async _otpSubmission(res, ctx) { + async _otpSubmission(res, ctx, appCb) { const _scope = _fileScope('_otpSubmission'); const { @@ -287,7 +293,7 @@ class SessionManager { if (!otp) { // Nothing submitted, but valid state, just present otp form again, do not count as attempt. ctx.otpState = stateBox; - res.end(Template.OTPHTML(ctx, this.options)); + res.end(Template.OTPHTML(ctx, this.options, appCb)); this.logger.info(_scope, 'finished otp, nothing entered, request again', { ctx }); return true; } @@ -313,7 +319,7 @@ class SessionManager { ...state, attempt: state.attempt + 1, }); - res.end(Template.OTPHTML(ctx, this.options)); + res.end(Template.OTPHTML(ctx, this.options, appCb)); this.logger.info(_scope, 'finished otp, invalid, request again', { ctx }); return true; @@ -335,7 +341,7 @@ class SessionManager { * @param {Object} ctx * @returns {Promise} true if handled, false if flow should continue */ - async _localUserAuth(res, ctx) { + async _localUserAuth(res, ctx, appCb) { const _scope = _fileScope('_localUserAuth'); // If Indiauth enabled and profile was submitted, defer to that. @@ -355,7 +361,7 @@ class SessionManager { } if (ctx.errors.length) { - res.end(Template.LoginHTML(ctx, this.options)); + res.end(Template.LoginHTML(ctx, this.options, appCb)); return true; } @@ -368,7 +374,7 @@ class SessionManager { attempt: 0, redirect, }); - res.end(Template.OTPHTML(ctx, this.options)); + res.end(Template.OTPHTML(ctx, this.options, appCb)); this.logger.info(_scope, 'finished local, otp required', { ctx }); return true; } @@ -412,8 +418,9 @@ class SessionManager { * This currently only redeems a scope-less profile. * @param {http.ServerResponse} res * @param {Object} ctx + * @param {AppTemplateCallback} appCb */ - async getAdminIA(res, ctx) { + async getAdminIA(res, ctx, appCb) { const _scope = _fileScope('getAdminIA'); this.logger.debug(_scope, 'called', { ctx }); @@ -504,7 +511,7 @@ class SessionManager { if (ctx.errors.length) { await this._sessionCookieClear(res); - res.end(Template.IAHTML(ctx, this.options)); + res.end(Template.IAHTML(ctx, this.options, appCb)); return; } @@ -528,8 +535,9 @@ class SessionManager { * Page for modifying credentials and OTP. * @param {http.ServerResponse} res * @param {Object} ctx + * @param {AppTemplateCallback} appCb */ - async getAdminSettings(res, ctx) { + async getAdminSettings(res, ctx, appCb) { const _scope = _fileScope('getAdminSettings'); this.logger.debug(_scope, 'called', { ctx }); @@ -547,7 +555,7 @@ class SessionManager { ctx.errors.push('An error was encountered. Sorry that is not very helpful.'); } - res.end(Template.SettingsHTML(ctx, this.options)); + res.end(Template.SettingsHTML(ctx, this.options, appCb)); this.logger.info(_scope, 'finished', { ctx }); } @@ -556,8 +564,10 @@ class SessionManager { * Page for modifying credentials and OTP. * @param {http.ServerResponse} res * @param {Object} ctx + * @param {Object[]=} appNavLinks + * @param {AppTemplateCallback} appCb */ - async postAdminSettings(res, ctx) { + async postAdminSettings(res, ctx, appCb) { const _scope = _fileScope('postAdminSettings'); this.logger.debug(_scope, 'called', { ctx }); @@ -597,7 +607,7 @@ class SessionManager { ctx.errors.push('An error was encountered. Sorry that is not very helpful.'); } - res.end(Template.SettingsHTML(ctx, this.options)); + res.end(Template.SettingsHTML(ctx, this.options, appCb)); this.logger.info(_scope, 'finished', { ctx }); } @@ -738,7 +748,6 @@ class SessionManager { } } - } module.exports = SessionManager; diff --git a/lib/template/helpers.js b/lib/template/helpers.js new file mode 100644 index 0000000..67162f8 --- /dev/null +++ b/lib/template/helpers.js @@ -0,0 +1,46 @@ +'use strict'; + +/** + * Populates nevLinks with (currently hardcoded) session-related links. + * @param {Number} pagePathLevel relative to base + * @param {Object} ctx + * @param {String=} ctx.url redirect on logout + * @param {Object=} ctx.session + * @param {String=} ctx.session.authenticatedIdentifier + * @param {String=} ctx.session.authenticatedProfile + * @param {Object} options + * @param {Object[]=} options.navLinks created if not present + * @param {String=} options.pageIdentifier + */ +function sessionNavLinks(pagePathLevel, ctx, options) { + if (!options.navLinks) { + options.navLinks = []; + } + const rootPath = '../'.repeat(pagePathLevel); + const redirectQuery = ctx?.url ? `?r=${encodeURIComponent(ctx.url)}` : rootPath; + const adminPath = rootPath + 'admin/'; + + const user = ctx?.session?.authenticatedProfile || ctx?.session?.authenticatedIdentifier; + if (user) { + if (options.pageIdentifier !== 'account') { + options.navLinks.push({ + text: 'Account', + href: `${adminPath}settings`, + }); + } + options.navLinks.push({ + text: `Logout (${user})`, + href: `${adminPath}logout${redirectQuery}`, + }); + } else { + options.navLinks.push({ + text: 'Login', + href: `${adminPath}login${redirectQuery}`, + }); + } +} + + +module.exports = { + sessionNavLinks, +}; diff --git a/lib/template/ia-html.js b/lib/template/ia-html.js index 5de476a..8a27479 100644 --- a/lib/template/ia-html.js +++ b/lib/template/ia-html.js @@ -1,6 +1,7 @@ 'use strict'; const { TemplateHelper: th } = require('@squeep/html-template-helper'); +const { sessionNavLinks } = require('./helpers'); /** * @@ -23,10 +24,13 @@ function mainContent() { * @param {String} options.manager.pageTitle * @param {Object} options.dingus * @param {String} options.dingus.selfBaseUrl + * @param {(pagePathLevel, ctx, htmlOptions) => {void}} appCb * @returns {String} */ -module.exports = (ctx, options) => { +module.exports = (ctx, options, appCb = () => {}) => { + const pagePathLevel = 1; const htmlOptions = { + pageIdentifier: 'indieAuthError', pageTitle: options.manager.pageTitle, logoUrl: options.manager.logoUrl, footerEntries: options.manager.footerEntries, @@ -34,11 +38,14 @@ module.exports = (ctx, options) => { '

Problems were encountered while trying to authenticate your profile URL.

', ], }; + appCb(pagePathLevel, ctx, htmlOptions); + sessionNavLinks(pagePathLevel, ctx, htmlOptions); + // Ensure there is always an error to report, even if we do not have one, as we ended up here somehow. if (!ctx?.errors?.length) { ctx.errors = [ 'Unknown Error — we are not sure what just happened', ]; } - return th.htmlPage(2, ctx, htmlOptions, mainContent()); + return th.htmlPage(pagePathLevel, ctx, htmlOptions, mainContent()); }; \ No newline at end of file diff --git a/lib/template/index.js b/lib/template/index.js index f645bee..ce326db 100644 --- a/lib/template/index.js +++ b/lib/template/index.js @@ -1,11 +1,13 @@ 'use strict'; +const Helpers = require('./helpers'); const IAHTML = require('./ia-html'); const LoginHTML = require('./login-html'); const OTPHTML = require('./otp-html'); const SettingsHTML = require('./settings-html'); module.exports = { + Helpers, IAHTML, LoginHTML, OTPHTML, diff --git a/lib/template/login-html.js b/lib/template/login-html.js index f59e002..8ee7392 100644 --- a/lib/template/login-html.js +++ b/lib/template/login-html.js @@ -1,6 +1,7 @@ 'use strict'; const { TemplateHelper: th } = require('@squeep/html-template-helper'); +const { sessionNavLinks } = require('./helpers'); /** * Login form. @@ -103,10 +104,13 @@ ${userBlurb} * @param {String=} options.manager.logoUrl * @param {Object} options.dingus * @param {String} options.dingus.selfBaseUrl + * @param {() => {}} appCb * @returns {String} */ -module.exports = (ctx, options) => { +module.exports = (ctx, options, appCb = () => {}) => { + const pagePathLevel = 1; const htmlOptions = { + pageIdentifier: 'login', pageTitle: options.manager.pageTitle, logoUrl: options.manager.logoUrl, footerEntries: options.manager.footerEntries, @@ -115,11 +119,13 @@ module.exports = (ctx, options) => { indieAuthBlurb: options.authenticator.indieAuthBlurb, userBlurb: options.authenticator.userBlurb, }; + appCb(pagePathLevel, ctx, htmlOptions); + sessionNavLinks(pagePathLevel, ctx, htmlOptions); const mainContent = [ ...(options.authenticator.loginBlurb || []), indieAuthURLTrySecureFirstScript(ctx, htmlOptions), indieAuthSection(ctx, htmlOptions), userSection(ctx, htmlOptions), ]; - return th.htmlPage(2, ctx, htmlOptions, mainContent); + return th.htmlPage(pagePathLevel, ctx, htmlOptions, mainContent); }; \ No newline at end of file diff --git a/lib/template/otp-html.js b/lib/template/otp-html.js index 066acce..389f686 100644 --- a/lib/template/otp-html.js +++ b/lib/template/otp-html.js @@ -1,6 +1,7 @@ 'use strict'; const { TemplateHelper: th } = require('@squeep/html-template-helper'); +const { sessionNavLinks } = require('./helpers'); /** * Login form, continued. @@ -37,18 +38,23 @@ ${otpBlurb} * @param {String=} options.manager.logoUrl * @param {Object} options.dingus * @param {String} options.dingus.selfBaseUrl + * @param {() => {}} appCb * @returns {String} */ -module.exports = (ctx, options) => { +module.exports = (ctx, options, appCb = () => {}) => { + const pagePathLevel = 1; const htmlOptions = { + pageIdentifier: 'otp', pageTitle: options.manager.pageTitle, logoUrl: options.manager.logoUrl, footerEntries: options.manager.footerEntries, otpBlurb: options.authenticator?.otpBlurb, }; + appCb(pagePathLevel, ctx, htmlOptions); + sessionNavLinks(pagePathLevel, ctx, htmlOptions); const mainContent = [ ...(options.authenticator?.loginBlurb || []), otpSection(ctx, htmlOptions), ]; - return th.htmlPage(2, ctx, htmlOptions, mainContent); + return th.htmlPage(pagePathLevel, ctx, htmlOptions, mainContent); }; \ No newline at end of file diff --git a/lib/template/settings-html.js b/lib/template/settings-html.js index c5bb6c0..42bfdcd 100644 --- a/lib/template/settings-html.js +++ b/lib/template/settings-html.js @@ -3,6 +3,7 @@ /* eslint-disable no-unused-vars */ const { TemplateHelper: th } = require('@squeep/html-template-helper'); +const { sessionNavLinks } = require('./helpers'); const { TOTP } = require('@squeep/totp'); function updatePasswordSection(ctx, htmlOptions) { @@ -93,16 +94,20 @@ function OTPSection(ctx, htmlOptions) { } -module.exports = (ctx, options) => { +module.exports = (ctx, options, appCb = () => {}) => { + const pagePathLevel = 1; const htmlOptions = { + pageIdentifier: 'account', pageTitle: options.manager.pageTitle, logoUrl: options.manager.logoUrl, footerEntries: options.manager.footerEntries, }; + appCb(pagePathLevel, ctx, htmlOptions); + sessionNavLinks(pagePathLevel, ctx, htmlOptions); const mainContent = [ OTPSection(ctx, htmlOptions), updatePasswordSection(ctx, htmlOptions), ]; - return th.htmlPage(1, ctx, htmlOptions, mainContent); + return th.htmlPage(pagePathLevel, ctx, htmlOptions, mainContent); }; diff --git a/package-lock.json b/package-lock.json index abb3d3e..977ca4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@squeep/api-dingus": "^2.1.0", - "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.5.3", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.0", "@squeep/indieauth-helper": "^1.4.1", "@squeep/mystery-box": "^2.0.2", "@squeep/totp": "^1.1.4" @@ -58,113 +58,42 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -196,14 +125,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -270,12 +199,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -325,9 +254,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -352,13 +281,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", "dev": true, "dependencies": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0" }, "engines": { @@ -366,14 +295,15 @@ } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -451,9 +381,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -477,18 +407,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", + "@babel/parser": "^7.24.1", "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" @@ -986,8 +916,8 @@ } }, "node_modules/@squeep/html-template-helper": { - "version": "1.5.3", - "resolved": "git+https://git.squeep.com/squeep-html-template-helper#084ad86d1dde896c0f49498f5573fcc6a60fddd8", + "version": "1.6.0", + "resolved": "git+https://git.squeep.com/squeep-html-template-helper#2d0ba72a2ea35f45c1ab1ac81fce3d0cbe7db419", "license": "ISC", "dependencies": { "@squeep/lazy-property": "^1.1.2" @@ -1428,9 +1358,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "dev": true, "funding": [ { @@ -1789,9 +1719,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.708", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", - "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==", "dev": true }, "node_modules/emoji-regex": { @@ -1894,10 +1824,13 @@ } }, "node_modules/eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, "engines": { "node": ">=12" }, @@ -1906,14 +1839,14 @@ } }, "node_modules/eslint-plugin-es-x": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", - "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz", + "integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.1.2", "@eslint-community/regexpp": "^4.6.0", - "eslint-compat-utils": "^0.1.2" + "eslint-compat-utils": "^0.5.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2604,9 +2537,9 @@ "dev": true }, "node_modules/html-validate": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.15.0.tgz", - "integrity": "sha512-kqRgG8IDb6rMuQkMAsH7tmzkKTU7a67c0ZZDu4JlncIhImoPFra3H4CzdtIxF7hWaFTXR//QRGEwFiidjh0wfQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.17.1.tgz", + "integrity": "sha512-jBcyyC7/O+ag/gSNfPMtjJ4HrSvASsxLv9FRgpZmK1BGHTF8l7zBibmWFRRYS/s+QsdmBF6dG9JyM1/378/Izw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.0", @@ -5000,9 +4933,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "optional": true, "dependencies": { "chownr": "^2.0.0", @@ -5525,94 +5458,36 @@ } }, "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", "dev": true }, "@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -5636,14 +5511,14 @@ } }, "@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "requires": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, @@ -5694,12 +5569,12 @@ } }, "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "requires": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" } }, "@babel/helper-module-transforms": { @@ -5734,9 +5609,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true }, "@babel/helper-validator-identifier": { @@ -5752,25 +5627,26 @@ "dev": true }, "@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", "dev": true, "requires": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0" } }, "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -5832,9 +5708,9 @@ } }, "@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true }, "@babel/template": { @@ -5849,18 +5725,18 @@ } }, "@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", + "@babel/parser": "^7.24.1", "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" @@ -6242,8 +6118,8 @@ } }, "@squeep/html-template-helper": { - "version": "git+https://git.squeep.com/squeep-html-template-helper#084ad86d1dde896c0f49498f5573fcc6a60fddd8", - "from": "@squeep/html-template-helper@git+https://git.squeep.com/squeep-html-template-helper#v1.5.3", + "version": "git+https://git.squeep.com/squeep-html-template-helper#2d0ba72a2ea35f45c1ab1ac81fce3d0cbe7db419", + "from": "@squeep/html-template-helper@git+https://git.squeep.com/squeep-html-template-helper#v1.6.0", "requires": { "@squeep/lazy-property": "^1.1.2" } @@ -6566,9 +6442,9 @@ "devOptional": true }, "caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "dev": true }, "chalk": { @@ -6825,9 +6701,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.708", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", - "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==", "dev": true }, "emoji-regex": { @@ -6906,21 +6782,23 @@ } }, "eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", "dev": true, - "requires": {} + "requires": { + "semver": "^7.5.4" + } }, "eslint-plugin-es-x": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", - "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz", + "integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.1.2", "@eslint-community/regexpp": "^4.6.0", - "eslint-compat-utils": "^0.1.2" + "eslint-compat-utils": "^0.5.0" } }, "eslint-plugin-n": { @@ -7413,9 +7291,9 @@ "dev": true }, "html-validate": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.15.0.tgz", - "integrity": "sha512-kqRgG8IDb6rMuQkMAsH7tmzkKTU7a67c0ZZDu4JlncIhImoPFra3H4CzdtIxF7hWaFTXR//QRGEwFiidjh0wfQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.17.1.tgz", + "integrity": "sha512-jBcyyC7/O+ag/gSNfPMtjJ4HrSvASsxLv9FRgpZmK1BGHTF8l7zBibmWFRRYS/s+QsdmBF6dG9JyM1/378/Izw==", "dev": true, "requires": { "@babel/code-frame": "^7.10.0", @@ -9204,9 +9082,9 @@ "dev": true }, "tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "optional": true, "requires": { "chownr": "^2.0.0", diff --git a/package.json b/package.json index 1a280a6..e0901c7 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ ], "dependencies": { "@squeep/api-dingus": "^2.1.0", - "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.5.3", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.0", "@squeep/indieauth-helper": "^1.4.1", "@squeep/mystery-box": "^2.0.2", "@squeep/totp": "^1.1.4" diff --git a/test/lib/template/helpers.js b/test/lib/template/helpers.js new file mode 100644 index 0000000..686f72b --- /dev/null +++ b/test/lib/template/helpers.js @@ -0,0 +1,54 @@ +'use strict'; + +const assert = require('node:assert'); +const Helpers = require('../../../lib/template/helpers'); + +describe('Template Helpers', function () { + describe('sessionNavLinks', function () { + let pagePathLevel, ctx, options; + beforeEach(function () { + pagePathLevel = 0; + ctx = { + session: { + authenticatedIdentifier: 'username', + } + }; + options = {}; + }); + it('adds nav links', function () { + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 2); + }); + it('extends nav links', function () { + options.navLinks = [ {} ]; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 3); + }); + it('add login link if no user', function () { + delete ctx.session.authenticatedIdentifier; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 1); + }); + it('adds nav links for profile', function () { + delete ctx.session.authenticatedIdentifier; + ctx.session.authenticatedProfile = 'https://example.com/'; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 2); + }); + it('covers logout redirect', function () { + ctx.url = '../relative'; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 2); + }); + it('covers page depth', function () { + pagePathLevel = 2; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 2); + }); + it('elides account link on account page', function () { + options.pageIdentifier = 'account'; + Helpers.sessionNavLinks(pagePathLevel, ctx, options); + assert.strictEqual(options.navLinks.length, 1); + }); + }); // sessionNavLinks +}); // Template Helpers diff --git a/test/lib/template/login-html.js b/test/lib/template/login-html.js index d9ed065..04e7555 100644 --- a/test/lib/template/login-html.js +++ b/test/lib/template/login-html.js @@ -31,6 +31,13 @@ describe('Template LoginHTML', function () { assert(result); }); + it('covers local user', async function () { + options.authenticator.authnEnabled = ['argon2']; + const result = LoginHTML(ctx, options); + await lintHtml(result); + assert(result); + }); + it('renders errors and additional content', async function () { ctx.errors = ['an error', 'another error']; options.manager.logoUrl = 'https://example.com/logo.png'; -- 2.43.2