Skip to content

Commit 85d1bae

Browse files
committed
feat(pencil): added webhook middleware support
1 parent 67d22f1 commit 85d1bae

8 files changed

Lines changed: 161 additions & 6 deletions

src/enums/interception-module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
enum InterceptionModule {
2+
Express = "/node_modules/express/lib/express.js",
3+
Koa = "/node_modules/koa/lib/application.js",
4+
Hapi = "/node_modules/@hapi/hapi/lib/index.js"
5+
};
6+
7+
export default InterceptionModule;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
import { Middleware, IMiddleware } from './middleware';
3+
import { SecureNativeOptions } from '../types/securenative-options';
4+
5+
export default class ExpressMiddleware extends Middleware implements IMiddleware {
6+
private _routes: Array<string> = [];
7+
constructor(options: SecureNativeOptions) {
8+
super(options);
9+
}
10+
11+
verifyWebhook(req: Request, res: Response, next: NextFunction) {
12+
const { body = null, headers = null } = req;
13+
14+
if (!body || !headers) {
15+
return res.status(400).send('Bad Request');
16+
}
17+
18+
if (!super.verifySignature(headers, body, this.options.apiKey)) {
19+
return res.status(401).send('Mismatched signatures');
20+
}
21+
22+
return next();
23+
}
24+
}

src/middleware/koa-middleware.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Context } from 'koa';
2+
import {} from 'koa-bodyparser';
3+
import { Middleware, IMiddleware } from './middleware';
4+
import { SecureNativeOptions } from '../types/securenative-options';
5+
6+
export default class KoaMiddleware extends Middleware implements IMiddleware {
7+
constructor(options: SecureNativeOptions) {
8+
super(options);
9+
}
10+
11+
verifyWebhook(ctx: Context, next: Function) {
12+
const { request: { body } = null, req: { headers } = null } = ctx;
13+
14+
if (!body || !headers) {
15+
return ctx.throw(400, 'Bad Request');
16+
}
17+
18+
if (!super.verifySignature(headers, body, this.options.apiKey)) {
19+
return ctx.throw(401, 'Mismatched signatures');
20+
}
21+
22+
return next();
23+
}
24+
}

src/middleware/middleware.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createHmac, timingSafeEqual } from 'crypto';
2+
import { SecureNativeOptions } from '../types/securenative-options';
3+
4+
const SIGNATURE_KEY = 'x-securenative';
5+
6+
export interface IMiddleware {
7+
verifyWebhook(...params: any[]);
8+
}
9+
10+
export abstract class Middleware {
11+
constructor(protected options: SecureNativeOptions) {}
12+
13+
verifySignature(headers, body, apiKey): Boolean {
14+
const signature = headers[SIGNATURE_KEY] || '';
15+
// calculating signature
16+
const hmac = createHmac('sha512', apiKey);
17+
const comparison_signature = hmac.update(JSON.stringify(body)).digest('hex');
18+
19+
// comparing signatures
20+
if (!timingSafeEqual(Buffer.from(signature.toString()), Buffer.from(comparison_signature))) {
21+
return false;
22+
}
23+
24+
return true;
25+
}
26+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import KoaMiddleware from './koa-middleware';
2+
import ExpressMiddleware from './express-middleware';
3+
import { IMiddleware } from './middleware';
4+
import InterceptionModule from '../enums/interception-module';
5+
import ModuleManager from '../module-manager';
6+
import { SecureNativeOptions } from '../types/securenative-options';
7+
8+
export function createMiddleware(moduleManager: ModuleManager, options: SecureNativeOptions): IMiddleware {
9+
if (moduleManager.Modules[InterceptionModule.Koa]) {
10+
return new KoaMiddleware(options);
11+
}
12+
13+
//make express as default middleware
14+
return new ExpressMiddleware(options);
15+
}

src/module-manager.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import ModuleManager from './module-manager';
4+
5+
chai.use(chaiAsPromised);
6+
const expect = chai.expect;
7+
8+
describe('ModuleManager', () => {
9+
it('Should have all methods defined', () => {
10+
const moduleManager = new ModuleManager(null);
11+
expect(moduleManager.Modules).to.not.be.null;
12+
expect(Object.keys(moduleManager.Modules), 'Loaded modules').length.above(0);
13+
expect(moduleManager.pkg).to.be.null;
14+
expect(moduleManager.framework).to.be.undefined;
15+
});
16+
});

src/module-manager.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Package } from "./package-manager";
2+
3+
export default class ModuleManager {
4+
private _modules: Object;
5+
public framework: string;
6+
7+
constructor(public pkg: Package) {
8+
this._modules = this.getLoadedModules();
9+
}
10+
11+
get Modules(): Object {
12+
return this._modules;
13+
}
14+
15+
private getLoadedModules() {
16+
const loadedModules = {};
17+
const dirname = process.cwd();
18+
const modules = require.cache;
19+
20+
Object.entries(modules).forEach(([key, val]) => {
21+
const moduleName = key.replace(dirname, '');
22+
loadedModules[moduleName] = val;
23+
});
24+
25+
return loadedModules;
26+
}
27+
}

src/securenative.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,35 @@ import { Logger } from './logger';
77
import { PackageManager, Package } from './package-manager';
88
import ConfigurationManager from './configuration-manager';
99
import { join } from 'path';
10+
import ModuleManager from './module-manager';
11+
import { IMiddleware } from './middleware/middleware';
12+
import { createMiddleware } from './middleware/midlleware-factory';
1013

1114
const PACKAGE_FILE_NAME = 'package.json';
1215

1316
export default class SecureNative {
1417
private apiManager: ApiManager;
15-
private static instance = null;
18+
public middleware: IMiddleware;
19+
private static instance: SecureNative;
1620

17-
private constructor(eventManager: EventManager, options: SecureNativeOptions) {
18-
if (!eventManager || !options) {
21+
private constructor(eventManager: EventManager, moduleManager: ModuleManager, private options: SecureNativeOptions) {
22+
if (!eventManager || !moduleManager || !options) {
1923
throw new Error('Unable to create SecureNative instance, invalid config provided');
2024
}
25+
26+
if (!options.disable) {
27+
// create middleware
28+
this.middleware = createMiddleware(moduleManager, options);
29+
this.middleware.verifyWebhook = this.middleware.verifyWebhook.bind(this.middleware);
30+
}
31+
2132
this.apiManager = new ApiManager(eventManager, options);
2233
}
2334

2435
public static init(options: SecureNativeOptions) {
2536
const defaultOptions = ConfigurationManager.getConfig();
2637
const config: SecureNativeOptions = { ...options, ...defaultOptions };
27-
38+
2839
const eventManager = new EventManager(fetch, config);
2940
SecureNative.initialize(eventManager, config);
3041
}
@@ -33,18 +44,23 @@ export default class SecureNative {
3344
if (SecureNative.instance) {
3445
throw new Error('This SDK was already initialized');
3546
}
36-
47+
3748
const appPkg: Package = PackageManager.getPackage(join(process.cwd(), PACKAGE_FILE_NAME));
3849
// set default app name
3950
if (!options.appName) {
4051
ConfigurationManager.setConfigKey('appName', appPkg.name);
4152
}
4253

54+
// create moduleManager
55+
const moduleManager = new ModuleManager(appPkg);
56+
4357
// init logger
4458
Logger.initLogger(options);
4559
Logger.debug('Loaded Configurations', JSON.stringify(options));
4660

47-
SecureNative.instance = new SecureNative(eventManager, options);
61+
const secureNative = new SecureNative(eventManager, moduleManager, options);
62+
63+
SecureNative.instance = secureNative;
4864
}
4965

5066
public static getInstance(): SecureNative {

0 commit comments

Comments
 (0)