@evervault/sdk
Advanced tools
Comparing version 0.1.0 to 0.2.0
275
index.js
@@ -5,41 +5,7 @@ /** @format */ | ||
module.exports = (MACHINE_NAME) => { | ||
const urlKey = window.location.hash.substring(2); | ||
window.location.hash = '/'; | ||
const cachedPrivateKey = localStorage.getItem('evervault-privateKey'); | ||
const accessToken = localStorage.getItem('evervault-accessToken'); | ||
const refreshToken = localStorage.getItem('evervault-refreshToken'); | ||
const authRedirectUrl = 'https://auth.evervault.com'; | ||
class EvervaultKeyStore { | ||
constructor() { | ||
let _privateKey = undefined; | ||
const hasStorageCredentials = accessToken && refreshToken && cachedPrivateKey; | ||
if (!hasStorageCredentials && !urlKey) { | ||
Utils.handleRedirect(`${authRedirectUrl}/${MACHINE_NAME}`); | ||
} else if (urlKey) { | ||
//user has key in url but not stored in memory | ||
Utils.setUserKeysInStorage(urlKey); | ||
} | ||
return { | ||
/** | ||
* @return void | ||
*/ | ||
logout: function() { | ||
localStorage.removeItem('evervault-privateKey'); | ||
localStorage.removeItem('evervault-accessToken'); | ||
localStorage.removeItem('evervault-refreshToken'); | ||
localStorage.removeItem('evervault-haiku'); | ||
Utils.handleRedirect(`${authRedirectUrl}/${MACHINE_NAME}`); | ||
}, | ||
getKey: async function(privateKey) { | ||
if (!evervault.privateKey) { | ||
const keyObj = await Utils.getEncryptionKey( | ||
privateKey ? privateKey : localStorage.getItem('evervault-privateKey') | ||
); | ||
evervault.privateKey = keyObj; | ||
} | ||
}, | ||
_encryptString: function(str) { | ||
this.encryptString = (str) => { | ||
const dataBuffer = Utils.dataToBuffer(str); | ||
@@ -53,3 +19,3 @@ const iv = window.crypto.getRandomValues(new Uint8Array(12)); | ||
}, | ||
evervault.privateKey, | ||
_privateKey, | ||
dataBuffer | ||
@@ -61,50 +27,5 @@ ) | ||
}); | ||
}, | ||
}; | ||
_encryptObject: function(data, fields, deep) { | ||
if (data) { | ||
const handleEncryption = async (obj, fields) => { | ||
let encryptedObject = Object.assign({}, obj); | ||
for (let index = 0; index < fields.length; index++) { | ||
let key = fields[index]; | ||
if (encryptedObject[key]) { | ||
encryptedObject[key] = await this._encryptString( | ||
Utils.ensureString(encryptedObject[key]) | ||
); | ||
} | ||
} | ||
return encryptedObject; | ||
}; | ||
return handleEncryption(data, fields); | ||
} | ||
//data is falsey - undefined | ||
return; | ||
}, | ||
/** | ||
* @param data: string or object containing the data to be encrypted | ||
* @param options: see evervault developer docs | ||
* @return Promise resolving to string to send to db | ||
*/ | ||
encrypt: function(data, options = {}) { | ||
if ( | ||
typeof data === 'object' && | ||
data && | ||
data.constructor.name !== 'Array' && | ||
options.preserveObjectShape | ||
) { | ||
const fields = options.fieldsToEncrypt || Object.keys(data); | ||
return this.getKey(options.privateKey).then(() => { | ||
return this._encryptObject(data, fields); | ||
}); | ||
} else if (typeof data !== 'undefined' && typeof data !== 'symbol') { | ||
return this.getKey(options.privateKey).then(() => { | ||
return this._encryptString(Utils.ensureString(data)); | ||
}); | ||
} | ||
return; | ||
}, | ||
_decryptEvString: function(str) { | ||
this.decryptString = (str) => { | ||
if (Utils.isEvervaultString(str)) { | ||
@@ -120,3 +41,3 @@ const parsedData = Utils.parseEvervaultString(str); | ||
}, | ||
evervault.privateKey, | ||
_privateKey, | ||
dataBuffer | ||
@@ -132,30 +53,64 @@ ) | ||
return str; | ||
}, | ||
}; | ||
_decryptObject: function(data, fields) { | ||
this.updateKey = async (newKey) => { | ||
if (!_privateKey) { | ||
const keyObj = await Utils.getEncryptionKey( | ||
newKey ? newKey : localStorage.getItem('evervault-privateKey') | ||
); | ||
_privateKey = keyObj; | ||
} | ||
}; | ||
} | ||
} | ||
module.exports = { | ||
keyStore: new EvervaultKeyStore(), | ||
encrypt: function(data, options = {}) { | ||
const _encryptObject = (data, fields) => { | ||
if (data) { | ||
const handleDecryption = async (obj, fields) => { | ||
let decryptedObject = Object.assign({}, obj); | ||
const handleEncryption = async (obj, fields) => { | ||
let encryptedObject = Object.assign({}, obj); | ||
for (let index = 0; index < fields.length; index++) { | ||
let key = fields[index]; | ||
if (decryptedObject[key]) { | ||
decryptedObject[key] = await this._decryptEvString( | ||
Utils.ensureString(decryptedObject[key]) | ||
if (encryptedObject[key]) { | ||
encryptedObject[key] = await this.keyStore.encryptString( | ||
Utils.ensureString(encryptedObject[key]) | ||
); | ||
} | ||
} | ||
return decryptedObject; | ||
return encryptedObject; | ||
}; | ||
return handleDecryption(data, fields); | ||
return handleEncryption(data, fields); | ||
} | ||
//data is falsey - undefined | ||
return; | ||
}, | ||
}; | ||
_decryptArray: function(data) { | ||
if ( | ||
typeof data === 'object' && | ||
data && | ||
data.constructor.name !== 'Array' && | ||
options.preserveObjectShape | ||
) { | ||
const fields = options.fieldsToEncrypt || Object.keys(data); | ||
return this.keyStore.updateKey(options.privateKey).then(() => { | ||
return _encryptObject(data, fields); | ||
}); | ||
} else if (typeof data !== 'undefined' && typeof data !== 'symbol') { | ||
return this.keyStore.updateKey(options.privateKey).then(() => { | ||
return this.keyStore.encryptString(Utils.ensureString(data)); | ||
}); | ||
} | ||
return; | ||
}, | ||
decrypt: function(data, privateKey) { | ||
const _decryptArray = (data) => { | ||
const handleDecryption = async (array) => { | ||
let decryptedArr = new Array(array.length); | ||
for (let index = 0; index < data.length; index++) { | ||
decryptedArr[index] = await this._decryptEvString( | ||
decryptedArr[index] = await this.keyStore.decryptString( | ||
Utils.ensureString(array[index]) | ||
@@ -168,43 +123,93 @@ ); | ||
return handleDecryption(data); | ||
}, | ||
}; | ||
/** | ||
* @param data: string or object containing encrypted data | ||
* @param options: decrypt options | ||
* @return Promise resolving to decrypted payload | ||
*/ | ||
decrypt: function(data, options = {}) { | ||
if (typeof data === 'object') { | ||
if (data.constructor.name === 'Array') { | ||
return this.getKey(options.privateKey).then(() => { | ||
return this._decryptArray(data); | ||
}); | ||
} | ||
return this.getKey(options.privateKey).then(() => { | ||
return this._decryptObject(data, Object.keys(data)); | ||
const _decryptObject = (data, fields) => { | ||
if (data) { | ||
const handleDecryption = async (obj, fields) => { | ||
let decryptedObject = Object.assign({}, obj); | ||
for (let index = 0; index < fields.length; index++) { | ||
let key = fields[index]; | ||
if (decryptedObject[key]) { | ||
decryptedObject[key] = await this.keyStore.decryptString( | ||
Utils.ensureString(decryptedObject[key]) | ||
); | ||
} | ||
} | ||
return decryptedObject; | ||
}; | ||
return handleDecryption(data, fields); | ||
} | ||
//data is falsey - undefined | ||
return; | ||
}; | ||
if (typeof data === 'object') { | ||
if (data.constructor.name === 'Array') { | ||
return this.keyStore.updateKey(privateKey).then(() => { | ||
return _decryptArray(data); | ||
}); | ||
} else if (typeof data !== 'undefined' && typeof data !== 'symbol') { | ||
return this.getKey(options.privateKey).then(() => { | ||
return this._decryptEvString(Utils.ensureString(data)); | ||
}); | ||
} | ||
return data; | ||
}, | ||
return this.keyStore.updateKey(privateKey).then(() => { | ||
return _decryptObject(data, Object.keys(data)); | ||
}); | ||
} else if (typeof data !== 'undefined' && typeof data !== 'symbol') { | ||
return this.keyStore.updateKey(privateKey).then(() => { | ||
return this.keyStore.decryptString(Utils.ensureString(data)); | ||
}); | ||
} | ||
return data; | ||
}, | ||
getUrlParameters: function() { | ||
return new Promise((resolve, reject) => { | ||
const urlKey = window.location.hash.substring(2); | ||
if (urlKey) { | ||
const params = urlKey.split(':'); | ||
resolve({ | ||
privateKey: params[0], | ||
authToken: params[1], | ||
refreshToken: params[2], | ||
}); | ||
} else { | ||
reject(new Error('URL parameters not found')); | ||
} | ||
logout: () => { | ||
localStorage.removeItem('evervault-privateKey'); | ||
localStorage.removeItem('evervault-accessToken'); | ||
localStorage.removeItem('evervault-refreshToken'); | ||
localStorage.removeItem('evervault-haiku'); | ||
Utils.handleRedirect(`${authRedirectUrl}/${MACHINE_NAME}`); | ||
}, | ||
refreshAccessToken: (accessToken, refreshToken) => { | ||
const requestBody = { | ||
accessToken: accessToken || localStorage.getItem('evervault-accessToken'), | ||
refreshToken: | ||
refreshToken || localStorage.getItem('evervault-refreshToken'), | ||
}; | ||
return fetch('https://api.evervault.dev/v1/token/refresh', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(requestBody), | ||
}) | ||
.then((res) => res.json()) | ||
.then(({ accessToken }) => accessToken) | ||
.catch((err) => { | ||
throw err; | ||
}); | ||
}, | ||
}; | ||
}, | ||
/** | ||
* This function checks your user's auth status and will either redirect them to login, or | ||
* will update their cached credentials | ||
* @param [string] appId - unique identifier for your app as given on the evervault dashboard | ||
*/ | ||
checkAuth: (appId) => { | ||
const urlKey = window.location.hash.substring(2); | ||
window.location.hash = '/'; | ||
const cachedPrivateKey = localStorage.getItem('evervault-privateKey'); | ||
const accessToken = localStorage.getItem('evervault-accessToken'); | ||
const refreshToken = localStorage.getItem('evervault-refreshToken'); | ||
const authRedirectUrl = process.ENV.AUTH_REDIRECT_URL; | ||
const hasStorageCredentials = | ||
accessToken && refreshToken && cachedPrivateKey; | ||
if (!hasStorageCredentials && !urlKey) { | ||
Utils.handleRedirect(`${authRedirectUrl}/${appId}`); | ||
} else if (urlKey) { | ||
//user has key in url but not stored in memory | ||
Utils.setUserKeysInStorage(urlKey); | ||
} | ||
}, | ||
}; |
{ | ||
"name": "@evervault/sdk", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "evervault Browser SDK", | ||
@@ -14,3 +14,3 @@ "repository": { | ||
"scripts": { | ||
"build": "./node_modules/.bin/webpack --mode=production" | ||
"build": "npx webpack --mode=production" | ||
}, | ||
@@ -20,2 +20,4 @@ "license": "MIT", | ||
"devDependencies": { | ||
"webpack": "^4.41.2", | ||
"webpack-cli": "^3.3.10", | ||
"@babel/core": "^7.6.2", | ||
@@ -25,6 +27,4 @@ "babel-preset-es2015": "^6.24.1", | ||
"chai": "^4.2.0", | ||
"mocha": "^7.0.1", | ||
"webpack": "^4.41.2", | ||
"webpack-cli": "^3.3.10" | ||
"mocha": "^7.0.1" | ||
} | ||
} |
@@ -7,18 +7,65 @@ <img src="https://raw.githubusercontent.com/evervault/evervault-sdk/master/logo.png" height="35" /> | ||
## API | ||
## API Reference | ||
## encrypt : <code>function</code> => <code>Promise</code> | ||
### checkAuth | ||
```javascript | ||
checkAuth(appId): void | ||
``` | ||
Check a user's auth status in the system, and redirect them if they are unauthenticated. | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| privateKey | <code>String</code> | the private key retrieved from the client's Local Storage | | ||
| data | <code>String or Object</code> | the data to be encrypted | | ||
| appId | `String` | The unique identifier for your app in the evervault system | | ||
### encrypt | ||
```javascript | ||
encrypt(data[, encryptOptions]): Promise<String> | ||
``` | ||
## decrypt : <code>function</code> => <code>Promise</code> | ||
Encrypt data using a user's secret key. The encrypt function will handle any data excluding `undefined` and `Symbol`. | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| privateKey | <code>String</code> | the private key retrieved from the client's Local Storage | | ||
| data | <code>String or Object</code> | the data to be decrypted | | ||
| data | `any` | the data to be encrypted | | ||
| [encryptOptions](#encryptoptions) | `object` | control how your data is encrypted | | ||
### encryptOptions | ||
| Key | Value | Description | | ||
| --- | --- | --- | | ||
| preserveObjectShape | `boolean` | if true, javascript objects will only have their values encrypted, if false objects will be stringified | | ||
| fieldsToEncrypt | `Array<String>` | a list of fields in an object to encrypt | | ||
| privateKey | `String` | a base64 representation of a user's evervault secret key | | ||
### decrypt | ||
```javascript | ||
decrypt(data[, privateKey]): Promise<String> | ||
``` | ||
Decrypt evervault encrypted data. Decrypt will preserve the shape of any object it's given (e.g. Array or Object). Any stringified data that enters decrypt will be parsable when it has been decrypted. | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| data | `any` | the data to be decrypted, be it an object, array or a string | | ||
| privateKey | `String` | a base64 representation of a user's evervault secret key | | ||
### logout | ||
```javascript | ||
logout(): void | ||
``` | ||
Remove a user's credentials and redirect them to evervault auth. | ||
### refreshAccessToken | ||
```javascript | ||
refreshAccessToken([accessToken, refreshToken]): Promise<Object> | ||
``` | ||
Refresh a user's access token in the evervault system. | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| accessToken | `String` | a user's evervault access token to authenticate them in your system | | ||
| refreshToken | `String` | a user's evervault refresh token to keep them authenticated | |
@@ -11,6 +11,7 @@ /** @format */ | ||
localStorage.setItem('evervault-haiku', 'big-bertha-21'); | ||
const ev = evervault('test-app'); | ||
const testEncStr = 'test string'; | ||
it('should return ev formatted encrypted string', function(done) { | ||
ev.encrypt(testEncStr) | ||
evervault | ||
.encrypt(testEncStr) | ||
.then((encString) => { | ||
@@ -34,4 +35,5 @@ const splitEncString = encString.split(':'); | ||
it('should decrypt and encrypted string', function(done) { | ||
ev.encrypt(testEncStr) | ||
.then((str) => ev.decrypt(str)) | ||
evervault | ||
.encrypt(testEncStr) | ||
.then((str) => evervault.decrypt(str)) | ||
.then((decStr) => { | ||
@@ -53,4 +55,5 @@ chai.assert.equal( | ||
it('should decrypt and encrypt object', function(done) { | ||
ev.encrypt(obj, { preserveObjectShape: true }) | ||
.then((encObj) => ev.decrypt(encObj)) | ||
evervault | ||
.encrypt(obj, { preserveObjectShape: true }) | ||
.then((encObj) => evervault.decrypt(encObj)) | ||
.then((decObj) => { | ||
@@ -68,4 +71,5 @@ chai.assert.deepEqual( | ||
it('should decrypt and encrypt object only one key of the object', function(done) { | ||
ev.encrypt(obj, { preserveObjectShape: true, fieldsToEncrypt: ['a'] }) | ||
.then((encObj) => ev.decrypt(encObj)) | ||
evervault | ||
.encrypt(obj, { preserveObjectShape: true, fieldsToEncrypt: ['a'] }) | ||
.then((encObj) => evervault.decrypt(encObj)) | ||
.then((decObj) => { | ||
@@ -83,4 +87,5 @@ chai.assert.deepEqual( | ||
it('should encrypt an object as a blob and decrypted to a stringified object', function(done) { | ||
ev.encrypt(obj) | ||
.then((encObj) => ev.decrypt(encObj)) | ||
evervault | ||
.encrypt(obj) | ||
.then((encObj) => evervault.decrypt(encObj)) | ||
.then((decObj) => { | ||
@@ -104,4 +109,5 @@ chai.assert.equal( | ||
it('should encrypt an array as a string and decrypt it to a parsable string', function(done) { | ||
ev.encrypt(arr) | ||
.then((encArr) => ev.decrypt(encArr)) | ||
evervault | ||
.encrypt(arr) | ||
.then((encArr) => evervault.decrypt(encArr)) | ||
.then((decArr) => { | ||
@@ -120,4 +126,5 @@ chai.assert.deepEqual( | ||
it('should encrypt numbers to strings and decrypt to parseable numbers', function(done) { | ||
ev.encrypt(test_number) | ||
.then((encNum) => ev.decrypt(encNum)) | ||
evervault | ||
.encrypt(test_number) | ||
.then((encNum) => evervault.decrypt(encNum)) | ||
.then((decNum) => { | ||
@@ -134,17 +141,24 @@ chai.assert.deepEqual( | ||
const hugeBin = BigInt( | ||
'0b11111111111111111111111111111111111111111111111111111' | ||
); | ||
it('should encrypt a bigint and decrypt it to a stringified bigint', function(done) { | ||
ev.encrypt(hugeBin) | ||
.then((encBin) => ev.decrypt(encBin)) | ||
.then((decBin) => { | ||
chai.assert.equal( | ||
decBin, | ||
hugeBin.toString(), | ||
'The decrypted bigint does not match the stringified big int' | ||
); | ||
done(); | ||
}); | ||
}); | ||
const userAgent = navigator.userAgent.toLowerCase(); | ||
if ( | ||
userAgent.indexOf('safari') === -1 && | ||
userAgent.indexOf('mozilla') === -1 | ||
) { | ||
const hugeBin = BigInt( | ||
'0b11111111111111111111111111111111111111111111111111111' | ||
); | ||
it('should encrypt a bigint and decrypt it to a stringified bigint', function(done) { | ||
evervault | ||
.encrypt(hugeBin) | ||
.then((encBin) => evervault.decrypt(encBin)) | ||
.then((decBin) => { | ||
chai.assert.equal( | ||
decBin, | ||
hugeBin.toString(), | ||
'The decrypted bigint does not match the stringified big int' | ||
); | ||
done(); | ||
}); | ||
}); | ||
} | ||
@@ -155,4 +169,5 @@ function fn() { | ||
it('should encrypt a function and decrypt it to a stringified function', function(done) { | ||
ev.encrypt(fn) | ||
.then((encFn) => ev.decrypt(encFn)) | ||
evervault | ||
.encrypt(fn) | ||
.then((encFn) => evervault.decrypt(encFn)) | ||
.then((decFn) => { | ||
@@ -172,4 +187,5 @@ chai.assert.equal( | ||
it('should encrypt an arrow function and decrypt it to a stringified arrow function', function(done) { | ||
ev.encrypt(arrowFn) | ||
.then((encFn) => ev.decrypt(encFn)) | ||
evervault | ||
.encrypt(arrowFn) | ||
.then((encFn) => evervault.decrypt(encFn)) | ||
.then((decFn) => { | ||
@@ -187,4 +203,5 @@ chai.assert.equal( | ||
it('should encrypt null and decrypt it to a parsable null string', function(done) { | ||
ev.encrypt(nullVal) | ||
.then((encNull) => ev.decrypt(encNull)) | ||
evervault | ||
.encrypt(nullVal) | ||
.then((encNull) => evervault.decrypt(encNull)) | ||
.then((decNull) => { | ||
@@ -206,3 +223,3 @@ chai.assert.equal( | ||
it('should ignore calls to encrypt with undefined values', function() { | ||
const result = ev.encrypt(undefined); | ||
const result = evervault.encrypt(undefined); | ||
chai.assert.isUndefined( | ||
@@ -215,3 +232,3 @@ result, | ||
it('should ignore calls to decrypt with undefined values', function() { | ||
const result = ev.decrypt(undefined); | ||
const result = evervault.decrypt(undefined); | ||
chai.assert.isUndefined( | ||
@@ -225,3 +242,3 @@ result, | ||
it('should ignore calls to encrypt with symbols', function() { | ||
const result = ev.encrypt(symbolTest); | ||
const result = evervault.encrypt(symbolTest); | ||
chai.assert.isUndefined( | ||
@@ -234,3 +251,3 @@ result, | ||
it('should ignore calls to decrypt with symbols', function() { | ||
const result = ev.decrypt(symbolTest); | ||
const result = evervault.decrypt(symbolTest); | ||
chai.assert.equal( | ||
@@ -245,3 +262,3 @@ result, | ||
it('should ignore calls to decrypt with unencrypted data', function(done) { | ||
ev.decrypt(testDecrypt).then((result) => { | ||
evervault.decrypt(testDecrypt).then((result) => { | ||
chai.assert.equal( | ||
@@ -248,0 +265,0 @@ result, |
22
utils.js
@@ -26,10 +26,14 @@ /** @format */ | ||
static ensureString(data) { | ||
const isString = typeof data === 'string'; | ||
if (!isString) { | ||
if (['bigint', 'function'].includes(typeof data)) { | ||
return data.toString(); | ||
const getString = (data) => { | ||
const isString = typeof data === 'string'; | ||
if (!isString) { | ||
if (['bigint', 'function'].includes(typeof data)) { | ||
return data.toString(); | ||
} | ||
return JSON.stringify(data); | ||
} | ||
return JSON.stringify(data); | ||
} | ||
return data; | ||
return data; | ||
}; | ||
return getString(data).trim(); | ||
} | ||
@@ -77,4 +81,4 @@ | ||
static isEvervaultString(str) { | ||
const splitString = str.split(':'); | ||
return splitString.length >= 4 && splitString[0] === 'enc'; | ||
const encryptionSchemeRegex = /^enc:v\d:(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?:(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/s; | ||
return encryptionSchemeRegex.test(str); | ||
} | ||
@@ -81,0 +85,0 @@ |
/** @format */ | ||
const webpack = require('webpack'); | ||
module.exports = { | ||
@@ -6,0 +4,0 @@ entry: './index.js', |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
43317
13
566
71
1