Skip to content

Instantly share code, notes, and snippets.

@sumbad
Created February 2, 2022 14:06
Show Gist options
  • Save sumbad/48bce0a19d9bfed6bec0a87689592dad to your computer and use it in GitHub Desktop.
Save sumbad/48bce0a19d9bfed6bec0a87689592dad to your computer and use it in GitHub Desktop.
import http from 'http';
import { newEnforcer, newModel, StringAdapter, Util } from 'casbin';
//#region SERVER
const hostname = '127.0.0.1';
const port = 3005;
const server = http.createServer(async (request, response) => {
const isNext = await authMiddleware(request, response);
if (!isNext) {
return;
}
switch (true) {
case request.url.startsWith('/article'):
articleController(request, response);
break;
case request.url.startsWith('/admin_panel'):
adminController(request, response);
break;
default:
notFound(request, response);
break;
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
//#endregion SERVER
//#region CONTROLLERS
function articleController(req, res) {
if (req.method === 'GET' && req.url === '/article') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Read an article');
return;
}
if (req.method === 'POST' && req.url === '/article/find') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Find an article by filter');
return;
}
if (req.method === 'POST' && req.url === '/article/write') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Write a new article');
return;
}
notFound(req, res);
}
function adminController(req, res) {
if (req.method === 'GET' && req.url === '/admin_panel') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Admin panel');
return;
}
if (req.method === 'PUT' && req.url === '/admin_panel/author') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Create a new author');
return;
}
notFound(req, res);
}
function notFound(req, res) {
res.statusCode = 404;
res.end();
}
//#endregion CONTROLLERS
//#region MIDDLEWARE
async function authMiddleware(req, res) {
// Get user token
// const token = req.headers['authorization'];
// Validate token etc...
// Get user name and groups from the token
// for simplification, we will use values directly from headers
const name = req.headers['name'];
const groups = req.headers['groups']?.split(',');
if (name == null || groups == null) {
res.statusCode = 401;
res.end(JSON.stringify({ message: 'Unauthorized' }));
return false;
}
const model = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && (g2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && keyMatch(r.act, p.act)
`;
const policy = `
p, group:user, entity:article, read
p, group:author, entity:article, write
p, group:admin, *, *
p, group:user, /article/find, *
g, group:author, group:user
g, group:admin, group:author
g2, /article, entity:article
g2, /article/*, entity:article
`;
const sub = name; // use a user name as subject
const obj = req.url; // use URL as object
const act = req.method === 'GET' ? 'read' : 'write'; // GET is 'read', others are 'write'
const enforcer = await newEnforcer(newModel(model), new StringAdapter(policy));
enforcer.addNamedMatchingFunc('g2', Util.keyMatchFunc);
const addGroupsToUser = async (sub, groups) => {
await Promise.all(groups.map((group) => enforcer.addRoleForUser(sub, `group:${group}`)));
};
await addGroupsToUser(name, groups);
const isPermitted = await enforcer.enforce(sub, obj, act);
if (!isPermitted) {
res.statusCode = 403;
res.end(JSON.stringify({ message: 'Forbidden' }));
return false;
}
return true;
}
//#endregion MIDDLEWARE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment