mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
Adding basic MFA in the form of TOTP
This commit is contained in:
parent
a68b75f069
commit
3fb4d95fd7
30
package-lock.json
generated
30
package-lock.json
generated
@ -73,6 +73,7 @@
|
|||||||
"semver": "7.6.0",
|
"semver": "7.6.0",
|
||||||
"serve-favicon": "2.5.0",
|
"serve-favicon": "2.5.0",
|
||||||
"session-file-store": "1.5.0",
|
"session-file-store": "1.5.0",
|
||||||
|
"speakeasy": "^2.0.0",
|
||||||
"split.js": "1.6.5",
|
"split.js": "1.6.5",
|
||||||
"stream-throttle": "0.1.3",
|
"stream-throttle": "0.1.3",
|
||||||
"striptags": "3.2.0",
|
"striptags": "3.2.0",
|
||||||
@ -2708,6 +2709,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||||
},
|
},
|
||||||
|
"node_modules/base32.js": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-EGHIRiegFa62/SsA1J+Xs2tIzludPdzM064N9wjbiEgHnGnJ1V0WEpA4pEwCYT5nDvZk3ubf0shqaCS7k6xeUQ=="
|
||||||
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
@ -11795,6 +11801,17 @@
|
|||||||
"integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==",
|
"integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/speakeasy": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-lW2A2s5LKi8rwu77ewisuUOtlCydF/hmQSOJjpTqTj1gZLkNgTaYnyvfxy2WBr4T/h+9c4g8HIITfj83OkFQFw==",
|
||||||
|
"dependencies": {
|
||||||
|
"base32.js": "0.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split.js": {
|
"node_modules/split.js": {
|
||||||
"version": "1.6.5",
|
"version": "1.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz",
|
||||||
@ -15542,6 +15559,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||||
},
|
},
|
||||||
|
"base32.js": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-EGHIRiegFa62/SsA1J+Xs2tIzludPdzM064N9wjbiEgHnGnJ1V0WEpA4pEwCYT5nDvZk3ubf0shqaCS7k6xeUQ=="
|
||||||
|
},
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
@ -22382,6 +22404,14 @@
|
|||||||
"integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==",
|
"integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"speakeasy": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-lW2A2s5LKi8rwu77ewisuUOtlCydF/hmQSOJjpTqTj1gZLkNgTaYnyvfxy2WBr4T/h+9c4g8HIITfj83OkFQFw==",
|
||||||
|
"requires": {
|
||||||
|
"base32.js": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"split.js": {
|
"split.js": {
|
||||||
"version": "1.6.5",
|
"version": "1.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz",
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
"semver": "7.6.0",
|
"semver": "7.6.0",
|
||||||
"serve-favicon": "2.5.0",
|
"serve-favicon": "2.5.0",
|
||||||
"session-file-store": "1.5.0",
|
"session-file-store": "1.5.0",
|
||||||
|
"speakeasy": "^2.0.0",
|
||||||
"split.js": "1.6.5",
|
"split.js": "1.6.5",
|
||||||
"stream-throttle": "0.1.3",
|
"stream-throttle": "0.1.3",
|
||||||
"striptags": "3.2.0",
|
"striptags": "3.2.0",
|
||||||
|
@ -32,6 +32,7 @@ import DatabaseAnonymizationOptions from "./options/advanced/database_anonymizat
|
|||||||
import BackendLogWidget from "./content/backend_log.js";
|
import BackendLogWidget from "./content/backend_log.js";
|
||||||
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
|
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
|
||||||
import RibbonOptions from "./options/appearance/ribbon.js";
|
import RibbonOptions from "./options/appearance/ribbon.js";
|
||||||
|
import MultiFactorAuthenticationOptions from "./options/multi_factor_authentication.js";
|
||||||
|
|
||||||
const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
||||||
<style>
|
<style>
|
||||||
@ -53,79 +54,84 @@ const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
const CONTENT_WIDGETS = {
|
const CONTENT_WIDGETS = {
|
||||||
_optionsAppearance: [
|
_optionsAppearance: [
|
||||||
ThemeOptions,
|
ThemeOptions,
|
||||||
FontsOptions,
|
FontsOptions,
|
||||||
ZoomFactorOptions,
|
ZoomFactorOptions,
|
||||||
NativeTitleBarOptions,
|
NativeTitleBarOptions,
|
||||||
MaxContentWidthOptions,
|
MaxContentWidthOptions,
|
||||||
RibbonOptions
|
RibbonOptions,
|
||||||
],
|
],
|
||||||
_optionsShortcuts: [ KeyboardShortcutsOptions ],
|
_optionsShortcuts: [KeyboardShortcutsOptions],
|
||||||
_optionsTextNotes: [
|
_optionsTextNotes: [
|
||||||
HeadingStyleOptions,
|
HeadingStyleOptions,
|
||||||
TableOfContentsOptions,
|
TableOfContentsOptions,
|
||||||
HighlightsListOptions,
|
HighlightsListOptions,
|
||||||
TextAutoReadOnlySizeOptions
|
TextAutoReadOnlySizeOptions,
|
||||||
],
|
],
|
||||||
_optionsCodeNotes: [
|
_optionsCodeNotes: [
|
||||||
VimKeyBindingsOptions,
|
VimKeyBindingsOptions,
|
||||||
WrapLinesOptions,
|
WrapLinesOptions,
|
||||||
CodeAutoReadOnlySizeOptions,
|
CodeAutoReadOnlySizeOptions,
|
||||||
CodeMimeTypesOptions
|
CodeMimeTypesOptions,
|
||||||
],
|
],
|
||||||
_optionsImages: [ ImageOptions ],
|
_optionsMFA: [MultiFactorAuthenticationOptions],
|
||||||
_optionsSpellcheck: [ SpellcheckOptions ],
|
_optionsImages: [ImageOptions],
|
||||||
_optionsPassword: [ PasswordOptions ],
|
_optionsSpellcheck: [SpellcheckOptions],
|
||||||
_optionsEtapi: [ EtapiOptions ],
|
_optionsPassword: [PasswordOptions],
|
||||||
_optionsBackup: [ BackupOptions ],
|
_optionsEtapi: [EtapiOptions],
|
||||||
_optionsSync: [ SyncOptions ],
|
_optionsBackup: [BackupOptions],
|
||||||
_optionsOther: [
|
_optionsSync: [SyncOptions],
|
||||||
SearchEngineOptions,
|
_optionsOther: [
|
||||||
TrayOptions,
|
SearchEngineOptions,
|
||||||
NoteErasureTimeoutOptions,
|
TrayOptions,
|
||||||
AttachmentErasureTimeoutOptions,
|
NoteErasureTimeoutOptions,
|
||||||
RevisionsSnapshotIntervalOptions,
|
AttachmentErasureTimeoutOptions,
|
||||||
NetworkConnectionsOptions
|
RevisionsSnapshotIntervalOptions,
|
||||||
],
|
NetworkConnectionsOptions,
|
||||||
_optionsAdvanced: [
|
],
|
||||||
DatabaseIntegrityCheckOptions,
|
_optionsAdvanced: [
|
||||||
ConsistencyChecksOptions,
|
DatabaseIntegrityCheckOptions,
|
||||||
DatabaseAnonymizationOptions,
|
ConsistencyChecksOptions,
|
||||||
AdvancedSyncOptions,
|
DatabaseAnonymizationOptions,
|
||||||
VacuumDatabaseOptions
|
AdvancedSyncOptions,
|
||||||
],
|
VacuumDatabaseOptions,
|
||||||
_backendLog: [ BackendLogWidget ]
|
],
|
||||||
|
_backendLog: [BackendLogWidget],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ContentWidgetTypeWidget extends TypeWidget {
|
export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||||
static getType() { return "contentWidget"; }
|
static getType() {
|
||||||
|
return "contentWidget";
|
||||||
|
}
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$content = this.$widget.find(".note-detail-content-widget-content");
|
this.$content = this.$widget.find(".note-detail-content-widget-content");
|
||||||
|
|
||||||
super.doRender();
|
super.doRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
this.$content.empty();
|
this.$content.empty();
|
||||||
this.children = [];
|
this.children = [];
|
||||||
|
|
||||||
const contentWidgets = CONTENT_WIDGETS[note.noteId];
|
const contentWidgets = CONTENT_WIDGETS[note.noteId];
|
||||||
|
|
||||||
if (contentWidgets) {
|
if (contentWidgets) {
|
||||||
for (const clazz of contentWidgets) {
|
for (const clazz of contentWidgets) {
|
||||||
const widget = new clazz();
|
const widget = new clazz();
|
||||||
|
|
||||||
await widget.handleEvent('setNoteContext', { noteContext: this.noteContext });
|
await widget.handleEvent("setNoteContext", {
|
||||||
this.child(widget);
|
noteContext: this.noteContext,
|
||||||
|
});
|
||||||
this.$content.append(widget.render());
|
this.child(widget);
|
||||||
await widget.refresh();
|
|
||||||
}
|
this.$content.append(widget.render());
|
||||||
} else {
|
await widget.refresh();
|
||||||
this.$content.append(`Unknown widget for "${note.noteId}"`);
|
}
|
||||||
}
|
} else {
|
||||||
|
this.$content.append(`Unknown widget for "${note.noteId}"`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
import server from "../../../services/server.js";
|
||||||
|
import protectedSessionHolder from "../../../services/protected_session_holder.js";
|
||||||
|
import toastService from "../../../services/toast.js";
|
||||||
|
import OptionsWidget from "./options_widget.js";
|
||||||
|
// import { randomBytes } from "crypto";
|
||||||
|
|
||||||
|
// import { generateSecret } from "../../../services/totp.js";
|
||||||
|
|
||||||
|
// const speakeasy = require("speakeasy");
|
||||||
|
// ${speakeasy.generateSecret().base32}
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<div class="options-section">
|
||||||
|
<h4 class="mfa-heading"></h4>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" role="alert" style="font-weight: bold; color: red !important;">
|
||||||
|
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>
|
||||||
|
|
||||||
|
<span class="totp-secret" > </span>
|
||||||
|
<br>
|
||||||
|
<button class="regenerate-totp"> Regenerate TOTP Secret </button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
export default class MultiFactorAuthenticationOptions extends OptionsWidget {
|
||||||
|
doRender() {
|
||||||
|
this.$widget = $(TPL);
|
||||||
|
|
||||||
|
this.$mfaHeadding = this.$widget.find(".mfa-heading");
|
||||||
|
this.$regenerateTotpButton = this.$widget.find(".regenerate-totp");
|
||||||
|
this.$totpSecret = this.$widget.find(".totp-secret");
|
||||||
|
|
||||||
|
this.$mfaHeadding.text("Multi-Factor Authentication");
|
||||||
|
this.generateKey();
|
||||||
|
|
||||||
|
// var gen = require("speakeasy");
|
||||||
|
// toastService.showMessage("***REMOVED***");
|
||||||
|
|
||||||
|
this.$regenerateTotpButton.on("click", async () => {
|
||||||
|
this.generateKey();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$protectedSessionTimeout = this.$widget.find(
|
||||||
|
".protected-session-timeout-in-seconds"
|
||||||
|
);
|
||||||
|
this.$protectedSessionTimeout.on("change", () =>
|
||||||
|
this.updateOption(
|
||||||
|
"protectedSessionTimeout",
|
||||||
|
this.$protectedSessionTimeout.val()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateKey() {
|
||||||
|
server.get("totp/generate").then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
// password changed so current protected session is invalid and needs to be cleared
|
||||||
|
this.$totpSecret.text(result.message);
|
||||||
|
} else {
|
||||||
|
toastService.showError(result.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsLoaded(options) {
|
||||||
|
const isPasswordSet = options.isPasswordSet === "true";
|
||||||
|
|
||||||
|
this.$widget.find(".old-password-form-group").toggle(isPasswordSet);
|
||||||
|
this.$savePasswordButton.text(
|
||||||
|
isPasswordSet ? "Change fff Password" : "Set Password"
|
||||||
|
);
|
||||||
|
this.$protectedSessionTimeout.val(options.protectedSessionTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const oldPassword = this.$oldPassword.val();
|
||||||
|
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
|
||||||
|
.post("password/change", {
|
||||||
|
current_password: oldPassword,
|
||||||
|
new_password: newPassword1,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
toastService.showError(
|
||||||
|
"Password has been changed. Trilium will be reloaded after you press OK."
|
||||||
|
);
|
||||||
|
|
||||||
|
// password changed so current protected session is invalid and needs to be cleared
|
||||||
|
protectedSessionHolder.resetProtectedSession();
|
||||||
|
} else {
|
||||||
|
toastService.showError(result.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
24
src/routes/api/totp.ts
Normal file
24
src/routes/api/totp.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const speakeasy = require("speakeasy");
|
||||||
|
|
||||||
|
function verifyOTPToken(guessedToken: any) {
|
||||||
|
console.log("[" + guessedToken + "]");
|
||||||
|
console.log(typeof guessedToken);
|
||||||
|
|
||||||
|
const tokenValidates = speakeasy.totp.verify({
|
||||||
|
secret: process.env.MFA_SECRET,
|
||||||
|
encoding: "base32",
|
||||||
|
token: guessedToken,
|
||||||
|
window: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
return tokenValidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSecret() {
|
||||||
|
return { success: "true", message: speakeasy.generateSecret().base32 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
|
verifyOTPToken,
|
||||||
|
generateSecret,
|
||||||
|
};
|
1489
src/routes/routes.ts
1489
src/routes/routes.ts
File diff suppressed because it is too large
Load Diff
@ -1,28 +1,45 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import optionService = require('../options');
|
import optionService = require("../options");
|
||||||
import crypto = require('crypto');
|
import crypto = require("crypto");
|
||||||
|
|
||||||
function getVerificationHash(password: crypto.BinaryLike) {
|
function getVerificationHash(password: crypto.BinaryLike) {
|
||||||
const salt = optionService.getOption('passwordVerificationSalt');
|
const salt = optionService.getOption("passwordVerificationSalt");
|
||||||
|
|
||||||
return getScryptHash(password, salt);
|
return getScryptHash(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPasswordDerivedKey(password: crypto.BinaryLike) {
|
function getPasswordDerivedKey(password: crypto.BinaryLike) {
|
||||||
const salt = optionService.getOption('passwordDerivedKeySalt');
|
const salt = optionService.getOption("passwordDerivedKeySalt");
|
||||||
|
|
||||||
return getScryptHash(password, salt);
|
return getScryptHash(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScryptHash(password: crypto.BinaryLike, salt: crypto.BinaryLike) {
|
function getScryptHash(password: crypto.BinaryLike, salt: crypto.BinaryLike) {
|
||||||
const hashed = crypto.scryptSync(password, salt, 32,
|
const hashed = crypto.scryptSync(password, salt, 32, {
|
||||||
{N: 16384, r:8, p:1});
|
N: 16384,
|
||||||
|
r: 8,
|
||||||
|
p: 1,
|
||||||
|
});
|
||||||
|
|
||||||
return hashed;
|
return hashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotpSecretVerificationHash(secret: crypto.BinaryLike) {
|
||||||
|
const salt = optionService.getOption("totpSecretVerificationSalt");
|
||||||
|
|
||||||
|
return getScryptHash(secret, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotpSecretDerivedKey(secret: crypto.BinaryLike) {
|
||||||
|
const salt = optionService.getOption("totpSecretDerivedKeySalt");
|
||||||
|
|
||||||
|
return getScryptHash(secret, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
getVerificationHash,
|
getVerificationHash,
|
||||||
getPasswordDerivedKey
|
getPasswordDerivedKey,
|
||||||
|
getTotpSecretVerificationHash,
|
||||||
|
getTotpSecretDerivedKey,
|
||||||
};
|
};
|
||||||
|
@ -1,40 +1,50 @@
|
|||||||
import optionService = require('../options');
|
import optionService = require("../options");
|
||||||
import myScryptService = require('./my_scrypt');
|
import myScryptService = require("./my_scrypt");
|
||||||
import utils = require('../utils');
|
import utils = require("../utils");
|
||||||
import dataEncryptionService = require('./data_encryption');
|
import dataEncryptionService = require("./data_encryption");
|
||||||
|
|
||||||
function verifyPassword(password: string) {
|
function verifyPassword(password: string) {
|
||||||
const givenPasswordHash = utils.toBase64(myScryptService.getVerificationHash(password));
|
const givenPasswordHash = utils.toBase64(
|
||||||
|
myScryptService.getVerificationHash(password)
|
||||||
|
);
|
||||||
|
|
||||||
const dbPasswordHash = optionService.getOptionOrNull('passwordVerificationHash');
|
const dbPasswordHash = optionService.getOptionOrNull(
|
||||||
|
"passwordVerificationHash"
|
||||||
|
);
|
||||||
|
|
||||||
if (!dbPasswordHash) {
|
if (!dbPasswordHash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return givenPasswordHash === dbPasswordHash;
|
return givenPasswordHash === dbPasswordHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDataKey(password: string, plainTextDataKey: string | Buffer) {
|
function setDataKey(password: string, plainTextDataKey: string | Buffer) {
|
||||||
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
||||||
|
|
||||||
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey);
|
const newEncryptedDataKey = dataEncryptionService.encrypt(
|
||||||
|
passwordDerivedKey,
|
||||||
|
plainTextDataKey
|
||||||
|
);
|
||||||
|
|
||||||
optionService.setOption('encryptedDataKey', newEncryptedDataKey);
|
optionService.setOption("encryptedDataKey", newEncryptedDataKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataKey(password: string) {
|
function getDataKey(password: string) {
|
||||||
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
|
||||||
|
|
||||||
const encryptedDataKey = optionService.getOption('encryptedDataKey');
|
const encryptedDataKey = optionService.getOption("encryptedDataKey");
|
||||||
|
|
||||||
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey);
|
const decryptedDataKey = dataEncryptionService.decrypt(
|
||||||
|
passwordDerivedKey,
|
||||||
|
encryptedDataKey
|
||||||
|
);
|
||||||
|
|
||||||
return decryptedDataKey;
|
return decryptedDataKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
verifyPassword,
|
verifyPassword,
|
||||||
getDataKey,
|
getDataKey,
|
||||||
setDataKey
|
setDataKey,
|
||||||
};
|
};
|
||||||
|
110
src/services/encryption/totp_secret.ts
Normal file
110
src/services/encryption/totp_secret.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import sql = require("../sql");
|
||||||
|
import optionService = require("../options");
|
||||||
|
import myScryptService = require("./my_scrypt");
|
||||||
|
import utils = require("../utils");
|
||||||
|
import totpEncryptionService = require("./totp_secret_encryption");
|
||||||
|
|
||||||
|
function isTotpSecretSet() {
|
||||||
|
return !!sql.getValue(
|
||||||
|
"SELECT value FROM options WHERE name = 'passwordVerificationHash'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePassword(currentSecret: string, newSecret: string) {
|
||||||
|
if (!isTotpSecretSet()) {
|
||||||
|
throw new Error(
|
||||||
|
"TOTP Secret has not been set yet, so it cannot be changed. Use 'setTotpSecret' instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.transactional(() => {
|
||||||
|
const decryptedDataKey = totpEncryptionService.getDataKey(currentSecret);
|
||||||
|
|
||||||
|
optionService.setOption(
|
||||||
|
"totpSecretVerificationSalt",
|
||||||
|
utils.randomSecureToken(32)
|
||||||
|
);
|
||||||
|
optionService.setOption(
|
||||||
|
"totpSecretDerivedKeySalt",
|
||||||
|
utils.randomSecureToken(32)
|
||||||
|
);
|
||||||
|
|
||||||
|
const newTotpSecretVerificationKey = utils.toBase64(
|
||||||
|
myScryptService.getTotpSecretVerificationHash(newSecret)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decryptedDataKey) {
|
||||||
|
// TODO: what should happen if the decrypted data key is null?
|
||||||
|
totpEncryptionService.setDataKey(newSecret, decryptedDataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
optionService.setOption(
|
||||||
|
"totpSecretVerificationHash",
|
||||||
|
newTotpSecretVerificationKey
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTotpSecret(secret: string) {
|
||||||
|
if (isTotpSecretSet()) {
|
||||||
|
throw new Error(
|
||||||
|
"TOTP Secret is set already. Either change it or perform 'reset TOTP' first."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
optionService.createOption(
|
||||||
|
"totpSecretVerificationSalt",
|
||||||
|
utils.randomSecureToken(32),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
optionService.createOption(
|
||||||
|
"totpSecretDerivedKeySalt",
|
||||||
|
utils.randomSecureToken(32),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const totpSecretVerificationKey = utils.toBase64(
|
||||||
|
myScryptService.getTotpSecretVerificationHash(secret)
|
||||||
|
);
|
||||||
|
optionService.createOption(
|
||||||
|
"totpSecretVerificationHash",
|
||||||
|
totpSecretVerificationKey,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// totpEncryptionService expects these options to already exist
|
||||||
|
optionService.createOption("encryptedTotpSecretDataKey", "", true);
|
||||||
|
|
||||||
|
totpEncryptionService.setDataKey(secret, utils.randomSecureToken(16));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPassword() {
|
||||||
|
// user forgot the password,
|
||||||
|
sql.transactional(() => {
|
||||||
|
optionService.setOption("passwordVerificationSalt", "");
|
||||||
|
optionService.setOption("passwordDerivedKeySalt", "");
|
||||||
|
optionService.setOption("encryptedDataKey", "");
|
||||||
|
optionService.setOption("passwordVerificationHash", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
|
isTotpSecretSet,
|
||||||
|
changePassword,
|
||||||
|
setTotpSecret,
|
||||||
|
resetPassword,
|
||||||
|
};
|
52
src/services/encryption/totp_secret_encryption.ts
Normal file
52
src/services/encryption/totp_secret_encryption.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import optionService = require("../options");
|
||||||
|
import myScryptService = require("./my_scrypt");
|
||||||
|
import utils = require("../utils");
|
||||||
|
import dataEncryptionService = require("./data_encryption");
|
||||||
|
|
||||||
|
// function verifyPassword(password: string) {
|
||||||
|
// const givenPasswordHash = utils.toBase64(
|
||||||
|
// myScryptService.getVerificationHash(password)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const dbPasswordHash = optionService.getOptionOrNull(
|
||||||
|
// "passwordVerificationHash"
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (!dbPasswordHash) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return givenPasswordHash === dbPasswordHash;
|
||||||
|
// }
|
||||||
|
|
||||||
|
function setDataKey(secret: string, plainTextDataKey: string | Buffer) {
|
||||||
|
const totpSecretDerivedKey = myScryptService.getTotpSecretDerivedKey(secret);
|
||||||
|
|
||||||
|
const newEncryptedDataKey = dataEncryptionService.encrypt(
|
||||||
|
totpSecretDerivedKey,
|
||||||
|
plainTextDataKey
|
||||||
|
);
|
||||||
|
|
||||||
|
optionService.setOption("encryptedTotpSecretDataKey", newEncryptedDataKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataKey(secret: string) {
|
||||||
|
const totpSecretDerivedKey = myScryptService.getTotpSecretDerivedKey(secret);
|
||||||
|
|
||||||
|
const encryptedDataKey = optionService.getOption(
|
||||||
|
"encryptedTotpSecretDataKey"
|
||||||
|
);
|
||||||
|
|
||||||
|
const decryptedDataKey = dataEncryptionService.decrypt(
|
||||||
|
totpSecretDerivedKey,
|
||||||
|
encryptedDataKey
|
||||||
|
);
|
||||||
|
|
||||||
|
return decryptedDataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
|
// verifyPassword,
|
||||||
|
getDataKey,
|
||||||
|
setDataKey,
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import BAttribute = require("../becca/entities/battribute");
|
import BAttribute = require("../becca/entities/battribute");
|
||||||
import { AttributeType, NoteType } from "../becca/entities/rows";
|
import { AttributeType, NoteType } from "../becca/entities/rows";
|
||||||
|
|
||||||
import becca = require('../becca/becca');
|
import becca = require("../becca/becca");
|
||||||
import noteService = require('./notes');
|
import noteService = require("./notes");
|
||||||
import log = require('./log');
|
import log = require("./log");
|
||||||
import migrationService = require('./migration');
|
import migrationService = require("./migration");
|
||||||
|
|
||||||
const LBTPL_ROOT = "_lbTplRoot";
|
const LBTPL_ROOT = "_lbTplRoot";
|
||||||
const LBTPL_BASE = "_lbTplBase";
|
const LBTPL_BASE = "_lbTplBase";
|
||||||
@ -16,26 +16,38 @@ const LBTPL_SPACER = "_lbTplSpacer";
|
|||||||
const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget";
|
const LBTPL_CUSTOM_WIDGET = "_lbTplCustomWidget";
|
||||||
|
|
||||||
interface Attribute {
|
interface Attribute {
|
||||||
type: AttributeType;
|
type: AttributeType;
|
||||||
name: string;
|
name: string;
|
||||||
isInheritable?: boolean;
|
isInheritable?: boolean;
|
||||||
value?: string
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
notePosition?: number;
|
notePosition?: number;
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
type: NoteType;
|
type: NoteType;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
attributes?: Attribute[];
|
attributes?: Attribute[];
|
||||||
children?: Item[];
|
children?: Item[];
|
||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
baseSize?: string;
|
baseSize?: string;
|
||||||
growthFactor?: string;
|
growthFactor?: string;
|
||||||
targetNoteId?: "_backendLog" | "_globalNoteMap";
|
targetNoteId?: "_backendLog" | "_globalNoteMap";
|
||||||
builtinWidget?: "bookmarks" | "spacer" | "backInHistoryButton" | "forwardInHistoryButton" | "syncStatus" | "protectedSession" | "todayInJournal" | "calendar";
|
builtinWidget?:
|
||||||
command?: "jumpToNote" | "searchNotes" | "createNoteIntoInbox" | "showRecentChanges";
|
| "bookmarks"
|
||||||
|
| "spacer"
|
||||||
|
| "backInHistoryButton"
|
||||||
|
| "forwardInHistoryButton"
|
||||||
|
| "syncStatus"
|
||||||
|
| "protectedSession"
|
||||||
|
| "todayInJournal"
|
||||||
|
| "calendar";
|
||||||
|
command?:
|
||||||
|
| "jumpToNote"
|
||||||
|
| "searchNotes"
|
||||||
|
| "createNoteIntoInbox"
|
||||||
|
| "showRecentChanges";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -45,331 +57,555 @@ interface Item {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const HIDDEN_SUBTREE_DEFINITION: Item = {
|
const HIDDEN_SUBTREE_DEFINITION: Item = {
|
||||||
id: '_hidden',
|
id: "_hidden",
|
||||||
title: 'Hidden Notes',
|
title: "Hidden Notes",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
icon: 'bx bx-chip',
|
icon: "bx bx-chip",
|
||||||
// we want to keep the hidden subtree always last, otherwise there will be problems with e.g., keyboard navigation
|
// we want to keep the hidden subtree always last, otherwise there will be problems with e.g., keyboard navigation
|
||||||
// over tree when it's in the middle
|
// over tree when it's in the middle
|
||||||
notePosition: 999_999_999,
|
notePosition: 999_999_999,
|
||||||
attributes: [
|
attributes: [
|
||||||
{ type: 'label', name: 'excludeFromNoteMap', isInheritable: true },
|
{ type: "label", name: "excludeFromNoteMap", isInheritable: true },
|
||||||
{ type: 'label', name: 'docName', value: 'hidden' }
|
{ type: "label", name: "docName", value: "hidden" },
|
||||||
],
|
],
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: "_search",
|
||||||
|
title: "Search History",
|
||||||
|
type: "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_globalNoteMap",
|
||||||
|
title: "Note Map",
|
||||||
|
type: "noteMap",
|
||||||
|
attributes: [
|
||||||
|
{ type: "label", name: "mapRootNoteId", value: "hoisted" },
|
||||||
|
{ type: "label", name: "keepCurrentHoisting" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_sqlConsole",
|
||||||
|
title: "SQL Console History",
|
||||||
|
type: "doc",
|
||||||
|
icon: "bx-data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_share",
|
||||||
|
title: "Shared Notes",
|
||||||
|
type: "doc",
|
||||||
|
attributes: [{ type: "label", name: "docName", value: "share" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_bulkAction",
|
||||||
|
title: "Bulk Action",
|
||||||
|
type: "doc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_backendLog",
|
||||||
|
title: "Backend Log",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-terminal",
|
||||||
|
attributes: [{ type: "label", name: "keepCurrentHoisting" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// place for user scripts hidden stuff (scripts should not create notes directly under hidden root)
|
||||||
|
id: "_userHidden",
|
||||||
|
title: "User Hidden",
|
||||||
|
type: "doc",
|
||||||
|
attributes: [{ type: "label", name: "docName", value: "user_hidden" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: LBTPL_ROOT,
|
||||||
|
title: "Launch Bar Templates",
|
||||||
|
type: "doc",
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
id: '_search',
|
id: LBTPL_BASE,
|
||||||
title: 'Search History',
|
title: "Base Abstract Launcher",
|
||||||
type: 'doc'
|
type: "doc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_globalNoteMap',
|
id: LBTPL_COMMAND,
|
||||||
title: 'Note Map',
|
title: "Command Launcher",
|
||||||
type: 'noteMap',
|
type: "doc",
|
||||||
attributes: [
|
attributes: [
|
||||||
{ type: 'label', name: 'mapRootNoteId', value: 'hoisted' },
|
{ type: "relation", name: "template", value: LBTPL_BASE },
|
||||||
{ type: 'label', name: 'keepCurrentHoisting' }
|
{ type: "label", name: "launcherType", value: "command" },
|
||||||
]
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "docName",
|
||||||
|
value: "launchbar_command_launcher",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_sqlConsole',
|
id: LBTPL_NOTE_LAUNCHER,
|
||||||
title: 'SQL Console History',
|
title: "Note Launcher",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
icon: 'bx-data'
|
attributes: [
|
||||||
|
{ type: "relation", name: "template", value: LBTPL_BASE },
|
||||||
|
{ type: "label", name: "launcherType", value: "note" },
|
||||||
|
{ type: "label", name: "relation:target", value: "promoted" },
|
||||||
|
{ type: "label", name: "relation:hoistedNote", value: "promoted" },
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "label:keyboardShortcut",
|
||||||
|
value: "promoted,text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "docName",
|
||||||
|
value: "launchbar_note_launcher",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_share',
|
id: LBTPL_SCRIPT,
|
||||||
title: 'Shared Notes',
|
title: "Script Launcher",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'share' } ]
|
attributes: [
|
||||||
|
{ type: "relation", name: "template", value: LBTPL_BASE },
|
||||||
|
{ type: "label", name: "launcherType", value: "script" },
|
||||||
|
{ type: "label", name: "relation:script", value: "promoted" },
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "label:keyboardShortcut",
|
||||||
|
value: "promoted,text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "docName",
|
||||||
|
value: "launchbar_script_launcher",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_bulkAction',
|
id: LBTPL_BUILTIN_WIDGET,
|
||||||
title: 'Bulk Action',
|
title: "Built-in Widget",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
|
attributes: [
|
||||||
|
{ type: "relation", name: "template", value: LBTPL_BASE },
|
||||||
|
{ type: "label", name: "launcherType", value: "builtinWidget" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_backendLog',
|
id: LBTPL_SPACER,
|
||||||
title: 'Backend Log',
|
title: "Spacer",
|
||||||
type: 'contentWidget',
|
type: "doc",
|
||||||
icon: 'bx-terminal',
|
icon: "bx-move-vertical",
|
||||||
attributes: [
|
attributes: [
|
||||||
{ type: 'label', name: 'keepCurrentHoisting' }
|
{ type: "relation", name: "template", value: LBTPL_BUILTIN_WIDGET },
|
||||||
]
|
{ type: "label", name: "builtinWidget", value: "spacer" },
|
||||||
|
{ type: "label", name: "label:baseSize", value: "promoted,number" },
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "label:growthFactor",
|
||||||
|
value: "promoted,number",
|
||||||
|
},
|
||||||
|
{ type: "label", name: "docName", value: "launchbar_spacer" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// place for user scripts hidden stuff (scripts should not create notes directly under hidden root)
|
id: LBTPL_CUSTOM_WIDGET,
|
||||||
id: '_userHidden',
|
title: "Custom Widget",
|
||||||
title: 'User Hidden',
|
type: "doc",
|
||||||
type: 'doc',
|
attributes: [
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'user_hidden' } ]
|
{ type: "relation", name: "template", value: LBTPL_BASE },
|
||||||
|
{ type: "label", name: "launcherType", value: "customWidget" },
|
||||||
|
{ type: "label", name: "relation:widget", value: "promoted" },
|
||||||
|
{
|
||||||
|
type: "label",
|
||||||
|
name: "docName",
|
||||||
|
value: "launchbar_widget_launcher",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbRoot",
|
||||||
|
title: "Launch Bar",
|
||||||
|
type: "doc",
|
||||||
|
icon: "bx-sidebar",
|
||||||
|
isExpanded: true,
|
||||||
|
attributes: [
|
||||||
|
{ type: "label", name: "docName", value: "launchbar_intro" },
|
||||||
|
],
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
id: LBTPL_ROOT,
|
id: "_lbAvailableLaunchers",
|
||||||
title: 'Launch Bar Templates',
|
title: "Available Launchers",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
children: [
|
icon: "bx-hide",
|
||||||
|
isExpanded: true,
|
||||||
|
attributes: [
|
||||||
|
{ type: "label", name: "docName", value: "launchbar_intro" },
|
||||||
|
],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "_lbBackInHistory",
|
||||||
|
title: "Go to Previous Note",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "backInHistoryButton",
|
||||||
|
icon: "bx bxs-left-arrow-square",
|
||||||
|
attributes: [
|
||||||
{
|
{
|
||||||
id: LBTPL_BASE,
|
type: "label",
|
||||||
title: 'Base Abstract Launcher',
|
name: "docName",
|
||||||
type: 'doc'
|
value: "launchbar_history_navigation",
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbForwardInHistory",
|
||||||
|
title: "Go to Next Note",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "forwardInHistoryButton",
|
||||||
|
icon: "bx bxs-right-arrow-square",
|
||||||
|
attributes: [
|
||||||
{
|
{
|
||||||
id: LBTPL_COMMAND,
|
type: "label",
|
||||||
title: 'Command Launcher',
|
name: "docName",
|
||||||
type: 'doc',
|
value: "launchbar_history_navigation",
|
||||||
attributes: [
|
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BASE },
|
|
||||||
{ type: 'label', name: 'launcherType', value: 'command' },
|
|
||||||
{ type: 'label', name: 'docName', value: 'launchbar_command_launcher' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
id: LBTPL_NOTE_LAUNCHER,
|
},
|
||||||
title: 'Note Launcher',
|
{
|
||||||
type: 'doc',
|
id: "_lbBackendLog",
|
||||||
attributes: [
|
title: "Backend Log",
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BASE },
|
type: "launcher",
|
||||||
{ type: 'label', name: 'launcherType', value: 'note' },
|
targetNoteId: "_backendLog",
|
||||||
{ type: 'label', name: 'relation:target', value: 'promoted' },
|
icon: "bx bx-terminal",
|
||||||
{ type: 'label', name: 'relation:hoistedNote', value: 'promoted' },
|
},
|
||||||
{ type: 'label', name: 'label:keyboardShortcut', value: 'promoted,text' },
|
],
|
||||||
{ type: 'label', name: 'docName', value: 'launchbar_note_launcher' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: LBTPL_SCRIPT,
|
|
||||||
title: 'Script Launcher',
|
|
||||||
type: 'doc',
|
|
||||||
attributes: [
|
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BASE },
|
|
||||||
{ type: 'label', name: 'launcherType', value: 'script' },
|
|
||||||
{ type: 'label', name: 'relation:script', value: 'promoted' },
|
|
||||||
{ type: 'label', name: 'label:keyboardShortcut', value: 'promoted,text' },
|
|
||||||
{ type: 'label', name: 'docName', value: 'launchbar_script_launcher' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: LBTPL_BUILTIN_WIDGET,
|
|
||||||
title: 'Built-in Widget',
|
|
||||||
type: 'doc',
|
|
||||||
attributes: [
|
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BASE },
|
|
||||||
{ type: 'label', name: 'launcherType', value: 'builtinWidget' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: LBTPL_SPACER,
|
|
||||||
title: 'Spacer',
|
|
||||||
type: 'doc',
|
|
||||||
icon: 'bx-move-vertical',
|
|
||||||
attributes: [
|
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BUILTIN_WIDGET },
|
|
||||||
{ type: 'label', name: 'builtinWidget', value: 'spacer' },
|
|
||||||
{ type: 'label', name: 'label:baseSize', value: 'promoted,number' },
|
|
||||||
{ type: 'label', name: 'label:growthFactor', value: 'promoted,number' },
|
|
||||||
{ type: 'label', name: 'docName', value: 'launchbar_spacer' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: LBTPL_CUSTOM_WIDGET,
|
|
||||||
title: 'Custom Widget',
|
|
||||||
type: 'doc',
|
|
||||||
attributes: [
|
|
||||||
{ type: 'relation', name: 'template', value: LBTPL_BASE },
|
|
||||||
{ type: 'label', name: 'launcherType', value: 'customWidget' },
|
|
||||||
{ type: 'label', name: 'relation:widget', value: 'promoted' },
|
|
||||||
{ type: 'label', name: 'docName', value: 'launchbar_widget_launcher' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_lbRoot',
|
id: "_lbVisibleLaunchers",
|
||||||
title: 'Launch Bar',
|
title: "Visible Launchers",
|
||||||
type: 'doc',
|
type: "doc",
|
||||||
icon: 'bx-sidebar',
|
icon: "bx-show",
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
|
attributes: [
|
||||||
children: [
|
{ type: "label", name: "docName", value: "launchbar_intro" },
|
||||||
{
|
],
|
||||||
id: '_lbAvailableLaunchers',
|
children: [
|
||||||
title: 'Available Launchers',
|
{
|
||||||
type: 'doc',
|
id: "_lbNewNote",
|
||||||
icon: 'bx-hide',
|
title: "New Note",
|
||||||
isExpanded: true,
|
type: "launcher",
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
|
command: "createNoteIntoInbox",
|
||||||
children: [
|
icon: "bx bx-file-blank",
|
||||||
{ id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square',
|
},
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
|
{
|
||||||
{ id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square',
|
id: "_lbSearch",
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
|
title: "Search Notes",
|
||||||
{ id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' },
|
type: "launcher",
|
||||||
]
|
command: "searchNotes",
|
||||||
},
|
icon: "bx bx-search",
|
||||||
{
|
attributes: [{ type: "label", name: "desktopOnly" }],
|
||||||
id: '_lbVisibleLaunchers',
|
},
|
||||||
title: 'Visible Launchers',
|
{
|
||||||
type: 'doc',
|
id: "_lbJumpTo",
|
||||||
icon: 'bx-show',
|
title: "Jump to Note",
|
||||||
isExpanded: true,
|
type: "launcher",
|
||||||
attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
|
command: "jumpToNote",
|
||||||
children: [
|
icon: "bx bx-send",
|
||||||
{ id: '_lbNewNote', title: 'New Note', type: 'launcher', command: 'createNoteIntoInbox', icon: 'bx bx-file-blank' },
|
attributes: [{ type: "label", name: "desktopOnly" }],
|
||||||
{ id: '_lbSearch', title: 'Search Notes', type: 'launcher', command: 'searchNotes', icon: 'bx bx-search', attributes: [
|
},
|
||||||
{ type: 'label', name: 'desktopOnly' }
|
{
|
||||||
] },
|
id: "_lbNoteMap",
|
||||||
{ id: '_lbJumpTo', title: 'Jump to Note', type: 'launcher', command: 'jumpToNote', icon: 'bx bx-send', attributes: [
|
title: "Note Map",
|
||||||
{ type: 'label', name: 'desktopOnly' }
|
type: "launcher",
|
||||||
] },
|
targetNoteId: "_globalNoteMap",
|
||||||
{ id: '_lbNoteMap', title: 'Note Map', type: 'launcher', targetNoteId: '_globalNoteMap', icon: 'bx bx-map-alt' },
|
icon: "bx bx-map-alt",
|
||||||
{ id: '_lbCalendar', title: 'Calendar', type: 'launcher', builtinWidget: 'calendar', icon: 'bx bx-calendar' },
|
},
|
||||||
{ id: '_lbRecentChanges', title: 'Recent Changes', type: 'launcher', command: 'showRecentChanges', icon: 'bx bx-history', attributes: [
|
{
|
||||||
{ type: 'label', name: 'desktopOnly' }
|
id: "_lbCalendar",
|
||||||
] },
|
title: "Calendar",
|
||||||
{ id: '_lbSpacer1', title: 'Spacer', type: 'launcher', builtinWidget: 'spacer', baseSize: "50", growthFactor: "0" },
|
type: "launcher",
|
||||||
{ id: '_lbBookmarks', title: 'Bookmarks', type: 'launcher', builtinWidget: 'bookmarks', icon: 'bx bx-bookmark' },
|
builtinWidget: "calendar",
|
||||||
{ id: '_lbToday', title: "Open Today's Journal Note", type: 'launcher', builtinWidget: 'todayInJournal', icon: 'bx bx-calendar-star' },
|
icon: "bx bx-calendar",
|
||||||
{ id: '_lbSpacer2', title: 'Spacer', type: 'launcher', builtinWidget: 'spacer', baseSize: "0", growthFactor: "1" },
|
},
|
||||||
{ id: '_lbProtectedSession', title: 'Protected Session', type: 'launcher', builtinWidget: 'protectedSession', icon: 'bx bx bx-shield-quarter' },
|
{
|
||||||
{ id: '_lbSyncStatus', title: 'Sync Status', type: 'launcher', builtinWidget: 'syncStatus', icon: 'bx bx-wifi' }
|
id: "_lbRecentChanges",
|
||||||
]
|
title: "Recent Changes",
|
||||||
}
|
type: "launcher",
|
||||||
]
|
command: "showRecentChanges",
|
||||||
|
icon: "bx bx-history",
|
||||||
|
attributes: [{ type: "label", name: "desktopOnly" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbSpacer1",
|
||||||
|
title: "Spacer",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "spacer",
|
||||||
|
baseSize: "50",
|
||||||
|
growthFactor: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbBookmarks",
|
||||||
|
title: "Bookmarks",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "bookmarks",
|
||||||
|
icon: "bx bx-bookmark",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbToday",
|
||||||
|
title: "Open Today's Journal Note",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "todayInJournal",
|
||||||
|
icon: "bx bx-calendar-star",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbSpacer2",
|
||||||
|
title: "Spacer",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "spacer",
|
||||||
|
baseSize: "0",
|
||||||
|
growthFactor: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbProtectedSession",
|
||||||
|
title: "Protected Session",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "protectedSession",
|
||||||
|
icon: "bx bx bx-shield-quarter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_lbSyncStatus",
|
||||||
|
title: "Sync Status",
|
||||||
|
type: "launcher",
|
||||||
|
builtinWidget: "syncStatus",
|
||||||
|
icon: "bx bx-wifi",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_options",
|
||||||
|
title: "Options",
|
||||||
|
type: "book",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "_optionsAppearance",
|
||||||
|
title: "Appearance",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-layout",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '_options',
|
id: "_optionsShortcuts",
|
||||||
title: 'Options',
|
title: "Shortcuts",
|
||||||
type: 'book',
|
type: "contentWidget",
|
||||||
children: [
|
icon: "bxs-keyboard",
|
||||||
{ id: '_optionsAppearance', title: 'Appearance', type: 'contentWidget', icon: 'bx-layout' },
|
},
|
||||||
{ id: '_optionsShortcuts', title: 'Shortcuts', type: 'contentWidget', icon: 'bxs-keyboard' },
|
{
|
||||||
{ id: '_optionsTextNotes', title: 'Text Notes', type: 'contentWidget', icon: 'bx-text' },
|
id: "_optionsTextNotes",
|
||||||
{ id: '_optionsCodeNotes', title: 'Code Notes', type: 'contentWidget', icon: 'bx-code' },
|
title: "Text Notes",
|
||||||
{ id: '_optionsImages', title: 'Images', type: 'contentWidget', icon: 'bx-image' },
|
type: "contentWidget",
|
||||||
{ id: '_optionsSpellcheck', title: 'Spellcheck', type: 'contentWidget', icon: 'bx-check-double' },
|
icon: "bx-text",
|
||||||
{ id: '_optionsPassword', title: 'Password', type: 'contentWidget', icon: 'bx-lock' },
|
},
|
||||||
{ id: '_optionsEtapi', title: 'ETAPI', type: 'contentWidget', icon: 'bx-extension' },
|
{
|
||||||
{ id: '_optionsBackup', title: 'Backup', type: 'contentWidget', icon: 'bx-data' },
|
id: "_optionsMFA",
|
||||||
{ id: '_optionsSync', title: 'Sync', type: 'contentWidget', icon: 'bx-wifi' },
|
title: "MFA",
|
||||||
{ id: '_optionsOther', title: 'Other', type: 'contentWidget', icon: 'bx-dots-horizontal' },
|
type: "contentWidget",
|
||||||
{ id: '_optionsAdvanced', title: 'Advanced', type: 'contentWidget' }
|
icon: "bx-lock",
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
]
|
id: "_optionsCodeNotes",
|
||||||
|
title: "Code Notes",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-code",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsImages",
|
||||||
|
title: "Images",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsSpellcheck",
|
||||||
|
title: "Spellcheck",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-check-double",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsPassword",
|
||||||
|
title: "Password",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-lock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsEtapi",
|
||||||
|
title: "ETAPI",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-extension",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsBackup",
|
||||||
|
title: "Backup",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsSync",
|
||||||
|
title: "Sync",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-wifi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "_optionsOther",
|
||||||
|
title: "Other",
|
||||||
|
type: "contentWidget",
|
||||||
|
icon: "bx-dots-horizontal",
|
||||||
|
},
|
||||||
|
{ id: "_optionsAdvanced", title: "Advanced", type: "contentWidget" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkHiddenSubtree(force = false) {
|
function checkHiddenSubtree(force = false) {
|
||||||
if (!force && !migrationService.isDbUpToDate()) {
|
if (!force && !migrationService.isDbUpToDate()) {
|
||||||
// on-delete hook might get triggered during some future migration and cause havoc
|
// on-delete hook might get triggered during some future migration and cause havoc
|
||||||
log.info("Will not check hidden subtree until migration is finished.");
|
log.info("Will not check hidden subtree until migration is finished.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkHiddenSubtreeRecursively('root', HIDDEN_SUBTREE_DEFINITION);
|
checkHiddenSubtreeRecursively("root", HIDDEN_SUBTREE_DEFINITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item) {
|
function checkHiddenSubtreeRecursively(parentNoteId: string, item: Item) {
|
||||||
if (!item.id || !item.type || !item.title) {
|
if (!item.id || !item.type || !item.title) {
|
||||||
throw new Error(`Item does not contain mandatory properties: ${JSON.stringify(item)}`);
|
throw new Error(
|
||||||
}
|
`Item does not contain mandatory properties: ${JSON.stringify(item)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (item.id.charAt(0) !== '_') {
|
if (item.id.charAt(0) !== "_") {
|
||||||
throw new Error(`ID has to start with underscore, given '${item.id}'`);
|
throw new Error(`ID has to start with underscore, given '${item.id}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = becca.notes[item.id];
|
let note = becca.notes[item.id];
|
||||||
let branch;
|
let branch;
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
({note, branch} = noteService.createNewNote({
|
({ note, branch } = noteService.createNewNote({
|
||||||
noteId: item.id,
|
noteId: item.id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
content: '',
|
content: "",
|
||||||
ignoreForbiddenParents: true
|
ignoreForbiddenParents: true,
|
||||||
}));
|
}));
|
||||||
|
} else {
|
||||||
|
branch = note
|
||||||
|
.getParentBranches()
|
||||||
|
.find((branch) => branch.parentNoteId === parentNoteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrs = [...(item.attributes || [])];
|
||||||
|
|
||||||
|
if (item.icon) {
|
||||||
|
attrs.push({ type: "label", name: "iconClass", value: `bx ${item.icon}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === "launcher") {
|
||||||
|
if (item.command) {
|
||||||
|
attrs.push({ type: "relation", name: "template", value: LBTPL_COMMAND });
|
||||||
|
attrs.push({ type: "label", name: "command", value: item.command });
|
||||||
|
} else if (item.builtinWidget) {
|
||||||
|
if (item.builtinWidget === "spacer") {
|
||||||
|
attrs.push({ type: "relation", name: "template", value: LBTPL_SPACER });
|
||||||
|
attrs.push({ type: "label", name: "baseSize", value: item.baseSize });
|
||||||
|
attrs.push({
|
||||||
|
type: "label",
|
||||||
|
name: "growthFactor",
|
||||||
|
value: item.growthFactor,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
attrs.push({
|
||||||
|
type: "relation",
|
||||||
|
name: "template",
|
||||||
|
value: LBTPL_BUILTIN_WIDGET,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs.push({
|
||||||
|
type: "label",
|
||||||
|
name: "builtinWidget",
|
||||||
|
value: item.builtinWidget,
|
||||||
|
});
|
||||||
|
} else if (item.targetNoteId) {
|
||||||
|
attrs.push({
|
||||||
|
type: "relation",
|
||||||
|
name: "template",
|
||||||
|
value: LBTPL_NOTE_LAUNCHER,
|
||||||
|
});
|
||||||
|
attrs.push({
|
||||||
|
type: "relation",
|
||||||
|
name: "target",
|
||||||
|
value: item.targetNoteId,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
branch = note.getParentBranches().find(branch => branch.parentNoteId === parentNoteId);
|
throw new Error(`No action defined for launcher ${JSON.stringify(item)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.type !== item.type) {
|
||||||
|
// enforce a correct note type
|
||||||
|
note.type = item.type;
|
||||||
|
note.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
// in case of launchers the branch ID is not preserved and should not be relied upon - launchers which move between
|
||||||
|
// visible and available will change branch since the branch's parent-child relationship is immutable
|
||||||
|
if (
|
||||||
|
item.notePosition !== undefined &&
|
||||||
|
branch.notePosition !== item.notePosition
|
||||||
|
) {
|
||||||
|
branch.notePosition = item.notePosition;
|
||||||
|
branch.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
const attrs = [...(item.attributes || [])];
|
if (
|
||||||
|
item.isExpanded !== undefined &&
|
||||||
if (item.icon) {
|
branch.isExpanded !== item.isExpanded
|
||||||
attrs.push({ type: 'label', name: 'iconClass', value: `bx ${item.icon}` });
|
) {
|
||||||
|
branch.isExpanded = item.isExpanded;
|
||||||
|
branch.save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.type === 'launcher') {
|
for (const attr of attrs) {
|
||||||
if (item.command) {
|
const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name;
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_COMMAND });
|
|
||||||
attrs.push({ type: 'label', name: 'command', value: item.command });
|
|
||||||
} else if (item.builtinWidget) {
|
|
||||||
if (item.builtinWidget === 'spacer') {
|
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_SPACER });
|
|
||||||
attrs.push({ type: 'label', name: 'baseSize', value: item.baseSize });
|
|
||||||
attrs.push({ type: 'label', name: 'growthFactor', value: item.growthFactor });
|
|
||||||
} else {
|
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_BUILTIN_WIDGET });
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs.push({ type: 'label', name: 'builtinWidget', value: item.builtinWidget });
|
if (!note.getAttributes().find((attr) => attr.attributeId === attrId)) {
|
||||||
} else if (item.targetNoteId) {
|
new BAttribute({
|
||||||
attrs.push({ type: 'relation', name: 'template', value: LBTPL_NOTE_LAUNCHER });
|
attributeId: attrId,
|
||||||
attrs.push({ type: 'relation', name: 'target', value: item.targetNoteId });
|
noteId: note.noteId,
|
||||||
} else {
|
type: attr.type,
|
||||||
throw new Error(`No action defined for launcher ${JSON.stringify(item)}`);
|
name: attr.name,
|
||||||
}
|
value: attr.value,
|
||||||
|
isInheritable: false,
|
||||||
|
}).save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note.type !== item.type) {
|
for (const child of item.children || []) {
|
||||||
// enforce a correct note type
|
checkHiddenSubtreeRecursively(item.id, child);
|
||||||
note.type = item.type;
|
}
|
||||||
note.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branch) {
|
|
||||||
// in case of launchers the branch ID is not preserved and should not be relied upon - launchers which move between
|
|
||||||
// visible and available will change branch since the branch's parent-child relationship is immutable
|
|
||||||
if (item.notePosition !== undefined && branch.notePosition !== item.notePosition) {
|
|
||||||
branch.notePosition = item.notePosition;
|
|
||||||
branch.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.isExpanded !== undefined && branch.isExpanded !== item.isExpanded) {
|
|
||||||
branch.isExpanded = item.isExpanded;
|
|
||||||
branch.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const attr of attrs) {
|
|
||||||
const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name;
|
|
||||||
|
|
||||||
if (!note.getAttributes().find(attr => attr.attributeId === attrId)) {
|
|
||||||
new BAttribute({
|
|
||||||
attributeId: attrId,
|
|
||||||
noteId: note.noteId,
|
|
||||||
type: attr.type,
|
|
||||||
name: attr.name,
|
|
||||||
value: attr.value,
|
|
||||||
isInheritable: false
|
|
||||||
}).save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const child of item.children || []) {
|
|
||||||
checkHiddenSubtreeRecursively(item.id, child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
checkHiddenSubtree,
|
checkHiddenSubtree,
|
||||||
LBTPL_ROOT,
|
LBTPL_ROOT,
|
||||||
LBTPL_BASE,
|
LBTPL_BASE,
|
||||||
LBTPL_COMMAND,
|
LBTPL_COMMAND,
|
||||||
LBTPL_NOTE_LAUNCHER,
|
LBTPL_NOTE_LAUNCHER,
|
||||||
LBTPL_SCRIPT,
|
LBTPL_SCRIPT,
|
||||||
LBTPL_BUILTIN_WIDGET,
|
LBTPL_BUILTIN_WIDGET,
|
||||||
LBTPL_SPACER,
|
LBTPL_SPACER,
|
||||||
LBTPL_CUSTOM_WIDGET
|
LBTPL_CUSTOM_WIDGET,
|
||||||
};
|
};
|
||||||
|
@ -1,83 +1,111 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
|
/>
|
||||||
<title>Login</title>
|
<title>Login</title>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= assetPath %>/images/app-icons/ios/apple-touch-icon.png">
|
<link
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
rel="apple-touch-icon"
|
||||||
</head>
|
sizes="180x180"
|
||||||
<body>
|
href="<%= assetPath %>/images/app-icons/ios/apple-touch-icon.png"
|
||||||
<div class="container">
|
/>
|
||||||
<div class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto" style="padding-top: 25px;">
|
<link rel="shortcut icon" href="favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div
|
||||||
|
class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto"
|
||||||
|
style="padding-top: 25px"
|
||||||
|
>
|
||||||
<h1>Trilium login</h1>
|
<h1>Trilium login</h1>
|
||||||
|
|
||||||
<% if (failedAuth) { %>
|
<% if (failedAuth) { %>
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
Password is incorrect. Please try again.
|
Password is incorrect. Please try again.
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<form action="login" method="POST">
|
<form action="login" method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input id="password" name="password" placeholder="" class="form-control" type="password">
|
<input
|
||||||
</div>
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder=""
|
||||||
|
class="form-control"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<div class="checkbox">
|
<div class="form-group">
|
||||||
<label>
|
<div class="checkbox">
|
||||||
<input id="remember-me" name="rememberMe" value="1" type="checkbox"> Remember me
|
<label>
|
||||||
</label>
|
<input
|
||||||
</div>
|
id="remember-me"
|
||||||
</div>
|
name="rememberMe"
|
||||||
<div class="form-group">
|
value="1"
|
||||||
<button class="btn btn-success">Login</button>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-success">Login</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Required for correct loading of scripts in Electron
|
// Required for correct loading of scripts in Electron
|
||||||
if (typeof module === 'object') {window.module = module; module = undefined;}
|
if (typeof module === "object") {
|
||||||
|
window.module = module;
|
||||||
|
module = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let device;
|
let device;
|
||||||
|
|
||||||
if (window.location.search === '?desktop') {
|
if (window.location.search === "?desktop") {
|
||||||
device = "desktop";
|
device = "desktop";
|
||||||
}
|
} else if (window.location.search === "?mobile") {
|
||||||
else if (window.location.search === '?mobile') {
|
|
||||||
device = "mobile";
|
device = "mobile";
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
device = isMobile() ? "mobile" : "desktop";
|
device = isMobile() ? "mobile" : "desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Setting device cookie to:", device);
|
console.log("Setting device cookie to:", device);
|
||||||
|
|
||||||
setCookie("trilium-device", device);
|
setCookie("trilium-device", device);
|
||||||
|
|
||||||
function setCookie(name, value) {
|
function setCookie(name, value) {
|
||||||
const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000);
|
const date = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000);
|
||||||
const expires = "; expires=" + date.toUTCString();
|
const expires = "; expires=" + date.toUTCString();
|
||||||
|
|
||||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/73731646/944162
|
// https://stackoverflow.com/a/73731646/944162
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
const mQ = matchMedia?.('(pointer:coarse)');
|
const mQ = matchMedia?.("(pointer:coarse)");
|
||||||
if (mQ?.media === '(pointer:coarse)') return !!mQ.matches;
|
if (mQ?.media === "(pointer:coarse)") return !!mQ.matches;
|
||||||
|
|
||||||
if ('orientation' in window) return true;
|
if ("orientation" in window) return true;
|
||||||
|
|
||||||
return /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(navigator.userAgent) ||
|
return (
|
||||||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(navigator.userAgent);
|
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(navigator.userAgent) ||
|
||||||
}
|
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(navigator.userAgent)
|
||||||
</script>
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<link href="<%= assetPath %>/libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link
|
||||||
</body>
|
href="<%= assetPath %>/libraries/bootstrap/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user