feat(storage-manager): add en-/decryption stub (WIP) and restructure

This commit is contained in:
Sarah Vaupel 2020-02-01 14:10:04 +01:00 committed by Gregor Kleen
parent 50e4212224
commit 54d852f308

View File

@ -1,6 +1,7 @@
/* global global:writable */
import * as semver from 'semver';
import sodium from 'sodium-javascript';
import { HttpClient } from '../../services/http-client/http-client';
@ -45,7 +46,7 @@ export class StorageManager {
throw new Error('Cannot setup StorageManager without window or global');
if (this._options.encryption) {
Object.values(LOCATION).forEach((location) => {
[LOCATION.LOCAL, LOCATION.SESSION].forEach((location) => {
const encryption = this._options.encryption.all || this._options.encryption[location];
if (encryption) this._requestStorageKey({ location: location, encryption: encryption });
});
@ -65,15 +66,15 @@ export class StorageManager {
switch (location) {
case LOCATION.LOCAL: {
this._saveToLocalStorage({ ...this._getFromLocalStorage(), [key]: value });
this._saveToLocalStorage(this._updateStorage(this._getFromLocalStorage(options), { [key]: value }, LOCATION.LOCAL, options));
break;
}
case LOCATION.SESSION: {
this._saveToSessionStorage({ ...this._getFromSessionStorage(), [key]: value });
this._saveToSessionStorage(this._updateStorage(this._getFromSessionStorage(options), { [key]: value }, LOCATION.SESSION, options));
break;
}
case LOCATION.WINDOW: {
this._saveToWindow({ ...this._getFromLocalStorage(), [key]: value });
this._saveToWindow({ ...this._getFromWindow(), [key]: value });
break;
}
default:
@ -94,11 +95,11 @@ export class StorageManager {
switch (location) {
case LOCATION.LOCAL: {
val = this._getFromLocalStorage()[key];
val = this._getFromLocalStorage(options)[key];
break;
}
case LOCATION.SESSION: {
val = this._getFromSessionStorage()[key];
val = this._getFromSessionStorage(options)[key];
break;
}
case LOCATION.WINDOW: {
@ -125,14 +126,14 @@ export class StorageManager {
for (const location of locations) {
switch (location) {
case LOCATION.LOCAL: {
let val = this._getFromLocalStorage();
let val = this._getFromLocalStorage(options);
delete val[key];
return this._saveToLocalStorage(val);
}
case LOCATION.SESSION: {
let val = this._getFromSessionStorage();
let val = this._getFromSessionStorage(options);
delete val[key];
@ -174,7 +175,7 @@ export class StorageManager {
}
_getFromLocalStorage() {
_getFromLocalStorage(options=this._options) {
let state;
try {
@ -190,10 +191,10 @@ export class StorageManager {
}
if ('state' in state)
return state.state;
return this._getFromStorage(state.state, LOCATION.LOCAL, options);
else {
delete state.version;
return state;
return this._getFromStorage(state, LOCATION.LOCAL, options);
}
}
@ -224,12 +225,12 @@ export class StorageManager {
if (!this._global.App.Storage)
this._global.App.Storage = {};
return this._global.App.Storage[this.namespace] || {};
return this._global.App.Storage;
}
_saveToWindow(value) {
if (!this._global || !this._global.App) {
throw new Error('StorageManager._saveToWindow called when window.App is not available');
return console.error('StorageManager._saveToWindow called when window.App is not available');
}
if (!value)
@ -243,7 +244,7 @@ export class StorageManager {
_clearWindow() {
if (!this._global || !this._global.App) {
throw new Error('StorageManager._saveToWindow called when window.App is not available');
return console.error('StorageManager._saveToWindow called when window.App is not available');
}
if (this._global.App.Storage) {
@ -252,7 +253,8 @@ export class StorageManager {
}
_getFromSessionStorage() {
_getFromSessionStorage(options=this._options) {
console.log('_getFromSessionStorage with args', options);
let state;
try {
@ -268,10 +270,10 @@ export class StorageManager {
}
if ('state' in state)
return state.state;
return this._getFromStorage(state.state, LOCATION.SESSION, options);
else {
delete state.version;
return state;
return this._getFromStorage(state, LOCATION.SESSION, options);
}
}
@ -294,6 +296,26 @@ export class StorageManager {
window.sessionStorage.removeItem(this.namespace);
}
_getFromStorage(storage, location, options=this._options) {
const encryption = options.encryption && (options.encryption.all || options.encryption[location]);
if (encryption && storage.encryption) {
return { ...storage, ...JSON.parse(decrypt(storage.encryption.ciphertext, encryption.key) || '{}') };
} else {
return storage;
}
}
_updateStorage(storage, update, location, options=this._options) {
const encryption = options.encryption && (options.encryption.all || options.encryption[location]);
if (encryption && storage.encryption) {
const updatedDecryptedStorage = { ...JSON.parse(decrypt(storage.encryption.ciphertext, this._encryptionKey[location]) || '{}'), ...update };
return { ...storage, encryption: { ...storage.encryption, ciphertext: encrypt(JSON.stringify(updatedDecryptedStorage), this._encryptionKey[location]) } };
} else {
return { ...storage, ...update };
}
}
_requestStorageKey(options=this._options) {
if (!(options && options.location && options.encryption))
throw new Error('Storage Manager cannot request storage key with unsupported options!');
@ -301,7 +323,7 @@ export class StorageManager {
const requestBody = {
type : options.encryption,
length : 42,
...this.load('encryption', options),
...this.load('encryption', { ...options, encryption: false }),
};
this._global.App.httpClient.post({
@ -318,9 +340,50 @@ export class StorageManager {
if (response.salt !== requestBody.salt || response.timestamp !== requestBody.timestamp) {
this.clear(options);
}
this.save('encryption', { salt: response.salt, timestamp: response.timestamp }, options);
this.save('encryption', { salt: response.salt, timestamp: response.timestamp }, { ...options, encryption: false });
this._encryptionKey[options.location] = response.key;
}).catch(console.error);
}
}
// TODO debug unnecessary calls of encrypt
function encrypt(plaintext, key) {
console.log('args encrypt', plaintext, key);
if (!plaintext) return '';
if (!key) throw new Error('Cannot encrypt plaintext without a valid key!');
/* eslint-disable no-undef */
// TODO use const if possible
let plaintextB = Buffer.from(plaintext);
let cipherB = Buffer.alloc(plaintextB.length + sodium.crypto_secretbox_MACBYTES);
let nonceB = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES);
let keyB = Buffer.from(key);
/* eslint-enable no-undef */
sodium.crypto_secretbox_easy(cipherB, plaintextB, nonceB, keyB);
return cipherB.toString('base64');
}
// TODO debug unnecessary calls of decrypt
function decrypt(ciphertext, key) {
console.log('args decrypt', ciphertext, key);
if (!ciphertext) return '';
if (!key) throw new Error('Cannot decrypt ciphertext without a valid key!');
/* eslint-disable no-undef */
// TODO use const if possible
let cipherB = Buffer.from(ciphertext);
let plaintextB = Buffer.alloc(cipherB.length - sodium.crypto_secretbox_MACBYTES);
let nonceB = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES);
let keyB = Buffer.from(key);
/* eslint-enable no-undef */
sodium.crypto_secretbox_open_easy(plaintextB, cipherB, nonceB, keyB);
return plaintextB.toString();
}