From 28df5d4aa2bde2ce440fa86ccec0266f738a6dd5 Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 12 Jan 2022 19:32:23 +0100 Subject: [PATCH] etapi improvements and more tests --- db/schema.sql | 4 +- package-lock.json | 301 ++++++++++-------- package.json | 9 +- src/app.js | 9 +- src/becca/becca.js | 1 - src/becca/entities/attribute.js | 2 +- src/becca/entities/etapi_token.js | 3 + src/etapi/attributes.js | 37 ++- src/etapi/branches.js | 25 +- src/etapi/etapi.openapi.yaml | 17 +- src/etapi/etapi_utils.js | 23 +- src/etapi/notes.js | 30 +- src/etapi/validators.js | 87 ++++- src/public/app/services/load_results.js | 2 +- src/public/stylesheets/calendar.css | 0 src/routes/routes.js | 4 +- src/routes/setup.js | 2 + src/services/auth.js | 15 +- src/services/consistency_checks.js | 5 +- src/services/entity_changes.js | 4 +- src/services/etapi_tokens.js | 2 +- src/services/notes.js | 2 - src/services/options.js | 18 +- src/services/password_encryption.js | 6 +- src/services/tree.js | 4 - src/www | 0 test-etapi/_login.http | 2 +- test-etapi/create-entities.http | 73 ++--- test-etapi/delete-attribute.http | 6 +- test-etapi/delete-cloned-branch.http | 10 +- test-etapi/delete-note-with-all-branches.http | 10 +- test-etapi/get-date-notes.http | 57 +--- test-etapi/other.http | 6 +- test-etapi/patch-attribute.http | 4 +- test-etapi/patch-branch.http | 2 +- test-etapi/patch-note.http | 2 +- 36 files changed, 449 insertions(+), 335 deletions(-) mode change 100755 => 100644 src/public/stylesheets/calendar.css mode change 100755 => 100644 src/www diff --git a/db/schema.sql b/db/schema.sql index d3bf07a78..01a40c554 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIM `noteId` TEXT NOT NULL, type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL, - `title` TEXT, + `title` TEXT NOT NULL, `isProtected` INT NOT NULL DEFAULT 0, `utcDateLastEdited` TEXT NOT NULL, `utcDateCreated` TEXT NOT NULL, @@ -66,7 +66,7 @@ CREATE TABLE IF NOT EXISTS "note_revision_contents" (`noteRevisionId` TEXT NOT N CREATE TABLE IF NOT EXISTS "options" ( name TEXT not null PRIMARY KEY, - value TEXT, + value TEXT not null, isSynced INTEGER default 0 not null, utcDateModified TEXT NOT NULL ); diff --git a/package-lock.json b/package-lock.json index 451d765b0..133c67f70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "trilium", - "version": "0.49.3", + "version": "0.49.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -546,9 +546,9 @@ } }, "@npmcli/fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz", - "integrity": "sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.0.tgz", + "integrity": "sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==", "dev": true, "requires": { "@gar/promisify": "^1.0.1", @@ -989,9 +989,9 @@ } }, "agentkeepalive": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz", - "integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", + "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", "dev": true, "requires": { "debug": "^4.1.0", @@ -1466,33 +1466,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" }, - "body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", - "requires": { - "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - } - } - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1829,11 +1802,6 @@ } } }, - "bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" - }, "cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -2224,18 +2192,18 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, "colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2909,9 +2877,9 @@ } }, "electron": { - "version": "16.0.6", - "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.6.tgz", - "integrity": "sha512-Xs9dYLYhcJf3wXn8m2gDqFTb1L862KEhMxOx9swfFBHj6NoUPPtUgw/RyPQ0tXN1XPxG9vnBkoI0BdcKwrLKuQ==", + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.7.tgz", + "integrity": "sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==", "dev": true, "requires": { "@electron/get": "^1.13.0", @@ -2937,9 +2905,9 @@ } }, "@types/node": { - "version": "14.18.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.1.tgz", - "integrity": "sha512-fTFWOFrgAkj737w1o0HLTIgisgYHnsZfeiqhG1Ltrf/iJjudEbUwetQAsfrtVE49JGwvpEzQR+EbMkIqG4227g==", + "version": "14.18.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.5.tgz", + "integrity": "sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==", "dev": true }, "boolean": { @@ -3554,18 +3522,18 @@ } }, "electron-rebuild": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.5.tgz", - "integrity": "sha512-U9dKi10V9w/BdIVB8a8dTKYLK3Q1d2WZ+Yo5qfM3XX/O4jI7KpnwgvWgGoVv0jTWPC2NlebF00ffWS/8NfUAtA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-3.2.7.tgz", + "integrity": "sha512-WvaW1EgRinDQ61khHFZfx30rkPQG5ItaOT0wrI7iJv9A3SbghriQGfZQfHZs25fWLBe6/vkv05LOqg6aDw6Wzw==", "dev": true, "requires": { "@malept/cross-spawn-promise": "^2.0.0", - "colors": "^1.3.3", + "chalk": "^4.0.0", "debug": "^4.1.1", "detect-libc": "^1.0.3", "fs-extra": "^10.0.0", "got": "^11.7.0", - "lzma-native": "^8.0.1", + "lzma-native": "^8.0.5", "node-abi": "^3.0.0", "node-api-version": "^0.1.4", "node-gyp": "^8.4.0", @@ -3585,9 +3553,9 @@ } }, "@sindresorhus/is": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", - "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.1.tgz", + "integrity": "sha512-BrzrgtaqEre0qfvI8sMTaEvx+bayuhPmfe2rfeUGPPHYr/PLxCOqkOe4TQTDPb+qcqgNcsAtXV/Ew74mcDIE8w==", "dev": true }, "@szmarczak/http-timer": { @@ -3599,6 +3567,15 @@ "defer-to-connect": "^2.0.0" } }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cacheable-request": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", @@ -3614,6 +3591,31 @@ "responselike": "^2.0.0" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -3639,9 +3641,9 @@ } }, "got": { - "version": "11.8.2", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", - "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "version": "11.8.3", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", + "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", "dev": true, "requires": { "@sindresorhus/is": "^4.0.0", @@ -3649,7 +3651,7 @@ "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.1", + "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", @@ -3664,9 +3666,9 @@ "dev": true }, "keyv": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", - "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.5.tgz", + "integrity": "sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -4092,9 +4094,9 @@ "integrity": "sha512-o1JrraDGpMFaPtkuvtZ4cIBC/xuJn90KBGlxRrm3FxcfER1bPaBnBsTnypF65p+CMTXul2KrZodb3Vv3MScB4A==" }, "express-rate-limit": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.0.5.tgz", - "integrity": "sha512-EB1mRTrzyyPfEsQZIQFXocd8NKZoDZbEwrtbdgkc20Yed6oYg02Xfjza2HHPI/0orp54BrFeHeT92ICB9ydokw==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.1.0.tgz", + "integrity": "sha512-OWyJUDYVq/hRxGU3ufTnXDer5bRBwFiq5D35ZSZ9B2EHdjulWO4bwrbg+iIrapodDZse/35obeOj7igRHuP3Zw==" }, "express-session": { "version": "1.17.2", @@ -4770,35 +4772,6 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - } - } - }, "http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -5183,9 +5156,9 @@ } }, "jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.1.tgz", - "integrity": "sha512-NAf9b80ie0pAXLW2l+Fxc8s0Q6SjVgi81jOyHJRQuZ+fPjbVAnXNfN2nIwf5yoRjoSTROyRiETjr9Cr+nNBTVw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.2.tgz", + "integrity": "sha512-YsrgxJQEggxzByYe4j68eQLOiQeSrPDYGv4sHhGBp3c6HHdq+uPXeAQ73kOAQpdLZ3/0zN7x/TZTloqeE1/qIA==", "dev": true, "requires": { "glob": "^7.1.6", @@ -5678,9 +5651,9 @@ } }, "lzma-native": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.1.tgz", - "integrity": "sha512-Ryr9X3yDVZhRYOxR8QhUBCNe6GdEfy9BvFDIFtUvEkocvSvnrYt9lRm6FR1z0eQn0QSMenrgrDIJRMgUf9zsKQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-8.0.5.tgz", + "integrity": "sha512-lEkBBmePuYBycdlK8ul/sKQuZW47FMxAdjeTgDZLY4duX5Q067JJLUueyzN0wCAw6t2Y6YXCcAqHA5A1jQ9ttQ==", "dev": true, "requires": { "node-addon-api": "^3.1.0", @@ -6050,9 +6023,9 @@ } }, "node-gyp": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.0.tgz", - "integrity": "sha512-Bi/oCm5bH6F+FmzfUxJpPaxMEyIhszULGR3TprmTeku8/dMFcdTcypk120NeZqEt54r1BrgEKtm2jJiuIKE28Q==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "dev": true, "requires": { "env-paths": "^2.2.0", @@ -6060,11 +6033,95 @@ "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", - "npmlog": "^4.1.2", + "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "npmlog": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "dev": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "node-gyp-build": { @@ -6739,11 +6796,6 @@ "escape-goat": "^2.0.0" } }, - "qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" - }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -6774,17 +6826,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, - "raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "requires": { - "bytes": "3.1.1", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -7452,9 +7493,9 @@ } }, "socks-proxy-agent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz", - "integrity": "sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", "dev": true, "requires": { "agent-base": "^6.0.2", @@ -7463,9 +7504,9 @@ }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" diff --git a/package.json b/package.json index 3391ad2b9..27ac5898f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "async-mutex": "0.3.2", "axios": "0.24.0", "better-sqlite3": "7.4.5", - "body-parser": "1.19.1", "chokidar": "3.5.2", "cls-hooked": "4.2.2", "commonmark": "0.30.0", @@ -43,7 +42,7 @@ "@electron/remote": "2.0.1", "express": "4.17.2", "express-partial-content": "^1.0.2", - "express-rate-limit": "6.0.5", + "express-rate-limit": "6.1.0", "express-session": "1.17.2", "fs-extra": "10.0.0", "helmet": "5.0.1", @@ -83,12 +82,12 @@ }, "devDependencies": { "cross-env": "7.0.3", - "electron": "16.0.6", + "electron": "16.0.7", "electron-builder": "22.14.5", "electron-packager": "15.4.0", - "electron-rebuild": "3.2.6", + "electron-rebuild": "3.2.7", "esm": "3.2.25", - "jasmine": "4.0.1", + "jasmine": "4.0.2", "jsdoc": "3.6.7", "lorem-ipsum": "2.0.4", "rcedit": "3.0.1", diff --git a/src/app.js b/src/app.js index 3e104b93e..6ba7c1b78 100644 --- a/src/app.js +++ b/src/app.js @@ -3,7 +3,6 @@ const express = require('express'); const path = require('path'); const favicon = require('serve-favicon'); const cookieParser = require('cookie-parser'); -const bodyParser = require('body-parser'); const helmet = require('helmet'); const session = require('express-session'); const FileStore = require('session-file-store')(session); @@ -20,13 +19,13 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); app.use(helmet({ - hidePoweredBy: false, // deactivated because electron 4.0 crashes on this right after startup + hidePoweredBy: false, // errors out in electron contentSecurityPolicy: false })); -app.use(bodyParser.text({limit: '500mb'})); -app.use(bodyParser.json({limit: '500mb'})); -app.use(bodyParser.urlencoded({extended: false})); +app.use(express.text({limit: '500mb'})); +app.use(express.json({limit: '500mb'})); +app.use(express.urlencoded({extended: false})); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/libraries', express.static(path.join(__dirname, '..', 'libraries'))); diff --git a/src/becca/becca.js b/src/becca/becca.js index 15ccb0f5c..97d3dd9b0 100644 --- a/src/becca/becca.js +++ b/src/becca/becca.js @@ -2,7 +2,6 @@ const sql = require("../services/sql"); const NoteSet = require("../services/search/note_set"); -const EtapiToken = require("./entities/etapi_token"); /** * Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca. diff --git a/src/becca/entities/attribute.js b/src/becca/entities/attribute.js index bf7b89a07..5cdc23231 100644 --- a/src/becca/entities/attribute.js +++ b/src/becca/entities/attribute.js @@ -51,7 +51,7 @@ class Attribute extends AbstractEntity { /** @type {int} */ this.position = position; /** @type {string} */ - this.value = value; + this.value = value || ""; /** @type {boolean} */ this.isInheritable = !!isInheritable; /** @type {string} */ diff --git a/src/becca/entities/etapi_token.js b/src/becca/entities/etapi_token.js index 4d4c6de4c..c25f35d60 100644 --- a/src/becca/entities/etapi_token.js +++ b/src/becca/entities/etapi_token.js @@ -9,6 +9,9 @@ const sql = require("../../services/sql.js"); * Used by: * - Trilium Sender * - ETAPI clients + * + * The format user is presented with is "_". This is also called "authToken" to distinguish it + * from tokenHash and token. */ class EtapiToken extends AbstractEntity { static get entityName() { return "etapi_tokens"; } diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index 57c517a64..4b5edcc89 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -2,7 +2,7 @@ const becca = require("../becca/becca"); const eu = require("./etapi_utils"); const mappers = require("./mappers"); const attributeService = require("../services/attributes"); -const validators = require("./validators"); +const v = require("./validators"); function register(router) { eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => { @@ -11,18 +11,23 @@ function register(router) { res.json(mappers.mapAttributeToPojo(attribute)); }); + const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE = { + 'attributeId': [v.mandatory, v.notNull, v.isValidEntityId], + 'noteId': [v.mandatory, v.notNull, v.isNoteId], + 'type': [v.mandatory, v.notNull, v.isAttributeType], + 'name': [v.mandatory, v.notNull, v.isString], + 'value': [v.notNull, v.isString], + 'isInheritable': [v.notNull, v.isBoolean] + }; + eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => { - const params = req.body; - - eu.getAndCheckNote(params.noteId); - - if (params.type === 'relation') { - eu.getAndCheckNote(params.value); - } - - if (params.type !== 'relation' && params.type !== 'label') { - throw new eu.EtapiError(400, eu.GENERIC_CODE, `Only "relation" and "label" are supported attribute types, "${params.type}" given.`); + if (req.body.type === 'relation') { + eu.getAndCheckNote(req.body.value); } + + const params = {}; + + eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE); try { const attr = attributeService.createAttribute(params); @@ -30,18 +35,24 @@ function register(router) { res.json(mappers.mapAttributeToPojo(attr)); } catch (e) { - throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message); + throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message); } }); const ALLOWED_PROPERTIES_FOR_PATCH = { - 'value': validators.isString + 'value': [v.notNull, v.isString] }; eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => { const attribute = eu.getAndCheckAttribute(req.params.attributeId); + if (attribute.type === 'relation') { + eu.getAndCheckNote(req.body.value); + } + eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH); + + attribute.save(); res.json(mappers.mapAttributeToPojo(attribute)); }); diff --git a/src/etapi/branches.js b/src/etapi/branches.js index f3176c05d..589165478 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -5,7 +5,7 @@ const Branch = require("../becca/entities/branch"); const noteService = require("../services/notes"); const TaskContext = require("../services/task_context"); const entityChangesService = require("../services/entity_changes"); -const validators = require("./validators"); +const v = require("./validators"); function register(router) { eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => { @@ -14,11 +14,19 @@ function register(router) { res.json(mappers.mapBranchToPojo(branch)); }); + const ALLOWED_PROPERTIES_FOR_CREATE_BRANCH = { + 'branchId': [v.mandatory, v.notNull, v.isValidEntityId], + 'noteId': [v.mandatory, v.notNull, v.isNoteId], + 'parentNoteId': [v.mandatory, v.notNull, v.isNoteId], + 'notePosition': [v.notNull, v.isInteger], + 'prefix': [v.isString], + 'isExpanded': [v.notNull, v.isBoolean] + }; + eu.route(router, 'post' ,'/etapi/branches', (req, res, next) => { - const params = req.body; - - eu.getAndCheckNote(params.noteId); - eu.getAndCheckNote(params.parentNoteId); + const params = {}; + + eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH); const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId); @@ -41,15 +49,16 @@ function register(router) { }); const ALLOWED_PROPERTIES_FOR_PATCH = { - 'notePosition': validators.isInteger, - 'prefix': validators.isStringOrNull, - 'isExpanded': validators.isBoolean + 'notePosition': [v.notNull, v.isInteger], + 'prefix': [v.isString], + 'isExpanded': [v.notNull, v.isBoolean] }; eu.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => { const branch = eu.getAndCheckBranch(req.params.branchId); eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH); + branch.save(); res.json(mappers.mapBranchToPojo(branch)); }); diff --git a/src/etapi/etapi.openapi.yaml b/src/etapi/etapi.openapi.yaml index 1a175b4dc..7cdba426f 100644 --- a/src/etapi/etapi.openapi.yaml +++ b/src/etapi/etapi.openapi.yaml @@ -591,13 +591,15 @@ components: type: object required: - parentNoteId - - type - title + - type - content properties: parentNoteId: $ref: '#/components/schemas/EntityId' description: Note ID of the parent note in the tree + title: + type: string type: type: string enum: @@ -613,8 +615,6 @@ components: type: string description: this needs to be specified only for note types 'code', 'file', 'image'. example: application/json - title: - type: string content: type: string notePosition: @@ -628,6 +628,9 @@ components: Prefix is branch (placement) specific title prefix for the note. Let's say you have your note placed into two different places in the tree, but you want to change the title a bit in one of the placements. For this you can use prefix. + isExpanded: + type: boolean + description: true if this note (as a folder) should appear expanded noteId: $ref: '#/components/schemas/EntityId' description: DON'T specify unless you want to force a specific noteId @@ -644,7 +647,7 @@ components: type: string type: type: string - enum: [text, code, book, image, file, mermaid, relation-map, render, search, note-map] + enum: [text, code, render, file, image, search, relation-map, book, note-map, mermaid] mime: type: string isProtected: @@ -686,7 +689,6 @@ components: properties: branchId: $ref: '#/components/schemas/EntityId' - readOnly: true noteId: $ref: '#/components/schemas/EntityId' readOnly: true @@ -700,7 +702,7 @@ components: notePosition: type: integer format: int32 - isExanded: + isExpanded: type: boolean utcDateModified: $ref: '#/components/schemas/UtcDateTime' @@ -713,7 +715,6 @@ components: properties: attributeId: $ref: '#/components/schemas/EntityId' - readOnly: true noteId: $ref: '#/components/schemas/EntityId' readOnly: true @@ -753,7 +754,7 @@ components: description: debugging info on parsing the search query enabled with &debug=true parameter EntityId: type: string - pattern: '[a-zA-Z0-9]{4,12}' + pattern: '[a-zA-Z0-9]{4,32}' example: evnnmvHTCgIn EntityIdList: type: array diff --git a/src/etapi/etapi_utils.js b/src/etapi/etapi_utils.js index d2ee90650..afda3f6d0 100644 --- a/src/etapi/etapi_utils.js +++ b/src/etapi/etapi_utils.js @@ -103,27 +103,26 @@ function getAndCheckAttribute(attributeId) { } } -function validateAndPatch(entity, props, allowedProperties) { - for (const key of Object.keys(props)) { +function validateAndPatch(target, source, allowedProperties) { + for (const key of Object.keys(source)) { if (!(key in allowedProperties)) { - throw new EtapiError(400, "PROPERTY_NOT_ALLOWED_FOR_PATCH", `Property '${key}' is not allowed for PATCH.`); + throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for PATCH.`); } else { - const validator = allowedProperties[key]; - const validationResult = validator(props[key]); - - if (validationResult) { - throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}`); + for (const validator of allowedProperties[key]) { + const validationResult = validator(source[key]); + + if (validationResult) { + throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}`); + } } } } // validation passed, let's patch - for (const propName of Object.keys(props)) { - entity[propName] = props[propName]; + for (const propName of Object.keys(source)) { + target[propName] = source[propName]; } - - entity.save(); } module.exports = { diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 73e062697..1aa66d135 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -4,7 +4,7 @@ const eu = require("./etapi_utils"); const mappers = require("./mappers"); const noteService = require("../services/notes"); const TaskContext = require("../services/task_context"); -const validators = require("./validators"); +const v = require("./validators"); const searchService = require("../services/search/services/search"); const SearchContext = require("../services/search/search_context"); @@ -39,10 +39,23 @@ function register(router) { res.json(mappers.mapNoteToPojo(note)); }); + const ALLOWED_PROPERTIES_FOR_CREATE_NOTE = { + 'parentNoteId': [v.mandatory, v.notNull, v.isNoteId], + 'title': [v.mandatory, v.notNull, v.isString], + 'type': [v.mandatory, v.notNull, v.isNoteType], + 'mime': [v.notNull, v.isString], + 'content': [v.notNull, v.isString], + 'notePosition': [v.notNull, v.isInteger], + 'prefix': [v.notNull, v.isInteger], + 'isExpanded': [v.notNull, v.isBoolean], + 'noteId': [v.notNull, v.isValidEntityId], + 'branchId': [v.notNull, v.isValidEntityId], + }; + eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => { - const params = req.body; - - eu.getAndCheckNote(params.parentNoteId); + const params = {}; + + eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE); try { const resp = noteService.createNewNote(params); @@ -53,14 +66,14 @@ function register(router) { }); } catch (e) { - return eu.sendError(res, 400, eu.GENERIC_CODE, e.message); + return eu.sendError(res, 500, eu.GENERIC_CODE, e.message); } }); const ALLOWED_PROPERTIES_FOR_PATCH = { - 'title': validators.isString, - 'type': validators.isString, - 'mime': validators.isString + 'title': [v.notNull, v.isString], + 'type': [v.notNull, v.isString], + 'mime': [v.notNull, v.isString] }; eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { @@ -71,6 +84,7 @@ function register(router) { } eu.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH); + note.save(); res.json(mappers.mapNoteToPojo(note)); }); diff --git a/src/etapi/validators.js b/src/etapi/validators.js index 201e8d7c0..73f8ac311 100644 --- a/src/etapi/validators.js +++ b/src/etapi/validators.js @@ -1,30 +1,101 @@ +const noteTypes = require("../services/note_types"); + +function mandatory(obj) { + if (obj === undefined ) { + return `mandatory, but not set`; + } +} + +function notNull(obj) { + if (obj === null) { + return `cannot be null`; + } +} + function isString(obj) { + if (obj === undefined || obj === null) { + return; + } + if (typeof obj !== 'string') { return `'${obj}' is not a string`; } } -function isStringOrNull(obj) { - if (obj) { - return isString(obj); - } -} - function isBoolean(obj) { + if (obj === undefined || obj === null) { + return; + } + if (typeof obj !== 'boolean') { return `'${obj}' is not a boolean`; } } function isInteger(obj) { + if (obj === undefined || obj === null) { + return; + } + if (!Number.isInteger(obj)) { return `'${obj}' is not an integer`; } } +function isNoteId(obj) { + if (obj === undefined || obj === null) { + return; + } + + const becca = require('../becca/becca'); + + if (typeof obj !== 'string') { + return `'${obj}' is not a valid noteId`; + } + + if (!(obj in becca.notes)) { + return `Note '${obj}' does not exist`; + } +} + +function isNoteType(obj) { + if (obj === undefined || obj === null) { + return; + } + + if (!noteTypes.includes(obj)) { + return `'${obj}' is not a valid note type, allowed types are: ` + noteTypes.join(", "); + } +} + +function isAttributeType(obj) { + if (obj === undefined || obj === null) { + return; + } + + if (!['label', 'relation'].includes(obj)) { + return `'${obj}' is not a valid attribute type, allowed types are: label, relation`; + } +} + +function isValidEntityId(obj) { + if (obj === undefined || obj === null) { + return; + } + + if (typeof obj !== 'string' || !/^[A-Za-z0-9]{4,32}$/.test(obj)) { + return `'${obj}' is not a valid entityId. Only alphanumeric characters are allowed of length 4 to 32.`; + } +} + module.exports = { + mandatory, + notNull, isString, - isStringOrNull, isBoolean, - isInteger + isInteger, + isNoteId, + isNoteType, + isAttributeType, + isValidEntityId }; \ No newline at end of file diff --git a/src/public/app/services/load_results.js b/src/public/app/services/load_results.js index 036bc0e06..bf5f712ba 100644 --- a/src/public/app/services/load_results.js +++ b/src/public/app/services/load_results.js @@ -91,7 +91,7 @@ export default class LoadResults { } const componentIds = this.noteIdToComponentId[noteId]; - return componentIds && !!componentIds.find(sId => sId !== componentId); + return componentIds && componentIds.find(sId => sId !== componentId) !== undefined; } addNoteContent(noteId, componentId) { diff --git a/src/public/stylesheets/calendar.css b/src/public/stylesheets/calendar.css old mode 100755 new mode 100644 diff --git a/src/routes/routes.js b/src/routes/routes.js index 31a84de8d..1e7eb5674 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -190,7 +190,7 @@ const uploadMiddleware = multer.single('upload'); function register(app) { route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index); route(GET, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); - route(GET, '/set-password', [auth.checkAppInitialized], loginRoute.setPasswordPage); + route(GET, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage); const loginRateLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes @@ -199,7 +199,7 @@ function register(app) { route(POST, '/login', [loginRateLimiter], loginRoute.login); route(POST, '/logout', [csrfMiddleware, auth.checkAuth], loginRoute.logout); - route(POST, '/set-password', [auth.checkAppInitialized], loginRoute.setPassword); + route(POST, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPassword); route(GET, '/setup', [], setupRoute.setupPage); apiRoute(GET, '/api/tree', treeApiRoute.getTree); diff --git a/src/routes/setup.js b/src/routes/setup.js index fbed06491..13718dedd 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -14,6 +14,8 @@ function setupPage(req, res) { else { res.redirect('/'); } + + return; } // we got here because DB is not completely initialized so if schema exists diff --git a/src/services/auth.js b/src/services/auth.js index 73c693219..26e5aecea 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -15,11 +15,7 @@ function checkAuth(req, res, next) { res.redirect("setup"); } else if (!req.session.loggedIn && !utils.isElectron() && !noAuthentication) { - if (passwordService.isPasswordSet()) { - res.redirect("login"); - } else { - res.redirect("set-password"); - } + res.redirect("login"); } else { next(); @@ -63,6 +59,14 @@ function checkPasswordSet(req, res, next) { } } +function checkPasswordNotSet(req, res, next) { + if (!utils.isElectron() && passwordService.isPasswordSet()) { + res.redirect("login"); + } else { + next(); + } +} + function checkAppNotInitialized(req, res, next) { if (sqlInit.isDbInitialized()) { reject(req, res, "App already initialized."); @@ -111,6 +115,7 @@ module.exports = { checkApiAuth, checkAppInitialized, checkPasswordSet, + checkPasswordNotSet, checkAppNotInitialized, checkApiAuthOrElectron, checkEtapiToken, diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index b7ba1fff6..3a0a4028d 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -13,6 +13,7 @@ const attributeService = require('./attributes'); const noteRevisionService = require('./note_revisions'); const becca = require("../becca/becca"); const utils = require("../services/utils"); +const noteTypes = require("../services/note_types"); class ConsistencyChecks { constructor(autoFix) { @@ -281,11 +282,13 @@ class ConsistencyChecks { } findLogicIssues() { + const noteTypesStr = noteTypes.map(nt => `'${nt}'`).join(", "); + this.findAndFixIssues(` SELECT noteId, type FROM notes WHERE isDeleted = 0 - AND type NOT IN ('text', 'code', 'render', 'file', 'image', 'search', 'relation-map', 'book', 'note-map', 'mermaid')`, + AND type NOT IN (${noteTypesStr})`, ({noteId, type}) => { if (this.autoFix) { const note = becca.getNote(noteId); diff --git a/src/services/entity_changes.js b/src/services/entity_changes.js index 7e040b269..209e0c7f5 100644 --- a/src/services/entity_changes.js +++ b/src/services/entity_changes.js @@ -23,7 +23,7 @@ function addEntityChange(origEntityChange) { ec.changeId = utils.randomString(12); } - ec.componentId = ec.componentId || cls.getComponentId() || ""; + ec.componentId = ec.componentId || cls.getComponentId() || "NA"; // NA = not available ec.instanceId = ec.instanceId || instanceId; ec.isSynced = ec.isSynced ? 1 : 0; ec.isErased = ec.isErased ? 1 : 0; @@ -43,7 +43,7 @@ function addNoteReorderingEntityChange(parentNoteId, componentId) { utcDateChanged: dateUtils.utcNowDateTime(), isSynced: true, componentId, - instanceId: instanceId + instanceId }); const eventService = require('./events'); diff --git a/src/services/etapi_tokens.js b/src/services/etapi_tokens.js index 145e6d089..abc8c09c0 100644 --- a/src/services/etapi_tokens.js +++ b/src/services/etapi_tokens.js @@ -12,7 +12,7 @@ function getTokenHash(token) { } function createToken(tokenName) { - const token = utils.randomSecureToken(); + const token = utils.randomSecureToken(32); const tokenHash = getTokenHash(token); const etapiToken = new EtapiToken({ diff --git a/src/services/notes.js b/src/services/notes.js index 304b406dc..a5a733619 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -18,8 +18,6 @@ const Branch = require('../becca/entities/branch'); const Note = require('../becca/entities/note'); const Attribute = require('../becca/entities/attribute'); -// TODO: patch/put note content - function getNewNotePosition(parentNoteId) { const note = becca.notes[parentNoteId]; diff --git a/src/services/options.js b/src/services/options.js index b9abb526b..46c427d9b 100644 --- a/src/services/options.js +++ b/src/services/options.js @@ -1,22 +1,27 @@ const becca = require('../becca/becca'); const sql = require("./sql"); -function getOption(name) { +function getOptionOrNull(name) { let option; if (becca.loaded) { option = becca.getOption(name); - } - else { + } else { // e.g. in initial sync becca is not loaded because DB is not initialized option = sql.getRow("SELECT * FROM options WHERE name = ?", name); } + + return option ? option.value : null; +} - if (!option) { +function getOption(name) { + const val = getOptionOrNull(name); + + if (val === null) { throw new Error(`Option "${name}" doesn't exist`); } - return option.value; + return val; } /** @@ -96,5 +101,6 @@ module.exports = { setOption, createOption, getOptions, - getOptionsMap + getOptionsMap, + getOptionOrNull }; diff --git a/src/services/password_encryption.js b/src/services/password_encryption.js index 87b0e52b1..3c38d4e91 100644 --- a/src/services/password_encryption.js +++ b/src/services/password_encryption.js @@ -6,8 +6,12 @@ const dataEncryptionService = require('./data_encryption'); function verifyPassword(password) { const givenPasswordHash = utils.toBase64(myScryptService.getVerificationHash(password)); - const dbPasswordHash = optionService.getOption('passwordVerificationHash'); + const dbPasswordHash = optionService.getOptionOrNull('passwordVerificationHash'); + if (!dbPasswordHash) { + return false; + } + return givenPasswordHash === dbPasswordHash; } diff --git a/src/services/tree.js b/src/services/tree.js index 65ff0903b..152c351da 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -154,10 +154,6 @@ function sortNotes(parentNoteId, customSortBy = 'title', reverse = false, folder const topAEl = fetchValue(a, 'top'); const topBEl = fetchValue(b, 'top'); - console.log(a.title, topAEl); - console.log(b.title, topBEl); - console.log("comp", compare(topAEl, topBEl) && !reverse); - if (topAEl !== topBEl) { // since "top" should not be reversible, we'll reverse it once more to nullify this effect return compare(topAEl, topBEl) * (reverse ? -1 : 1); diff --git a/src/www b/src/www old mode 100755 new mode 100644 diff --git a/test-etapi/_login.http b/test-etapi/_login.http index c003ec407..1af4bc2de 100644 --- a/test-etapi/_login.http +++ b/test-etapi/_login.http @@ -6,7 +6,7 @@ Content-Type: application/json } > {% - client.assert(response.status === 200, "Response status is not 200"); + client.assert(response.status === 200); client.global.set("authToken", response.body.authToken); %} \ No newline at end of file diff --git a/test-etapi/create-entities.http b/test-etapi/create-entities.http index d70226c87..a01555a9e 100644 --- a/test-etapi/create-entities.http +++ b/test-etapi/create-entities.http @@ -3,18 +3,20 @@ Authorization: {{authToken}} Content-Type: application/json { + "noteId": "forcedId{{$randomInt}}", + "branchId": "forcedId{{$randomInt}}", "parentNoteId": "root", "title": "Hello", "type": "text", "content": "Hi there!" } -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.note.title == "Hello"); - client.assert(response.body.branch.parentNoteId == "root"); - }); +> {% + client.assert(response.status === 200); + client.assert(response.body.note.noteId.startsWith("forcedId")); + client.assert(response.body.note.title == "Hello"); + client.assert(response.body.branch.branchId.startsWith("forcedId")); + client.assert(response.body.branch.parentNoteId == "root"); client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId); @@ -29,15 +31,14 @@ Authorization: {{authToken}} Content-Type: application/json { + "branchId": "forcedClonedId", "noteId": "{{createdNoteId}}", "parentNoteId": "hidden" } > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.parentNoteId == "hidden"); - }); + client.assert(response.status === 200); + client.assert(response.body.parentNoteId == "hidden"); client.global.set("clonedBranchId", response.body.branchId); @@ -50,14 +51,12 @@ GET {{triliumHost}}/etapi/notes/{{createdNoteId}} Authorization: {{authToken}} > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.noteId == client.global.get("createdNoteId")); - client.assert(response.body.title == "Hello"); - // order is not defined and may fail in the future - client.assert(response.body.parentBranchIds[0] == client.global.get("clonedBranchId")) - client.assert(response.body.parentBranchIds[1] == client.global.get("createdBranchId")); - }); + client.assert(response.status === 200); + client.assert(response.body.noteId == client.global.get("createdNoteId")); + client.assert(response.body.title == "Hello"); + // order is not defined and may fail in the future + client.assert(response.body.parentBranchIds[0] == client.global.get("clonedBranchId")) + client.assert(response.body.parentBranchIds[1] == client.global.get("createdBranchId")); %} ### @@ -66,10 +65,8 @@ GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content Authorization: {{authToken}} > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body == "Hi there!"); - }); + client.assert(response.status === 200); + client.assert(response.body == "Hi there!"); %} ### @@ -78,11 +75,9 @@ GET {{triliumHost}}/etapi/branches/{{createdBranchId}} Authorization: {{authToken}} > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.branchId == client.global.get("createdBranchId")); - client.assert(response.body.parentNoteId == "root"); - }); + client.assert(response.status === 200); + client.assert(response.body.branchId == client.global.get("createdBranchId")); + client.assert(response.body.parentNoteId == "root"); %} ### @@ -91,11 +86,9 @@ GET {{triliumHost}}/etapi/branches/{{clonedBranchId}} Authorization: {{authToken}} > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.branchId == client.global.get("clonedBranchId")); - client.assert(response.body.parentNoteId == "hidden"); - }); + client.assert(response.status === 200); + client.assert(response.body.branchId == client.global.get("clonedBranchId")); + client.assert(response.body.parentNoteId == "hidden"); %} ### @@ -105,19 +98,17 @@ Content-Type: application/json Authorization: {{authToken}} { + "attributeId": "forcedAttributeId{{$randomInt}}", "noteId": "{{createdNoteId}}", "type": "label", "name": "mylabel", "value": "val", - "isInheritable": "true" + "isInheritable": true } > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); - - client.log(`Created attribute ` + response.body.attributeId); + client.assert(response.status === 200); + client.assert(response.body.attributeId.startsWith("forcedAttributeId")); client.global.set("createdAttributeId", response.body.attributeId); %} @@ -128,8 +119,6 @@ GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}} Authorization: {{authToken}} > {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - client.assert(response.body.attributeId == client.global.get("createdAttributeId")); - }); + client.assert(response.status === 200); + client.assert(response.body.attributeId == client.global.get("createdAttributeId")); %} \ No newline at end of file diff --git a/test-etapi/delete-attribute.http b/test-etapi/delete-attribute.http index e6bcdd2af..7433715e8 100644 --- a/test-etapi/delete-attribute.http +++ b/test-etapi/delete-attribute.http @@ -25,7 +25,7 @@ Content-Type: application/json "type": "label", "name": "mylabel", "value": "val", - "isInheritable": "true" + "isInheritable": true } > {% client.global.set("createdAttributeId", response.body.attributeId); %} @@ -35,14 +35,14 @@ Content-Type: application/json GET {{triliumHost}}/etapi/notes/{{createdNoteId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/branches/{{createdBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### diff --git a/test-etapi/delete-cloned-branch.http b/test-etapi/delete-cloned-branch.http index a71317c20..d82d6f777 100644 --- a/test-etapi/delete-cloned-branch.http +++ b/test-etapi/delete-cloned-branch.http @@ -32,21 +32,21 @@ Content-Type: application/json GET {{triliumHost}}/etapi/notes/{{createdNoteId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/branches/{{createdBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/branches/{{clonedBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### @@ -77,11 +77,11 @@ Authorization: {{authToken}} GET {{triliumHost}}/etapi/branches/{{clonedBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/notes/{{createdNoteId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} diff --git a/test-etapi/delete-note-with-all-branches.http b/test-etapi/delete-note-with-all-branches.http index ab9837ea2..f5b7fe761 100644 --- a/test-etapi/delete-note-with-all-branches.http +++ b/test-etapi/delete-note-with-all-branches.http @@ -25,7 +25,7 @@ Content-Type: application/json "type": "label", "name": "mylabel", "value": "val", - "isInheritable": "true" + "isInheritable": true } > {% client.global.set("createdAttributeId", response.body.attributeId); %} @@ -48,28 +48,28 @@ Content-Type: application/json GET {{triliumHost}}/etapi/notes/{{createdNoteId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/branches/{{createdBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/branches/{{clonedBranchId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/attributes/{{createdAttributeId}} Authorization: {{authToken}} -> {% client.assert(response.status === 200, "Response status is not 200"); %} +> {% client.assert(response.status === 200); %} ### diff --git a/test-etapi/get-date-notes.http b/test-etapi/get-date-notes.http index 4bac485f8..19f0b4fc9 100644 --- a/test-etapi/get-date-notes.http +++ b/test-etapi/get-date-notes.http @@ -1,22 +1,14 @@ GET {{triliumHost}}/etapi/inbox/2022-01-01 Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} +> {% client.assert(response.status === 200); %} ### GET {{triliumHost}}/etapi/calendar/days/2022-01-01 Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} +> {% client.assert(response.status === 200); %} ### @@ -24,10 +16,8 @@ GET {{triliumHost}}/etapi/calendar/days/2022-1 Authorization: {{authToken}} > {% - client.test("Correct error handling", function() { - client.assert(response.status === 400, "Response status is not 400"); - client.assert(response.body.code === "DATE_INVALID"); - }); + client.assert(response.status === 400); + client.assert(response.body.code === "DATE_INVALID"); %} ### @@ -35,11 +25,7 @@ Authorization: {{authToken}} GET {{triliumHost}}/etapi/calendar/weeks/2022-01-01 Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} +> {% client.assert(response.status === 200); %} ### @@ -47,10 +33,8 @@ GET {{triliumHost}}/etapi/calendar/weeks/2022-1 Authorization: {{authToken}} > {% - client.test("Correct error handling", function() { - client.assert(response.status === 400, "Response status is not 400"); - client.assert(response.body.code === "DATE_INVALID"); - }); + client.assert(response.status === 400); + client.assert(response.body.code === "DATE_INVALID"); %} ### @@ -58,11 +42,7 @@ Authorization: {{authToken}} GET {{triliumHost}}/etapi/calendar/months/2022-01 Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} +> {% client.assert(response.status === 200); %} ### @@ -70,10 +50,8 @@ GET {{triliumHost}}/etapi/calendar/months/2022-1 Authorization: {{authToken}} > {% - client.test("Correct error handling", function() { - client.assert(response.status === 400, "Response status is not 400"); - client.assert(response.body.code === "MONTH_INVALID"); - }); + client.assert(response.status === 400); + client.assert(response.body.code === "MONTH_INVALID"); %} ### @@ -81,11 +59,7 @@ Authorization: {{authToken}} GET {{triliumHost}}/etapi/calendar/years/2022 Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} +> {% client.assert(response.status === 200); %} ### @@ -93,11 +67,6 @@ GET {{triliumHost}}/etapi/calendar/years/202 Authorization: {{authToken}} > {% - client.test("Correct error handling", function() { - client.assert(response.status === 400, "Response status is not 400"); - client.assert(response.body.code === "YEAR_INVALID"); - }); + client.assert(response.status === 400); + client.assert(response.body.code === "YEAR_INVALID"); %} - -### - diff --git a/test-etapi/other.http b/test-etapi/other.http index 6465dc74c..c3f92fc94 100644 --- a/test-etapi/other.http +++ b/test-etapi/other.http @@ -1,8 +1,4 @@ POST {{triliumHost}}/etapi/refresh-note-ordering/root Authorization: {{authToken}} -> {% - client.test("Request executed successfully", function() { - client.assert(response.status === 200, "Response status is not 200"); - }); -%} \ No newline at end of file +> {% client.assert(response.status === 200); %} \ No newline at end of file diff --git a/test-etapi/patch-attribute.http b/test-etapi/patch-attribute.http index b5658853b..625c19446 100644 --- a/test-etapi/patch-attribute.http +++ b/test-etapi/patch-attribute.http @@ -25,7 +25,7 @@ Content-Type: application/json "type": "label", "name": "mylabel", "value": "val", - "isInheritable": "true" + "isInheritable": true } > {% client.global.set("createdAttributeId", response.body.attributeId); %} @@ -61,7 +61,7 @@ Content-Type: application/json > {% client.assert(response.status === 400); - client.assert(response.body.code == "PROPERTY_NOT_ALLOWED_FOR_PATCH"); + client.assert(response.body.code == "PROPERTY_NOT_ALLOWED"); %} ### diff --git a/test-etapi/patch-branch.http b/test-etapi/patch-branch.http index d265b148a..48116120c 100644 --- a/test-etapi/patch-branch.http +++ b/test-etapi/patch-branch.http @@ -47,7 +47,7 @@ Content-Type: application/json > {% client.assert(response.status === 400); - client.assert(response.body.code == "PROPERTY_NOT_ALLOWED_FOR_PATCH"); + client.assert(response.body.code == "PROPERTY_NOT_ALLOWED"); %} ### diff --git a/test-etapi/patch-note.http b/test-etapi/patch-note.http index 9c0dedade..a56c5e3b6 100644 --- a/test-etapi/patch-note.http +++ b/test-etapi/patch-note.http @@ -60,7 +60,7 @@ Content-Type: application/json > {% client.assert(response.status === 400); - client.assert(response.body.code == "PROPERTY_NOT_ALLOWED_FOR_PATCH"); + client.assert(response.body.code == "PROPERTY_NOT_ALLOWED"); %} ###