From 4430ddb05988470bc8f0479e7d07db1f7d4646ba Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Tue, 13 Aug 2024 17:54:12 -0700 Subject: [PATCH] Encryption in JWT for single-user password mode (#2111) * wip encrypting jwt value * Encrypt/Decrypt pass in JWT value for verification in single-user password mode --- .github/workflows/dev-build.yaml | 2 +- server/endpoints/system.js | 6 +++++- server/utils/middleware/validatedRequest.js | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index e3bb1d55..aef9a6c3 100644 --- a/.github/workflows/dev-build.yaml +++ b/.github/workflows/dev-build.yaml @@ -6,7 +6,7 @@ concurrency: on: push: - branches: ['pipertts-support'] # put your current branch to create a build. Core team only. + branches: ['encrypt-jwt-value'] # put your current branch to create a build. Core team only. paths-ignore: - '**.md' - 'cloud-deployments/*' diff --git a/server/endpoints/system.js b/server/endpoints/system.js index ffc5e820..2b5e5c01 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -51,6 +51,7 @@ const { generateRecoveryCodes, } = require("../utils/PasswordRecovery"); const { SlashCommandPresets } = require("../models/slashCommandsPresets"); +const { EncryptionManager } = require("../utils/EncryptionManager"); function systemEndpoints(app) { if (!app) return; @@ -236,7 +237,10 @@ function systemEndpoints(app) { }); response.status(200).json({ valid: true, - token: makeJWT({ p: password }, "30d"), + token: makeJWT( + { p: new EncryptionManager().encrypt(password) }, + "30d" + ), message: null, }); } diff --git a/server/utils/middleware/validatedRequest.js b/server/utils/middleware/validatedRequest.js index 551090a0..f78709de 100644 --- a/server/utils/middleware/validatedRequest.js +++ b/server/utils/middleware/validatedRequest.js @@ -1,6 +1,8 @@ const { SystemSettings } = require("../../models/systemSettings"); const { User } = require("../../models/user"); +const { EncryptionManager } = require("../EncryptionManager"); const { decodeJWT } = require("../http"); +const EncryptionMgr = new EncryptionManager(); async function validatedRequest(request, response, next) { const multiUserMode = await SystemSettings.isMultiUserMode(); @@ -39,14 +41,24 @@ async function validatedRequest(request, response, next) { const bcrypt = require("bcrypt"); const { p } = decodeJWT(token); - if (p === null) { + if (p === null || !/\w{32}:\w{32}/.test(p)) { response.status(401).json({ error: "Token expired or failed validation.", }); return; } - if (!bcrypt.compareSync(p, bcrypt.hashSync(process.env.AUTH_TOKEN, 10))) { + // Since the blame of this comment we have been encrypting the `p` property of JWTs with the persistent + // encryptionManager PEM's. This prevents us from storing the `p` unencrypted in the JWT itself, which could + // be unsafe. As a consequence, existing JWTs with invalid `p` values that do not match the regex + // in ln:44 will be marked invalid so they can be logged out and forced to log back in and obtain an encrypted token. + // This kind of methodology only applies to single-user password mode. + if ( + !bcrypt.compareSync( + EncryptionMgr.decrypt(p), + bcrypt.hashSync(process.env.AUTH_TOKEN, 10) + ) + ) { response.status(401).json({ error: "Invalid auth credentials.", });