mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-18 16:32:33 +08:00
TOTP working
This commit is contained in:
parent
9c748f326a
commit
e232c6634e
@ -21,6 +21,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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": "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-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-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",
|
"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": "^1.11.12",
|
||||||
"dayjs-plugin-utc": "0.1.2",
|
"dayjs-plugin-utc": "0.1.2",
|
||||||
"debounce": "^2.1.0",
|
"debounce": "^2.1.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"electron-debug": "3.2.0",
|
"electron-debug": "3.2.0",
|
||||||
"electron-dl": "3.5.2",
|
"electron-dl": "3.5.2",
|
||||||
|
@ -16,6 +16,7 @@ import { startScheduledCleanup } from "./services/erase.js";
|
|||||||
import sql_init from "./services/sql_init.js";
|
import sql_init from "./services/sql_init.js";
|
||||||
import oidc from "express-openid-connect";
|
import oidc from "express-openid-connect";
|
||||||
import openID from "./services/open_id.js";
|
import openID from "./services/open_id.js";
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
await import('./services/handlers.js');
|
await import('./services/handlers.js');
|
||||||
await import('./becca/becca_loader.js');
|
await import('./becca/becca_loader.js');
|
||||||
@ -24,6 +25,9 @@ const app = express();
|
|||||||
|
|
||||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Configure environment variables
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
// Initialize DB
|
// Initialize DB
|
||||||
sql_init.initializeDb();
|
sql_init.initializeDb();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const TPL = `
|
|||||||
Multi-Factor Authentication (MFA) adds an extra layer of security to your account. Instead
|
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
|
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
|
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
|
It's like adding an extra lock to your door, making it much harder for anyone else to
|
||||||
break in.</i>
|
break in.</i>
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +51,7 @@ const TPL = `
|
|||||||
<div>
|
<div>
|
||||||
<span class="totp-secret" > TOTP Secret Key </span>
|
<span class="totp-secret" > TOTP Secret Key </span>
|
||||||
<br>
|
<br>
|
||||||
<button class="regenerate-totp" disabled="true"> Regenerate TOTP Secret </button>
|
<button class="regenerate-totp"> Regenerate TOTP Secret </button>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<h4> Single Sign-on Recovery Keys </h4>
|
<h4> Single Sign-on Recovery Keys </h4>
|
||||||
@ -117,9 +117,10 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
for (let i = 0; i < 8; i++)
|
for (let i = 0; i < 8; i++)
|
||||||
this.$recoveryKeys.push(this.$widget.find(".key_" + i));
|
this.$recoveryKeys.push(this.$widget.find(".key_" + i));
|
||||||
|
|
||||||
this.$totpEnabled.on("change", async () => {
|
// Depricated. Will use .env to control.
|
||||||
this.updateSecret();
|
// this.$totpEnabled.on("change", async () => {
|
||||||
});
|
// this.updateSecret();
|
||||||
|
// });
|
||||||
|
|
||||||
this.$oAuthEnabledCheckbox.on("change", async () => {
|
this.$oAuthEnabledCheckbox.on("change", async () => {
|
||||||
this.updateOAuthStatus();
|
this.updateOAuthStatus();
|
||||||
@ -159,15 +160,16 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
this.displayRecoveryKeys();
|
this.displayRecoveryKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSecret() {
|
// Depricated. Will use .env to control.
|
||||||
if (this.$totpEnabled.prop("checked")) {
|
// async updateSecret() {
|
||||||
server.post("totp/enable");
|
// if (this.$totpEnabled.prop("checked")) {
|
||||||
|
// server.post("totp/enable");
|
||||||
|
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
server.post("totp/disable");
|
// server.post("totp/disable");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async updateOAuthStatus() {
|
async updateOAuthStatus() {
|
||||||
if (this.$oAuthEnabledCheckbox.prop("checked")){
|
if (this.$oAuthEnabledCheckbox.prop("checked")){
|
||||||
@ -248,12 +250,11 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
server.get("totp/status").then((result) => {
|
server.get("totp/status").then((result) => {
|
||||||
|
console.log(result);
|
||||||
if (result.enabled)
|
if (result.enabled)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.$totpEnabled.prop("checked", result.message);
|
this.$totpEnabled.prop("checked", result.message);
|
||||||
this.$totpSecretInput.prop("disabled", !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.$authenticatorCode.prop("disabled", !result.message);
|
||||||
this.$generateRecoveryCodeButton.prop("disabled", !result.message);
|
this.$generateRecoveryCodeButton.prop("disabled", !result.message);
|
||||||
} else {
|
} else {
|
||||||
@ -263,13 +264,11 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
this.$totpEnabled.prop("checked", false);
|
this.$totpEnabled.prop("checked", false);
|
||||||
this.$totpEnabled.prop("disabled", true);
|
this.$totpEnabled.prop("disabled", true);
|
||||||
this.$totpSecretInput.prop("disabled", true);
|
this.$totpSecretInput.prop("disabled", true);
|
||||||
this.$totpSecret.prop("disapbled", true);
|
|
||||||
this.$regenerateTotpButton.prop("disabled", true);
|
|
||||||
this.$authenticatorCode.prop("disabled", true);
|
this.$authenticatorCode.prop("disabled", true);
|
||||||
this.$generateRecoveryCodeButton.prop("disabled", true);
|
this.$generateRecoveryCodeButton.prop("disabled", true);
|
||||||
|
|
||||||
this.$envEnabledTOTP.text(
|
this.$envEnabledTOTP.text(
|
||||||
"TOTP_ENABLED is not set in environment variable. Requires restart."
|
"TOTP_ENABLED is set as environment variable to enable (Requires restart)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,8 +17,9 @@ function getTotpEnabled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTOTPStatus() {
|
function getTOTPStatus() {
|
||||||
const totpEnabled = options.getOptionBool('totpEnabled');
|
// const totpEnabled = options.getOptionBool('totpEnabled');
|
||||||
return {success: 'true', message: totpEnabled, enabled: getTotpEnabled()};
|
const totpEnabled = getTotpEnabled();
|
||||||
|
return {success: true, message: totpEnabled, enabled: getTotpEnabled()};
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableTOTP() {
|
function enableTOTP() {
|
||||||
|
@ -22,7 +22,7 @@ function loginPage(req: Request, res: Response) {
|
|||||||
} else {
|
} else {
|
||||||
res.render('login', {
|
res.render('login', {
|
||||||
failedAuth: false,
|
failedAuth: false,
|
||||||
totpEnabled: optionService.getOptionBool('totpEnabled') && totp.checkForTotSecret(),
|
totpEnabled: totp.isTotpEnabled(),
|
||||||
assetPath: assetPath,
|
assetPath: assetPath,
|
||||||
appPath: appPath,
|
appPath: appPath,
|
||||||
});
|
});
|
||||||
@ -71,17 +71,19 @@ function login(req: AppRequest, res: Response) {
|
|||||||
const guessedPassword = req.body.password;
|
const guessedPassword = req.body.password;
|
||||||
const guessedTotp = req.body.token;
|
const guessedTotp = req.body.token;
|
||||||
|
|
||||||
|
if (totp.isTotpEnabled()){
|
||||||
|
if (!verifyTOTP(guessedTotp)) {
|
||||||
|
sendLoginError(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (verifyPassword(guessedPassword)) {
|
if (verifyPassword(guessedPassword)) {
|
||||||
if (!verifyPassword(guessedPassword)) {
|
if (!verifyPassword(guessedPassword)) {
|
||||||
sendLoginError(req, res);
|
sendLoginError(req, res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optionService.getOptionBool('totpEnabled') && totp.checkForTotSecret())
|
|
||||||
if (!verifyTOTP(guessedTotp)) {
|
|
||||||
sendLoginError(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rememberMe = req.body.rememberMe;
|
const rememberMe = req.body.rememberMe;
|
||||||
|
|
||||||
req.session.regenerate(() => {
|
req.session.regenerate(() => {
|
||||||
@ -96,13 +98,7 @@ function login(req: AppRequest, res: Response) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// note that logged IP address is usually meaningless since the traffic should come from a reverse proxy
|
sendLoginError(req, res);
|
||||||
log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`);
|
|
||||||
|
|
||||||
res.status(401).render('login', {
|
|
||||||
failedAuth: true,
|
|
||||||
assetPath: assetPath
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +120,11 @@ function verifyPassword(guessedPassword: string) {
|
|||||||
|
|
||||||
function sendLoginError(req: AppRequest, res: Response) {
|
function sendLoginError(req: AppRequest, res: Response) {
|
||||||
// note that logged IP address is usually meaningless since the traffic should come from a reverse proxy
|
// note that logged IP address is usually meaningless since the traffic should come from a reverse proxy
|
||||||
|
if ( totp.isTotpEnabled( )){
|
||||||
log.info(`WARNING: Wrong password or TOTP from ${req.ip}, rejecting.`);
|
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', {
|
res.status(401).render('login', {
|
||||||
failedAuth: true,
|
failedAuth: true,
|
||||||
|
19
src/services/environment_variable_loader.ts
Normal file
19
src/services/environment_variable_loader.ts
Normal file
@ -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
|
||||||
|
}
|
@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
import {Totp} from 'time2fa';
|
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() {
|
function getTotpSecret() {
|
||||||
return process.env.TOTP_SECRET;
|
return process.env.TOTP_SECRET;
|
||||||
}
|
}
|
||||||
@ -26,6 +41,7 @@ function validateTOTP(guessedPasscode: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
isTotpEnabled,
|
||||||
getTotpSecret,
|
getTotpSecret,
|
||||||
checkForTotSecret,
|
checkForTotSecret,
|
||||||
validateTOTP
|
validateTOTP
|
||||||
|
Loading…
x
Reference in New Issue
Block a user