updates to support IndieAuth spec 20220212 metadata and issuer
[squeep-authentication-module] / lib / authenticator.js
index 15089a020f5a977f09239537e210c40c6ca61173..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;
     }
 
@@ -335,6 +349,40 @@ class Authenticator {
     return this.sessionCheck(req, res, ctx, undefined, false);
   }
 
+
+  /**
+   * Require auth for an API endpoint.
+   * Check for valid local identifier in Authorization header; optionally
+   * fall back to session cookie if no header provided.
+   * Prompts for Basic auth if not valid.
+   * @param {http.ClientRequest} req
+   * @param {http.ServerResponse} res
+   * @param {Object} ctx
+   * @param {Boolean} sessionAlsoValid
+   */
+  async apiRequiredLocal(req, res, ctx, sessionAlsoValid = true) {
+    const _scope = _fileScope('apiRequiredLocal');
+    this.logger.debug(_scope, 'called', { ctx, sessionAlsoValid });
+
+    // If a Authorization header was provided, never consider session as a fallback.
+    const authorizationHeader = req.getHeader(Enum.Header.Authorization);
+    if (authorizationHeader) {
+      if (await this.isValidAuthorization(authorizationHeader, ctx)) {
+        this.logger.debug(_scope, 'valid authorization', { ctx, sessionAlsoValid });
+        return true;
+      }
+    } else {
+      if (sessionAlsoValid
+      &&  await this.sessionCheck(req, res, ctx, undefined, false, false)) {
+        this.logger.debug(_scope, 'valid session', { ctx, sessionAlsoValid });
+        return true;
+      }
+    }
+
+    this.logger.debug(_scope, 'invalid authorization', { ctx, sessionAlsoValid });
+    this.requestBasic(res);
+  }
+
 }
 
 module.exports = Authenticator;
\ No newline at end of file