|
| 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