mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
Continued integrating TOTP.
This commit is contained in:
parent
baca395c0a
commit
fad51409a9
2
package-lock.json
generated
2
package-lock.json
generated
@ -7855,7 +7855,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/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="
|
||||||
},
|
},
|
||||||
"node_modules/isobject": {
|
"node_modules/isobject": {
|
||||||
|
@ -39,7 +39,7 @@ const TPL = `
|
|||||||
<h4> Generate TOTP Secret </h4>
|
<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" disabled="true"> Regenerate TOTP Secret </button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
||||||
@ -56,9 +56,6 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
this.$mfaHeadding.text("Multi-Factor Authentication");
|
this.$mfaHeadding.text("Multi-Factor Authentication");
|
||||||
this.generateKey();
|
this.generateKey();
|
||||||
|
|
||||||
// var gen = require("speakeasy");
|
|
||||||
// toastService.showMessage("***REMOVED***");
|
|
||||||
|
|
||||||
this.$totpEnabled.on("change", async () => {
|
this.$totpEnabled.on("change", async () => {
|
||||||
this.updateCheckboxOption("totpEnabled", this.$totpEnabled);
|
this.updateCheckboxOption("totpEnabled", this.$totpEnabled);
|
||||||
});
|
});
|
||||||
@ -67,6 +64,10 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
this.generateKey();
|
this.generateKey();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$saveTotpButton.on("click", async () => {
|
||||||
|
this.save();
|
||||||
|
});
|
||||||
|
|
||||||
this.$protectedSessionTimeout = this.$widget.find(
|
this.$protectedSessionTimeout = this.$widget.find(
|
||||||
".protected-session-timeout-in-seconds"
|
".protected-session-timeout-in-seconds"
|
||||||
);
|
);
|
||||||
@ -100,49 +101,30 @@ export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
optionsLoaded(options) {
|
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) => {
|
server.get("totp/enabled").then((result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log("Result message: " + typeof result.message);
|
this.$totpEnabled.prop("checked", result.message);
|
||||||
console.log("Result message: " + result.message);
|
this.$totpSecretInput.prop("disabled", !result.message);
|
||||||
this.setCheckboxState(this.$totpEnabled, result.message);
|
this.$saveTotpButton.prop("disabled", !result.message);
|
||||||
|
this.$totpSecret.prop("disapbled", !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 {
|
} else {
|
||||||
toastService.showError(result.message);
|
toastService.showError(result.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.get("totp/get").then((result) => {
|
||||||
|
this.$totpSecretInput.text(result.secret);
|
||||||
|
});
|
||||||
|
|
||||||
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
|
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
const oldPassword = this.$oldPassword.val();
|
// TODO: CHECK VALIDITY OF SECRET
|
||||||
const newPassword1 = this.$newPassword1.val();
|
|
||||||
const newPassword2 = this.$newPassword2.val();
|
|
||||||
|
|
||||||
this.$oldPassword.val("");
|
|
||||||
this.$newPassword1.val("");
|
|
||||||
this.$newPassword2.val("");
|
|
||||||
|
|
||||||
if (newPassword1 !== newPassword2) {
|
|
||||||
toastService.showError("New passwords are not the same.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server
|
server
|
||||||
.post("password/change", {
|
.post("totp/set", {
|
||||||
current_password: oldPassword,
|
secret: this.$totpSecretInput.val(),
|
||||||
new_password: newPassword1,
|
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
@ -1,145 +1,152 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import optionService = require('../../services/options');
|
import optionService = require("../../services/options");
|
||||||
import log = require('../../services/log');
|
import log = require("../../services/log");
|
||||||
import searchService = require('../../services/search/services/search');
|
import searchService = require("../../services/search/services/search");
|
||||||
import ValidationError = require('../../errors/validation_error');
|
import ValidationError = require("../../errors/validation_error");
|
||||||
import { Request } from 'express';
|
import { Request } from "express";
|
||||||
|
|
||||||
// options allowed to be updated directly in the Options dialog
|
// options allowed to be updated directly in the Options dialog
|
||||||
const ALLOWED_OPTIONS = new Set([
|
const ALLOWED_OPTIONS = new Set([
|
||||||
'eraseEntitiesAfterTimeInSeconds',
|
"eraseEntitiesAfterTimeInSeconds",
|
||||||
'protectedSessionTimeout',
|
"protectedSessionTimeout",
|
||||||
'revisionSnapshotTimeInterval',
|
"revisionSnapshotTimeInterval",
|
||||||
'zoomFactor',
|
"zoomFactor",
|
||||||
'theme',
|
"theme",
|
||||||
'syncServerHost',
|
"syncServerHost",
|
||||||
'syncServerTimeout',
|
"syncServerTimeout",
|
||||||
'syncProxy',
|
"syncProxy",
|
||||||
'hoistedNoteId',
|
"hoistedNoteId",
|
||||||
'mainFontSize',
|
"mainFontSize",
|
||||||
'mainFontFamily',
|
"mainFontFamily",
|
||||||
'treeFontSize',
|
"treeFontSize",
|
||||||
'treeFontFamily',
|
"treeFontFamily",
|
||||||
'detailFontSize',
|
"detailFontSize",
|
||||||
'detailFontFamily',
|
"detailFontFamily",
|
||||||
'monospaceFontSize',
|
"monospaceFontSize",
|
||||||
'monospaceFontFamily',
|
"monospaceFontFamily",
|
||||||
'openNoteContexts',
|
"openNoteContexts",
|
||||||
'vimKeymapEnabled',
|
"vimKeymapEnabled",
|
||||||
'codeLineWrapEnabled',
|
"codeLineWrapEnabled",
|
||||||
'codeNotesMimeTypes',
|
"codeNotesMimeTypes",
|
||||||
'spellCheckEnabled',
|
"spellCheckEnabled",
|
||||||
'spellCheckLanguageCode',
|
"spellCheckLanguageCode",
|
||||||
'imageMaxWidthHeight',
|
"imageMaxWidthHeight",
|
||||||
'imageJpegQuality',
|
"imageJpegQuality",
|
||||||
'leftPaneWidth',
|
"leftPaneWidth",
|
||||||
'rightPaneWidth',
|
"rightPaneWidth",
|
||||||
'leftPaneVisible',
|
"leftPaneVisible",
|
||||||
'rightPaneVisible',
|
"rightPaneVisible",
|
||||||
'nativeTitleBarVisible',
|
"nativeTitleBarVisible",
|
||||||
'headingStyle',
|
"headingStyle",
|
||||||
'autoCollapseNoteTree',
|
"autoCollapseNoteTree",
|
||||||
'autoReadonlySizeText',
|
"autoReadonlySizeText",
|
||||||
'autoReadonlySizeCode',
|
"autoReadonlySizeCode",
|
||||||
'overrideThemeFonts',
|
"overrideThemeFonts",
|
||||||
'dailyBackupEnabled',
|
"dailyBackupEnabled",
|
||||||
'weeklyBackupEnabled',
|
"weeklyBackupEnabled",
|
||||||
'monthlyBackupEnabled',
|
"monthlyBackupEnabled",
|
||||||
'maxContentWidth',
|
"maxContentWidth",
|
||||||
'compressImages',
|
"compressImages",
|
||||||
'downloadImagesAutomatically',
|
"downloadImagesAutomatically",
|
||||||
'minTocHeadings',
|
"minTocHeadings",
|
||||||
'highlightsList',
|
"highlightsList",
|
||||||
'checkForUpdates',
|
"checkForUpdates",
|
||||||
'disableTray',
|
"disableTray",
|
||||||
'eraseUnusedAttachmentsAfterSeconds',
|
"eraseUnusedAttachmentsAfterSeconds",
|
||||||
'disableTray',
|
"disableTray",
|
||||||
'customSearchEngineName',
|
"customSearchEngineName",
|
||||||
'customSearchEngineUrl',
|
"customSearchEngineUrl",
|
||||||
'promotedAttributesOpenInRibbon',
|
"promotedAttributesOpenInRibbon",
|
||||||
'editedNotesOpenInRibbon'
|
"editedNotesOpenInRibbon",
|
||||||
|
"totpEnabled",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function getOptions() {
|
function getOptions() {
|
||||||
const optionMap = optionService.getOptionMap();
|
const optionMap = optionService.getOptionMap();
|
||||||
const resultMap: Record<string, string> = {};
|
const resultMap: Record<string, string> = {};
|
||||||
|
|
||||||
for (const optionName in optionMap) {
|
for (const optionName in optionMap) {
|
||||||
if (isAllowed(optionName)) {
|
if (isAllowed(optionName)) {
|
||||||
resultMap[optionName] = optionMap[optionName];
|
resultMap[optionName] = optionMap[optionName];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resultMap['isPasswordSet'] = optionMap['passwordVerificationHash'] ? 'true' : 'false';
|
resultMap["isPasswordSet"] = optionMap["passwordVerificationHash"]
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
|
||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateOption(req: Request) {
|
function updateOption(req: Request) {
|
||||||
const {name, value} = req.params;
|
const { name, value } = req.params;
|
||||||
|
|
||||||
if (!update(name, value)) {
|
if (!update(name, value)) {
|
||||||
throw new ValidationError("not allowed option to change");
|
throw new ValidationError("not allowed option to change");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateOptions(req: Request) {
|
function updateOptions(req: Request) {
|
||||||
for (const optionName in req.body) {
|
for (const optionName in req.body) {
|
||||||
console.log( optionName )
|
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
|
||||||
throw new Error(`Option '${optionName}' is not allowed to be changed`);
|
throw new Error(`Option '${optionName}' is not allowed to be changed`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(name: string, value: string) {
|
function update(name: string, value: string) {
|
||||||
if (!isAllowed(name)) {
|
if (!isAllowed(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name !== 'openNoteContexts') {
|
if (name !== "openNoteContexts") {
|
||||||
log.info(`Updating option '${name}' to '${value}'`);
|
log.info(`Updating option '${name}' to '${value}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
optionService.setOption(name, value);
|
optionService.setOption(name, value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserThemes() {
|
function getUserThemes() {
|
||||||
const notes = searchService.searchNotes("#appTheme", {ignoreHoistedNote: true});
|
const notes = searchService.searchNotes("#appTheme", {
|
||||||
const ret = [];
|
ignoreHoistedNote: true,
|
||||||
|
});
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
let value = note.getOwnedLabelValue('appTheme');
|
let value = note.getOwnedLabelValue("appTheme");
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = note.title.toLowerCase().replace(/[^a-z0-9]/gi, '-');
|
value = note.title.toLowerCase().replace(/[^a-z0-9]/gi, "-");
|
||||||
}
|
|
||||||
|
|
||||||
ret.push({
|
|
||||||
val: value,
|
|
||||||
title: note.title,
|
|
||||||
noteId: note.noteId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
ret.push({
|
||||||
|
val: value,
|
||||||
|
title: note.title,
|
||||||
|
noteId: note.noteId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAllowed(name: string) {
|
function isAllowed(name: string) {
|
||||||
return ALLOWED_OPTIONS.has(name)
|
return (
|
||||||
|| name.startsWith("keyboardShortcuts")
|
ALLOWED_OPTIONS.has(name) ||
|
||||||
|| name.endsWith("Collapsed")
|
name.startsWith("keyboardShortcuts") ||
|
||||||
|| name.startsWith("hideArchivedNotes");
|
name.endsWith("Collapsed") ||
|
||||||
|
name.startsWith("hideArchivedNotes")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
getOptions,
|
getOptions,
|
||||||
updateOption,
|
updateOption,
|
||||||
updateOptions,
|
updateOptions,
|
||||||
getUserThemes
|
getUserThemes,
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import options = require("../../services/options");
|
import options = require("../../services/options");
|
||||||
|
import totp_secret = require("../../services/encryption/totp_secret");
|
||||||
|
import { Request } from "express";
|
||||||
const speakeasy = require("speakeasy");
|
const speakeasy = require("speakeasy");
|
||||||
|
|
||||||
function verifyOTPToken(guessedToken: any) {
|
function verifyOTPToken(guessedToken: any) {
|
||||||
@ -20,24 +22,35 @@ function generateSecret() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkForTOTP() {
|
function checkForTOTP() {
|
||||||
const totpEnabled = options.getOptionBool("totpEnabled")
|
const totpEnabled = options.getOptionBool("totpEnabled");
|
||||||
return { success: "true", message: totpEnabled };
|
return { success: "true", message: totpEnabled };
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableTOTP() {
|
function enableTOTP() {
|
||||||
options.setOption("totpEnabled", true)
|
options.setOption("totpEnab| voidled", true);
|
||||||
return { success: "true" };
|
return { success: "true" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableTOTP() {
|
function disableTOTP() {
|
||||||
options.setOption("totpEnabled", false)
|
options.setOption("totpEnabled", false);
|
||||||
return { success: "true" };
|
return { success: "true" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setTotpSecret(req: Request) {
|
||||||
|
console.log("TODO: Save Secret");
|
||||||
|
// totp_secret.setTotpSecret(req.body.secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSecret() {
|
||||||
|
return "TODO: Get Secret";
|
||||||
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
verifyOTPToken,
|
verifyOTPToken,
|
||||||
generateSecret,
|
generateSecret,
|
||||||
checkForTOTP,
|
checkForTOTP,
|
||||||
enableTOTP,
|
enableTOTP,
|
||||||
disableTOTP
|
disableTOTP,
|
||||||
|
setTotpSecret,
|
||||||
|
getSecret,
|
||||||
};
|
};
|
||||||
|
@ -157,8 +157,10 @@ function register(app: express.Application) {
|
|||||||
|
|
||||||
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/enabled", totp.checkForTOTP);
|
||||||
apiRoute(GET, "/api/totp/enable", totp.enableTOTP);
|
apiRoute(PST, "/api/totp/enable", totp.enableTOTP);
|
||||||
apiRoute(GET, "/api/totp/disable", totp.disableTOTP);
|
apiRoute(PST, "/api/totp/disable", totp.disableTOTP);
|
||||||
|
apiRoute(PST, "/api/totp/set", totp.setTotpSecret);
|
||||||
|
apiRoute(GET, "/api/totp/get", totp.getSecret);
|
||||||
|
|
||||||
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);
|
||||||
@ -185,7 +187,7 @@ function register(app: express.Application) {
|
|||||||
apiRoute(PUT, "/api/notes/:noteId/title", notesApiRoute.changeTitle);
|
apiRoute(PUT, "/api/notes/:noteId/title", notesApiRoute.changeTitle);
|
||||||
apiRoute(
|
apiRoute(
|
||||||
PST,
|
PST,
|
||||||
"/api/notes/:noteId/duplicate/:parentNoteId",
|
"/api/notes/:noteId/duplicPOSTate/:parentNoteId",
|
||||||
notesApiRoute.duplicateSubtree
|
notesApiRoute.duplicateSubtree
|
||||||
);
|
);
|
||||||
apiRoute(
|
apiRoute(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user