is-able
Keep the buisness logic of authentication seperate from its implementation details.
Define the implementation details of authentication in a User object, then assert rules using user.mustBeAbleTo
, user.mustNotBeAbleTo
, or implement custom handling using user.can
and user.cannot
.
import { User } from 'is-able';
const user = new User({
rules: {
create: id => id === 301,
read: (id, age) => id !== 0 && age >= 14,
update: (...names) => names && names.includes('The boss'),
delete: ({ id, age }) => id !== 0 && age >= 14,
}
});
...
console.log(user.can('create', 301));
console.log(user.cannot('read', 301, 14));
user.mustBeAbleTo('update', 'jimmy', 'The boss steven');
user.mustNotBeAbleTo('delete', { id: 123, age: 7 });
is-able also supports the alternate forms user.mustBe
, user.mustNotBe
, user.is
, and user.isNot
.
import { User } from 'is-able';
const user = new User({
rules: {
'logged in': ({ id }) => id !== undefined && id > 0,
admin: ({ id }) => id === 1000,
}
});
...
user.mustBe('logged in', { id: userId });
user.mustBe('admin', { id: userId });
console.log('Authenticated as admin!');
if (user.isNot('logged in', { id: userId })) {
throw new Error();
}
if (user.isNot('admin', { id: userId })) {
throw new Error();
}
console.log('Authenticated as admin!');
Wrap user in a function to pass in global or per-request variables, like user token or other auth credentials.
Authentication and authorization errors are given a code of 401 so they can be easily identified by your server.
import { User } from 'is-able';
export function auth(token) => new User({
rules: {
'view protected resource A': () => token.id;
}
});
import { auth } from './auth';
import express from 'express';
const app = express();
app.use((req, res, next) => {
req.user = auth(req.headers.authorization);
next();
});
app.get('/protected-resource', (req, res) => {
req.user.mustBeAbleTo('view protected resource A');
res.send('Super secret data!!');
});
app.use(function (err, req, res, next) {
if (err.code === 401) {
res.status(401).send('Unauthorized');
return;
}
next(err);
});
app.listen(8080);