Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

QueryM, Database Client
Querym, Database Client
Copyright (C) 2023 Visal .In

This program is free software: you can redistribute it and/or modify
Expand All @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

QueryM Copyright (C) 2023 Visal .In
m Copyright (C) 2023 Visal .In
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@
},
"build": {
"protocols": {
"name": "QueryM",
"name": "Querym",
"schemes": [
"querymaster"
]
},
"productName": "QueryM",
"productName": "Querym",
"appId": "com.invisal.querymaster",
"asar": true,
"asarUnpack": "**\\*.{node,dll}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ConnectionStoreItem } from 'drivers/base/SQLLikeConnection';
import { v1 as uuidv1 } from 'uuid';
import { db } from 'renderer/db';

export default class ConnectionListStorage {
export default class ConnectionListLocalStorage {
protected connections: ConnectionStoreItem[] = [];
protected dict: Record<string, ConnectionStoreItem> = {};

Expand Down
98 changes: 98 additions & 0 deletions src/libs/ConnectionListStorage/ConnectionListRemoteStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
ConnectionStoreConfig,
ConnectionStoreItem,
} from 'drivers/base/SQLLikeConnection';
import { QueryDialetType } from 'libs/QueryBuilder';
import RemoteAPI from 'renderer/utils/RemoteAPI';

export default class ConnectionListRemoteStorage {
protected connections: ConnectionStoreItem[] = [];
protected dict: Record<string, ConnectionStoreItem> = {};
protected masterPassword: string;
protected salt: string;
protected api: RemoteAPI;

constructor(api: RemoteAPI, masterPassword: string, salt: string) {
this.api = api;
this.masterPassword = masterPassword;
this.salt = salt;
}

async loadAll() {
const conns = await this.api.getAll();
this.connections = [];

for (const conn of conns.nodes) {
try {
const config: ConnectionStoreConfig = JSON.parse(
await window.electron.decrypt(
conn.content,
this.masterPassword,
this.salt,
),
);

if (config) {
this.connections.push({
config,
id: conn.id,
createdAt: conn.created_at,
lastUsedAt: conn.last_used_at,
name: conn.name,
type: conn.connection_type as QueryDialetType,
});
}
} catch (e) {
console.error(e);
}
}

this.dict = this.connections.reduce(
(acc, cur) => {
acc[cur.id] = cur;
return acc;
},
{} as Record<string, ConnectionStoreItem>,
);
}

get(id: string): ConnectionStoreItem | undefined {
return this.dict[id];
}

getAll(): ConnectionStoreItem[] {
return this.connections;
}

async save(
data: Omit<ConnectionStoreItem, 'id'> & { id?: string },
): Promise<ConnectionStoreItem> {
const r = await this.api.saveConnection(data.id, {
connection_type: data.type,
content: await window.electron.encrypt(
JSON.stringify(data.config),
this.masterPassword,
this.salt,
),
name: data.name,
});

const newData = { ...data, id: r.id };
this.dict[newData.id] = newData;

return newData;
}

async remove(id: string) {
delete this.dict[id];
this.connections = this.connections.filter((conn) => conn.id !== id);
this.api.removeConnection(id);
}

async updateLastUsed(id: string) {
if (this.dict[id]) {
this.dict[id].lastUsedAt = Math.ceil(Date.now() / 1000);
this.api.updateConnectionLastUsed(id);
}
}
}
12 changes: 12 additions & 0 deletions src/libs/ConnectionListStorage/IConnectionListStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ConnectionStoreItem } from 'drivers/base/SQLLikeConnection';

export default abstract class IConnectionListStorage {
abstract loadAll(): Promise<void>;
abstract get(id: string): ConnectionStoreItem | undefined;
abstract getAll(): ConnectionStoreItem[];
abstract save(
data: Omit<ConnectionStoreItem, 'id'> & { id?: string },
): Promise<ConnectionStoreItem>;
abstract remove(id: string): Promise<void>;
abstract updateLastUsed(id: string): Promise<void>;
}
14 changes: 6 additions & 8 deletions src/libs/SqlRunnerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export interface SqlStatementWithAnalyze extends SqlStatement {

export type BeforeAllEventCallback = (
statements: SqlStatementWithAnalyze[],
skipProtection?: boolean
skipProtection?: boolean,
) => Promise<boolean>;

export type BeforeEachEventCallback = (
statements: SqlStatementWithAnalyze,
skipProtection?: boolean
skipProtection?: boolean,
) => Promise<boolean>;

export interface SqlStatementResult {
Expand All @@ -41,13 +41,11 @@ export class SqlRunnerManager {

async execute(
statements: SqlStatement[],
options?: SqlExecuteOption
options?: SqlExecuteOption,
): Promise<SqlStatementResult[]> {
const result: SqlStatementResult[] = [];
const parser = new Parser();

console.log(statements);

// We only wrap transaction if it is multiple statement and
// insideTransactin is specified. Single statement, by itself, is
// transactional already.
Expand Down Expand Up @@ -89,7 +87,7 @@ export class SqlRunnerManager {
const startTime = Date.now();
const returnedResult = await this.executor(
statement.sql,
statement.params
statement.params,
);

if (!returnedResult?.error) {
Expand Down Expand Up @@ -117,7 +115,7 @@ export class SqlRunnerManager {

unregisterBeforeAll(cb: BeforeAllEventCallback) {
this.beforeAllCallbacks = this.beforeAllCallbacks.filter(
(prevCb) => prevCb !== cb
(prevCb) => prevCb !== cb,
);
}

Expand All @@ -127,7 +125,7 @@ export class SqlRunnerManager {

unregisterBeforeEach(cb: BeforeEachEventCallback) {
this.beforeEachCallbacks = this.beforeEachCallbacks.filter(
(prevCb) => prevCb !== cb
(prevCb) => prevCb !== cb,
);
}
}
1 change: 1 addition & 0 deletions src/main/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import './ipc_native_menu';
import './ipc_other';
import './ipc_rdms';
import './ipc_auto_update';
import './ipc_cipher';
64 changes: 64 additions & 0 deletions src/main/ipc/ipc_cipher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import crypto from 'crypto';
import CommunicateHandler from './../CommunicateHandler';

export class Encryption {
protected key: Buffer;

constructor(masterkey: string, salt: string) {
this.key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');
}

decrypt(encdata: string) {
const buffer = Buffer.from(encdata, 'base64');
const iv = buffer.subarray(0, 16);
const data = buffer.subarray(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv);
const text =
decipher.update(data).toString('utf8') + decipher.final('utf8');
return text;
}

encrypt(plain: string) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv);
return Buffer.concat([
iv,
cipher.update(plain, 'utf8'),
cipher.final(),
]).toString('base64');
}
}

const EncryptionDict: Record<string, Encryption> = {};

CommunicateHandler.handle(
'encrypt',
([text, masterkey, salt]: [string, string, string]) => {
try {
const key = masterkey + '_' + salt;
if (!EncryptionDict[key]) {
EncryptionDict[key] = new Encryption(masterkey, salt);
}

return EncryptionDict[key].encrypt(text);
} catch {
return null;
}
},
);

CommunicateHandler.handle(
'decrypt',
([encrypted, masterkey, salt]: [string, string, string]) => {
try {
const key = masterkey + '_' + salt;
if (!EncryptionDict[key]) {
EncryptionDict[key] = new Encryption(masterkey, salt);
}

return EncryptionDict[key].decrypt(encrypted);
} catch {
return null;
}
},
);
22 changes: 14 additions & 8 deletions src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const electronHandler = {
// Related to File O/I
// ----------------------------------
showSaveDialog: (
options: SaveDialogSyncOptions
options: SaveDialogSyncOptions,
): Promise<string | undefined> =>
ipcRenderer.invoke('show-save-dialog', [options]),

Expand All @@ -78,7 +78,7 @@ const electronHandler = {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),

handleMenuClick: (
callback: (event: IpcRendererEvent, id: string) => void
callback: (event: IpcRendererEvent, id: string) => void,
) => {
if (cacheHandleMenuClickCb) {
ipcRenderer.off('native-menu-click', cacheHandleMenuClickCb);
Expand All @@ -88,7 +88,7 @@ const electronHandler = {
},

listenDeeplink: (
callback: (event: IpcRendererEvent, url: string) => void
callback: (event: IpcRendererEvent, url: string) => void,
) => {
ipcRenderer.removeAllListeners('deeplink');
return ipcRenderer.on('deeplink', callback);
Expand All @@ -100,41 +100,47 @@ const electronHandler = {
},

listenUpdateAvailable: (
callback: (event: IpcRendererEvent, e: UpdateInfo) => void
callback: (event: IpcRendererEvent, e: UpdateInfo) => void,
) => {
ipcRenderer.removeAllListeners('update-available');
return ipcRenderer.on('update-available', callback);
},

listenUpdateNotAvailable: (
callback: (event: IpcRendererEvent, e: UpdateInfo) => void
callback: (event: IpcRendererEvent, e: UpdateInfo) => void,
) => {
ipcRenderer.removeAllListeners('update-not-available');
return ipcRenderer.on('update-not-available', callback);
},

listenUpdateDownloadProgress: (
callback: (event: IpcRendererEvent, e: ProgressInfo) => void
callback: (event: IpcRendererEvent, e: ProgressInfo) => void,
) => {
ipcRenderer.removeAllListeners('update-download-progress');
return ipcRenderer.on('update-download-progress', callback);
},

listenUpdateDownloaded: (
callback: (event: IpcRendererEvent, e: UpdateDownloadedEvent) => void
callback: (event: IpcRendererEvent, e: UpdateDownloadedEvent) => void,
) => {
ipcRenderer.removeAllListeners('update-downloaded');
return ipcRenderer.on('update-downloaded', callback);
},

listen: function listen<T = unknown[]>(
name: string,
callback: (event: IpcRendererEvent, ...args: T[]) => void
callback: (event: IpcRendererEvent, ...args: T[]) => void,
) {
return ipcRenderer.on(name, callback);
},

openExternal: (url: string) => ipcRenderer.invoke('open-external', [url]),

// Encryption
encrypt: (text: string, masterKey: string, salt: string) =>
ipcRenderer.invoke('encrypt', [text, masterKey, salt]),
decrypt: (encrypted: string, masterKey: string, salt: string) =>
ipcRenderer.invoke('decrypt', [encrypted, masterKey, salt]),
};

contextBridge.exposeInMainWorld('electron', electronHandler);
Expand Down
Loading