diff --git a/packages/app-desktop/main-html.js b/packages/app-desktop/main-html.js
index 982cc7e02a..0129e55c6e 100644
--- a/packages/app-desktop/main-html.js
+++ b/packages/app-desktop/main-html.js
@@ -29,6 +29,7 @@ const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService')
const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default;
const React = require('react');
const nodeSqlite = require('sqlite3');
+const nodeSqliteCipher = require('@journeyapps/sqlcipher');
const initLib = require('@joplin/lib/initLib').default;
const pdfJs = require('pdfjs-dist');
require('@sentry/electron/renderer');
@@ -109,6 +110,7 @@ const main = async () => {
appVersion,
electronBridge: bridge(),
nodeSqlite,
+ nodeSqliteCipher,
pdfJs,
});
diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json
index 22a89adbf4..b7d0f86f79 100644
--- a/packages/app-desktop/package.json
+++ b/packages/app-desktop/package.json
@@ -166,6 +166,7 @@
"@joplin/lib": "~3.3",
"@joplin/renderer": "~3.3",
"@joplin/utils": "~3.3",
+ "@journeyapps/sqlcipher": "5.3.1",
"@sentry/electron": "4.24.0",
"@types/mustache": "4.2.5",
"async-mutex": "0.5.0",
diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock
index abe680bbc6..f297736307 100644
--- a/packages/app-mobile/ios/Podfile.lock
+++ b/packages/app-mobile/ios/Podfile.lock
@@ -1701,7 +1701,7 @@ SPEC CHECKSUMS:
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
JoplinRNShareExtension: e158a4b53ee0aa9cd3037a16221dc8adbd6f7860
OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4
- RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
+ RCT-Folly: 5dc73daec3476616d19e8a53f0156176f7b55461
RCTDeprecation: efb313d8126259e9294dc4ee0002f44a6f676aba
RCTRequired: f49ea29cece52aee20db633ae7edc4b271435562
RCTTypeSafety: a11979ff0570d230d74de9f604f7d19692157bc4
diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts
index 074c7f3b6e..932e389d7d 100644
--- a/packages/lib/BaseApplication.ts
+++ b/packages/lib/BaseApplication.ts
@@ -14,7 +14,7 @@ import { createStore, applyMiddleware, Store } from 'redux';
import { defaultState, stateUtils } from './reducer';
import JoplinDatabase from './JoplinDatabase';
import { cancelTimers as folderScreenUtilsCancelTimers, refreshFolders, scheduleRefreshFolders } from './folders-screen-utils';
-const { DatabaseDriverNode } = require('./database-driver-node.js');
+const { DatabaseDriverNode } = require('./database-driver-node-sqlcipher.js');
import BaseModel from './BaseModel';
import Folder from './models/Folder';
import BaseItem from './models/BaseItem';
diff --git a/packages/lib/database-driver-node-sqlcipher.js b/packages/lib/database-driver-node-sqlcipher.js
new file mode 100644
index 0000000000..9134369bc0
--- /dev/null
+++ b/packages/lib/database-driver-node-sqlcipher.js
@@ -0,0 +1,90 @@
+const shim = require('./shim').default;
+const Promise = require('promise');
+
+class DatabaseDriverNode {
+
+ async open(options) {
+ await this.open_(options);
+ await this.exec('PRAGMA key = \'mysecret\'');
+ }
+
+ open_(options) {
+ return new Promise((resolve, reject) => {
+ const sqlite3 = shim.nodeSqliteCipher().verbose();
+
+ this.db_ = new sqlite3.Database(options.name, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, error => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+
+ sqliteErrorToJsError(error, sql = null, params = null) {
+ const msg = [error.toString()];
+ if (sql) msg.push(sql);
+ if (params) msg.push(params);
+ const output = new Error(msg.join(': '));
+ if (error.code) output.code = error.code;
+ return output;
+ }
+
+ selectOne(sql, params = null) {
+ if (!params) params = {};
+ return new Promise((resolve, reject) => {
+ this.db_.get(sql, params, (error, row) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(row);
+ });
+ });
+ }
+
+ loadExtension(path) {
+ return new Promise((resolve, reject) => {
+ this.db_.loadExtension(path, (error) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve();
+ }
+ });
+ });
+ }
+
+ selectAll(sql, params = null) {
+ if (!params) params = {};
+ return new Promise((resolve, reject) => {
+ this.db_.all(sql, params, (error, row) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(row);
+ });
+ });
+ }
+
+ exec(sql, params = null) {
+ if (!params) params = {};
+ return new Promise((resolve, reject) => {
+ this.db_.run(sql, params, error => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+
+ lastInsertId() {
+ throw new Error('NOT IMPLEMENTED');
+ }
+}
+
+module.exports = { DatabaseDriverNode };
diff --git a/packages/lib/jest.setup.js b/packages/lib/jest.setup.js
index 113a1374d2..34c452c7d6 100644
--- a/packages/lib/jest.setup.js
+++ b/packages/lib/jest.setup.js
@@ -2,6 +2,7 @@ const { afterEachCleanUp } = require('./testing/test-utils.js');
const { shimInit } = require('./shim-init-node.js');
const sharp = require('sharp');
const nodeSqlite = require('sqlite3');
+const nodeSqliteCipher = require('@journeyapps/sqlcipher');
const pdfJs = require('pdfjs-dist');
const packageInfo = require('./package.json');
@@ -10,7 +11,7 @@ const React = require('react');
require('../../jest.base-setup.js')();
-shimInit({ sharp, nodeSqlite, pdfJs, React, appVersion: () => packageInfo.version });
+shimInit({ sharp, nodeSqlite, nodeSqliteCipher, pdfJs, React, appVersion: () => packageInfo.version });
global.afterEach(async () => {
await afterEachCleanUp();
diff --git a/packages/lib/package.json b/packages/lib/package.json
index 786ee274fd..a989355bac 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -52,6 +52,7 @@
"@joplin/turndown": "^4.0.79",
"@joplin/turndown-plugin-gfm": "^1.0.61",
"@joplin/utils": "~3.3",
+ "@journeyapps/sqlcipher": "5.3.1",
"@types/nanoid": "3.0.0",
"adm-zip": "0.5.16",
"async-mutex": "0.5.0",
diff --git a/packages/lib/shim-init-node.ts b/packages/lib/shim-init-node.ts
index 0086323eeb..5a15710a08 100644
--- a/packages/lib/shim-init-node.ts
+++ b/packages/lib/shim-init-node.ts
@@ -110,6 +110,8 @@ interface ShimInitOptions {
electronBridge: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
nodeSqlite: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
+ nodeSqliteCipher: any;
pdfJs: typeof pdfJsNamespace;
}
@@ -122,6 +124,7 @@ function shimInit(options: ShimInitOptions = null) {
electronBridge: null,
nodeSqlite: null,
pdfJs: null,
+ nodeSqliteCipher: null,
...options,
};
@@ -131,6 +134,7 @@ function shimInit(options: ShimInitOptions = null) {
const pdfJs = options.pdfJs;
shim.setNodeSqlite(options.nodeSqlite);
+ shim.setNodeSqliteCipher(options.nodeSqliteCipher);
shim.fsDriver = () => {
throw new Error('Not implemented');
diff --git a/packages/lib/shim.ts b/packages/lib/shim.ts
index 68d690a4be..ccea48af01 100644
--- a/packages/lib/shim.ts
+++ b/packages/lib/shim.ts
@@ -76,6 +76,8 @@ let isTestingEnv_ = false;
let react_: typeof React = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
let nodeSqlite_: any = null;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
+let nodeSqliteCipher_: any = null;
const shim = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -480,11 +482,21 @@ const shim = {
nodeSqlite_ = nodeSqlite;
},
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
+ setNodeSqliteCipher: (nodeSqlite: any) => {
+ nodeSqliteCipher_ = nodeSqlite;
+ },
+
nodeSqlite: () => {
if (!nodeSqlite_) throw new Error('Trying to access nodeSqlite before it has been set!!!');
return nodeSqlite_;
},
+ nodeSqliteCipher: () => {
+ if (!nodeSqliteCipher_) throw new Error('Trying to access nodeSqliteCipher before it has been set!!!');
+ return nodeSqliteCipher_;
+ },
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
setReact: (react: any) => {
react_ = react;
diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts
index efa84d3cff..d5366416d8 100644
--- a/packages/lib/testing/test-utils.ts
+++ b/packages/lib/testing/test-utils.ts
@@ -18,7 +18,7 @@ import OneDriveApi from '../onedrive-api';
import SyncTargetOneDrive from '../SyncTargetOneDrive';
import JoplinDatabase from '../JoplinDatabase';
import * as fs from 'fs-extra';
-const { DatabaseDriverNode } = require('../database-driver-node.js');
+const { DatabaseDriverNode } = require('../database-driver-node-sqlcipher.js');
import Folder from '../models/Folder';
import Note from '../models/Note';
import ItemChange from '../models/ItemChange';
diff --git a/packages/tools/cspell/dictionary4.txt b/packages/tools/cspell/dictionary4.txt
index 49aaa01bf9..820ad0d443 100644
--- a/packages/tools/cspell/dictionary4.txt
+++ b/packages/tools/cspell/dictionary4.txt
@@ -172,4 +172,5 @@ sideloading
ggml
Minidump
collapseall
-newfolder
\ No newline at end of file
+newfolder
+sqlcipher
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 5d7ae26fb7..f6b1bed924 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8279,6 +8279,7 @@ __metadata:
"@joplin/renderer": ~3.3
"@joplin/tools": ~3.3
"@joplin/utils": ~3.3
+ "@journeyapps/sqlcipher": 5.3.1
"@playwright/test": 1.45.3
"@sentry/electron": 4.24.0
"@testing-library/react-hooks": 8.0.1
@@ -8604,6 +8605,7 @@ __metadata:
"@joplin/turndown": ^4.0.79
"@joplin/turndown-plugin-gfm": ^1.0.61
"@joplin/utils": ~3.3
+ "@journeyapps/sqlcipher": 5.3.1
"@testing-library/react-hooks": 8.0.1
"@types/adm-zip": 0.5.7
"@types/fs-extra": 11.0.4
@@ -8998,6 +9000,16 @@ __metadata:
languageName: unknown
linkType: soft
+"@journeyapps/sqlcipher@npm:5.3.1":
+ version: 5.3.1
+ resolution: "@journeyapps/sqlcipher@npm:5.3.1"
+ dependencies:
+ "@mapbox/node-pre-gyp": ^1.0.0
+ node-addon-api: ^3.0.0
+ checksum: 3e3dfeb85dffd1bab20f41cb6b38df1dc57baa33266293e55ad883d7d181294e45e8496cd888457689fa9e440d5629712c72124e05ae9ce00fe5175129e2c9db
+ languageName: node
+ linkType: hard
+
"@jridgewell/gen-mapping@npm:^0.3.0":
version: 0.3.3
resolution: "@jridgewell/gen-mapping@npm:0.3.3"
@@ -35378,6 +35390,15 @@ __metadata:
languageName: node
linkType: hard
+"node-addon-api@npm:^3.0.0":
+ version: 3.2.1
+ resolution: "node-addon-api@npm:3.2.1"
+ dependencies:
+ node-gyp: latest
+ checksum: 2369986bb0881ccd9ef6bacdf39550e07e089a9c8ede1cbc5fc7712d8e2faa4d50da0e487e333d4125f8c7a616c730131d1091676c9d499af1d74560756b4a18
+ languageName: node
+ linkType: hard
+
"node-addon-api@npm:^4.2.0, node-addon-api@npm:^4.3.0":
version: 4.3.0
resolution: "node-addon-api@npm:4.3.0"