mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
Started integrating totp into startup sequance
This commit is contained in:
parent
3fb4d95fd7
commit
baca395c0a
@ -17,6 +17,26 @@ const TPL = `
|
|||||||
Use TOTP (Time-based One-Time Password) to safeguard your data in this application because it adds an additional layer of security by generating unique passcodes that expire quickly, making it harder for unauthorized access. TOTP also reduces the risk of account compromise through common threats like phishing attacks or password breaches.
|
Use TOTP (Time-based One-Time Password) to safeguard your data in this application because it adds an additional layer of security by generating unique passcodes that expire quickly, making it harder for unauthorized access. TOTP also reduces the risk of account compromise through common threats like phishing attacks or password breaches.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<h4>TOTP Settings</h4>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Enable TOTP
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" class="totp-enabled" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<div class="options-section">
|
||||||
|
<label>
|
||||||
|
TOTP Secret
|
||||||
|
</label>
|
||||||
|
<input class="totp-secret-input form-control" disabled="true" type="text">
|
||||||
|
<button class="save-totp" disabled="true"> Save TOTP Secret </button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<h4> Generate TOTP Secret </h4>
|
||||||
<span class="totp-secret" > </span>
|
<span class="totp-secret" > </span>
|
||||||
<br>
|
<br>
|
||||||
<button class="regenerate-totp"> Regenerate TOTP Secret </button>
|
<button class="regenerate-totp"> Regenerate TOTP Secret </button>
|
||||||
@ -28,7 +48,10 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
|
|
||||||
this.$mfaHeadding = this.$widget.find(".mfa-heading");
|
this.$mfaHeadding = this.$widget.find(".mfa-heading");
|
||||||
this.$regenerateTotpButton = this.$widget.find(".regenerate-totp");
|
this.$regenerateTotpButton = this.$widget.find(".regenerate-totp");
|
||||||
|
this.$totpEnabled = this.$widget.find(".totp-enabled");
|
||||||
this.$totpSecret = this.$widget.find(".totp-secret");
|
this.$totpSecret = this.$widget.find(".totp-secret");
|
||||||
|
this.$totpSecretInput = this.$widget.find(".totp-secret-input");
|
||||||
|
this.$saveTotpButton = this.$widget.find(".save-totp");
|
||||||
|
|
||||||
this.$mfaHeadding.text("Multi-Factor Authentication");
|
this.$mfaHeadding.text("Multi-Factor Authentication");
|
||||||
this.generateKey();
|
this.generateKey();
|
||||||
@ -36,6 +59,10 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
// var gen = require("speakeasy");
|
// var gen = require("speakeasy");
|
||||||
// toastService.showMessage("***REMOVED***");
|
// toastService.showMessage("***REMOVED***");
|
||||||
|
|
||||||
|
this.$totpEnabled.on("change", async () => {
|
||||||
|
this.updateCheckboxOption("totpEnabled", this.$totpEnabled);
|
||||||
|
});
|
||||||
|
|
||||||
this.$regenerateTotpButton.on("click", async () => {
|
this.$regenerateTotpButton.on("click", async () => {
|
||||||
this.generateKey();
|
this.generateKey();
|
||||||
});
|
});
|
||||||
@ -54,7 +81,6 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
async generateKey() {
|
async generateKey() {
|
||||||
server.get("totp/generate").then((result) => {
|
server.get("totp/generate").then((result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// password changed so current protected session is invalid and needs to be cleared
|
|
||||||
this.$totpSecret.text(result.message);
|
this.$totpSecret.text(result.message);
|
||||||
} else {
|
} else {
|
||||||
toastService.showError(result.message);
|
toastService.showError(result.message);
|
||||||
@ -62,13 +88,40 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsLoaded(options) {
|
// async toggleTOTP() {
|
||||||
const isPasswordSet = options.isPasswordSet === "true";
|
// 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) {
|
||||||
|
// I need to make sure that this is actually pinging the server and that this information is being pulled
|
||||||
|
// because it is telling me "totpEnabled is not allowed to be changed" in a toast every time I check the box
|
||||||
|
server.get("totp/enabled").then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
console.log("Result message: " + typeof result.message);
|
||||||
|
console.log("Result message: " + result.message);
|
||||||
|
this.setCheckboxState(this.$totpEnabled, result.message);
|
||||||
|
|
||||||
|
console.log("TOTP Status: " + typeof result.message);
|
||||||
|
|
||||||
|
if (result.message) {
|
||||||
|
this.$totpSecretInput.prop("disabled", false);
|
||||||
|
this.$saveTotpButton.prop("disabled", false);
|
||||||
|
} else {
|
||||||
|
this.$totpSecretInput.prop("disabled", true);
|
||||||
|
this.$saveTotpButton.prop("disabled", true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toastService.showError(result.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.$widget.find(".old-password-form-group").toggle(isPasswordSet);
|
|
||||||
this.$savePasswordButton.text(
|
|
||||||
isPasswordSet ? "Change fff Password" : "Set Password"
|
|
||||||
);
|
|
||||||
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
|
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,55 +3,55 @@ import toastService from "../../../services/toast.js";
|
|||||||
import NoteContextAwareWidget from "../../note_context_aware_widget.js";
|
import NoteContextAwareWidget from "../../note_context_aware_widget.js";
|
||||||
|
|
||||||
export default class OptionsWidget extends NoteContextAwareWidget {
|
export default class OptionsWidget extends NoteContextAwareWidget {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.contentSized();
|
this.contentSized();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOption(name, value) {
|
async updateOption(name, value) {
|
||||||
const opts = { [name]: value };
|
const opts = { [name]: value };
|
||||||
|
|
||||||
await this.updateMultipleOptions(opts);
|
await this.updateMultipleOptions(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMultipleOptions(opts) {
|
async updateMultipleOptions(opts) {
|
||||||
await server.put('options', opts);
|
await server.put("options", opts);
|
||||||
|
|
||||||
this.showUpdateNotification();
|
this.showUpdateNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
showUpdateNotification() {
|
showUpdateNotification() {
|
||||||
toastService.showPersistent({
|
toastService.showPersistent({
|
||||||
id: "options-change-saved",
|
id: "options-change-saved",
|
||||||
title: "Options status",
|
title: "Options status",
|
||||||
message: "Options change have been saved.",
|
message: "Options change have been saved.",
|
||||||
icon: "slider",
|
icon: "slider",
|
||||||
closeAfter: 2000
|
closeAfter: 2000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCheckboxOption(name, $checkbox) {
|
async updateCheckboxOption(name, $checkbox) {
|
||||||
const isChecked = $checkbox.prop("checked");
|
const isChecked = $checkbox.prop("checked");
|
||||||
|
|
||||||
return await this.updateOption(name, isChecked ? 'true' : 'false');
|
return await this.updateOption(name, isChecked ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
setCheckboxState($checkbox, optionValue) {
|
setCheckboxState($checkbox, optionValue) {
|
||||||
$checkbox.prop('checked', optionValue === 'true');
|
$checkbox.prop("checked", optionValue === "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsLoaded(options) {}
|
optionsLoaded(options) {}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note) {
|
||||||
const options = await server.get('options');
|
const options = await server.get("options");
|
||||||
|
|
||||||
this.optionsLoaded(options);
|
this.optionsLoaded(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({loadResults}) {
|
async entitiesReloadedEvent({ loadResults }) {
|
||||||
if (loadResults.getOptionNames().length > 0) {
|
if (loadResults.getOptionNames().length > 0) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ function updateOption(req: Request) {
|
|||||||
|
|
||||||
function updateOptions(req: Request) {
|
function updateOptions(req: Request) {
|
||||||
for (const optionName in req.body) {
|
for (const optionName in req.body) {
|
||||||
|
console.log( optionName )
|
||||||
if (!update(optionName, req.body[optionName])) {
|
if (!update(optionName, req.body[optionName])) {
|
||||||
// this should be improved
|
// this should be improved
|
||||||
// it should return 400 instead of current 500, but at least it now rollbacks transaction
|
// it should return 400 instead of current 500, but at least it now rollbacks transaction
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import options = require("../../services/options");
|
||||||
const speakeasy = require("speakeasy");
|
const speakeasy = require("speakeasy");
|
||||||
|
|
||||||
function verifyOTPToken(guessedToken: any) {
|
function verifyOTPToken(guessedToken: any) {
|
||||||
@ -18,7 +19,25 @@ function generateSecret() {
|
|||||||
return { success: "true", message: speakeasy.generateSecret().base32 };
|
return { success: "true", message: speakeasy.generateSecret().base32 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkForTOTP() {
|
||||||
|
const totpEnabled = options.getOptionBool("totpEnabled")
|
||||||
|
return { success: "true", message: totpEnabled };
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableTOTP() {
|
||||||
|
options.setOption("totpEnabled", true)
|
||||||
|
return { success: "true" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableTOTP() {
|
||||||
|
options.setOption("totpEnabled", false)
|
||||||
|
return { success: "true" };
|
||||||
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
verifyOTPToken,
|
verifyOTPToken,
|
||||||
generateSecret,
|
generateSecret,
|
||||||
|
checkForTOTP,
|
||||||
|
enableTOTP,
|
||||||
|
disableTOTP
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,8 @@ import { AppRequest } from './route-interface';
|
|||||||
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: false,
|
||||||
assetPath: assetPath,
|
assetPath: assetPath,
|
||||||
appPath: appPath
|
appPath: appPath
|
||||||
});
|
});
|
||||||
@ -59,6 +61,7 @@ function setPassword(req: Request, res: Response) {
|
|||||||
|
|
||||||
function login(req: AppRequest, res: Response) {
|
function login(req: AppRequest, res: Response) {
|
||||||
const guessedPassword = req.body.password;
|
const guessedPassword = req.body.password;
|
||||||
|
const guessedTotp = req.body.token;
|
||||||
|
|
||||||
if (verifyPassword(guessedPassword)) {
|
if (verifyPassword(guessedPassword)) {
|
||||||
const rememberMe = req.body.rememberMe;
|
const rememberMe = req.body.rememberMe;
|
||||||
|
@ -156,6 +156,9 @@ function register(app: express.Application) {
|
|||||||
route(GET, "/setup", [], setupRoute.setupPage);
|
route(GET, "/setup", [], setupRoute.setupPage);
|
||||||
|
|
||||||
apiRoute(GET, "/api/totp/generate", totp.generateSecret);
|
apiRoute(GET, "/api/totp/generate", totp.generateSecret);
|
||||||
|
apiRoute(GET, "/api/totp/enabled", totp.checkForTOTP);
|
||||||
|
apiRoute(GET, "/api/totp/enable", totp.enableTOTP);
|
||||||
|
apiRoute(GET, "/api/totp/disable", totp.disableTOTP);
|
||||||
|
|
||||||
apiRoute(GET, "/api/tree", treeApiRoute.getTree);
|
apiRoute(GET, "/api/tree", treeApiRoute.getTree);
|
||||||
apiRoute(PST, "/api/tree/load", treeApiRoute.load);
|
apiRoute(PST, "/api/tree/load", treeApiRoute.load);
|
||||||
|
@ -96,7 +96,8 @@ const defaultOptions = [
|
|||||||
{ name: 'customSearchEngineName', value: 'DuckDuckGo', isSynced: true },
|
{ name: 'customSearchEngineName', value: 'DuckDuckGo', isSynced: true },
|
||||||
{ 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 }
|
||||||
];
|
];
|
||||||
|
|
||||||
function initStartupOptions() {
|
function initStartupOptions() {
|
||||||
|
@ -41,6 +41,21 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% if( totpEnabled ) { %>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="otp-token">OTP Token</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input
|
||||||
|
id="token"
|
||||||
|
name="token"
|
||||||
|
placeholder=""
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
maxlength="6"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user