Skip to content

Commit

Permalink
Add authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
derbenoo committed Jan 5, 2019
1 parent 078d1b0 commit 7275f1d
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 28 deletions.
5 changes: 4 additions & 1 deletion example/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import { Server } from '../src/index';

const server = new Server();
const server = new Server({
authentication: true,
apikeyhash: 'b37e50cedcd3e3f1ff64f4afc0422084ae694253cf399326868e07a35f4a45fb',
});

server.start();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"homepage": "https://github.com/decentro-gmbh/pm2-rpc-api#readme",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.4",
"nconf": "^0.10.0",
"pm2": "^3.2.4"
Expand Down
27 changes: 27 additions & 0 deletions src/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Authentication middleware
*/

import { ILogger } from './interfaces';
import * as crypto from 'crypto';

export function generateAuthMiddleware(apiKeyHash: string, log: ILogger) {
return (req, res, next) => {
const apiKey = req.get('authorization');

// Check if API key was provided
if (!apiKey) {
return res.status(401).json({ error: 'MISSING_APIKEY', message: 'No API key provided via the HTTP \'authorization\' header' });
}

// Check if API key is correct
const hash: string = crypto.createHash('sha256').update(apiKey).digest('hex');

if (hash !== apiKeyHash) {
return res.status(401).json({ error: 'WRONG_APIKEY', message: 'The provided API key is incorrect'});
}

// Authenticated successfully
return next();
};
}
62 changes: 35 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,35 @@

import * as nconf from 'nconf';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import { ILogger, IServerOptions } from './interfaces';
import { generateAuthMiddleware } from './authentication';

export interface IServerOptions {
/** Server host */
host?: string;
/** Server port */
port?: number;
/** Whether the API is disabled by default. Results in the start() method exiting immediately (default: false) */
disabled?: boolean;
/** Namespace for environment variables (default: 'PM2API') */
envNamespace?: string;
/** Logging function for info messages (default: console.log) */
info?: Function;
/** Logging function for warnings (default: console.log) */
warn?: Function;
/** Logging function for errors (default: console.log) */
err?: Function;
}

export class Server {
private envNamespace: string;
private info: Function;
private warn: Function;
private err: Function;
private log: ILogger;

private host: string;
private port: number;
private disabled: boolean;
private authentication: boolean;
private apikeyhash: null|string;

constructor(options: IServerOptions = {}) {
this.envNamespace = (options.envNamespace || 'PM2API_').toLowerCase();

this.info = options.info || console.log; // tslint:disable-line:no-console
this.warn = options.warn || console.log; // tslint:disable-line:no-console
this.err = options.err || console.log; // tslint:disable-line:no-console
this.log = options.logger || {
info: msg => console.log(`[INFO] ${msg}`), // tslint:disable-line:no-console
warn: msg => console.log(`[WARNING] ${msg}`), // tslint:disable-line:no-console
err: msg => console.log(`[ERROR] ${msg}`), // tslint:disable-line:no-console
};

this.initialize(options);
}

/** Initialize class attributes based on the provided command line arguments, environment variables and provided instance options */
private initialize(options: IServerOptions) {
private initialize(options: IServerOptions): void {
const store = new nconf.Provider();

// Add command line arguments
Expand Down Expand Up @@ -73,36 +62,55 @@ export class Server {
host: options.host,
port: options.port,
disabled: options.disabled,
authentication: options.authentication,
apikeyhash: options.apikeyhash,
});

// Add default values
store.defaults({
host: 'localhost',
port: 1337,
disabled: false,
authentication: false,
apikeyhash: null,
});

// Set final values for class attributes
this.host = store.get('host');
this.port = store.get('port');
this.disabled = store.get('disabled');
this.authentication = store.get('authentication');
this.apikeyhash = store.get('apikeyhash');
}

/** Start the HTTP JSON-RCP API */
start() {
start(): void {
if (this.disabled) {
this.err('Server is disabled, exiting.');
this.log.err('Server is disabled, exiting.');
return;
}

const server = express();
server.use(bodyParser.json());

// Register authentication middleware
if (!this.authentication && this.apikeyhash) {
this.log.warn('An API key hash was provided but authentication is disabled, NOT authenticating API requests!');
} else if (this.authentication && !this.apikeyhash) {
this.log.err('Authentication is enabled but no API key hash is given, exiting.');
process.exit(1);
} else if (this.authentication && this.apikeyhash) {
server.use('*', generateAuthMiddleware(this.apikeyhash, this.log));
} else {
this.log.info('Authentication disabled');
}

// Register integration middlewares

server.get('/test', (req, res, next) => { res.status(200).end(); });

// Start listening
server.listen(this.port, this.host, () => {
this.info(`Server listening on ${this.host}:${this.port}`);
this.log.info(`Server listening on ${this.host}:${this.port}`);
});
}

Expand Down
26 changes: 26 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

export interface IServerOptions {
/** Server host */
host?: string;
/** Server port */
port?: number;
/** Whether the API is disabled by default. Results in the start() method exiting immediately (default: false) */
disabled?: boolean;
/** Enable authentication (default: false) */
authentication?: boolean;
/** Provide the SHA256 hash (in hexadecimal format) of the API key that is used for authentication */
apikeyhash?: string;
/** Namespace for environment variables (default: 'PM2API') */
envNamespace?: string;
/** Logger */
logger?: ILogger;
}

export interface ILogger {
/** Logging function for info messages (default: console.log) */
info?: Function;
/** Logging function for warnings (default: console.log) */
warn?: Function;
/** Logging function for errors (default: console.log) */
err?: Function;
}

0 comments on commit 7275f1d

Please sign in to comment.