TOTP fully working MVP

This commit is contained in:
Chesspro13 2024-05-03 14:54:32 -07:00
parent fad51409a9
commit 4574b81caf
6 changed files with 97 additions and 40 deletions

2
package-lock.json generated
View File

@ -19447,7 +19447,7 @@
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/ie/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isobject": {

View File

@ -57,7 +57,7 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
this.generateKey();
this.$totpEnabled.on("change", async () => {
this.updateCheckboxOption("totpEnabled", this.$totpEnabled);
this.updateSecret();
});
this.$regenerateTotpButton.on("click", async () => {
@ -79,6 +79,11 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
);
}
async updateSecret() {
if (this.$totpEnabled.prop("checked")) server.post("totp/enable");
else server.post("totp/disable");
}
async generateKey() {
server.get("totp/generate").then((result) => {
if (result.success) {
@ -89,17 +94,6 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
});
}
// async toggleTOTP() {
// if (this.$totpEnabled)
// server.post("totp/enable").then((result) => {
// if (!result.success) toastService.showError(result.message);
// });
// else
// server.post("totp/disable").then((result) => {
// if (!result.success) toastService.showError(result.message);
// });
// }
optionsLoaded(options) {
server.get("totp/enabled").then((result) => {
if (result.success) {
@ -112,10 +106,6 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
}
});
server.get("totp/get").then((result) => {
this.$totpSecretInput.text(result.secret);
});
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
}

View File

@ -1,6 +1,7 @@
import options = require("../../services/options");
import totp_secret = require("../../services/encryption/totp_secret");
import { Request } from "express";
import totp_fs = require("../../services/totp_secret")
const speakeasy = require("speakeasy");
function verifyOTPToken(guessedToken: any) {
@ -27,22 +28,23 @@ function checkForTOTP() {
}
function enableTOTP() {
options.setOption("totpEnab| voidled", true);
options.setOption("totpEnabled", true);
return { success: "true" };
}
function disableTOTP() {
options.setOption("totpEnabled", false);
return { success: "true" };
return { success: totp_fs.removeTotpSecret() };
}
function setTotpSecret(req: Request) {
console.log("TODO: Save Secret");
// totp_secret.setTotpSecret(req.body.secret);
options.setOption
totp_fs.saveTotpSecret(req.body.secret)
}
function getSecret() {
return "TODO: Get Secret";
return totp_fs.getTotpSecret()
}
export = {

View File

@ -8,14 +8,17 @@ import passwordService = require('../services/encryption/password');
import assetPath = require('../services/asset_path');
import appPath = require('../services/app_path');
import ValidationError = require('../errors/validation_error');
import totp_secret = require('../services/totp_secret');
import { Request, Response } from 'express';
import { AppRequest } from './route-interface';
const speakeasy = require("speakeasy")
function loginPage(req: Request, res: Response) {
res.render('login', {
failedAuth: false,
// totpEnabled: optionService.getOption("hasOTPEnabled"),
totpEnabled: false,
totpEnabled: optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret(),
assetPath: assetPath,
appPath: appPath
});
@ -63,29 +66,54 @@ function login(req: AppRequest, res: Response) {
const guessedPassword = req.body.password;
const guessedTotp = req.body.token;
if (verifyPassword(guessedPassword)) {
const rememberMe = req.body.rememberMe;
req.session.regenerate(() => {
if (rememberMe) {
req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
} else {
req.session.cookie.expires = null;
}
req.session.loggedIn = true;
res.redirect('.');
});
if (!verifyPassword(guessedPassword))
{
sendLoginError( req, res)
return
}
else {
if (optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret())
if (!verifyTOTP(guessedTotp))
{
sendLoginError( req, res)
return
}
const rememberMe = req.body.rememberMe;
req.session.regenerate(() => {
if (rememberMe) {
req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
} else {
req.session.cookie.expires = null;
}
req.session.loggedIn = true;
res.redirect('.');
});
}
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 from ${req.ip}, rejecting.`);
log.info(`WARNING: Wrong password or TOTP from ${req.ip}, rejecting.`);
res.status(401).render('login', {
failedAuth: true,
totpEnabled: optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret(),
assetPath: assetPath
});
}
}
function verifyTOTP( guessedToken: string) {
const tokenValidates = speakeasy.totp.verify({
secret: totp_secret.getTotpSecret(),
encoding: "base32",
token: guessedToken,
window: 1,
});
return tokenValidates
}
function verifyPassword(guessedPassword: string) {

View File

@ -97,7 +97,7 @@ const defaultOptions = [
{ name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: true },
{ name: 'promotedAttributesOpenInRibbon', value: 'true', isSynced: true },
{ name: 'editedNotesOpenInRibbon', value: 'true', isSynced: true },
// { name: 'totpEnabled', value: 'false', isSynced: false }
{ name: 'totpEnabled', value: 'false', isSynced: true }
];
function initStartupOptions() {

View File

@ -0,0 +1,37 @@
"use strict";
import fs = require('fs');
import crypto = require('crypto');
import dataDir = require('./data_dir');
import log = require('./log');
const totpSecretPath = `${dataDir.TRILIUM_DATA_DIR}/totp_secret.txt`;
function saveTotpSecret(secret: string)
{
if (!fs.existsSync(totpSecretPath))
log.info("Generated totp secret file");
fs.writeFileSync(totpSecretPath, secret, "utf8");
}
function getTotpSecret(){
return fs.readFileSync(totpSecretPath, "utf8");
}
function checkForTotSecret() {
return fs.existsSync(totpSecretPath)
}
function removeTotpSecret() {
console.log("Attempting to remove secret")
fs.unlink( totpSecretPath, a => {
console.log("Unable to remove totp secret")
console.log(a)
return false
})
return true
}
export = {saveTotpSecret, getTotpSecret, checkForTotSecret, removeTotpSecret};