diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml index e3bb1d556e76e96d987b8cb8d1faa7f1e916cc9f..aef9a6c35c6b1d070e7d34b42c0779e67dc52c40 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 ffc5e8205b57b9b96859f3194ded54d17dfa1efb..2b5e5c01b105d9c2da94f8443485b6194cb9205c 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 551090a07ae6fe30f4c9a565a968c567b72961d9..f78709de21cc2e011c4a4d76a9cfb9bc9454f623 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.", });