diff --git a/packages/kms/.eslintignore b/packages/kms/.eslintignore new file mode 100644 index 000000000..58464a086 --- /dev/null +++ b/packages/kms/.eslintignore @@ -0,0 +1,2 @@ +node_modules/** +lib/ diff --git a/packages/kms/.gitignore b/packages/kms/.gitignore new file mode 100644 index 000000000..de4d1f007 --- /dev/null +++ b/packages/kms/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/packages/kms/.mocharc.js b/packages/kms/.mocharc.js new file mode 100644 index 000000000..6d4c063de --- /dev/null +++ b/packages/kms/.mocharc.js @@ -0,0 +1,4 @@ +const baseConfig = require('../../.mocharc'); +module.exports = { + ...baseConfig, +}; diff --git a/packages/kms/CHANGELOG.md b/packages/kms/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/kms/README.md b/packages/kms/README.md new file mode 100644 index 000000000..604a8e6f0 --- /dev/null +++ b/packages/kms/README.md @@ -0,0 +1,4 @@ +# steveo-kms + +A AWS KMS provider for [Steveo](https://github.com/ordermentum/steveo) pub/pub background processing +library. diff --git a/packages/kms/package.json b/packages/kms/package.json new file mode 100644 index 000000000..e95118484 --- /dev/null +++ b/packages/kms/package.json @@ -0,0 +1,39 @@ +{ + "name": "@steveojs/kms", + "version": "7.1.3", + "main": "lib/index.js", + "repository": "git@github.com:ordermentum/steveo.git", + "license": "Apache-2.0", + "author": "Ordermentum <engineering@ordermentum.com>", + "scripts": { + "build": "yarn run tsc", + "test": "NODE_ENV=test nyc yarn spec", + "spec": "tsc && yarn mocha", + "lint": "yarn eslint 'src/**/*.{ts,js}'", + "lint:test": "yarn eslint 'test/**/*.{ts,js}'", + "autotest": "yarn run mocha --watch", + "prepublish": "yarn run build", + "typecheck": "yarn run tsc --noEmit" + }, + "files": [ + "lib" + ], + "devDependencies": { + "@ordermentum/eslint-config-ordermentum": "^2.3.3", + "@types/chai": "^4.3.11", + "chai": "4.4.1", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "2.8.8", + "source-map-support": "0.5.21", + "steveo": "*", + "ts-node": "10.9.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "aws-sdk": "2.1692.0" + }, + "peerDependencies": { + "steveo": "*" + } +} \ No newline at end of file diff --git a/packages/kms/src/index.ts b/packages/kms/src/index.ts new file mode 100644 index 000000000..529cc71a0 --- /dev/null +++ b/packages/kms/src/index.ts @@ -0,0 +1,61 @@ +import { Middleware, MiddlewareCallback, MiddlewareContext } from 'steveo'; +import AWS from 'aws-sdk'; + +export class KMSMiddleware implements Middleware { + client: AWS.KMS; + + keyId: string; + + constructor(keyId: string) { + this.client = new AWS.KMS(); + this.keyId = keyId; + } + + public async publish<Ctx = any, C = MiddlewareCallback>( + context: MiddlewareContext<Ctx>, + _next: C + ) { + context.payload = await this.encrypt(context.payload); + } + + private async encrypt<T = any>(data: T): Promise<string> { + const value = JSON.stringify(data); + + const params = { + KeyId: this.keyId, + Plaintext: Buffer.from(value), + }; + + const result = await this.client.encrypt(params).promise(); + + if (!result.CiphertextBlob) { + throw new Error('Failed to encrypt message'); + } + + return result.CiphertextBlob.toString('base64'); + } + + private async decrypt<T = any>(data: string): Promise<T> { + const params = { + KeyId: this.keyId, + CiphertextBlob: Buffer.from(data), + }; + + const result = await this.client.decrypt(params).promise(); + + if (!result.Plaintext) { + throw new Error('Failed to decrypt message'); + } + + return JSON.parse(result.Plaintext.toString()); + } + + public async consume<Ctx = any, C = MiddlewareCallback>( + context: MiddlewareContext<Ctx>, + _next: C + ) { + context.payload = await this.decrypt(context.payload); + } +} + +export default KMSMiddleware; diff --git a/packages/kms/test/.eslintrc b/packages/kms/test/.eslintrc new file mode 100644 index 000000000..d9c04d4b1 --- /dev/null +++ b/packages/kms/test/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "@ordermentum/ordermentum/mocha", + "@ordermentum/ordermentum/jest" + ] + } \ No newline at end of file diff --git a/packages/kms/test/index_test.ts b/packages/kms/test/index_test.ts new file mode 100644 index 000000000..108e8336a --- /dev/null +++ b/packages/kms/test/index_test.ts @@ -0,0 +1,49 @@ +import { expect } from 'chai'; +import { Steveo } from 'steveo'; + +import sinon from 'sinon'; +import KMSMiddleware from '../src'; + +describe('KMSMiddleware', () => { + let sandbox; + let middleware; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('add to steveo', () => { + middleware = new KMSMiddleware('keyId'); + + const steveo = new Steveo({ + engine: 'dummy', + middleware: [middleware], + }); + + expect(steveo.middleware.length).to.equal(1); + }); + + describe('publish', () => { + it('should encrypt message', async () => { + sandbox.stub(middleware, 'encrypt').resolves('encrypted'); + const context = { message: 'message' }; + const next = sinon.stub().resolves(); + await middleware.publish(context, next); + expect(context.message).to.equal('encrypted'); + }); + }); + + describe('consume', () => { + it('should decrypt message', async () => { + sandbox.stub(middleware, 'decrypt').resolves({ message: 'message' }); + const context = { message: 'data' }; + const next = sinon.stub().resolves(); + await middleware.consume(context, next); + expect(context.message).to.deep.equal({ message: 'message' }); + }); + }); +}); diff --git a/packages/kms/tsconfig.json b/packages/kms/tsconfig.json new file mode 100644 index 000000000..245f8f588 --- /dev/null +++ b/packages/kms/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "lib": [ + "es2022" + ], + "target": "es2022", + "module": "commonjs", + "allowJs": false, + "declaration": true, + "outDir": "lib", + "strict": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noImplicitAny": false, + "noImplicitThis": false, + "resolveJsonModule": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "esModuleInterop": true + }, + "include": [ + "src" + ], + "exclude": [ + "lib", + "node_modules" + ] +} diff --git a/packages/steveo/src/index.ts b/packages/steveo/src/index.ts index 3b9f314d5..54cc3d36b 100644 --- a/packages/steveo/src/index.ts +++ b/packages/steveo/src/index.ts @@ -46,7 +46,7 @@ export { DummyConfiguration, } from './common'; -export { Middleware }; +export { Middleware, MiddlewareContext, MiddlewareCallback } from './common'; export class Steveo implements ISteveo { config: KafkaConfiguration | RedisConfiguration | SQSConfiguration; diff --git a/yarn.lock b/yarn.lock index fa181c9e0..f6ae53929 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3102,6 +3102,22 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +aws-sdk@2.1692.0: + version "2.1692.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1692.0.tgz#9dac5f7bfcc5ab45825cc8591b12753aa7d2902c" + integrity sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + util "^0.12.4" + uuid "8.0.0" + xml2js "0.6.2" + aws-sdk@^2.1043.0: version "2.1691.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1691.0.tgz#9d6ccdcbae03c806fc62667b76eb3e33e5294dcc"