updates to support IndieAuth spec 20220212 metadata and issuer
authorJustin Wind <justin.wind+git@gmail.com>
Mon, 21 Feb 2022 22:31:40 +0000 (14:31 -0800)
committerJustin Wind <justin.wind+git@gmail.com>
Mon, 21 Feb 2022 22:31:40 +0000 (14:31 -0800)
README.md
lib/authenticator.js
lib/session-manager.js
package-lock.json
package.json
test/lib/session-manager.js

index 3004f71e272272300f5530f78cdf3a4067ddb0f1..13d210d54bf24a6e3f6d28af8c0b5098e5208922 100644 (file)
--- 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.
index 1cff2108f309e2ac51245070281e0ec346f6168c..c388ee883c60bb0fa79380696b87b9894528d209 100644 (file)
@@ -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;
     }
 
index f7cd33a1dcf31ac13104255610b2a0b01244b3b2..a97a0e0fd3f0edf2ed897e89c9f565e41843525c 100644 (file)
@@ -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 {
index 5758b52fbeebad3c14c30e76a661056b6473d6e4..eda7b79332f6bd77bcaf749e8c530ff6776e8db5 100644 (file)
       }
     },
     "@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",
       }
     },
     "@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",
       }
     },
     "@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",
       }
     },
     "@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"
       }
       }
     },
     "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"
       }
     },
       "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": {
       }
     },
     "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",
       "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",
         "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",
       }
     },
     "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",
       }
     },
     "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": {
       }
     },
     "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",
       }
     },
     "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"
       "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"
             "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",
             "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",
       "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": {
       "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": {
       }
     },
     "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": {
       "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"
       }
     },
       "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": {
index 4a65e1cceb5ff943f517587689cf45b0ebfca2eb..ca01710ba6fc0053949577cadee68ab9ef9ba58d 100644 (file)
   ],
   "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"
   }
 }
index daff0501e3f0cab2cd8a00a414cf01c4a30ab303..bf8df03b94f8875bfa5306e6f9a59f51546cfbe6 100644 (file)
@@ -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