
Security News
/Research
Wallet-Draining npm Package Impersonates Nodemailer to Hijack Crypto Transactions
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
okanjo-app-server
Advanced tools
Configurable web and API server powered by HAPI for the Okanjo App ecosystem.
This package bundles all the common things needed to build a web or API server, such as:
Setup is done mostly through configuration. Using all of these modules together requires a fair amount of boilerplate. This module attempts to eliminate most of the boilerplate setup with a reusable, configurable module, so your app can development time can focus on building the app, not boilerplate.
You should have a basic understanding of how HAPI works, otherwise this module won't make a ton of sense to you.
Add to your project like so:
npm install okanjo-app-server
Note: requires the
okanjo-app
module.
Note: v2 and on uses Hapi v18+. Use v1 for Hapi 16
Here's a super basic implementation.
Your directory structure might look like this:
example-app/
routes/
– place to put your route files
example-routes.js
– example route file, seen belowstatic/
– place to put your static assets like css, images, js, etcview-extensions/
– place to stick nunjucks extensions
example-ext.js
– example extension file, seen belowviews/
– place to put your view templates
example.j2
– example template, seen belowconfig.js
– okanjo-app configindex.js
– app entrypointYou can find these example files here: docs/example-app
"use strict";
const Path = require('path');
module.exports = {
webServer: {
// Hapi server / global settings
hapiServerOptions: {
// Listening port
port: 3000, // Port to listen on, default: null (os assigned)
}, // HAPI server settings, see: // https://hapijs.com/api#server()
// Graceful shutdown handling
drainTime: 5000, // how long to wait to drain connections before killing the socket, in milliseconds, default: 5000
// Route configuration
routePath: Path.join(__dirname, 'routes'), // where to find route files, default: undefined
// Socket.io configuration
webSocketEnabled: true, // Whether to enable socket.io server, default: false
webSocketConfig: undefined, // socket.io server options, see: https://socket.io/docs/server-api/#new-server-httpserver-options (default: undefined)
// View handler configuration
viewHandlerEnabled: true, // Whether to enable template rendering, default: false
viewPath: Path.join(__dirname, 'views'), // The directory where view files are based from, required if viewHandlerEnabled is enabled.
cacheTemplates: false, // Whether to let hapi-vision cache templates for better performance, default: false
nunjucksEnvOptions: undefined, // http://mozilla.github.io/nunjucks/api.html#configure - e.g. { noCache: true }
nunjucksExtensionsPath: Path.join(__dirname, 'view-extensions'), // The directory where extension modules live, sig: function(env) { /* this = webServer */ }
// Static file handler configuration
staticHandlerEnabled: true, // Whether to enable static asset serving, default: false
staticPaths: [ // Array of path to route definitions for arbitrary paths, default: []
{ path: Path.join(__dirname, 'static'), routePrefix: '/' }, // exports the static/ directory under /
{ path: Path.join(__dirname, 'dist'), routePrefix: '/dist' } // exports the dist/ directory under /dist
],
staticListingEnabled: false, // Whether to allow directory listings, default: false
staticNpmModules: [ // Array of module names and paths to expose as static paths, useful for exposing dependencies on the frontend w/o build tools, default: []
{ moduleName: 'async', path: 'dist' } // e.g. node_modules/async/dist/async.min.js -> /vendor/async/async.min.js
]
}
};
This config.js
includes all available options. You may exclude or comment-out the ones that do not apply to your application.
"use strict";
const OkanjoApp = require('okanjo-app');
const OkanjoServer = require('okanjo-app-server');
// Configure the app
const config = require('./config.js');
const app = new OkanjoApp(config);
// Configure the server
const server = new OkanjoServer(app, app.config.webServer);
// Start it up
(async () => {
await server.init(); // optional, if you wish to do your own setup before starting HAPI
await server.start();
})()
.then(() => {
console.log('Server started at:', server.hapi.info.uri);
console.log('Use Control-C to quit')
})
.catch((err) => {
console.error('Something went horribly wrong', err);
process.exit(1);
})
;
You can make this much more elaborate by starting the server in a worker using okanjo-app-broker so you can hot-reload the entire server on changes, etc.
A route file needs to export a function. The context of the function (this
) will be the OkanjoServer instance.
Route files are loaded synchronously, so no async operations should be performed.
"use strict";
/**
* @this OkanjoServer
*/
module.exports = function() {
// This route replies with a rendered view using the example.j2 template and given context
this.hapi.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return h.view('example.j2', {
boom: "roasted"
});
},
config: {
// ... validation, authentication. tagging, etc
}
});
// This route replies with an api response
this.hapi.route({
method: 'GET',
path: '/api/sometimes/works',
handler: async (request, h) => {
const res = await pretendServiceFunction(); // Fire off a pretend service function
return this.app.response.ok(res); // Return the response
},
config: {
// ... validation, authentication. tagging, etc
}
});
/**
* Pretend service function that returns a payload or throws an error
*/
const pretendServiceFunction = async () => {
if (Math.random() >= 0.50) { // half the time, return an error
throw this.app.response.badRequest('Nope, not ready yet.');
} else {
return { all: 'good' };
}
};
};
A Nunjucks extension file needs to export a function. The context of the function (this
) will be the OkanjoServer instance.
Nunjucks extension files are loaded synchronously, so no async operations should be performed.
"use strict";
/**
* @this OkanjoServer
* @param env – Nunjucks environment
*/
module.exports = function(env) {
// Remember, this.app is available here :)
// You could add globals to Nunjucks
env.addGlobal('env', this.app.currentEnvironment);
env.addGlobal('pid', process.pid);
// You could add custom filters to Nunjucks
env.addFilter('doSomething', (str, count) => {
// return some string
return "yay fun " + str + " " + count;
});
};
Views are standard Nunjucks templates. For example:
<html>
<head>
<link rel="stylesheet" href="/css/example.css" />
</head>
<body>
<ul>
<li>Boom: {{boom}}</li><!-- Set by routes/example-routes.js's GET / route -->
<li>ENV: {{env}}</li><!-- Set by view-extensions/example-ext.js -->
<li>PID: {{pid}}</li><!-- Set by view-extensions/example-ext.js -->
<li>doSomething: {{ boom|doSomething(1) }}</li><!-- Custom filter defined by view-extensions/example-ext.js -->
</ul>
</body>
</html>
The template, when rendered via http://localhost:3000/
shows:
<html>
<head>
<link rel="stylesheet" href="/css/example.css" />
</head>
<body>
<ul>
<li>Boom: roasted</li><!-- Set by routes/example-routes.js's GET / route -->
<li>ENV: default</li><!-- Set by view-extensions/example-ext.js -->
<li>PID: 2875</li><!-- Set by view-extensions/example-ext.js -->
<li>doSomething: yay fun roasted 1</li><!-- Custom filter defined by view-extensions/example-ext.js -->
</ul>
</body>
</html>
You can create sub-directories and organize your views however you'd like. Utilize Nunjucks' extends
and include
operators as you wish. Remember, paths are relative to the configured by viewPath
.
Server class. Must be instantiated to be used.
OkanjoServer.extensions.jsonpResponseCodeFix
– Extension that replaces non 200-level responses with 200 so non-ok level responses can execute on the browserOkanjoServer.extensions.responseErrorReporter
– Extension that reports 500-level responses via app.report, useful for production monitoringserver.app
– (read-only) The OkanjoApp instance provided when constructedserver.config
– (read-only) The configuration provided when constructedserver.options
– (read-only) The options provided when constructedserver.hapi
– (read-only) The HAPI instance created when initialized.server.io
– (read-only) The socket.io instance created when initialized.new OkanjoServer(app, [config, [options]], [callback])
Creates a new server instance.
app
– The OkanjoApp instance to bind toconfig
– (optional, object) The OkanjoServer configuration, see config.jsoptions
– (optional, object) Server options object
options.extensions
– Array of functions to call when initializing. Useful for initializing async hapi plugins or custom configurations.For example:
new OkanjoServer(app, config, {
extensions: [
// Use the built-in extensions
OkanjoServer.extensions.jsonpResponseCodeFix, // replaces non 200-level responses with 200 so non-ok level responses can execute on the browser
OkanjoServer.extensions.responseErrorReporter, // reports
// Register a hapi extension, for example, query string parsing (like the old days)
async function giveMeQueryStringsBack() {
await this.hapi.register({
plugin: require('hapi-qs'),
options: {}
});
},
// Register authentication strategies, etc
async function registerAuthenticationStrategies() {
// plugin to use HTTP basic auth username as an api key
await this.hapi.register({
plugin: require('hapi-auth-basic-key'),
options: {}
});
// Register the strategy
this.hapi.auth.strategy('key-only', 'basic', {
validateFunc: (req, key, secret, authCallback) => {
// FIXME - put your real key authentication here (e.g. db or redis lookup)
let valid = key === 'my-secret-key';
let err = null;
// Pass back validity and credentials if valid
authCallback(err, valid, { key });
}
});
}
]
}, (err) => {
// server is configured, ready to start
});
await server.init()
Configures the underlying services, such as HAPI, Socket.io, etc. Called automatically by server.start
, if not done manually. Before v2, this was done in the constructor.
callback(err)
– Function to fire once the server has started. If err
is present, something went wrong.await server.start()
Starts the server instance.
callback(err)
– Function to fire once the server has started. If err
is present, something went wrong.await server.stop()
Attempts to gracefully shutdown the server instance. If config.drainTime
elapses, the socket will be forcibly killed.
callback(err)
– Function to fire once the server has stopped. If err
is present, something went wrong.This class fires no events.
Our goal is quality-driven development. Please ensure that 100% of the code is covered with testing.
Before contributing pull requests, please ensure that changes are covered with unit tests, and that all are passing.
To run unit tests and code coverage:
npm run report
This will perform:
Sometimes, that's overkill to quickly test a quick change. To run just the unit tests:
npm test
or if you have mocha installed globally, you may run mocha test
instead.
FAQs
Server framework using HAPI and friends
The npm package okanjo-app-server receives a total of 0 weekly downloads. As such, okanjo-app-server popularity was classified as not popular.
We found that okanjo-app-server demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.