autohost
Opinionated HTTP server lib based on express. Automatically configured based on convention driven resources.
Ripped from the guts of Anvil.
Features
- Resource-based: define protocol-agnostic resources that interact via HTTP or WebSockets
- Auto-reload resource on file change
- WebSocket support (no fallback to other protocols)
- UI dashboard to review resources' routes, topics and hosted paths
UI Dashboard
Simple navigate to /_autohost to review the current set of resources:
Quick Start
var host = require( 'autohost' )();
host.init();
Configuration
Configuration can be provided optionally to the init call or during instantiation after the require. The object literal follows the format:
{
processes: 1,
static: './public',
resources: './resource',
port: 8800,
allowedOrigin: 'leankit.com',
websockets: true,
socketIO: true
}
Resources
Resources are expected to be simple modules that return a parameterless function resulting in a JS literal that follows the format:
{
name: 'resource-name',
resources: '',
actions: [
{
alias: 'send',
verb: 'get',
topic: 'send',
path: '',
handle: function( envelope ) {
}
}
]
}
Authentication
Authentication support is supplied via Passport integration. Your application is expected to provide a strategy and authentication method. You can also provide a regex path to make part of your path open to anonymous access.
You must set this up BEFORE calling .init, calling it after initialization will probably cause 'splosions.
var passport = require( 'passport' );
var BasicStrategy = require( 'passport-http' ).BasicStrategy;
var host = require( '../src/autohost.js' )();
host.withPassportStrategy(
new BasicStrategy({}, function( username, password, done ) {
if( username == 'anon' || ( username == 'admin' && password == 'admin' ) ) {
done( null, username );
} else {
done( null, false );
}
} ),
passport.authenticate( 'basic', { session: false } ),
/^[\/]anon.*/ );
Authorization
The general approach is this:
- every action in the system is made available to the authorization strategy on start-up
- an action may be assigned to one or more roles in your authorization strategy
- a user may be assigned to one or more roles in your authorization strategy
- when a user attempts to activate an action, the action roles are checked against the user roles
- if a match is found in both lists, the action completes
- if the user has no roles that match any of the action's roles, the action is rejected (403)
- if the action has NO roles assigned to it, the user will be able to activate despite roles
This basically goes against least-priviledge. If this is a problem, assign a baseline 'authenticated' or 'user' role to every action returned to you during server start-up OR always return either of these during the 'getRolesFor' call.
The authorization strategy MUST implement 3 calls:
actionList( list, done )
Will recieve a hashmap of resources and resource action names. Action names follow a namespace convention of {resource name}.{alias}. The done call back MUST be called.
Here's an example of the format.
{
'_autohost': [
'_autohost.api',
'_autohost.resources',
'_autohost.actions',
'_autohost.connected-sockets'
]
}
This is provided to your auth provider so that actions can automatically be updated (as in storage) on-the-fly when the server spins up.
getUserRoles( user, done ) -- done( err, roles )
Given a user's id, this must return any roles assigned to the user in the system. Done must be called with error or roles. An exception should never be allowed to bubble up through this call.
getRolesFor( action ) -- done( err, roles )
During activation, the action name ( resource.alias ) is passed to this call to determine what roles are able to activate the action. If you don't want actions to default to enabled despite user role, ALWAYS return at least some baseline role ( i.e. 'user', 'authenticated', etc. ).
Done must be called with error or roles. An exception should never be allowed to bubble up through this call.
Auth admin panel (Incomplete/Experimental)
In order to use this feature, you must provide an authorization strategy with several additional calls. It should go without saying that you're expected invoke the call back with either the result or an error.
getUserList( done ) -- done( err, users )
Returns the list of users in the authentication store. Necessary in order to map roles to users.
getRoleList( ) -- done( err, roles )
Returns the list of roles in the authorization store.
setActionRoles( action, roles ) -- done( err )
Sets the total list of roles for an action. (destructive update)
setUserRoles( user, roles ) -- done( err )
Sets the total list of roles for a user. (destructive update)
addRole( role ) -- done( err )
Remove a role from the authorization strategy.
removeRole( role ) -- done( err )
Remove a role from the authorization strategy.
Web Socket Stuff
Methods
- sendToClient( id, topic, message ) - sends message to specific client via websocket (returns true if successful)
- notifyClients( topic, message ) - sends message to all clients via web sockets
Events
- 'socket.client.connected', { socket: socketConnection } - raised when a client connects
- 'socket.client.identified', { socket: socketConnection, id: id } - raised when client reports unique id
TO DO
- Add support for clustering (multiple listening processes)
- Add support for user authentication and authorization (access) logging
- Add support for rate limiting
- Better logging
- More thorough error handling
- Support either Socket.IO or WebSockets via config
License
MIT License - http://opensource.org/licenses/MIT