@vex-chat/libvex 0.27.1 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Client.d.ts +34 -40
- package/dist/Client.js +1343 -1473
- package/dist/Client.js.map +1 -1
- package/dist/IStorage.d.ts +9 -10
- package/dist/IStorage.js +1 -2
- package/dist/IStorage.js.map +1 -1
- package/dist/Storage.d.ts +11 -12
- package/dist/Storage.js +393 -447
- package/dist/Storage.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/utils/capitalize.js +1 -4
- package/dist/utils/capitalize.js.map +1 -1
- package/dist/utils/constants.js +2 -5
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/createLogger.js +8 -15
- package/dist/utils/createLogger.js.map +1 -1
- package/dist/utils/formatBytes.js +1 -4
- package/dist/utils/formatBytes.js.map +1 -1
- package/dist/utils/sqlSessionToCrypto.d.ts +2 -2
- package/dist/utils/sqlSessionToCrypto.js +5 -9
- package/dist/utils/sqlSessionToCrypto.js.map +1 -1
- package/dist/utils/uint8uuid.d.ts +2 -2
- package/dist/utils/uint8uuid.js +5 -10
- package/dist/utils/uint8uuid.js.map +1 -1
- package/package.json +42 -43
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/RELEASING.md +0 -95
- package/dist/__tests__/Client.d.ts +0 -1
- package/dist/__tests__/Client.js +0 -271
- package/dist/__tests__/Client.js.map +0 -1
- package/jest.config.js +0 -18
- package/mise.toml +0 -3
package/dist/Storage.js
CHANGED
|
@@ -1,47 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.Storage = void 0;
|
|
16
|
-
const sleep_1 = require("@extrahash/sleep");
|
|
17
|
-
const crypto_1 = require("@vex-chat/crypto");
|
|
18
|
-
const events_1 = require("events");
|
|
19
|
-
const knex_1 = __importDefault(require("knex"));
|
|
20
|
-
const parse_duration_1 = __importDefault(require("parse-duration"));
|
|
21
|
-
const tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
22
|
-
const createLogger_1 = require("./utils/createLogger");
|
|
23
|
-
const sqlSessionToCrypto_1 = require("./utils/sqlSessionToCrypto");
|
|
1
|
+
import { sleep } from "@extrahash/sleep";
|
|
2
|
+
import { XKeyConvert, XUtils } from "@vex-chat/crypto";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import knex, {} from "knex";
|
|
5
|
+
import parseDuration from "parse-duration";
|
|
6
|
+
import nacl from "tweetnacl";
|
|
7
|
+
import winston from "winston";
|
|
8
|
+
import { createLogger } from "./utils/createLogger.js";
|
|
9
|
+
import { sqlSessionToCrypto } from "./utils/sqlSessionToCrypto.js";
|
|
24
10
|
/**
|
|
25
11
|
* The default IStorage() implementation, using knex and sqlite3 driver
|
|
26
12
|
*
|
|
27
13
|
* @hidden
|
|
28
14
|
*/
|
|
29
|
-
class Storage extends
|
|
15
|
+
export class Storage extends EventEmitter {
|
|
16
|
+
ready = false;
|
|
17
|
+
closing = false;
|
|
18
|
+
dbPath;
|
|
19
|
+
db;
|
|
20
|
+
log;
|
|
21
|
+
idKeys;
|
|
22
|
+
saveHistory;
|
|
30
23
|
constructor(dbPath, SK, options) {
|
|
31
24
|
super();
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
this.log = createLogger_1.createLogger("db", (options === null || options === void 0 ? void 0 : options.dbLogLevel) || (options === null || options === void 0 ? void 0 : options.logLevel));
|
|
35
|
-
const idKeys = crypto_1.XKeyConvert.convertKeyPair(tweetnacl_1.default.sign.keyPair.fromSecretKey(crypto_1.XUtils.decodeHex(SK)));
|
|
25
|
+
this.log = createLogger("db", options?.dbLogLevel || options?.logLevel);
|
|
26
|
+
const idKeys = XKeyConvert.convertKeyPair(nacl.sign.keyPair.fromSecretKey(XUtils.decodeHex(SK)));
|
|
36
27
|
if (!idKeys) {
|
|
37
28
|
throw new Error("Can't convert SK!");
|
|
38
29
|
}
|
|
39
|
-
this.saveHistory =
|
|
30
|
+
this.saveHistory = options?.saveHistory || true;
|
|
40
31
|
this.idKeys = idKeys;
|
|
41
32
|
this.dbPath = dbPath;
|
|
42
33
|
this.log.info("Opening database file at " + this.dbPath);
|
|
43
|
-
this.db =
|
|
44
|
-
client: "sqlite3",
|
|
34
|
+
this.db = knex({
|
|
35
|
+
client: "better-sqlite3",
|
|
45
36
|
connection: {
|
|
46
37
|
filename: this.dbPath,
|
|
47
38
|
},
|
|
@@ -49,461 +40,416 @@ class Storage extends events_1.EventEmitter {
|
|
|
49
40
|
});
|
|
50
41
|
this.init();
|
|
51
42
|
}
|
|
52
|
-
close() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
yield this.db.destroy();
|
|
57
|
-
});
|
|
43
|
+
async close() {
|
|
44
|
+
this.closing = true;
|
|
45
|
+
this.log.info("Closing database.");
|
|
46
|
+
await this.db.destroy();
|
|
58
47
|
}
|
|
59
|
-
saveMessage(message) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
this.log.warn("Attempted to insert duplicate nonce into message table.");
|
|
48
|
+
async saveMessage(message) {
|
|
49
|
+
if (!this.saveHistory) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.closing) {
|
|
53
|
+
this.log.warn("Database is closing, saveMessage() will not complete.");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const copy = { ...message };
|
|
57
|
+
// encrypt plaintext with our idkey before it gets saved to disk
|
|
58
|
+
copy.message = XUtils.encodeHex(nacl.secretbox(XUtils.decodeUTF8(message.message), XUtils.decodeHex(message.nonce), this.idKeys.secretKey));
|
|
59
|
+
try {
|
|
60
|
+
await this.db("messages").insert(copy);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err.errno !== 19) {
|
|
64
|
+
throw err;
|
|
79
65
|
}
|
|
80
|
-
|
|
66
|
+
this.log.warn("Attempted to insert duplicate nonce into message table.");
|
|
67
|
+
}
|
|
81
68
|
}
|
|
82
|
-
deleteMessage(mailID) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.del();
|
|
92
|
-
});
|
|
69
|
+
async deleteMessage(mailID) {
|
|
70
|
+
if (this.closing) {
|
|
71
|
+
this.log.warn("Database is closing, saveMessage() will not complete.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await this.db
|
|
75
|
+
.from("messages")
|
|
76
|
+
.where({ mailID })
|
|
77
|
+
.del();
|
|
93
78
|
}
|
|
94
|
-
markSessionVerified(sessionID, status = true) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.update({ verified: status });
|
|
103
|
-
});
|
|
79
|
+
async markSessionVerified(sessionID, status = true) {
|
|
80
|
+
if (this.closing) {
|
|
81
|
+
this.log.warn("Database is closing, markSessionVerified() will not complete.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await this.db("sessions")
|
|
85
|
+
.where({ sessionID })
|
|
86
|
+
.update({ verified: status });
|
|
104
87
|
}
|
|
105
|
-
getMessageHistory(userID) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
message.message = crypto_1.XUtils.encodeUTF8(localDecrypt);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
throw new Error("Couldn't decrypt messages on disk!");
|
|
128
|
-
}
|
|
88
|
+
async getMessageHistory(userID) {
|
|
89
|
+
if (this.closing) {
|
|
90
|
+
this.log.warn("Database is closing, getMessageHistory() will not complete.");
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const messages = await this.db("messages")
|
|
94
|
+
.select()
|
|
95
|
+
.where({ direction: "incoming", authorID: userID, group: null })
|
|
96
|
+
.orWhere({ direction: "outgoing", readerID: userID, group: null })
|
|
97
|
+
.orderBy("timestamp", "desc");
|
|
98
|
+
const fixedMessages = messages.reverse().map((message) => {
|
|
99
|
+
// some cleanup because of how knex serializes the data
|
|
100
|
+
message.timestamp = new Date(message.timestamp);
|
|
101
|
+
// decrypt
|
|
102
|
+
message.decrypted = Boolean(message.decrypted);
|
|
103
|
+
if (message.decrypted) {
|
|
104
|
+
const localDecrypt = nacl.secretbox.open(XUtils.decodeHex(message.message), XUtils.decodeHex(message.nonce), this.idKeys.secretKey);
|
|
105
|
+
if (localDecrypt) {
|
|
106
|
+
message.message = XUtils.encodeUTF8(localDecrypt);
|
|
129
107
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
else {
|
|
109
|
+
throw new Error("Couldn't decrypt messages on disk!");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return message;
|
|
134
113
|
});
|
|
114
|
+
this.log.debug("getMessageHistory() => " + JSON.stringify(fixedMessages, null, 4));
|
|
115
|
+
return fixedMessages;
|
|
135
116
|
}
|
|
136
|
-
getGroupHistory(channelID) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
message.message = crypto_1.XUtils.encodeUTF8(localDecrypt);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
throw new Error("Couldn't decrypt messages on disk!");
|
|
158
|
-
}
|
|
117
|
+
async getGroupHistory(channelID) {
|
|
118
|
+
if (this.closing) {
|
|
119
|
+
this.log.warn("Database is closing, getGroupHistory() will not complete.");
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const messages = await this.db("messages")
|
|
123
|
+
.select()
|
|
124
|
+
.where({ group: channelID })
|
|
125
|
+
.orderBy("timestamp", "desc");
|
|
126
|
+
const fixedMessages = messages.reverse().map((message) => {
|
|
127
|
+
// some cleanup because of how knex serializes the data
|
|
128
|
+
message.timestamp = new Date(message.timestamp);
|
|
129
|
+
// decrypt
|
|
130
|
+
message.decrypted = Boolean(message.decrypted);
|
|
131
|
+
if (message.decrypted) {
|
|
132
|
+
const localDecrypt = nacl.secretbox.open(XUtils.decodeHex(message.message), XUtils.decodeHex(message.nonce), this.idKeys.secretKey);
|
|
133
|
+
if (localDecrypt) {
|
|
134
|
+
message.message = XUtils.encodeUTF8(localDecrypt);
|
|
159
135
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
136
|
+
else {
|
|
137
|
+
throw new Error("Couldn't decrypt messages on disk!");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return message;
|
|
164
141
|
});
|
|
142
|
+
this.log.debug("getGroupHistory() => " + JSON.stringify(fixedMessages, null, 4));
|
|
143
|
+
return fixedMessages;
|
|
165
144
|
}
|
|
166
|
-
savePreKeys(preKeys, oneTime) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
signature: crypto_1.XUtils.encodeHex(preKey.signature),
|
|
179
|
-
});
|
|
180
|
-
addedIndexes.push(index[0]);
|
|
181
|
-
}
|
|
182
|
-
const addedKeys = (yield this.db
|
|
183
|
-
.from(oneTime ? "oneTimeKeys" : "preKeys")
|
|
184
|
-
.select()
|
|
185
|
-
.whereIn("index", addedIndexes)).map((key) => {
|
|
186
|
-
delete key.privateKey;
|
|
187
|
-
return key;
|
|
145
|
+
async savePreKeys(preKeys, oneTime) {
|
|
146
|
+
const addedIndexes = [];
|
|
147
|
+
await this.untilReady();
|
|
148
|
+
if (this.closing) {
|
|
149
|
+
this.log.warn("Database is closing, savePreKeys() will not complete.");
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
for (const preKey of preKeys) {
|
|
153
|
+
const index = await this.db(oneTime ? "oneTimeKeys" : "preKeys").insert({
|
|
154
|
+
privateKey: XUtils.encodeHex(preKey.keyPair.secretKey),
|
|
155
|
+
publicKey: XUtils.encodeHex(preKey.keyPair.publicKey),
|
|
156
|
+
signature: XUtils.encodeHex(preKey.signature),
|
|
188
157
|
});
|
|
189
|
-
|
|
190
|
-
|
|
158
|
+
addedIndexes.push(index[0]);
|
|
159
|
+
}
|
|
160
|
+
const addedKeys = (await this.db
|
|
161
|
+
.from(oneTime ? "oneTimeKeys" : "preKeys")
|
|
162
|
+
.select()
|
|
163
|
+
.whereIn("index", addedIndexes)).map((key) => {
|
|
164
|
+
delete key.privateKey;
|
|
165
|
+
return key;
|
|
191
166
|
});
|
|
167
|
+
this.log.silly("savePreKeys() => " + JSON.stringify(addedKeys, null, 4));
|
|
168
|
+
return addedKeys;
|
|
192
169
|
}
|
|
193
|
-
getSessionByPublicKey(publicKey) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return wsSession;
|
|
214
|
-
});
|
|
170
|
+
async getSessionByPublicKey(publicKey) {
|
|
171
|
+
if (this.closing) {
|
|
172
|
+
this.log.warn("Database is closing, getGroupHistory() will not complete.");
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const str = XUtils.encodeHex(publicKey);
|
|
176
|
+
const rows = await this.db
|
|
177
|
+
.from("sessions")
|
|
178
|
+
.select()
|
|
179
|
+
.where({ publicKey: str })
|
|
180
|
+
.limit(1);
|
|
181
|
+
if (rows.length === 0) {
|
|
182
|
+
this.log.warn(`getSessionByPublicKey(${XUtils.encodeHex(publicKey)}) => ` +
|
|
183
|
+
JSON.stringify(null, null, 4));
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const [session] = rows;
|
|
187
|
+
const wsSession = sqlSessionToCrypto(session);
|
|
188
|
+
this.log.debug("getSessionByPublicKey() => " + JSON.stringify(wsSession, null, 4));
|
|
189
|
+
return wsSession;
|
|
215
190
|
}
|
|
216
|
-
markSessionUsed(sessionID) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
.where({ sessionID });
|
|
226
|
-
});
|
|
191
|
+
async markSessionUsed(sessionID) {
|
|
192
|
+
if (this.closing) {
|
|
193
|
+
this.log.warn("Database is closing, markSessionUsed() will not complete.");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
await this.db
|
|
197
|
+
.from("sessions")
|
|
198
|
+
.update({ lastUsed: new Date(Date.now()) })
|
|
199
|
+
.where({ sessionID });
|
|
227
200
|
}
|
|
228
|
-
getAllSessions() {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return session;
|
|
241
|
-
});
|
|
242
|
-
this.log.debug("getAllSessions() => " + JSON.stringify(fixedRows, null, 4));
|
|
243
|
-
return fixedRows;
|
|
201
|
+
async getAllSessions() {
|
|
202
|
+
if (this.closing) {
|
|
203
|
+
this.log.warn("Database is closing, getAllSessions() will not complete.");
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const rows = await this.db
|
|
207
|
+
.from("sessions")
|
|
208
|
+
.select()
|
|
209
|
+
.orderBy("lastUsed", "desc");
|
|
210
|
+
const fixedRows = rows.map((session) => {
|
|
211
|
+
session.verified = Boolean(session.verified);
|
|
212
|
+
return session;
|
|
244
213
|
});
|
|
214
|
+
this.log.debug("getAllSessions() => " + JSON.stringify(fixedRows, null, 4));
|
|
215
|
+
return fixedRows;
|
|
245
216
|
}
|
|
246
|
-
getSessionByDeviceID(deviceID) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return wsSession;
|
|
274
|
-
});
|
|
217
|
+
async getSessionByDeviceID(deviceID) {
|
|
218
|
+
if (this.closing) {
|
|
219
|
+
this.log.warn("Database is closing, getSessionByUserID() will not complete.");
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const rows = await this.db
|
|
223
|
+
.from("sessions")
|
|
224
|
+
.select()
|
|
225
|
+
.where({ deviceID })
|
|
226
|
+
.limit(1)
|
|
227
|
+
.orderBy("lastUsed", "desc");
|
|
228
|
+
if (rows.length === 0) {
|
|
229
|
+
this.log.debug("getSession() => " + JSON.stringify(null, null, 4));
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const [session] = rows;
|
|
233
|
+
const wsSession = {
|
|
234
|
+
sessionID: session.sessionID,
|
|
235
|
+
userID: session.userID,
|
|
236
|
+
mode: session.mode,
|
|
237
|
+
SK: XUtils.decodeHex(session.SK),
|
|
238
|
+
publicKey: XUtils.decodeHex(session.publicKey),
|
|
239
|
+
lastUsed: session.lastUsed,
|
|
240
|
+
fingerprint: XUtils.decodeHex(session.fingerprint),
|
|
241
|
+
};
|
|
242
|
+
this.log.debug("getSession() => " + JSON.stringify(wsSession, null, 4));
|
|
243
|
+
return wsSession;
|
|
275
244
|
}
|
|
276
|
-
getPreKeys() {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return preKeys;
|
|
297
|
-
});
|
|
245
|
+
async getPreKeys() {
|
|
246
|
+
await this.untilReady();
|
|
247
|
+
if (this.closing) {
|
|
248
|
+
this.log.warn("Database is closing, getPreKeys() will not complete.");
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const rows = await this.db
|
|
252
|
+
.from("preKeys")
|
|
253
|
+
.select();
|
|
254
|
+
if (rows.length === 0) {
|
|
255
|
+
this.log.debug("getPreKeys() => " + JSON.stringify(null, null, 4));
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const [preKeyInfo] = rows;
|
|
259
|
+
const preKeys = {
|
|
260
|
+
keyPair: nacl.box.keyPair.fromSecretKey(XUtils.decodeHex(preKeyInfo.privateKey)),
|
|
261
|
+
signature: XUtils.decodeHex(preKeyInfo.signature),
|
|
262
|
+
};
|
|
263
|
+
this.log.debug("getPreKeys() => " + JSON.stringify(preKeys, null, 4));
|
|
264
|
+
return preKeys;
|
|
298
265
|
}
|
|
299
|
-
getOneTimeKey(index) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return otk;
|
|
322
|
-
});
|
|
266
|
+
async getOneTimeKey(index) {
|
|
267
|
+
await this.untilReady();
|
|
268
|
+
if (this.closing) {
|
|
269
|
+
this.log.warn("Database is closing, getOneTimeKey() will not complete.");
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const rows = await this.db
|
|
273
|
+
.from("oneTimeKeys")
|
|
274
|
+
.select()
|
|
275
|
+
.where({ index });
|
|
276
|
+
if (rows.length === 0) {
|
|
277
|
+
this.log.debug("getOneTimeKey() => " + JSON.stringify(null, null, 4));
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const [otkInfo] = rows;
|
|
281
|
+
const otk = {
|
|
282
|
+
keyPair: nacl.box.keyPair.fromSecretKey(XUtils.decodeHex(otkInfo.privateKey)),
|
|
283
|
+
signature: XUtils.decodeHex(otkInfo.signature),
|
|
284
|
+
index: otkInfo.index,
|
|
285
|
+
};
|
|
286
|
+
this.log.debug("getOneTimeKey() => " + JSON.stringify(otk, null, 4));
|
|
287
|
+
return otk;
|
|
323
288
|
}
|
|
324
|
-
deleteOneTimeKey(index) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
.where({ index });
|
|
335
|
-
});
|
|
289
|
+
async deleteOneTimeKey(index) {
|
|
290
|
+
if (this.closing) {
|
|
291
|
+
this.log.warn("Database is closing, deleteOneTimeKey() will not complete.");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// delete the otk
|
|
295
|
+
await this.db
|
|
296
|
+
.from("oneTimeKeys")
|
|
297
|
+
.delete()
|
|
298
|
+
.where({ index });
|
|
336
299
|
}
|
|
337
|
-
saveSession(session) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
300
|
+
async saveSession(session) {
|
|
301
|
+
if (this.closing) {
|
|
302
|
+
this.log.warn("Database is closing, deleteOneTimeKey() will not complete.");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
await this.db("sessions").insert(session);
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
if (err.errno === 19) {
|
|
310
|
+
this.log.warn("Attempted to insert duplicate SK");
|
|
341
311
|
return;
|
|
342
312
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
catch (err) {
|
|
347
|
-
if (err.errno === 19) {
|
|
348
|
-
this.log.warn("Attempted to insert duplicate SK");
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
throw err;
|
|
353
|
-
}
|
|
313
|
+
else {
|
|
314
|
+
throw err;
|
|
354
315
|
}
|
|
355
|
-
}
|
|
316
|
+
}
|
|
356
317
|
}
|
|
357
|
-
getDevice(deviceID) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return rows[0];
|
|
367
|
-
});
|
|
318
|
+
async getDevice(deviceID) {
|
|
319
|
+
const rows = await this.db
|
|
320
|
+
.from("devices")
|
|
321
|
+
.select()
|
|
322
|
+
.where({ deviceID });
|
|
323
|
+
if (rows.length === 0) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return rows[0];
|
|
368
327
|
}
|
|
369
|
-
purgeHistory() {
|
|
370
|
-
|
|
371
|
-
yield this.db.from("messages").truncate();
|
|
372
|
-
});
|
|
328
|
+
async purgeHistory() {
|
|
329
|
+
await this.db.from("messages").truncate();
|
|
373
330
|
}
|
|
374
|
-
purgeKeyData() {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
331
|
+
async purgeKeyData() {
|
|
332
|
+
await this.db.from("sessions").truncate();
|
|
333
|
+
await this.db.from("oneTimeKeys").truncate();
|
|
334
|
+
await this.db.from("preKeys").truncate();
|
|
335
|
+
await this.db.from("messages").truncate();
|
|
336
|
+
}
|
|
337
|
+
async deleteHistory(channelOrUserID, olderThan) {
|
|
338
|
+
if (!olderThan) {
|
|
339
|
+
await this.db
|
|
340
|
+
.from("messages")
|
|
341
|
+
.where({ group: channelOrUserID })
|
|
342
|
+
.orWhere({ group: null, authorID: channelOrUserID })
|
|
343
|
+
.orWhere({ group: null, readerID: channelOrUserID })
|
|
344
|
+
.delete();
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const duration = parseDuration(olderThan);
|
|
348
|
+
if (!duration) {
|
|
349
|
+
throw new Error("Bad duration. See parse-duration library for more details.");
|
|
350
|
+
}
|
|
351
|
+
console.log(duration);
|
|
352
|
+
const res = await this.db
|
|
353
|
+
.from("messages")
|
|
354
|
+
.where("time", "<", new Date(Date.now() - duration).toISOString());
|
|
355
|
+
console.log(res);
|
|
356
|
+
}
|
|
381
357
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
358
|
+
async saveDevice(device) {
|
|
359
|
+
if (this.closing) {
|
|
360
|
+
this.log.warn("Database is closing, saveDevice() will not complete.");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
await this.db("devices").insert(device);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
if (err.errno === 19) {
|
|
368
|
+
this.log.warn("Attempted to insert duplicate deviceID");
|
|
369
|
+
return;
|
|
391
370
|
}
|
|
392
371
|
else {
|
|
393
|
-
|
|
394
|
-
if (!duration) {
|
|
395
|
-
throw new Error("Bad duration. See parse-duration library for more details.");
|
|
396
|
-
}
|
|
397
|
-
console.log(duration);
|
|
398
|
-
const res = yield this.db
|
|
399
|
-
.from("messages")
|
|
400
|
-
.where("time", "<", new Date(Date.now() - duration).toISOString());
|
|
401
|
-
console.log(res);
|
|
372
|
+
throw err;
|
|
402
373
|
}
|
|
403
|
-
}
|
|
374
|
+
}
|
|
404
375
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
376
|
+
async init() {
|
|
377
|
+
this.log.info("Initializing database tables.");
|
|
378
|
+
try {
|
|
379
|
+
if (!(await this.db.schema.hasTable("messages"))) {
|
|
380
|
+
await this.db.schema.createTable("messages", (table) => {
|
|
381
|
+
table.string("nonce").primary();
|
|
382
|
+
table.string("sender").index();
|
|
383
|
+
table.string("recipient").index();
|
|
384
|
+
table.string("group").index();
|
|
385
|
+
table.string("mailID");
|
|
386
|
+
table.string("message");
|
|
387
|
+
table.string("direction");
|
|
388
|
+
table.date("timestamp");
|
|
389
|
+
table.boolean("decrypted");
|
|
390
|
+
table.boolean("forward");
|
|
391
|
+
table.string("authorID");
|
|
392
|
+
table.string("readerID");
|
|
393
|
+
});
|
|
410
394
|
}
|
|
411
|
-
|
|
412
|
-
|
|
395
|
+
if (!(await this.db.schema.hasTable("devices"))) {
|
|
396
|
+
await this.db.schema.createTable("devices", (table) => {
|
|
397
|
+
table.string("deviceID").primary();
|
|
398
|
+
table.string("owner").index();
|
|
399
|
+
table.string("signKey");
|
|
400
|
+
table.string("name");
|
|
401
|
+
table.string("lastLogin");
|
|
402
|
+
table.boolean("deleted");
|
|
403
|
+
});
|
|
413
404
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
405
|
+
if (!(await this.db.schema.hasTable("sessions"))) {
|
|
406
|
+
await this.db.schema.createTable("sessions", (table) => {
|
|
407
|
+
table.string("sessionID").primary();
|
|
408
|
+
table.string("userID");
|
|
409
|
+
table.string("deviceID");
|
|
410
|
+
table.string("SK").unique();
|
|
411
|
+
table.string("publicKey");
|
|
412
|
+
table.string("fingerprint");
|
|
413
|
+
table.string("mode");
|
|
414
|
+
table.date("lastUsed");
|
|
415
|
+
table.boolean("verified");
|
|
416
|
+
});
|
|
422
417
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
table.string("recipient").index();
|
|
434
|
-
table.string("group").index();
|
|
435
|
-
table.string("mailID");
|
|
436
|
-
table.string("message");
|
|
437
|
-
table.string("direction");
|
|
438
|
-
table.date("timestamp");
|
|
439
|
-
table.boolean("decrypted");
|
|
440
|
-
table.boolean("forward");
|
|
441
|
-
table.string("authorID");
|
|
442
|
-
table.string("readerID");
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
if (!(yield this.db.schema.hasTable("devices"))) {
|
|
446
|
-
yield this.db.schema.createTable("devices", (table) => {
|
|
447
|
-
table.string("deviceID").primary();
|
|
448
|
-
table.string("owner").index();
|
|
449
|
-
table.string("signKey");
|
|
450
|
-
table.string("name");
|
|
451
|
-
table.string("lastLogin");
|
|
452
|
-
table.boolean("deleted");
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
if (!(yield this.db.schema.hasTable("sessions"))) {
|
|
456
|
-
yield this.db.schema.createTable("sessions", (table) => {
|
|
457
|
-
table.string("sessionID").primary();
|
|
458
|
-
table.string("userID");
|
|
459
|
-
table.string("deviceID");
|
|
460
|
-
table.string("SK").unique();
|
|
461
|
-
table.string("publicKey");
|
|
462
|
-
table.string("fingerprint");
|
|
463
|
-
table.string("mode");
|
|
464
|
-
table.date("lastUsed");
|
|
465
|
-
table.boolean("verified");
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
if (!(yield this.db.schema.hasTable("preKeys"))) {
|
|
469
|
-
yield this.db.schema.createTable("preKeys", (table) => {
|
|
470
|
-
table.increments("index");
|
|
471
|
-
table.string("keyID").unique();
|
|
472
|
-
table.string("userID");
|
|
473
|
-
table.string("deviceID");
|
|
474
|
-
table.string("privateKey");
|
|
475
|
-
table.string("publicKey");
|
|
476
|
-
table.string("signature");
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
if (!(yield this.db.schema.hasTable("oneTimeKeys"))) {
|
|
480
|
-
yield this.db.schema.createTable("oneTimeKeys", (table) => {
|
|
481
|
-
table.increments("index");
|
|
482
|
-
table.string("keyID").unique();
|
|
483
|
-
table.string("userID");
|
|
484
|
-
table.string("deviceID");
|
|
485
|
-
table.string("privateKey");
|
|
486
|
-
table.string("publicKey");
|
|
487
|
-
table.string("signature");
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
this.ready = true;
|
|
491
|
-
this.emit("ready");
|
|
418
|
+
if (!(await this.db.schema.hasTable("preKeys"))) {
|
|
419
|
+
await this.db.schema.createTable("preKeys", (table) => {
|
|
420
|
+
table.increments("index");
|
|
421
|
+
table.string("keyID").unique();
|
|
422
|
+
table.string("userID");
|
|
423
|
+
table.string("deviceID");
|
|
424
|
+
table.string("privateKey");
|
|
425
|
+
table.string("publicKey");
|
|
426
|
+
table.string("signature");
|
|
427
|
+
});
|
|
492
428
|
}
|
|
493
|
-
|
|
494
|
-
this.
|
|
429
|
+
if (!(await this.db.schema.hasTable("oneTimeKeys"))) {
|
|
430
|
+
await this.db.schema.createTable("oneTimeKeys", (table) => {
|
|
431
|
+
table.increments("index");
|
|
432
|
+
table.string("keyID").unique();
|
|
433
|
+
table.string("userID");
|
|
434
|
+
table.string("deviceID");
|
|
435
|
+
table.string("privateKey");
|
|
436
|
+
table.string("publicKey");
|
|
437
|
+
table.string("signature");
|
|
438
|
+
});
|
|
495
439
|
}
|
|
496
|
-
|
|
440
|
+
this.ready = true;
|
|
441
|
+
this.emit("ready");
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
this.emit("error", err);
|
|
445
|
+
}
|
|
497
446
|
}
|
|
498
|
-
untilReady() {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
});
|
|
447
|
+
async untilReady() {
|
|
448
|
+
let timeout = 1;
|
|
449
|
+
while (!this.ready) {
|
|
450
|
+
await sleep(timeout);
|
|
451
|
+
timeout *= 2;
|
|
452
|
+
}
|
|
506
453
|
}
|
|
507
454
|
}
|
|
508
|
-
exports.Storage = Storage;
|
|
509
455
|
//# sourceMappingURL=Storage.js.map
|