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"