From da5626cbae72cd14b77507ebc7700ed77fe5ad64 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 23 Jun 2023 18:35:18 +0200 Subject: [PATCH] TOTP: Use session token instead of JWT to prepare for SSO --- lib/challenge-commands/totp.js | 13 +++++++---- lib/http-worker.js | 40 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/challenge-commands/totp.js b/lib/challenge-commands/totp.js index 67667df11..45f2cedd6 100644 --- a/lib/challenge-commands/totp.js +++ b/lib/challenge-commands/totp.js @@ -87,7 +87,7 @@ const makeSession = (Env, publicKey, cb) => { const sessionId = Sessions.randomId(); var token; nThen(function (w) { - createJWT(Env, sessionId, publicKey, w(function (err, _token) { + /*createJWT(Env, sessionId, publicKey, w(function (err, _token) { if (err) { Env.Log.error("TOTP_VALIDATE_JWT_SIGN_ERROR", { error: Util.serializeError(err), @@ -97,10 +97,15 @@ const makeSession = (Env, publicKey, cb) => { return void cb("TOKEN_ERROR"); } token = _token; - })); + }));*/ }).nThen(function (w) { // store the token - Sessions.write(Env, publicKey, sessionId, token, w(function (err) { + Sessions.write(Env, publicKey, sessionId, JSON.stringify({ + mfa: { + type: 'otp', + exp: (+new Date()) + EXPIRATION + } + }), w(function (err) { if (err) { Env.Log.error("TOTP_VALIDATE_SESSION_WRITE", { error: Util.serializeError(err), @@ -114,7 +119,7 @@ const makeSession = (Env, publicKey, cb) => { })); }).nThen(function () { cb(void 0, { - bearer: token, + bearer: sessionId, }); }); diff --git a/lib/http-worker.js b/lib/http-worker.js index 77cb6909b..b899eb5b1 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -343,12 +343,47 @@ app.use('/block/', function (req, res, next) { let token = authorization.replace(/^Bearer\s+/, '').trim(); if (!token) { return void no(); } + Sessions.read(Env, name, token, function (err, contentStr) { + if (err) { + Log.error('SESSION_READ_ERROR', err); + return res.status(401).json({ + method: mfa_params.method, + code: 401, + }); + } + + let content = Util.tryParse(contentStr); + + if (content.mfa && content.mfa.exp && ((+new Date()) > content.mfa.exp)) { + Log.error("OTP_SESSION_EXPIRED", payload); + Sessions.delete(Env, name, token, function (err) { + if (err) { + Log.error('SESSION_DELETE_EXPIRED_ERROR', err); + return; + } + Log.info('SESSION_DELETE_EXPIRED', err); + }); + return void no(); + } + + // we could also check whether the content of the file matches the token, + // but clients don't have any influence over the reference and can only + // request to create tokens that are scoped to a public key they control. + // I don' think there's any practical benefit to such a check. + + // So, interpret the existence of a file in that location as the continued + // validity of the session. Fall through and let the built-in webserver + // handle the 404 or serving the file. + next(); + }); + // Otherwise we attempt to validate the token // Successful validation implies that the token was issued by the server // since only the server should possess the current bearer secret (unless it has leaked). // It is still possible that the token is not valid for this particular resource, // so the algorithm (HMAC SHA512) only asserts its integrity, not its validity. + /* JWT.verify(token, Env.bearerSecret, { algorithm: 'HS512', }, w(function (err, payload) { @@ -405,11 +440,13 @@ app.use('/block/', function (req, res, next) { // remember the payload for subsequent asynchronous checks jwt_payload = payload; })); + */ }).nThen(function () { // Finally, even if the JWT itself seems valid, the database // is the final authority as to whether the session is still valid, // as it might have been revoked - Sessions.read(Env, name, jwt_payload.ref, function (err /*, content */) { + /* + Sessions.read(Env, name, jwt_payload.ref, function (err) { if (err) { Log.error('JWT_SESSION_READ_ERROR', err); return res.status(401).json({ @@ -428,6 +465,7 @@ app.use('/block/', function (req, res, next) { // handle the 404 or serving the file. next(); }); + */ }); });