Skip to content

Commit

Permalink
Issue eventuate-clients#49: support asynchronous Encryption Key Store
Browse files Browse the repository at this point in the history
  • Loading branch information
dartvandru committed May 2, 2018
1 parent a6047a9 commit 2fb8df2
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 97 deletions.
14 changes: 10 additions & 4 deletions src/modules/Encryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ export default class Encryption {
}

encrypt(encryptionKeyId, eventData) {
const cipher = this.cipher(this.findKey(encryptionKeyId), eventData);
return `${this.prefix}${JSON.stringify({encryptionKeyId, data: cipher})}`;
return this.findKey(encryptionKeyId)
.then(key => {
const cipher = this.cipher(key, eventData);
return `${this.prefix}${JSON.stringify({encryptionKeyId, data: cipher})}`;
});
}

decrypt(encryptedEventData) {
const { encryptionKeyId, data } = JSON.parse(encryptedEventData.split(this.prefix)[1]);
return this.decipher(this.findKey(encryptionKeyId), data);
return this.findKey(encryptionKeyId)
.then(key => {
return this.decipher(key, data);
});
}

cipher(key, text) {
Expand All @@ -29,7 +35,7 @@ export default class Encryption {
}

findKey(id) {
return this.encryptionKeyStore[id];
return this.encryptionKeyStore.get(id);
}

isEncrypted(eventDataStr) {
Expand Down
141 changes: 78 additions & 63 deletions src/modules/EventuateClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,19 @@ export default class EventuateClient {
options = rest;

let events = this.prepareEvents(_events);
// Encrypt event data if needed
events = this.encryptEvents(encryptionKeyId, events);

const jsonData = { entityTypeName, events };
this.addBodyOptions(jsonData, options);
// Encrypt event data if needed
this.encryptEvents(encryptionKeyId, events)
.then(events => {
console.log('events:', events);
const jsonData = { entityTypeName, events };
this.addBodyOptions(jsonData, options);

const urlPath = path.join(this.baseUrlPath, this.spaceName);
const requestOptions = { path: urlPath, method: 'POST', apiKey: this.apiKey, jsonData, client: this };
const urlPath = path.join(this.baseUrlPath, this.spaceName);
const requestOptions = { path: urlPath, method: 'POST', apiKey: this.apiKey, jsonData, client: this };

this.attemptOperation({ handler: this.httpRequest, arg: requestOptions, retryConditionFn, context: this })
return this.attemptOperation({ handler: this.httpRequest, arg: requestOptions, retryConditionFn, context: this });
})
.then(({ res: httpResponse, body: jsonBody }) => {

const { entityId, entityVersion, eventIds } = jsonBody;
Expand Down Expand Up @@ -163,12 +166,10 @@ export default class EventuateClient {
.then(({ res: httpResponse, body: jsonBody }) => {

let { events } = jsonBody;
if (this.encryption) {
events = this.decryptEvens(events);
}

events = this.eventDataToObject(events);
result.success(events);
return this.decryptEvens(events);
})
.then(events => {
result.success(this.eventDataToObject(events));
})
.catch(err => {
result.failure(err);
Expand All @@ -195,21 +196,19 @@ export default class EventuateClient {
options = rest;

let events = this.prepareEvents(_events);
// Encrypt event data if needed
events = this.encryptEvents(encryptionKeyId, events);
const jsonData = {
entityId,
entityVersion,
events
};

this.addBodyOptions(jsonData, options);
// Encrypt event data if needed
this.encryptEvents(encryptionKeyId, events)
.then(events => {

const urlPath = path.join(this.baseUrlPath, this.spaceName, entityTypeName, entityId);
const jsonData = { entityId, entityVersion, events };
this.addBodyOptions(jsonData, options);

const requestOptions = { path: urlPath, method: 'POST', apiKey: this.apiKey, jsonData, client: this };
const urlPath = path.join(this.baseUrlPath, this.spaceName, entityTypeName, entityId);
const requestOptions = { path: urlPath, method: 'POST', apiKey: this.apiKey, jsonData, client: this };

this.attemptOperation({ handler: this.httpRequest, arg: requestOptions, retryConditionFn, context: this })
return this.attemptOperation({ handler: this.httpRequest, arg: requestOptions, retryConditionFn, context: this })
})
.then(({ res: httpResponse, body: jsonBody }) => {

const { entityId, entityVersion, eventIds} = jsonBody;
Expand Down Expand Up @@ -371,16 +370,16 @@ export default class EventuateClient {

body.forEach(eventStr => {

const { error, event } = this.makeEvent(eventStr, headers.ack);

if (error) {
throw new Error(error);
}

eventHandler(event)
.then(acknowledge)
.catch(err => {
logger.error('eventHandler error', err);
this.makeEvent(eventStr, headers.ack)
.then(event => {
eventHandler(event)
.then(acknowledge)
.catch(err => {
logger.error('eventHandler error', err);
});
})
.catch(error => {
throw new Error(error);
});
});
}
Expand Down Expand Up @@ -587,29 +586,31 @@ export default class EventuateClient {
}

makeEvent(eventStr, ack) {

try {
const parsedEvent = JSON.parse(eventStr);
const { id: eventId, eventType, entityId, entityType, swimlane, eventToken } = parsedEvent;
let { eventData: eventDataStr } = parsedEvent;
} catch (err) {
return Promise.reject(err);
}

eventDataStr = this.decrypt(eventDataStr);
const eventData = JSON.parse(eventDataStr);

const event = {
eventId,
eventType,
entityId,
swimlane,
eventData,
eventToken,
ack,
entityType: entityType.split('/').pop(),
};
return this.decrypt(eventDataStr)
.then(decryptedEventDataStr => {
const eventData = JSON.parse(decryptedEventDataStr);

return {
eventId,
eventType,
entityId,
swimlane,
eventData,
eventToken,
ack,
entityType: entityType.split('/').pop(),
};
});

return { event };
} catch (error) {
return { error };
}
}

serialiseObject(obj) {
Expand Down Expand Up @@ -719,39 +720,53 @@ export default class EventuateClient {
}

encryptEvents(encryptionKeyId, events) {
return events.map(({ eventData, ...rest }) => {
eventData = this.encrypt(encryptionKeyId, eventData);
return { eventData, ...rest };
const promises = events.map(({ eventData }) => {
return this.encrypt(encryptionKeyId, eventData);
});
return Promise.all(promises)
.then(encryptedEventDataArr => {
return events.map(({ eventData, ...rest }, index) => {
return {
eventData: encryptedEventDataArr[index],
...rest
};
})
});
}

decryptEvens(events) {
return events.map(({ eventData, ...rest }) => {
eventData = this.decrypt(eventData);
return { eventData, ...rest };
});
const promises = events.map(({ eventData }) => {
return this.decrypt(eventData);
});
return Promise.all(promises)
.then(decryptedEventDataArr => {
return events.map(({ eventData, ...rest }, index) => {
return {
eventData: decryptedEventDataArr[index],
...rest
};
})
});
}

encrypt(encryptionKeyId, eventData) {
if (encryptionKeyId && this.encryption) {
eventData = this.encryption.encrypt(encryptionKeyId, eventData);
return this.encryption.encrypt(encryptionKeyId, eventData);
}
return eventData;
return Promise.resolve(eventData);
}

decrypt(eventDataStr) {
if (this.encryption && this.encryption.isEncrypted(eventDataStr)) {
return this.encryption.decrypt(eventDataStr);
}

return eventDataStr;
return Promise.resolve(eventDataStr);
}
}

function statusCodeError(statusCode, message) {

if (statusCode !== 200) {

return new EventuateServerError({
error: `Server returned status code ${statusCode}`,
statusCode,
Expand Down
13 changes: 12 additions & 1 deletion test/AggregateRepositoryEncryption-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ const EntityClass = require('./lib/EncryptedEntityClass');
const { CreatedEntityCommand, UpdateEntityCommand, FailureCommand } = EntityClass;
const encryptionKeyId = 'id';
const keySecret = 'secret';
const encryptionKeyStore = { [encryptionKeyId]: keySecret };

class EncryptionStore {
constructor(keys) {
this.keys = keys;
}

get(encryptionKeyId) {
return Promise.resolve(this.keys[encryptionKeyId]);
}
}

const encryptionKeyStore = new EncryptionStore({ [encryptionKeyId]: keySecret });
const encryption = new Encryption(encryptionKeyStore);

const eventuateClient = createEventuateClient(encryption);
Expand Down
47 changes: 37 additions & 10 deletions test/Encryption-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ const Encryption = require('../dist/modules/Encryption');

const keyId = 'id';
const keySecret = 'secret';
const encryptionKeyStore = { [keyId]: keySecret };
class EncryptionStore {
constructor(keys) {
this.keys = keys;
}

get(encryptionKeyId) {
return Promise.resolve(this.keys[encryptionKeyId]);
}
}
const encryptionKeyStore = new EncryptionStore({ [keyId]: keySecret });
const encryptedPrefix = '__ENCRYPTED__';

const encryption = new Encryption(encryptionKeyStore);

describe('Encryption', () => {
it('should check structure', () => {
console.log('encryption:', encryption);
Expand All @@ -21,34 +31,51 @@ describe('Encryption', () => {
expect(encryption.cipher).to.be.a('function');
expect(encryption.decipher).to.be.a('function');
expect(encryption.isEncrypted).to.be.a('function');
expect(encryption.findKey).to.be.a('function');
});

it('isEncrypted() should return true', () => {
const str = encryptedPrefix + 'abcde';
expect(encryption.isEncrypted(str)).to.be.true;
});

it('isEncrypted() should return true', () => {
it('isEncrypted() should return false', () => {
const str = 'abcde';
expect(encryption.isEncrypted(str)).to.be.false;
});

it('should find encryption key', done => {
encryption.findKey(keyId)
.then(key => {
expect(key).to.equal(keySecret);
done();
})
.catch(done);
});

it('should cipher and decipher', () => {
const text = 'secret text';
const cipher = encryption.cipher(encryptionKeyStore[keyId], text);
const decipher = encryption.decipher(encryptionKeyStore[keyId], cipher);
const cipher = encryption.cipher(keySecret, text);
const decipher = encryption.decipher(keySecret, cipher);
expect(decipher).to.equal(text);
});

it('should encrypt and decrypt', () => {
it('should encrypt and decrypt', done => {
const eventData = { a: '1', b: 2 };
const eventDataString = JSON.stringify(eventData);
const encryptedEventData = encryption.encrypt(keyId, eventDataString);
const cipher = encryption.cipher(encryptionKeyStore[keyId], eventDataString);
expect(encryptedEventData).to.equal(`${encryptedPrefix}${JSON.stringify({ encryptionKeyId: keyId, data: cipher })}`);
encryption.encrypt(keyId, eventDataString)
.then(encryptedEventData => {
const cipher = encryption.cipher(keySecret, eventDataString);
expect(encryptedEventData).to.equal(`${encryptedPrefix}${JSON.stringify({ encryptionKeyId: keyId, data: cipher })}`);

return encryption.decrypt(encryptedEventData);
})
.then(decryptedEventData => {
expect(decryptedEventData).to.equal(eventDataString);
done();
})
.catch(done);

const decryptedEventData = encryption.decrypt(encryptedEventData);
expect(decryptedEventData).to.equal(eventDataString);
});
});

11 changes: 10 additions & 1 deletion test/EventuateClientEncryption-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ const Encryption = require('../dist/modules/Encryption');

const encryptionKeyId = 'id';
const keySecret = 'secret';
const encryptionKeyStore = { [encryptionKeyId]: keySecret };
class EncryptionStore {
constructor(keys) {
this.keys = keys;
}

get(encryptionKeyId) {
return Promise.resolve(this.keys[encryptionKeyId]);
}
}
const encryptionKeyStore = new EncryptionStore({ [encryptionKeyId]: keySecret });
const encryption = new Encryption(encryptionKeyStore);

const eventuateClient = helpers.createEventuateClient(encryption);
Expand Down
Loading

0 comments on commit 2fb8df2

Please sign in to comment.