1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const Rsa_1 = require("./Crypto/Rsa");
|
4 | const Aes_1 = require("./Crypto/Aes");
|
5 | const Pbkdf2_1 = require("./Crypto/Pbkdf2");
|
6 | exports.ALLOWED_ENVIROMENTS = ["SANDBOX", "PRODUCTION"];
|
7 | exports.URL_ENVIROMENTS = {
|
8 | SANDBOX: "https://public-api.sandbox.bunq.com",
|
9 | PRODUCTION: "https://api.bunq.com"
|
10 | };
|
11 | class Session {
|
12 | constructor(storageInterface, loggerInterface) {
|
13 | this.apiKey = null;
|
14 | this.apiKeyIdentifier = null;
|
15 | this.encryptionKey = false;
|
16 | this.allowedIps = [];
|
17 | this.isOAuthKey = false;
|
18 |
|
19 | this.publicKey = null;
|
20 | this.publicKeyPem = null;
|
21 | this.privateKey = null;
|
22 | this.privateKeyPem = null;
|
23 | this.serverPublicKey = null;
|
24 | this.serverPublicKeyPem = null;
|
25 |
|
26 | this.installCreated = null;
|
27 | this.installUpdated = null;
|
28 | this.installToken = null;
|
29 | this.deviceId = null;
|
30 |
|
31 | this.sessionToken = null;
|
32 | this.sessionTokenId = null;
|
33 | this.sessionId = null;
|
34 | this.sessionExpiryTime = null;
|
35 | this.sessionTimeout = 0;
|
36 | this.sessionExpiryTimeChecker = null;
|
37 | this.userInfo = {};
|
38 | this.storageInterface = storageInterface;
|
39 | this.logger = loggerInterface;
|
40 | this.environmentType = "SANDBOX";
|
41 | }
|
42 | |
43 |
|
44 |
|
45 |
|
46 |
|
47 | async setup(apiKey, allowedIps = [], environment = "SANDBOX", encryptionKey = false) {
|
48 | if (this.apiKey && this.apiKey !== apiKey) {
|
49 | this.logger.debug("current apiKey set and changed");
|
50 | }
|
51 | if (this.environment !== null && environment !== this.environment && !!apiKey) {
|
52 |
|
53 | await this.destroyInstallationMemory();
|
54 | }
|
55 |
|
56 | if (typeof apiKey === "string") {
|
57 | const derivedApiKey = await Pbkdf2_1.derivePasswordKey(apiKey.substring(0, 8), apiKey.substring(8, 16), 10000);
|
58 |
|
59 | if (this.apiKeyIdentifier && this.apiKeyIdentifier !== derivedApiKey.key) {
|
60 | await this.destroyInstallationMemory();
|
61 | }
|
62 | this.apiKeyIdentifier = derivedApiKey.key;
|
63 | }
|
64 | this.apiKey = apiKey;
|
65 | this.allowedIps = allowedIps;
|
66 | this.environmentType = environment;
|
67 |
|
68 | if (!encryptionKey) {
|
69 | return false;
|
70 | }
|
71 |
|
72 | if (!Aes_1.validateKey(encryptionKey)) {
|
73 | throw new Error("Invalid EAS key given! Invalid characters or length (16,24,32 length)");
|
74 | }
|
75 | this.encryptionKey = encryptionKey;
|
76 |
|
77 | const loadedStorage = await this.loadSession();
|
78 |
|
79 | if (loadedStorage === false && this.encryptionKey !== false) {
|
80 |
|
81 | await this.setupKeypair();
|
82 | }
|
83 | return true;
|
84 | }
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 | async setEncryptionKey(encryptionKey) {
|
91 |
|
92 | if (!Aes_1.validateKey(encryptionKey)) {
|
93 | throw new Error("Invalid EAS key given! Invalid characters or length (16,24,32 length)");
|
94 | }
|
95 | this.encryptionKey = encryptionKey;
|
96 |
|
97 | await this.storeSession();
|
98 | return true;
|
99 | }
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | async setupKeypair(forceNewKeypair = false, bitSize = 2048, ignoreCI = false) {
|
107 | if (forceNewKeypair === false && this.publicKey !== null && this.privateKey !== null) {
|
108 | return true;
|
109 | }
|
110 |
|
111 | if (typeof process !== "undefined" && process.env.ENV_CI === "true" && ignoreCI === false) {
|
112 |
|
113 | this.publicKeyPem = process.env.CI_PUBLIC_KEY_PEM;
|
114 | this.privateKeyPem = process.env.CI_PRIVATE_KEY_PEM;
|
115 | this.publicKey = await Rsa_1.publicKeyFromPem(this.publicKeyPem);
|
116 | this.privateKey = await Rsa_1.privateKeyFromPem(this.privateKeyPem);
|
117 | }
|
118 | else {
|
119 |
|
120 | const keyPair = await Rsa_1.createKeyPair(bitSize);
|
121 | const { publicKey, privateKey } = await Rsa_1.keyPairToPem(keyPair);
|
122 | this.publicKey = keyPair.publicKey;
|
123 | this.privateKey = keyPair.privateKey;
|
124 | this.publicKeyPem = publicKey;
|
125 | this.privateKeyPem = privateKey;
|
126 | }
|
127 | return true;
|
128 | }
|
129 | |
130 |
|
131 |
|
132 |
|
133 | async loadSession() {
|
134 | this.logger.debug(" === Loading session data === " + this.storageKeyLocation);
|
135 |
|
136 | const encryptedSession = await this.asyncStorageGet(this.storageKeyLocation);
|
137 |
|
138 | if (encryptedSession === undefined || encryptedSession === null) {
|
139 | this.logger.debug("No stored session found");
|
140 | return false;
|
141 | }
|
142 | let session;
|
143 | try {
|
144 |
|
145 | session = await this.decryptSession(encryptedSession);
|
146 | }
|
147 | catch (error) {
|
148 | this.logger.debug("Failed to decrypt session");
|
149 | this.logger.debug(error);
|
150 |
|
151 | return false;
|
152 | }
|
153 |
|
154 | if (this.apiKey !== false && this.apiKey !== null && session.apiKey !== this.apiKey) {
|
155 | this.logger.debug("Api key changed or is different (api key could be empty)");
|
156 | return false;
|
157 | }
|
158 |
|
159 | if (session.environment !== this.environment) {
|
160 | this.logger.debug("Environment changed, delete existing session");
|
161 | await this.destroySession();
|
162 | return false;
|
163 | }
|
164 | this.environment = session.environment;
|
165 |
|
166 | this.publicKeyPem = session.publicKeyPem;
|
167 | this.privateKeyPem = session.privateKeyPem;
|
168 | this.serverPublicKeyPem = session.serverPublicKeyPem;
|
169 | if (this.privateKeyPem !== null) {
|
170 | this.privateKey = await Rsa_1.privateKeyFromPem(session.privateKeyPem);
|
171 | }
|
172 | if (this.publicKeyPem !== null) {
|
173 | this.publicKey = await Rsa_1.publicKeyFromPem(session.publicKeyPem);
|
174 | }
|
175 | if (this.serverPublicKeyPem !== null) {
|
176 | this.serverPublicKey = await Rsa_1.publicKeyFromPem(session.serverPublicKeyPem);
|
177 | }
|
178 | this.installToken = session.installToken;
|
179 | this.installCreated = session.installCreated;
|
180 | this.installUpdated = session.installUpdated;
|
181 | this.sessionId = session.sessionId;
|
182 | this.sessionToken = session.sessionToken;
|
183 | this.sessionTimeout = session.sessionTimeout;
|
184 | this.sessionExpiryTime = new Date(session.sessionExpiryTime);
|
185 | this.deviceId = session.deviceId;
|
186 | this.userInfo = session.userInfo;
|
187 | this.logger.debug(`sessionId: ${session.sessionId}`);
|
188 | this.logger.debug(`installCreated: ${session.installCreated}`);
|
189 | this.logger.debug(`installUpdated: ${session.installUpdated}`);
|
190 | this.logger.debug(`sessionExpiryTime: ${session.sessionExpiryTime}`);
|
191 | this.logger.debug(`deviceId: ${session.deviceId}`);
|
192 |
|
193 |
|
194 | if (this.verifyInstallation() && this.verifyDeviceInstallation() && !this.verifySessionInstallation()) {
|
195 | await this.destroyApiSession(true);
|
196 | return false;
|
197 | }
|
198 | try {
|
199 | this.logger.debug(`sessionToken: ${session.sessionToken === null ? null : session.sessionToken.substring(0, 5)}`);
|
200 | this.logger.debug(`installToken: ${session.installToken === null ? null : session.installToken.substring(0, 5)}`);
|
201 | }
|
202 | catch (error) { }
|
203 | return true;
|
204 | }
|
205 | |
206 |
|
207 |
|
208 |
|
209 | async storeSession() {
|
210 | const dataToStore = {
|
211 | environment: this.environment,
|
212 | apiKey: this.apiKey,
|
213 | publicKeyPem: this.publicKeyPem,
|
214 | privateKeyPem: this.privateKeyPem,
|
215 | serverPublicKeyPem: this.serverPublicKeyPem,
|
216 | installUpdated: this.installUpdated,
|
217 | installCreated: this.installCreated,
|
218 | installToken: this.installToken,
|
219 | sessionId: this.sessionId,
|
220 | sessionToken: this.sessionToken,
|
221 | sessionExpiryTime: this.sessionExpiryTime,
|
222 | sessionTimeout: this.sessionTimeout,
|
223 | userInfo: this.userInfo,
|
224 | deviceId: this.deviceId
|
225 | };
|
226 | const serializedData = JSON.stringify(dataToStore);
|
227 |
|
228 | return await this.encryptSession(serializedData);
|
229 | }
|
230 | |
231 |
|
232 |
|
233 |
|
234 | async destroySession() {
|
235 | this.logger.debug(` -> #destroySession() `);
|
236 | this.apiKey = null;
|
237 | this.userInfo = {};
|
238 | await this.destroyApiSession();
|
239 | await this.destroyApiInstallation();
|
240 | await this.destroyApiDeviceInstallation();
|
241 | return await this.asyncStorageRemove(this.storageKeyLocation);
|
242 | }
|
243 | |
244 |
|
245 |
|
246 |
|
247 |
|
248 | async destroyInstallationMemory() {
|
249 | this.logger.debug(` -> #destroyInstallationMemory() `);
|
250 | this.userInfo = {};
|
251 | this.sessionId = null;
|
252 | this.sessionToken = null;
|
253 | this.sessionTokenId = null;
|
254 | this.sessionTimeout = null;
|
255 | this.sessionExpiryTime = null;
|
256 | this.publicKey = null;
|
257 | this.publicKeyPem = null;
|
258 | this.privateKey = null;
|
259 | this.privateKeyPem = null;
|
260 | this.serverPublicKey = null;
|
261 | this.serverPublicKeyPem = null;
|
262 | this.installUpdated = null;
|
263 | this.installCreated = null;
|
264 | this.installToken = null;
|
265 | this.deviceId = null;
|
266 | }
|
267 | |
268 |
|
269 |
|
270 |
|
271 |
|
272 | async destroyApiSession(save = false) {
|
273 | this.logger.debug(` -> #destroyApiSession(${save}) `);
|
274 | this.userInfo = {};
|
275 | this.sessionId = null;
|
276 | this.sessionToken = null;
|
277 | this.sessionTokenId = null;
|
278 | this.sessionTimeout = null;
|
279 | this.sessionExpiryTime = null;
|
280 | if (save)
|
281 | return await this.storeSession();
|
282 | }
|
283 | |
284 |
|
285 |
|
286 |
|
287 |
|
288 | async destroyApiInstallation(save = false) {
|
289 | this.logger.debug(` -> #destroyApiInstallation(${save}) `);
|
290 | this.publicKey = null;
|
291 | this.publicKeyPem = null;
|
292 | this.privateKey = null;
|
293 | this.privateKeyPem = null;
|
294 | this.serverPublicKey = null;
|
295 | this.serverPublicKeyPem = null;
|
296 | this.installUpdated = null;
|
297 | this.installCreated = null;
|
298 | this.installToken = null;
|
299 | if (save)
|
300 | return await this.storeSession();
|
301 | }
|
302 | |
303 |
|
304 |
|
305 |
|
306 |
|
307 | async destroyApiDeviceInstallation(save = false) {
|
308 | this.logger.debug(` -> #destroyApiDeviceInstallation(${save}) `);
|
309 | this.deviceId = null;
|
310 | if (save)
|
311 | return await this.storeSession();
|
312 | }
|
313 | |
314 |
|
315 |
|
316 |
|
317 |
|
318 | async decryptSession(encryptedSession) {
|
319 | const IV = await this.asyncStorageGet(this.storageIvLocation);
|
320 | if (this.encryptionKey === false) {
|
321 | throw new Error("No encryption key is set, failed to decrypt session");
|
322 | }
|
323 |
|
324 | const decryptedSession = await Aes_1.decryptString(encryptedSession, this.encryptionKey, IV);
|
325 | return JSON.parse(decryptedSession);
|
326 | }
|
327 | |
328 |
|
329 |
|
330 |
|
331 |
|
332 | async encryptSession(sessionData) {
|
333 | if (!this.encryptionKey)
|
334 | return false;
|
335 |
|
336 | const encryptedData = await Aes_1.encryptString(sessionData, this.encryptionKey);
|
337 |
|
338 | const ivStorageSuccess = this.asyncStorageSet(this.storageIvLocation, encryptedData.iv);
|
339 | const dataStorageSuccess = this.asyncStorageSet(this.storageKeyLocation, encryptedData.encryptedString);
|
340 |
|
341 | await ivStorageSuccess;
|
342 | await dataStorageSuccess;
|
343 | return true;
|
344 | }
|
345 | |
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | async storeEncryptedData(data, location) {
|
352 | if (!this.encryptionKey)
|
353 | return false;
|
354 |
|
355 | const encryptedData = await Aes_1.encryptString(JSON.stringify(data), this.encryptionKey);
|
356 |
|
357 | const ivStorage = this.asyncStorageSet(`${location}_IV`, encryptedData.iv);
|
358 | const dataStorage = this.asyncStorageSet(location, encryptedData.encryptedString);
|
359 | await ivStorage;
|
360 | await dataStorage;
|
361 | return true;
|
362 | }
|
363 | |
364 |
|
365 |
|
366 |
|
367 |
|
368 | async loadEncryptedData(data_location, iv_location = null) {
|
369 |
|
370 | iv_location = iv_location === null ? `${data_location}_IV` : iv_location;
|
371 |
|
372 | const storedData = await this.asyncStorageGet(data_location);
|
373 | const storedIv = await this.asyncStorageGet(iv_location);
|
374 |
|
375 | if (storedData === undefined || storedData === null || storedIv === undefined || storedIv === null) {
|
376 | return false;
|
377 | }
|
378 | if (this.encryptionKey === false) {
|
379 | throw new Error("No encryption key is set, failed to decrypt data");
|
380 | }
|
381 |
|
382 | const decryptedSession = await Aes_1.decryptString(storedData, this.encryptionKey, storedIv);
|
383 | return JSON.parse(decryptedSession);
|
384 | }
|
385 | |
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 | async asyncStorageRemove(key, silent = false) {
|
392 | try {
|
393 | return await this.storageInterface.remove(key);
|
394 | }
|
395 | catch (error) {
|
396 | if (silent) {
|
397 | return undefined;
|
398 | }
|
399 | throw error;
|
400 | }
|
401 | }
|
402 | |
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | async asyncStorageGet(key, silent = false) {
|
409 | try {
|
410 | return await this.storageInterface.get(key);
|
411 | }
|
412 | catch (error) {
|
413 | if (silent) {
|
414 | return undefined;
|
415 | }
|
416 | throw error;
|
417 | }
|
418 | }
|
419 | |
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 | async asyncStorageSet(key, value, silent = false) {
|
427 | try {
|
428 | return await this.storageInterface.set(key, value);
|
429 | }
|
430 | catch (error) {
|
431 | if (silent) {
|
432 | return undefined;
|
433 | }
|
434 | throw error;
|
435 | }
|
436 | }
|
437 | |
438 |
|
439 |
|
440 |
|
441 | verifyInstallation() {
|
442 | this.logger.debug(" === Testing installation === ");
|
443 | const installationValid = this.serverPublicKey !== null && this.installToken !== null;
|
444 | this.logger.debug("Installation valid: " + installationValid);
|
445 | this.logger.debug("this.serverPublicKey = " + this.serverPublicKey);
|
446 | this.logger.debug(`this.installToken = ${this.installToken === null ? null : this.installToken.substring(0, 5)}`);
|
447 | return installationValid;
|
448 | }
|
449 | |
450 |
|
451 |
|
452 |
|
453 | verifyDeviceInstallation() {
|
454 | this.logger.debug(" === Testing device installation === ");
|
455 | const deviceValid = this.deviceId !== null;
|
456 | this.logger.debug("Device valid: " + deviceValid);
|
457 | this.logger.debug("this.deviceId: " + this.deviceId);
|
458 | return deviceValid;
|
459 | }
|
460 | |
461 |
|
462 |
|
463 |
|
464 | verifySessionInstallation() {
|
465 | if (this.sessionId === null) {
|
466 | this.logger.debug(" === Session invalid: sessionId null === ");
|
467 | return false;
|
468 | }
|
469 | if (!this.verifySessionExpiry())
|
470 | return false;
|
471 | return true;
|
472 | }
|
473 | |
474 |
|
475 |
|
476 |
|
477 | verifySessionExpiry() {
|
478 | const currentTime = new Date();
|
479 | if (this.sessionExpiryTime.getTime() <= currentTime.getTime()) {
|
480 | this.logger.debug(" === Session invalid: expired === ");
|
481 | this.logger.debug("this.sessionExpiryTime.getTime() = " + this.sessionExpiryTime.getTime());
|
482 | this.logger.debug("currentTime.getTime() = " + currentTime.getTime());
|
483 | return false;
|
484 | }
|
485 | return true;
|
486 | }
|
487 | |
488 |
|
489 |
|
490 |
|
491 | set environmentType(environmentType) {
|
492 | if (exports.ALLOWED_ENVIROMENTS.includes(environmentType)) {
|
493 | this.environment = environmentType;
|
494 | this.environmentUrl = exports.URL_ENVIROMENTS[this.environment];
|
495 |
|
496 | this.storageKeyLocation = `BUNQJSCLIENT_${this.environment}_SESSION_${this.apiKeyIdentifier}`;
|
497 | this.storageIvLocation = `BUNQJSCLIENT_${this.environment}_IV_${this.apiKeyIdentifier}`;
|
498 | return;
|
499 | }
|
500 | throw new Error("Invalid enviroment given. " + JSON.stringify(exports.ALLOWED_ENVIROMENTS));
|
501 | }
|
502 | }
|
503 | exports.default = Session;
|