From e232c6634ea7c9db96b337a9e9ee3f1b7cf041bb Mon Sep 17 00:00:00 2001 From: chesspro13 Date: Sat, 7 Sep 2024 11:41:54 -0700 Subject: [PATCH] TOTP working --- package.json | 2 ++ src/app.ts | 4 +++ .../options/multi_factor_authentication.js | 35 +++++++++---------- src/routes/api/totp.ts | 5 +-- src/routes/login.ts | 32 ++++++++--------- src/services/environment_variable_loader.ts | 19 ++++++++++ src/services/totp.ts | 16 +++++++++ 7 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 src/services/environment_variable_loader.ts diff --git a/package.json b/package.json index 936937a61..7a20e9905 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "type": "module", "scripts": { "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", + "start-server-mfa": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon --env-file=.env src/www.ts", "start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", "start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", "start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts", @@ -69,6 +70,7 @@ "dayjs": "^1.11.12", "dayjs-plugin-utc": "0.1.2", "debounce": "^2.1.0", + "dotenv": "^16.4.5", "ejs": "^3.1.10", "electron-debug": "3.2.0", "electron-dl": "3.5.2", diff --git a/src/app.ts b/src/app.ts index 9106534b6..4b5c9b828 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,6 +16,7 @@ import { startScheduledCleanup } from "./services/erase.js"; import sql_init from "./services/sql_init.js"; import oidc from "express-openid-connect"; import openID from "./services/open_id.js"; +import * as dotenv from "dotenv"; await import('./services/handlers.js'); await import('./becca/becca_loader.js'); @@ -24,6 +25,9 @@ const app = express(); const scriptDir = dirname(fileURLToPath(import.meta.url)); +// Configure environment variables +dotenv.config(); + // Initialize DB sql_init.initializeDb(); diff --git a/src/public/app/widgets/type_widgets/options/multi_factor_authentication.js b/src/public/app/widgets/type_widgets/options/multi_factor_authentication.js index 3082a7990..99cd42ac9 100644 --- a/src/public/app/widgets/type_widgets/options/multi_factor_authentication.js +++ b/src/public/app/widgets/type_widgets/options/multi_factor_authentication.js @@ -10,7 +10,7 @@ const TPL = ` Multi-Factor Authentication (MFA) adds an extra layer of security to your account. Instead of just entering a password to log in, MFA requires you to provide one or more additional pieces of evidence to verify your identity. This way, even if someone gets hold of your - password, they still can't access your account without the second piece of information. + password, they still ca TOTP_ENABLED is not set in environment variable. Requires restart.n't access your account without the second piece of information. It's like adding an extra lock to your door, making it much harder for anyone else to break in. @@ -51,7 +51,7 @@ const TPL = `
TOTP Secret Key
- +

Single Sign-on Recovery Keys

@@ -117,9 +117,10 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { for (let i = 0; i < 8; i++) this.$recoveryKeys.push(this.$widget.find(".key_" + i)); - this.$totpEnabled.on("change", async () => { - this.updateSecret(); - }); + // Depricated. Will use .env to control. + // this.$totpEnabled.on("change", async () => { + // this.updateSecret(); + // }); this.$oAuthEnabledCheckbox.on("change", async () => { this.updateOAuthStatus(); @@ -159,15 +160,16 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { this.displayRecoveryKeys(); } - async updateSecret() { - if (this.$totpEnabled.prop("checked")) { - server.post("totp/enable"); + // Depricated. Will use .env to control. + // async updateSecret() { + // if (this.$totpEnabled.prop("checked")) { + // server.post("totp/enable"); - } - else { - server.post("totp/disable"); - } - } + // } + // else { + // server.post("totp/disable"); + // } + // } async updateOAuthStatus() { if (this.$oAuthEnabledCheckbox.prop("checked")){ @@ -248,12 +250,11 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { // }); server.get("totp/status").then((result) => { + console.log(result); if (result.enabled) if (result.success) { this.$totpEnabled.prop("checked", result.message); this.$totpSecretInput.prop("disabled", !result.message); - this.$totpSecret.prop("disapbled", !result.message); - this.$regenerateTotpButton.prop("disabled", !result.message); this.$authenticatorCode.prop("disabled", !result.message); this.$generateRecoveryCodeButton.prop("disabled", !result.message); } else { @@ -263,13 +264,11 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget { this.$totpEnabled.prop("checked", false); this.$totpEnabled.prop("disabled", true); this.$totpSecretInput.prop("disabled", true); - this.$totpSecret.prop("disapbled", true); - this.$regenerateTotpButton.prop("disabled", true); this.$authenticatorCode.prop("disabled", true); this.$generateRecoveryCodeButton.prop("disabled", true); this.$envEnabledTOTP.text( - "TOTP_ENABLED is not set in environment variable. Requires restart." + "TOTP_ENABLED is set as environment variable to enable (Requires restart)" ); } }); diff --git a/src/routes/api/totp.ts b/src/routes/api/totp.ts index 5dba37630..bfe1ce746 100644 --- a/src/routes/api/totp.ts +++ b/src/routes/api/totp.ts @@ -17,8 +17,9 @@ function getTotpEnabled() { } function getTOTPStatus() { - const totpEnabled = options.getOptionBool('totpEnabled'); - return {success: 'true', message: totpEnabled, enabled: getTotpEnabled()}; + // const totpEnabled = options.getOptionBool('totpEnabled'); + const totpEnabled = getTotpEnabled(); + return {success: true, message: totpEnabled, enabled: getTotpEnabled()}; } function enableTOTP() { diff --git a/src/routes/login.ts b/src/routes/login.ts index e0d6a8001..673cd14ab 100644 --- a/src/routes/login.ts +++ b/src/routes/login.ts @@ -22,7 +22,7 @@ function loginPage(req: Request, res: Response) { } else { res.render('login', { failedAuth: false, - totpEnabled: optionService.getOptionBool('totpEnabled') && totp.checkForTotSecret(), + totpEnabled: totp.isTotpEnabled(), assetPath: assetPath, appPath: appPath, }); @@ -71,17 +71,19 @@ function login(req: AppRequest, res: Response) { const guessedPassword = req.body.password; const guessedTotp = req.body.token; + if (totp.isTotpEnabled()){ + if (!verifyTOTP(guessedTotp)) { + sendLoginError(req, res); + return; + } + } + if (verifyPassword(guessedPassword)) { if (!verifyPassword(guessedPassword)) { sendLoginError(req, res); return; - } - - if (optionService.getOptionBool('totpEnabled') && totp.checkForTotSecret()) - if (!verifyTOTP(guessedTotp)) { - sendLoginError(req, res); - return; - } + } + const rememberMe = req.body.rememberMe; req.session.regenerate(() => { @@ -96,13 +98,7 @@ function login(req: AppRequest, res: Response) { }); } else { - // note that logged IP address is usually meaningless since the traffic should come from a reverse proxy - log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`); - - res.status(401).render('login', { - failedAuth: true, - assetPath: assetPath - }); + sendLoginError(req, res); } } @@ -124,7 +120,11 @@ function verifyPassword(guessedPassword: string) { function sendLoginError(req: AppRequest, res: Response) { // note that logged IP address is usually meaningless since the traffic should come from a reverse proxy - log.info(`WARNING: Wrong password or TOTP from ${req.ip}, rejecting.`); + if ( totp.isTotpEnabled( )){ + log.info(`WARNING: Wrong password or TOTP from ${req.ip}, rejecting.`); + }else{ + log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`); + } res.status(401).render('login', { failedAuth: true, diff --git a/src/services/environment_variable_loader.ts b/src/services/environment_variable_loader.ts new file mode 100644 index 000000000..0655339d1 --- /dev/null +++ b/src/services/environment_variable_loader.ts @@ -0,0 +1,19 @@ +import options from '../services/options.js'; + +function loadEnvironmentVariables(){ + if (process.env.TOTP_ENABLED === undefined) { + options.setOption("totpEnabled", false); + return false; + } + + if (process.env.TOTP_ENABLED.toLocaleLowerCase() !== 'true') { + options.setOption("totpEnabled", false); + return false; + } + + options.setOption("totpEnabled", true); +} + +export default { + loadEnvironmentVariables +} \ No newline at end of file diff --git a/src/services/totp.ts b/src/services/totp.ts index c90f731fb..dddff71ab 100644 --- a/src/services/totp.ts +++ b/src/services/totp.ts @@ -2,6 +2,21 @@ import {Totp} from 'time2fa'; +function isTotpEnabled() { + console.log("Reading ENV: " + process.env.TOTP_ENABLED ); + if (process.env.TOTP_ENABLED === undefined) { + return false; + } + if (process.env.TOTP_SECRET === undefined) { + return false; + } + if (process.env.TOTP_ENABLED.toLocaleLowerCase() !== 'true') { + return false; + } + + return true; +} + function getTotpSecret() { return process.env.TOTP_SECRET; } @@ -26,6 +41,7 @@ function validateTOTP(guessedPasscode: string) { } export default { + isTotpEnabled, getTotpSecret, checkForTotSecret, validateTOTP