mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
TOTP fully working MVP
This commit is contained in:
parent
fad51409a9
commit
4574b81caf
2
package-lock.json
generated
2
package-lock.json
generated
@ -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": {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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,7 +66,20 @@ function login(req: AppRequest, res: Response) {
|
||||
const guessedPassword = req.body.password;
|
||||
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;
|
||||
|
||||
req.session.regenerate(() => {
|
||||
@ -76,16 +92,28 @@ function login(req: AppRequest, res: Response) {
|
||||
req.session.loggedIn = true;
|
||||
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
|
||||
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) {
|
||||
|
@ -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() {
|
||||
|
37
src/services/totp_secret.ts
Normal file
37
src/services/totp_secret.ts
Normal 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};
|
Loading…
x
Reference in New Issue
Block a user