Skip to content

Commit 23fce2e

Browse files
committed
refactor: optimized code, added support for custom storage, option to pass custom metaKey
1 parent ca7ec1f commit 23fce2e

File tree

7 files changed

+414
-452
lines changed

7 files changed

+414
-452
lines changed

src/Base64.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let Base64 = {
1+
const Base64 = {
22
_keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
33
encode: function (e) {
44
let t = '';
@@ -11,8 +11,8 @@ let Base64 = {
1111
r = e.charCodeAt(f++);
1212
i = e.charCodeAt(f++);
1313
s = n >> 2;
14-
o = (n & 3) << 4 | r >> 4;
15-
u = (r & 15) << 2 | i >> 6;
14+
o = ((n & 3) << 4) | (r >> 4);
15+
u = ((r & 15) << 2) | (i >> 6);
1616
a = i & 63;
1717
if (isNaN(r)) {
1818
u = a = 64;
@@ -29,15 +29,15 @@ let Base64 = {
2929
let s, o, u, a;
3030
let f = 0;
3131

32-
e = e.replace(/[^A-Za-z0-9\+\/\=]/g, '');
32+
e = e.replace(/[^A-Za-z0-9+/=]/g, '');
3333
while (f < e.length) {
3434
s = this._keyStr.indexOf(e.charAt(f++));
3535
o = this._keyStr.indexOf(e.charAt(f++));
3636
u = this._keyStr.indexOf(e.charAt(f++));
3737
a = this._keyStr.indexOf(e.charAt(f++));
38-
n = s << 2 | o >> 4;
39-
r = (o & 15) << 4 | u >> 2;
40-
i = (u & 3) << 6 | a;
38+
n = (s << 2) | (o >> 4);
39+
r = ((o & 15) << 4) | (u >> 2);
40+
i = ((u & 3) << 6) | a;
4141
t = t + String.fromCharCode(n);
4242
if (u !== 64) {
4343
t = t + String.fromCharCode(r);
@@ -59,12 +59,12 @@ let Base64 = {
5959
if (r < 128) {
6060
t += String.fromCharCode(r);
6161
} else if (r > 127 && r < 2048) {
62-
t += String.fromCharCode(r >> 6 | 192);
63-
t += String.fromCharCode(r & 63 | 128);
62+
t += String.fromCharCode((r >> 6) | 192);
63+
t += String.fromCharCode((r & 63) | 128);
6464
} else {
65-
t += String.fromCharCode(r >> 12 | 224);
66-
t += String.fromCharCode(r >> 6 & 63 | 128);
67-
t += String.fromCharCode(r & 63 | 128);
65+
t += String.fromCharCode((r >> 12) | 224);
66+
t += String.fromCharCode(((r >> 6) & 63) | 128);
67+
t += String.fromCharCode((r & 63) | 128);
6868
}
6969
}
7070
return t;
@@ -82,17 +82,17 @@ let Base64 = {
8282
n++;
8383
} else if (r > 191 && r < 224) {
8484
c2 = e.charCodeAt(n + 1);
85-
t += String.fromCharCode((r & 31) << 6 | c2 & 63);
85+
t += String.fromCharCode(((r & 31) << 6) | (c2 & 63));
8686
n += 2;
8787
} else {
8888
c2 = e.charCodeAt(n + 1);
8989
c3 = e.charCodeAt(n + 2);
90-
t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
90+
t += String.fromCharCode(((r & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
9191
n += 3;
9292
}
9393
}
9494
return t;
95-
}
95+
},
9696
};
9797

98-
module.exports = Base64;
98+
export default Base64;

src/SecureLS.js

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import constants from './constants';
2+
import enc from './enc-utf8';
3+
import utils from './utils';
4+
5+
import AES from 'crypto-js/aes';
6+
import RABBIT from 'crypto-js/rabbit';
7+
import RC4 from 'crypto-js/rc4';
8+
import DES from 'crypto-js/tripledes';
9+
import { compressToUTF16, decompressFromUTF16 } from 'lz-string/libs/lz-string';
10+
import Base64 from './Base64';
11+
12+
const encryptors = {
13+
[constants.EncrytionTypes.AES]: AES,
14+
[constants.EncrytionTypes.DES]: DES,
15+
[constants.EncrytionTypes.RABBIT]: RABBIT,
16+
[constants.EncrytionTypes.RC4]: RC4,
17+
};
18+
19+
export class SecureLS {
20+
constructor({
21+
encryptionSecret = '',
22+
encryptionNamespace = '',
23+
isCompression = true,
24+
encodingType = constants.EncrytionTypes.BASE64,
25+
storage = localStorage,
26+
metaKey = constants.metaKey,
27+
} = {}) {
28+
// Assign libraries and utilities
29+
Object.assign(this, {
30+
_name: 'secure-ls',
31+
Base64,
32+
LZString: { compressToUTF16, decompressFromUTF16 },
33+
AES,
34+
DES,
35+
RABBIT,
36+
RC4,
37+
enc,
38+
});
39+
40+
// Configuration and property assignment
41+
this.config = {
42+
encryptionSecret,
43+
encryptionNamespace,
44+
isCompression,
45+
encodingType: encodingType.toLowerCase(),
46+
storage,
47+
metaKey,
48+
};
49+
this.encryptionSecret = encryptionSecret;
50+
this.storage = storage;
51+
this.metaKey = metaKey;
52+
53+
// Initialize the class
54+
this.init();
55+
}
56+
57+
init() {
58+
let metaData = this.getMetaData();
59+
60+
this._isBase64 = this._isBase64EncryptionType();
61+
this._isAES = this._isAESEncryptionType();
62+
this._isDES = this._isDESEncryptionType();
63+
this._isRabbit = this._isRabbitEncryptionType();
64+
this._isRC4 = this._isRC4EncryptionType();
65+
this._isCompression = this._isDataCompressionEnabled();
66+
67+
// fill the already present keys to the list of keys being used by secure-ls
68+
this.allKeys = metaData.keys || this.resetAllKeys();
69+
}
70+
71+
_isBase64EncryptionType() {
72+
return (
73+
Base64 &&
74+
(typeof this.config.encodingType === 'undefined' || this.config.encodingType === constants.EncrytionTypes.BASE64)
75+
);
76+
}
77+
78+
_isAESEncryptionType() {
79+
return AES && this.config.encodingType === constants.EncrytionTypes.AES;
80+
}
81+
82+
_isDESEncryptionType() {
83+
return DES && this.config.encodingType === constants.EncrytionTypes.DES;
84+
}
85+
86+
_isRabbitEncryptionType() {
87+
return RABBIT && this.config.encodingType === constants.EncrytionTypes.RABBIT;
88+
}
89+
90+
_isRC4EncryptionType() {
91+
return RC4 && this.config.encodingType === constants.EncrytionTypes.RC4;
92+
}
93+
94+
_isDataCompressionEnabled() {
95+
return this.config.isCompression;
96+
}
97+
98+
getEncryptionSecret(key) {
99+
let metaData = this.getMetaData();
100+
let obj = utils.getObjectFromKey(metaData.keys, key);
101+
102+
if (!obj) {
103+
return;
104+
}
105+
106+
if (this._isAES || this._isDES || this._isRabbit || this._isRC4) {
107+
if (typeof this.config.encryptionSecret === 'undefined') {
108+
this.encryptionSecret = obj.s;
109+
110+
if (!this.encryptionSecret) {
111+
this.encryptionSecret = utils.generateSecretKey();
112+
this.setMetaData();
113+
}
114+
} else {
115+
this.encryptionSecret = this.config.encryptionSecret || obj.s || '';
116+
}
117+
}
118+
}
119+
120+
getEncryptionType() {
121+
const encodingType = this.config.encodingType;
122+
return encodingType ? encodingType.toLowerCase() : constants.EncrytionTypes.BASE64;
123+
}
124+
125+
getDataFromLocalStorage(key) {
126+
return this.storage.getItem(key, true);
127+
}
128+
129+
setDataToLocalStorage(key, data) {
130+
this.storage.setItem(key, data);
131+
}
132+
133+
setMetaData() {
134+
let dataToStore = this.processData(
135+
{
136+
keys: this.allKeys,
137+
},
138+
true,
139+
);
140+
141+
// Store the data to localStorage
142+
this.setDataToLocalStorage(this.getMetaKey(), dataToStore);
143+
}
144+
145+
getMetaData() {
146+
return this.get(this.getMetaKey(), true) || {};
147+
}
148+
149+
getMetaKey() {
150+
return this.metaKey + (this.config.encryptionNamespace ? '__' + this.config.encryptionNamespace : '');
151+
}
152+
153+
resetAllKeys() {
154+
this.allKeys = [];
155+
return [];
156+
}
157+
158+
processData(data, isAllKeysData) {
159+
if (data === null || data === undefined || data === '') {
160+
return '';
161+
}
162+
163+
let jsonData;
164+
165+
try {
166+
jsonData = JSON.stringify(data);
167+
} catch (err) {
168+
throw new Error('Could not stringify data', err);
169+
}
170+
171+
// Encode Based on encoding type
172+
// If not set, default to Base64 for securing data
173+
let encodedData = jsonData;
174+
175+
if (this._isBase64 || isAllKeysData) {
176+
encodedData = Base64.encode(jsonData);
177+
} else {
178+
const encryptor = encryptors[this.getEncryptionType()];
179+
if (encryptor) {
180+
encodedData = encryptor.encrypt(jsonData, this.encryptionSecret);
181+
}
182+
183+
encodedData = encodedData && encodedData.toString();
184+
}
185+
186+
// Compress data if set to true
187+
let compressedData = encodedData;
188+
if (this._isCompression || isAllKeysData) {
189+
compressedData = this.LZString.compressToUTF16(encodedData);
190+
}
191+
192+
return compressedData;
193+
}
194+
195+
// PUBLIC APIs
196+
getAllKeys() {
197+
let data = this.getMetaData();
198+
199+
return utils.extractKeyNames(data) || [];
200+
}
201+
202+
get(key, isAllKeysData) {
203+
let decodedData = '';
204+
let jsonData = '';
205+
206+
if (!utils.is(key)) {
207+
utils.warn(constants.WarningEnum.KEY_NOT_PROVIDED);
208+
return jsonData;
209+
}
210+
211+
let data = this.getDataFromLocalStorage(key);
212+
213+
if (!data) {
214+
return jsonData;
215+
}
216+
217+
let deCompressedData = data; // saves else
218+
if (this._isCompression || isAllKeysData) {
219+
// meta data always compressed
220+
deCompressedData = this.LZString.decompressFromUTF16(data);
221+
}
222+
223+
decodedData = deCompressedData; // saves else
224+
if (this._isBase64 || isAllKeysData) {
225+
// meta data always Base64
226+
decodedData = Base64.decode(deCompressedData);
227+
} else {
228+
this.getEncryptionSecret(key);
229+
const encryptor = encryptors[this.getEncryptionType()];
230+
231+
if (encryptor) {
232+
const bytes = encryptor.decrypt(deCompressedData.toString(), this.encryptionSecret);
233+
234+
if (bytes) {
235+
decodedData = bytes.toString(enc._Utf8);
236+
}
237+
}
238+
}
239+
240+
try {
241+
jsonData = JSON.parse(decodedData);
242+
} catch (err) {
243+
throw new Error('Could not parse JSON', err);
244+
}
245+
246+
return jsonData;
247+
}
248+
249+
set(key, data) {
250+
let dataToStore = '';
251+
252+
if (!utils.is(key)) {
253+
utils.warn(constants.WarningEnum.KEY_NOT_PROVIDED);
254+
return;
255+
}
256+
257+
this.getEncryptionSecret(key);
258+
259+
// add key(s) to Array if not already added, only for keys other than meta key
260+
if (!(String(key) === String(this.metaKey))) {
261+
if (!utils.isKeyPresent(this.allKeys, key)) {
262+
this.allKeys.push({
263+
k: key,
264+
s: this.encryptionSecret,
265+
});
266+
this.setMetaData();
267+
}
268+
}
269+
270+
dataToStore = this.processData(data);
271+
// Store the data to localStorage
272+
this.setDataToLocalStorage(key, dataToStore);
273+
}
274+
275+
remove(key) {
276+
if (!utils.is(key)) {
277+
utils.warn(constants.WarningEnum.KEY_NOT_PROVIDED);
278+
return;
279+
}
280+
281+
if (key === this.metaKey && this.getAllKeys().length) {
282+
utils.warn(constants.WarningEnum.META_KEY_REMOVE);
283+
return;
284+
}
285+
286+
if (utils.isKeyPresent(this.allKeys, key)) {
287+
utils.removeFromKeysList(this.allKeys, key);
288+
this.setMetaData();
289+
}
290+
this.storage.removeItem(key);
291+
}
292+
293+
removeAll() {
294+
let keys = this.getAllKeys();
295+
296+
for (let i = 0; i < keys.length; i++) {
297+
this.storage.removeItem(keys[i]);
298+
}
299+
300+
this.storage.removeItem(this.metaKey);
301+
this.resetAllKeys();
302+
}
303+
304+
clear() {
305+
this.storage.clear();
306+
this.resetAllKeys();
307+
}
308+
}

0 commit comments

Comments
 (0)