From a44a8c411be1831e7b2418212ee0b295206ca27a Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Mon, 21 Feb 2022 14:31:40 -0800 Subject: [PATCH] updates to support IndieAuth spec 20220212 metadata and issuer --- README.md | 17 +++ lib/authenticator.js | 14 +++ lib/session-manager.js | 51 ++++++++- package-lock.json | 216 +++++++++++++++++------------------- package.json | 14 +-- test/lib/session-manager.js | 84 +++++++++++++- 6 files changed, 267 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 3004f71..13d210d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,23 @@ Class which fetches and validates identifiers and their credentials from databas There are some methods for dealing with Basic auth in here as well, but they are not used by sessions. +- `sessionRequiredLocal` redirect to login if session does not represent a valid local user +- `sessionRequired` redirect to login if session does not represent a valid local user or IA profile +- `sessionOptionalLocal` check if session represents a valid local user +- `sessionOptional` check if session represents a valid local user or IA profile + +If session is valid for any of these, ctx.session will be populated appropriately. + +- `ctx.authenticatedId` will be set to either the valid local identifier or the valid profile +- `ctx.session.authenticatedIdentifier` will be set if valid local identifier +- `ctx.session.authenticatedProfile` will be set if valid IA profile + ### SessionManager Class providing service handler functions for rendering and processing session login and logout pages. + +- `getAdminLogin` renders the HTML login form +- `postAdminLogin` ingests login form data, either validating or denying + for local users, or redirecting to IndieAuth server and persisting transient state + in session cookie. +- `getAdminIA` interprets the returning redirect from the IndieAuth server. diff --git a/lib/authenticator.js b/lib/authenticator.js index 1cff210..c388ee8 100644 --- a/lib/authenticator.js +++ b/lib/authenticator.js @@ -20,6 +20,7 @@ class Authenticator { * @param {Boolean} options.authenticator.secureAuthOnly * @param {String[]} options.authenticator.forbiddenPAMIdentifiers * @param {String[]} options.authenticator.authnEnabled + * @param {Number=} options.authenticator.inactiveSessionLifespanSeconds * @param {String[]=} options.authenticator.loginBlurb * @param {String[]=} options.authenticator.indieAuthBlurb * @param {String[]=} options.authenticator.userBlurb @@ -51,6 +52,8 @@ class Authenticator { } this.mysteryBox = new MysteryBox(logger, options); + + this.cookieLifespan = options.authenticator.inactiveSessionLifespanSeconds || 60 * 60 * 24 * 32; } @@ -259,6 +262,17 @@ class Authenticator { && (ctx.session.authenticatedIdentifier || (profilesAllowed && ctx.session.authenticatedProfile))) { this.logger.debug(_scope, 'valid session cookie', { ctx }); + // Refresh timeout on valid session. + const cookieParts = [ + sessionCookie, + 'HttpOnly', + `Path=${this.options.dingus.proxyPrefix}/`, + `Max-Age=${this.cookieLifespan}`, + ]; + if (this.options.authenticator.secureAuthOnly) { + cookieParts.push('Secure'); + } + res.setHeader(Enum.Header.SetCookie, cookieParts.join('; ')); return true; } diff --git a/lib/session-manager.js b/lib/session-manager.js index f7cd33a..a97a0e0 100644 --- a/lib/session-manager.js +++ b/lib/session-manager.js @@ -19,7 +19,8 @@ class SessionManager { * @param {Object} options * @param {Object} options.authenticator * @param {String[]} options.authenticator.authnEnabled - * @param {Object} options.authenticator.secureAuthOnly + * @param {Number=} options.authenticator.inactiveSessionLifespanSeconds + * @param {Boolean} options.authenticator.secureAuthOnly * @param {Object} options.dingus * @param {Object} options.dingus.proxyPrefix * @param {Object} options.dingus.selfBaseUrl @@ -31,7 +32,7 @@ class SessionManager { this.indieAuthCommunication = new IndieAuthCommunication(logger, options); this.mysteryBox = new MysteryBox(logger, options); - this.cookieLifespan = 60 * 60 * 24 * 32; + this.cookieLifespan = options.authenticator.inactiveSessionLifespanSeconds || 60 * 60 * 24 * 32; } @@ -121,6 +122,7 @@ class SessionManager { return; } + // Otherwise, carry on with IndieAuth handshake. let me, session, authorizationEndpoint; try { me = new URL(ctx.parsedBody['me']); @@ -132,28 +134,51 @@ class SessionManager { if (this.options.authenticator.authnEnabled.includes('indieAuth') && me) { const profile = await this.indieAuthCommunication.fetchProfile(me); - if (!profile || !profile.authorizationEndpoint) { + if (!profile || !profile.metadata) { 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); + authorizationEndpoint = new URL(profile.metadata.authorizationEndpoint); } catch (e) { - ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`); + ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.metadata.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`); + } + + if (profile.metadata.issuer) { + // Validate issuer + try { + const issuer = new URL(profile.metadata.issuer); + if (issuer.hash + || issuer.search + || issuer.protocol.toLowerCase() !== 'https:') { // stupid URL trailing colon thing + this.logger.debug(_scope, 'supplied issuer url invalid', { ctx }); + ctx.errors.push('Authorization server provided an invalid issuer field.'); + } + } catch (e) { + this.logger.debug(_scope, 'failed to parse supplied issuer url', { ctx }); + ctx.errors.push('Authorization server provided an unparsable issuer field.'); + } + } else { + this.logger.debug(_scope, 'no issuer in metadata, assuming legacy mode', { ctx }); + // Strict 20220212 compliance would error here. + // ctx.errors.push('Authorization server did not provide issuer field, as required by current specification.'); } } if (authorizationEndpoint) { const pkce = await IndieAuthCommunication.generatePKCE(); + session = { authorizationEndpoint: authorizationEndpoint.href, state: ctx.requestId, codeVerifier: pkce.codeVerifier, me, redirect, + issuer: profile.metadata.issuer, }; + // Update auth endpoint parameters Object.entries({ 'response_type': 'code', 'client_id': this.options.dingus.selfBaseUrl, @@ -203,6 +228,7 @@ class SessionManager { /** * GET request for returning IndieAuth redirect. + * This currently only redeems a scope-less profile. * @param {http.ServerResponse} res * @param {Object} ctx */ @@ -230,6 +256,7 @@ class SessionManager { } // Validate unpacked session values + // ... // Add any auth errors if (ctx.queryParams['error']) { @@ -251,6 +278,18 @@ class SessionManager { ctx.errors.push('invalid code'); } + // check issuer + if (ctx.session.issuer) { + if (ctx.queryParams['iss'] !== ctx.session.issuer) { + this.logger.debug(_scope, 'issuer mismatch', { ctx }); + ctx.errors.push('invalid issuer'); + } + } else { + this.logger.debug(_scope, 'no issuer in metadata, assuming legacy mode', { ctx }); + // Strict 20220212 compliance would error here. (Also earlier.) + // ctx.errors.push('invalid issuer'); + } + let redeemProfileUrl; try { redeemProfileUrl = new URL(ctx.session.authorizationEndpoint); @@ -272,7 +311,7 @@ class SessionManager { 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) { + if (newProfile.metadata.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 { diff --git a/package-lock.json b/package-lock.json index 5758b52..eda7b79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -328,14 +328,14 @@ } }, "@eslint/eslintrc": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", - "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", + "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.2.0", + "espree": "^9.3.1", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", @@ -353,9 +353,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", - "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", + "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -448,18 +448,18 @@ } }, "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.0.tgz", + "integrity": "sha512-M8vapsv9qQupMdzrVzkn5rb9jG7aUTEPAZdMtME2PuBaefksFZVE2C1g4LBRTkF/k3nRDNbDc5tp5NFC1PEYxA==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" } }, "@sinonjs/samsam": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", - "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -482,15 +482,15 @@ } }, "@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" + "version": "git+https://git.squeep.com/squeep-html-template-helper#5506e8de3b9c93e2ec2d37d71134eee8ee5fd27c", + "from": "git+https://git.squeep.com/squeep-html-template-helper#v1.0.2" }, "@squeep/indieauth-helper": { - "version": "git+https://git.squeep.com/squeep-indieauth-helper/#a15c4051aee22b76eca268e6a53b0944a9e40d0c", - "from": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.0.2", + "version": "git+https://git.squeep.com/squeep-indieauth-helper/#7ece3489799b5349e22e95e3bd9fe7a30a985ebf", + "from": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.1.0", "requires": { "@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.3", - "axios": "^0.25.0", + "axios": "^0.26.0", "iconv": "^3.0.1", "microformats-parser": "^1.4.1" } @@ -619,14 +619,14 @@ } }, "argon2": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.28.3.tgz", - "integrity": "sha512-NkEJOImg+T7nnkx6/Fy8EbjZsF20hbBBKdVP/YUxujuLTAjIODmrFeY4vVpekKwGAGDm6roXxluFQ+CIaoVrbg==", + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.28.4.tgz", + "integrity": "sha512-WsfqiDp/tf5+eieLc1+S7RtO7Y3cAiZQ1F6GIaskENoJy/6xuCN5WGBIc8dG7QVPDavy6jUSads8zwZTtrHVag==", "optional": true, "requires": { - "@mapbox/node-pre-gyp": "^1.0.7", + "@mapbox/node-pre-gyp": "^1.0.8", "@phc/format": "^1.0.0", - "node-addon-api": "^4.2.0", + "node-addon-api": "^4.3.0", "opencollective-postinstall": "^2.0.3" } }, @@ -637,11 +637,11 @@ "dev": true }, "axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz", + "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==", "requires": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.14.8" } }, "balanced-match": { @@ -757,9 +757,9 @@ } }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -1032,12 +1032,12 @@ "dev": true }, "eslint": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz", - "integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", + "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.0.5", + "@eslint/eslintrc": "^1.1.0", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -1045,10 +1045,10 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", + "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.2.0", - "espree": "^9.3.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1148,15 +1148,15 @@ } }, "eslint-plugin-sonarjs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.11.0.tgz", - "integrity": "sha512-ei/WuZiL0wP+qx2KrxKyZs3+eDbxiGAhFSm3GKCOOAUkg+G2ny6TSXDB2j67tvyqHefi+eoQsAgGQvz+nEtIBw==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.12.0.tgz", + "integrity": "sha512-soxjK67hoYxO8hesKqXWN50GSM+oG2r35N5WnAMehetahO6zoVpv3HZbdziP0jYWNopEe6te/BFUZOYAZI+qhg==", "dev": true }, "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -1181,20 +1181,20 @@ } }, "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", - "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "dev": true, "requires": { "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -1312,15 +1312,15 @@ } }, "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "foreground-child": { "version": "2.0.0", @@ -1428,9 +1428,9 @@ } }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -1940,32 +1940,32 @@ "optional": true }, "mocha": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.4.tgz", - "integrity": "sha512-+q2aV5VlJZuLgCWoBvGI5zEwPF9eEI0kr/sAA9Jm4xMND7RfIEyF8JE7C0JIg8WXRG+P1sdIAb5ccoHPlXLzcw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", + "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -1982,6 +1982,23 @@ "wrap-ansi": "^7.0.0" } }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1992,20 +2009,6 @@ "path-exists": "^4.0.0" } }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2094,9 +2097,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "natural-compare": { @@ -2106,27 +2109,16 @@ "dev": true }, "nise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", - "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } } }, "no-case": { @@ -2139,9 +2131,9 @@ } }, "node-addon-api": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", - "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "optional": true }, "node-fetch": { @@ -2800,16 +2792,16 @@ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" }, "sinon": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", - "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.1.tgz", + "integrity": "sha512-8yx2wIvkBjIq/MGY1D9h1LMraYW+z1X0mb648KZnKSdvLasvDu7maa0dFaNYdTDczFgbjNw2tOmWdTk9saVfwQ==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^8.1.0", - "@sinonjs/samsam": "^6.0.2", + "@sinonjs/fake-timers": "^9.0.0", + "@sinonjs/samsam": "^6.1.1", "diff": "^5.0.0", - "nise": "^5.1.0", + "nise": "^5.1.1", "supports-color": "^7.2.0" } }, @@ -3092,9 +3084,9 @@ "dev": true }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index 4a65e1c..ca01710 100644 --- a/package.json +++ b/package.json @@ -31,23 +31,23 @@ ], "dependencies": { "@squeep/api-dingus": "git+https://git.squeep.com/squeep-api-dingus/#v1.2.4", - "@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.2", + "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.0.2", + "@squeep/indieauth-helper": "git+https://git.squeep.com/squeep-indieauth-helper/#v1.1.0", "@squeep/mystery-box": "git+https://git.squeep.com/squeep-mystery-box/#v1.0.3" }, "optionalDependencies": { - "argon2": "^0.28.3", + "argon2": "^0.28.4", "node-linux-pam": "^0.2.1" }, "devDependencies": { - "eslint": "^8.7.0", + "eslint": "^8.9.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-security": "^1.4.0", - "eslint-plugin-sonarjs": "^0.11.0", + "eslint-plugin-sonarjs": "^0.12.0", "html-minifier-lint": "^2.0.0", - "mocha": "^9.1.4", + "mocha": "^9.2.1", "nyc": "^15.1.0", "pre-commit": "^1.2.2", - "sinon": "^12.0.1" + "sinon": "^13.0.1" } } diff --git a/test/lib/session-manager.js b/test/lib/session-manager.js index daff050..bf8df03 100644 --- a/test/lib/session-manager.js +++ b/test/lib/session-manager.js @@ -84,7 +84,9 @@ describe('SessionManager', function () { it('covers valid profile', async function () { ctx.parsedBody.me = 'https://example.com/profile'; manager.indieAuthCommunication.fetchProfile.resolves({ - authorizationEndpoint: 'https://example.com/auth', + metadata: { + authorizationEndpoint: 'https://example.com/auth', + }, }); await manager.postAdminLogin(res, ctx); assert.strictEqual(res.statusCode, 302); @@ -104,11 +106,48 @@ describe('SessionManager', function () { it('covers invalid profile response endpoint', async function () { ctx.parsedBody.me = 'https://example.com/profile'; manager.indieAuthCommunication.fetchProfile.resolves({ - authorizationEndpoint: 'not an auth endpoint', + metadata: { + authorizationEndpoint: 'not an auth endpoint', + }, }); await manager.postAdminLogin(res, ctx); assert(!res.setHeader.called); }); + describe('living-standard-20220212', function () { + it('covers valid profile', async function () { + ctx.parsedBody.me = 'https://example.com/profile'; + manager.indieAuthCommunication.fetchProfile.resolves({ + metadata: { + issuer: 'https://example.com/', + authorizationEndpoint: 'https://example.com/auth', + }, + }); + await manager.postAdminLogin(res, ctx); + assert.strictEqual(res.statusCode, 302); + }); + it('covers bad issuer url', async function () { + ctx.parsedBody.me = 'https://example.com/profile'; + manager.indieAuthCommunication.fetchProfile.resolves({ + metadata: { + issuer: 'http://example.com/?bah#foo', + authorizationEndpoint: 'https://example.com/auth', + }, + }); + await manager.postAdminLogin(res, ctx); + assert(!res.setHeader.called); + }); + it('covers unparsable issuer url', async function () { + ctx.parsedBody.me = 'https://example.com/profile'; + manager.indieAuthCommunication.fetchProfile.resolves({ + metadata: { + issuer: 'not a url', + authorizationEndpoint: 'https://example.com/auth', + }, + }); + await manager.postAdminLogin(res, ctx); + assert(!res.setHeader.called); + }); + }); // living-standard-20220212 }); // postAdminLogin describe('getAdminLogout', function () { @@ -128,7 +167,9 @@ describe('SessionManager', function () { me, }); manager.indieAuthCommunication.fetchProfile.resolves({ - authorizationEndpoint, + metadata: { + authorizationEndpoint, + }, }); sinon.stub(manager.mysteryBox, 'unpack').resolves({ authorizationEndpoint, @@ -242,13 +283,48 @@ describe('SessionManager', function () { }); manager.indieAuthCommunication.fetchProfile.restore(); sinon.stub(manager.indieAuthCommunication, 'fetchProfile').resolves({ - authorizationEndpoint: 'https://elsewhere.example.com/auth', + metadata: { + authorizationEndpoint: 'https://elsewhere.example.com/auth', + }, }); await manager.getAdminIA(res, ctx); assert(ctx.errors.length); }); + describe('living-standard-20220212', function () { + beforeEach(function () { + manager.indieAuthCommunication.fetchProfile.resolves({ + metadata: { + authorizationEndpoint, + issuer: 'https://example.com/', + }, + }); + manager.mysteryBox.unpack.resolves({ + authorizationEndpoint, + issuer: 'https://example.com/', + state, + me, + }); + }); + it('covers valid', async function () { + ctx.queryParams['state'] = state; + ctx.queryParams['code'] = 'codeCodeCode'; + ctx.queryParams['iss'] = 'https://example.com/'; + + await manager.getAdminIA(res, ctx); + + assert.strictEqual(res.statusCode, 302); + }); + it('covers mis-matched issuer', async function () { + ctx.queryParams['state'] = state; + ctx.queryParams['code'] = 'codeCodeCode'; + + await manager.getAdminIA(res, ctx); + + assert(ctx.errors.length); + }); + }); // living-standard-20220212 }); // getAdminIA }); // SessionManager \ No newline at end of file -- 2.43.2