Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Application and dependencies bootstrap for node.js.
npm install --save bootwire
Bootwire is a very simple library that leverages ES6 destructuring to provide a no-black-magick-please way to boot node js applications and perform dependency injection.
stub
and spies
.require
hijacking.module.exports['@something-magick-and-weird-here']
Bootwire provides a way to create an application context and pass it down to a boot procedure.
The context object is just an object that exposes a few methods to manipulate and use it:
$set
: set one or many properties$provide
: set a property to the result of the invocation of a provider function.$wire
: wire a function passing the context as parameter$wireGlob
: wire any files matching a glob pattern$waitFor
: await for specific dependencies to be wired$get
: get a value in the context by key or by pathUsing $set
and $provide
on the context object will ensure that all of its properties will be only set once, allowing to inject providers, services connections, configs and so on during tests.
The boot procedure to which the context is passed is a function that acts as the single starting point of an application.
As opposed to many IoC containers bootwire
takes a radical approach to handle dependencies:
Which result into an extremely simple way to replace a component or a setting during tests: just set it before the boot phase.
// index.js
require('./app').boot().catch(console.error);
// app.js
const bootwire = require('bootwire');
function bootProcedure({$provide, $set, $wire} /* this is the context object destructured */) {
$set({
logger: require('winston'),
config: require('./config')
});
await $provide('db', async function({config}) {
return await MongoClient.connect(config.mongodbUrl);
});
await $provide('userRepository', async function({db}) {
return new UserRepository({db});
});
await $wire(startExpress);
}
module.exports = bootwire(bootProcedure);
// user.routes.js
module.exports = function({router, userRepository}) {
router.get('/users', async function(req, res) {
res.json(await userRepository.find());
});
};
Integration tests are now extremely easy:
// app.spec.js
const app = require('./app');
it('does something', async function() {
await app.boot({
config: {port: await randomAvailablePort()},
db: fakeMongodb // db will not be set during the boot
// since is already set here
});
// ...
});
And unit tests as well:
const UserRepository = require('./services/UserRepository');
it('retrieves all the users', async function() {
const repo = new UserRepository({db: {
find() {
return Promise.resolve(usersFixture);
}
}});
deepEqual(await repo.find(), expectedUsers);
});
The boot procedure also accepts multiple initial contexts that will be merged together, doing so will be easy to $provide a default initial context on each tests and override it on each test case:
// app.spec.js
const app = require('./app');
const defaultTestContext = {
config: defaultConfig
};
it('does something', async function() {
await app.boot(defaultTestContext,
{
config: {port: await randomAvailablePort()},
db: fakeMongodb
}
);
// ...
});
// ./boot/index.js
const {promisify} = require('util');
const express = require('express');
const winston = require('winston');
module.exports = async function({$wireGlob, $set, $context}) {
const config = require('./config');
const app = express();
const logger = winston;
$set({
config,
app,
logger
});
await $wireGlob('./services/**/*.wire.js');
await $wireGlob('./middlewares/**/*.wire.js');
await $wireGlob('./routes/**/*.wire.js');
await promisify(app.listen)(config.port);
logger(`Application running on port ${config.port}`);
};
$wireGlob
never process a file twice and ensure files are always processed in
depth order from the most generic path to the deepest.
It can be leveraged to delegate complex wiring from a general boot file to more specialized procedures.
// ./index.js
const {promisify} = require('util');
const express = require('express');
module.exports = async function({$wireGlob, $set, $context}) {
const app = express();
$set({
app
});
await $wireGlob('./routes/**/*.wire.js');
await promisify(app.listen)(config.port);
};
// ./routes/wire.js
module.exports = async function({$wireGlob, $set, $context}) {
await $wireGlob('./middlewares/**/*.middeware.js');
await $wireGlob('./api/**/wire.js'); // NOTE: this will be processed only once
// and from this file even if the path
// matches also the glob from the call in
// ./index.js
};
Using $wireGlob
and $waitFor
is possible to create self contained modules that
can be wired together without having a main boot procedure knowing about everything.
// ./boot/index.js
module.exports = async function({$wireGlob}) {
await $wireGlob('./*.wire.js');
};
// ./boot/logger.wire.js
module.exports = async function({$waitFor, $set}) {
const {correlator} = await $waitFor('correlator');
$set('logger', new CorrelationLogger(correlator));
};
// ./boot/correlator.wire.js
module.exports = function({$set}) {
$set('correlator', new ZoneCorrelator());
};
One way to perform IoC without any magic container is to use explicitly the constructor of services to inject dependencies.
Although it may seem a tight constraint it is actually a good way to create independent components that are easy to reuse in different context and applications.
This explicit and manual injection is intended and is necessary to achieve one of
the goal of bootwire
: don't require components to depend on the dependency injection
framework.
// services/UserRepository.js
class UserRepository {
constructor({db}) {
this.collection = db.collection('users');
}
find() {
return this.collection.find().toArray();
}
}
Note how the UserRepository
class is completely usable both with bootwire
:
// boot/index.js
module.exports = function({$provide}) {
await $provide('db', async function({config}) {
return await MongoClient.connect(config.mongodbUrl);
});
await $provide('userRepository', async function({db}) {
return new UserRepository({db});
});
};
And without bootwire
:
// tasks/dumpUsers.js
async main() {
const db = await MongoClient.connect(process.env.MONGODB_URL);
const repo = UserRepository({db});
const users = await repo.find();
console.info(JSON.stringify(users, null, 2));
}
main().catch(console.error);
Object
App is a bootable application.
Object
Context
is the main application context object. It acts as dependency
container and is intended to be passed down through all the initialization
procedure.
App
Build a new App that will use invoke the boot and $wire procedure passed as parameter on boot.
Example usage:
const bootwire = require('bootwire');
const app = bootwire(require('./src/boot'));
if (require.main === module) {
app.boot()
.catch((err) => {
// Something extremely bad happened while booting
console.error(err);
process.exit(1);
});
}
module.exports = app;
module.exports = app;
Example tests:
const app = require('../..');
describe('app', function() { it('runs', async function() { const port = await getRandomPort();
await app.boot({ config: { port } });
await request('http://localhost:${port}/health'); // ... }); });
Object
App is a bootable application.
Promise
Start an application with an initialContext
Kind: instance method of App
Returns: Promise
- A promise resolving to Context when the boot procedure will complete.
Param | Type | Description |
---|---|---|
...initialContext | Object | One or more object to be merged in the context and build the initialContext. Note that any function already present in the prototype of Context (ie. $wire, $set, $provide) will NOT be overriden. |
Object
Context
is the main application context object. It acts as dependency
container and is intended to be passed down through all the initialization
procedure.
Kind: global class
Object
Context
Promise
Promise
Promise
Promise
Any
Context
Returns the same context instance.
Useful in factory and provider functions to destructure both the context and its internal properties.
ie.
module.exports = function setupRoutes({app, context}) {
// NOTE: config === context.config
app.get('/users', require('./users.routes')(context));
}
Kind: instance property of Context
Returns: Context
- the context object itself
$set
sets one or more keys in the context if they are not already present.
ie.
$set('logger', winston);
$set({
config: require('./config'),
logger: winston
});
Kind: instance method of Context
Param | Type | Description |
---|---|---|
keyOrObject | String | Object | a string key in case of single assignment or a key-value map in case of multiple assignment. |
value | Any | the value to be assigned in case a string key is provided. |
Promise
$provide
allows to assign to a contpext key the result of a function (provider)
that is invoked with context as parameter.
If the context key is already taken the $provide
returns without doing
anything.
The function to be evaluated can be synchronous or asynchronous. In either
cases $provide
returns a Promise to wait for to be sure the assignment took
place (or has been rejected).
Kind: instance method of Context
Returns: Promise
- a promise that will be resolved once $provide
has completed the
assignment or refused to assign.
Param | Type | Description |
---|---|---|
key | String | the key to be assigned |
fn | function | the function to be evaluated. Context will be passed as param to this function. |
Promise
$wire
invokes one or more asynchronous function passing the context as first parameter.
Kind: instance method of Context
Returns: Promise
- a promise that will be resolved once fn
will complete.
Param | Type | Description |
---|---|---|
...fns | function | the function or functions to be evaluated. Context will be passed as param. |
Promise
$wireGlob
requires and wires files by patterns from the caller folder.
ie.
await $wireGlob('routes/*.wire.js');
Kind: instance method of Context
Returns: Promise
- A promise that will be resolved once all the files are required
and wired
Param | Type | Description |
---|---|---|
...patterns | String | One or more pattern expression (see https://github.com/isaacs/minimatch#usage for help) NOTE: path patterns are relative to the caller file and not to process.cwd() |
Promise
$waitFor
wait for the resolution of the dependencies passed as argument and
then it returns the context;
const {logger} = await $waitFor('logger');
Kind: instance method of Context
Returns: Promise
- A promise resolving to the context once all the dependencies
are ready
Param | Type | Description |
---|---|---|
...keys | String | A list of dependencies to be awaited |
Any
Get a value from context by key or path.
const context = await app.boot();
const port = context.get('config.port');
const info = await request(`http://localhost:${port}/api/info`);
// ...
Kind: instance method of Context
Returns: Any
- the value if found or defaultValue
.
Param | Type | Description |
---|---|---|
key | String | a single key or a path of the form 'key1.key2.key3'. |
[defaultValue] | Any | a value to be returned if the key is not found. |
App
Build a new App that will use invoke the boot and $wire procedure passed as parameter on boot.
Example usage:
const bootwire = require('bootwire');
const app = bootwire(require('./src/boot'));
if (require.main === module) {
app.boot()
.catch((err) => {
// Something extremely bad happened while booting
console.error(err);
process.exit(1);
});
}
module.exports = app;
Example tests:
const app = require('../..');
describe('app', function() {
it('runs', async function() {
const port = await getRandomPort();
await app.boot({
config: { port }
});
await request('http://localhost:${port}/health');
// ...
});
});
Kind: global function
Returns: App
- A bootable App
instance.
Param | Type | Description |
---|---|---|
bootAndWireFn | function | The function to be called. |
FAQs
Application and dependencies bootstrap for node.js
We found that bootwire 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.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.