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": { "isexe": {
"version": "2.0.0", "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=" "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
}, },
"isobject": { "isobject": {

View File

@ -57,7 +57,7 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
this.generateKey(); this.generateKey();
this.$totpEnabled.on("change", async () => { this.$totpEnabled.on("change", async () => {
this.updateCheckboxOption("totpEnabled", this.$totpEnabled); this.updateSecret();
}); });
this.$regenerateTotpButton.on("click", async () => { 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() { async generateKey() {
server.get("totp/generate").then((result) => { server.get("totp/generate").then((result) => {
if (result.success) { 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) { optionsLoaded(options) {
server.get("totp/enabled").then((result) => { server.get("totp/enabled").then((result) => {
if (result.success) { 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); this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
} }

View File

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

View File

@ -8,14 +8,17 @@ import passwordService = require('../services/encryption/password');
import assetPath = require('../services/asset_path'); import assetPath = require('../services/asset_path');
import appPath = require('../services/app_path'); import appPath = require('../services/app_path');
import ValidationError = require('../errors/validation_error'); import ValidationError = require('../errors/validation_error');
import totp_secret = require('../services/totp_secret');
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { AppRequest } from './route-interface'; import { AppRequest } from './route-interface';
const speakeasy = require("speakeasy")
function loginPage(req: Request, res: Response) { function loginPage(req: Request, res: Response) {
res.render('login', { res.render('login', {
failedAuth: false, failedAuth: false,
// totpEnabled: optionService.getOption("hasOTPEnabled"), // totpEnabled: optionService.getOption("hasOTPEnabled"),
totpEnabled: false, totpEnabled: optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret(),
assetPath: assetPath, assetPath: assetPath,
appPath: appPath appPath: appPath
}); });
@ -63,7 +66,20 @@ 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 (verifyPassword(guessedPassword)) { if (!verifyPassword(guessedPassword))
{
sendLoginError( req, res)
return
}
if (optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret())
if (!verifyTOTP(guessedTotp))
{
sendLoginError( req, res)
return
}
const rememberMe = req.body.rememberMe; const rememberMe = req.body.rememberMe;
req.session.regenerate(() => { req.session.regenerate(() => {
@ -77,15 +93,27 @@ function login(req: AppRequest, res: Response) {
res.redirect('.'); res.redirect('.');
}); });
} }
else {
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
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', { res.status(401).render('login', {
failedAuth: true, failedAuth: true,
totpEnabled: optionService.getOption("totpEnabled") && totp_secret.checkForTotSecret(),
assetPath: assetPath 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) { function verifyPassword(guessedPassword: string) {

View File

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