@neoskop/ethereal-secrets-client
Advanced tools
Comparing version 4.0.0 to 4.0.1
@@ -19,5 +19,3 @@ export interface EtherealSecretsClientConfig { | ||
private _key; | ||
private _agent; | ||
constructor(config: EtherealSecretsClientConfig); | ||
private fromBase64; | ||
private toBase64; | ||
@@ -24,0 +22,0 @@ private decrypt; |
@@ -10,3 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { agent } from 'superagent'; | ||
import axios from 'axios'; | ||
export class EtherealSecretsClient { | ||
@@ -19,19 +19,5 @@ constructor(config) { | ||
: config.endpoint + '/'; | ||
this._agent = agent(); | ||
} | ||
fromBase64(data) { | ||
return new Uint8Array(window | ||
.atob(data) | ||
.split('') | ||
.map(function (c) { | ||
return c.charCodeAt(0); | ||
})); | ||
} | ||
toBase64(data) { | ||
const chunkSize = 0x1000; | ||
const chunks = []; | ||
for (let i = 0; i < data.length; i += chunkSize) { | ||
chunks.push(String.fromCharCode.apply(null, data.subarray(i, i + chunkSize))); | ||
} | ||
return window.btoa(chunks.join('')); | ||
return Buffer.from(data).toString('base64'); | ||
} | ||
@@ -41,8 +27,8 @@ decrypt(secret, cipherText) { | ||
const encryptedObj = JSON.parse(cipherText); | ||
const iv = this.fromBase64(encryptedObj.iv); | ||
const salt = this.fromBase64(encryptedObj.salt); | ||
const data = this.fromBase64(encryptedObj.encrypted); | ||
const additionalData = this.fromBase64(encryptedObj.additionalData); | ||
const iv = Buffer.from(encryptedObj.iv, 'base64'); | ||
const salt = Buffer.from(encryptedObj.salt, 'base64'); | ||
const data = Buffer.from(encryptedObj.encrypted, 'base64'); | ||
const additionalData = Buffer.from(encryptedObj.additionalData, 'base64'); | ||
const key = yield this.deriveKey(secret, salt); | ||
return new TextDecoder('utf8').decode(yield window.crypto.subtle.decrypt({ | ||
const clearText = yield window.crypto.subtle.decrypt({ | ||
name: 'AES-GCM', | ||
@@ -52,3 +38,4 @@ iv, | ||
additionalData, | ||
}, key, data)); | ||
}, key, data); | ||
return new TextDecoder('utf8').decode(clearText); | ||
}); | ||
@@ -70,6 +57,6 @@ } | ||
return JSON.stringify({ | ||
encrypted: this.toBase64(new Uint8Array(encrypted)), | ||
salt: this.toBase64(salt), | ||
iv: this.toBase64(iv), | ||
additionalData: this.toBase64(additionalData), | ||
encrypted: Buffer.from(new Uint8Array(encrypted)).toString('base64'), | ||
salt: Buffer.from(salt).toString('base64'), | ||
iv: Buffer.from(iv).toString('base64'), | ||
additionalData: Buffer.from(additionalData).toString('base64'), | ||
}); | ||
@@ -110,42 +97,41 @@ }); | ||
} | ||
getRemote(fragmentIdentifier, options = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
const keys = this.parseFragmentIdentifier(fragmentIdentifier); | ||
const { secondFactor } = options; | ||
const res = yield this._agent | ||
.get(this._endpoint + | ||
keys.remoteKey + | ||
(secondFactor ? `?secondFactor=${secondFactor}` : '')) | ||
.accept('application/json'); | ||
if (!res.body.data) { | ||
throw new Error('The server did not answer with any data'); | ||
} | ||
const clearText = yield this.decrypt(keys.localKey, res.body.data); | ||
const result = { | ||
clearText: clearText, | ||
}; | ||
if (res.body.expiryDate) { | ||
result.expiryDate = new Date(res.body.expiryDate); | ||
} | ||
return result; | ||
getRemote(fragmentIdentifier_1) { | ||
return __awaiter(this, arguments, void 0, function* (fragmentIdentifier, options = {}) { | ||
const keys = this.parseFragmentIdentifier(fragmentIdentifier); | ||
const { secondFactor } = options; | ||
const res = yield axios.get(this._endpoint + | ||
keys.remoteKey + | ||
(secondFactor ? `?secondFactor=${secondFactor}` : ''), { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
if (!res.data.data) { | ||
throw new Error('The server did not answer with any data'); | ||
} | ||
catch (err) { | ||
throw new Error(err.message); | ||
const clearText = yield this.decrypt(keys.localKey, res.data.data); | ||
const result = { | ||
clearText: clearText, | ||
}; | ||
if (res.data.expiryDate) { | ||
result.expiryDate = new Date(res.data.expiryDate); | ||
} | ||
return result; | ||
}); | ||
} | ||
removeRemote(fragmentIdentifier, options = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
removeRemote(fragmentIdentifier_1) { | ||
return __awaiter(this, arguments, void 0, function* (fragmentIdentifier, options = {}) { | ||
const keys = this.parseFragmentIdentifier(fragmentIdentifier); | ||
const { secondFactor } = options; | ||
yield this._agent | ||
.del(this._endpoint + | ||
yield axios.delete(this._endpoint + | ||
keys.remoteKey + | ||
(secondFactor ? `?secondFactor=${secondFactor}` : '')) | ||
.accept('application/json'); | ||
(secondFactor ? `?secondFactor=${secondFactor}` : ''), { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
}); | ||
} | ||
saveRemote(clearText, options = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
saveRemote(clearText_1) { | ||
return __awaiter(this, arguments, void 0, function* (clearText, options = {}) { | ||
try { | ||
@@ -155,14 +141,15 @@ const secret = this.generateLocalSecret(); | ||
const { secondFactor } = options; | ||
const res = yield this._agent | ||
.post(this._endpoint) | ||
.send({ data: cipherText.toString(), secondFactor }) | ||
.accept('application/json'); | ||
if (!res.body.hasOwnProperty('key')) { | ||
const res = yield axios.post(this._endpoint, { data: cipherText.toString(), secondFactor }, { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
if (!res.data.hasOwnProperty('key')) { | ||
throw new Error('The server did not answer with a key'); | ||
} | ||
const result = { | ||
fragmentIdentifier: res.body.key + ';' + secret, | ||
fragmentIdentifier: res.data.key + ';' + secret, | ||
}; | ||
if (res.body.hasOwnProperty('expiryDate')) { | ||
result.expiryDate = new Date(res.body.expiryDate); | ||
if (res.data.hasOwnProperty('expiryDate')) { | ||
result.expiryDate = new Date(res.data.expiryDate); | ||
} | ||
@@ -214,18 +201,15 @@ return result; | ||
} | ||
try { | ||
const res = yield this._agent | ||
.get(this._endpoint) | ||
.withCredentials() | ||
.accept('application/json'); | ||
if (!res.body.key) { | ||
throw new Error('The server did not answer with a key'); | ||
} | ||
if (this._cacheKey) { | ||
this._key = res.body.key; | ||
} | ||
return res.body.key; | ||
const res = yield axios.get(this._endpoint, { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
withCredentials: true, | ||
}); | ||
if (!res.data.key) { | ||
throw new Error('The server did not answer with a key'); | ||
} | ||
catch (err) { | ||
throw new Error(err.message); | ||
if (this._cacheKey) { | ||
this._key = res.data.key; | ||
} | ||
return res.data.key; | ||
}); | ||
@@ -232,0 +216,0 @@ } |
/** | ||
* @jest-environment jsdom | ||
* @jest-environment-options {"url": "http://server:8080"} | ||
*/ | ||
export {}; |
/** | ||
* @jest-environment jsdom | ||
* @jest-environment-options {"url": "http://server:8080"} | ||
*/ | ||
@@ -18,8 +19,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
Object.getOwnPropertyNames(config).forEach((key) => { | ||
if (config.hasOwnProperty(key)) { | ||
defaultConfig[key] = config[key]; | ||
} | ||
}); | ||
return new EtherealSecretsClient(defaultConfig); | ||
return new EtherealSecretsClient(Object.assign(defaultConfig, config)); | ||
} | ||
@@ -75,7 +71,7 @@ describe('Ethereal Secrets Client', () => { | ||
const sut = createClient(); | ||
return expect(sut.getRemote('invalididentifier')).rejects.toThrowError('invalid'); | ||
return expect(sut.getRemote('invalididentifier')).rejects.toThrow('invalid'); | ||
}); | ||
it('should reject unknown fragment identifier', () => { | ||
const sut = createClient(); | ||
return expect(sut.getRemote('decafbad-dead-dead-dead-decafbadadad;decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad')).rejects.toThrowError('Not Found'); | ||
return expect(sut.getRemote('decafbad-dead-dead-dead-decafbadadad;decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad')).rejects.toThrow(/404/); | ||
}); | ||
@@ -86,8 +82,7 @@ it('should not return data when the data was deleted beforehand', () => __awaiter(void 0, void 0, void 0, function* () { | ||
yield sut.removeRemote(result.fragmentIdentifier); | ||
return expect(sut.getRemote(result.fragmentIdentifier)).rejects.toThrowError('Not Found'); | ||
return expect(sut.getRemote(result.fragmentIdentifier)).rejects.toThrow(/404/); | ||
})); | ||
it('should allow storage of large inputs', () => { | ||
const sut = createClient(); | ||
const randomValues = new Uint8Array(200000); | ||
window.crypto.getRandomValues(randomValues); | ||
const randomValues = window.crypto.getRandomValues(new Uint8Array(20000)); | ||
const bigInput = Array.from(randomValues, (dec) => { | ||
@@ -108,3 +103,3 @@ return dec.toString(16).padStart(2, '0'); | ||
const result = yield sut.saveRemote('foo', { secondFactor: 'bar' }); | ||
return expect(sut.getRemote(result.fragmentIdentifier)).rejects.toThrowError(/unauthorized/i); | ||
return expect(sut.getRemote(result.fragmentIdentifier)).rejects.toThrow(/401/); | ||
})); | ||
@@ -114,3 +109,3 @@ it('should throw error when data stored with second factor is tried to be deleted without', () => __awaiter(void 0, void 0, void 0, function* () { | ||
const result = yield sut.saveRemote('foo', { secondFactor: 'bar' }); | ||
return expect(sut.removeRemote(result.fragmentIdentifier)).rejects.toThrowError(/unauthorized/i); | ||
return expect(sut.removeRemote(result.fragmentIdentifier)).rejects.toThrow(/401/); | ||
})); | ||
@@ -120,3 +115,3 @@ it('should throw error when data stored with second factor is tried to be deleted with the wrong second factor', () => __awaiter(void 0, void 0, void 0, function* () { | ||
const result = yield sut.saveRemote('foo', { secondFactor: 'baz' }); | ||
return expect(sut.removeRemote(result.fragmentIdentifier)).rejects.toThrowError(/unauthorized/i); | ||
return expect(sut.removeRemote(result.fragmentIdentifier)).rejects.toThrow(/401/); | ||
})); | ||
@@ -123,0 +118,0 @@ it('should allow deletion of data stored with second factor', () => __awaiter(void 0, void 0, void 0, function* () { |
@@ -1,8 +0,5 @@ | ||
import * as crypto from 'crypto'; | ||
import { webcrypto } from 'crypto'; | ||
import { TextEncoder, TextDecoder } from 'util'; | ||
Object.defineProperty(global.self, 'crypto', { | ||
value: { | ||
subtle: crypto.webcrypto.subtle, | ||
getRandomValues: (arr) => crypto.randomBytes(arr.length), | ||
}, | ||
Object.defineProperty(globalThis, 'crypto', { | ||
value: webcrypto, | ||
}); | ||
@@ -9,0 +6,0 @@ global.TextEncoder = TextEncoder; |
{ | ||
"name": "@neoskop/ethereal-secrets-client", | ||
"version": "4.0.0", | ||
"version": "4.0.1", | ||
"main": "dist/index.js", | ||
@@ -20,9 +20,7 @@ "typings": "dist/index.d.ts", | ||
"devDependencies": { | ||
"@peculiar/webcrypto": "1.4.3", | ||
"@types/jest": "29.5.5", | ||
"@types/superagent": "4.1.19", | ||
"concurrently": "8.2.1", | ||
"@types/jest": "29.5.12", | ||
"concurrently": "8.2.2", | ||
"jest": "29.7.0", | ||
"jest-environment-jsdom": "29.7.0", | ||
"jsdom": "22.1.0", | ||
"jsdom": "24.0.0", | ||
"jsdom-global": "3.0.2", | ||
@@ -32,10 +30,10 @@ "mock-local-storage": "1.1.24", | ||
"parse5": "7.1.2", | ||
"prettier": "3.0.3", | ||
"rimraf": "5.0.4", | ||
"ts-jest": "29.1.1", | ||
"ts-node": "10.9.1", | ||
"typescript": "5.2.2" | ||
"prettier": "3.2.5", | ||
"rimraf": "5.0.5", | ||
"ts-jest": "29.1.2", | ||
"ts-node": "10.9.2", | ||
"typescript": "5.4.5" | ||
}, | ||
"dependencies": { | ||
"superagent": "8.1.2" | ||
"axios": "1.6.8" | ||
}, | ||
@@ -42,0 +40,0 @@ "engines": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
14
32680
380
+ Addedaxios@1.6.8
+ Addedaxios@1.6.8(transitive)
+ Addedfollow-redirects@1.15.9(transitive)
+ Addedproxy-from-env@1.1.0(transitive)
- Removedsuperagent@8.1.2
- Removedasap@2.0.6(transitive)
- Removedcall-bind-apply-helpers@1.0.1(transitive)
- Removedcall-bound@1.0.3(transitive)
- Removedcomponent-emitter@1.3.1(transitive)
- Removedcookiejar@2.1.4(transitive)
- Removeddebug@4.4.0(transitive)
- Removeddezalgo@1.0.4(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.1.1(transitive)
- Removedfast-safe-stringify@2.1.1(transitive)
- Removedformidable@2.1.2(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.7(transitive)
- Removedget-proto@1.0.1(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhexoid@1.0.0(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedmethods@1.1.2(transitive)
- Removedmime@2.6.0(transitive)
- Removedms@2.1.3(transitive)
- Removedobject-inspect@1.13.3(transitive)
- Removedonce@1.4.0(transitive)
- Removedqs@6.14.0(transitive)
- Removedsemver@7.6.3(transitive)
- Removedside-channel@1.1.0(transitive)
- Removedside-channel-list@1.0.0(transitive)
- Removedside-channel-map@1.0.1(transitive)
- Removedside-channel-weakmap@1.0.2(transitive)
- Removedsuperagent@8.1.2(transitive)
- Removedwrappy@1.0.2(transitive)