Continued integrating TOTP.

This commit is contained in:
Brandon 2024-05-03 08:05:35 -07:00
parent baca395c0a
commit fad51409a9
5 changed files with 151 additions and 147 deletions

2
package-lock.json generated
View File

@ -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": {

View File

@ -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) {

View File

@ -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,
}; };

View File

@ -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,
}; };

View File

@ -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(