Implement authentication and access control
This commit is contained in:
1 parent
e1bdcda191
commit
327cb78f1a
27 files changed
+600
-167
No files matched your search
@@ -80,7 +80,7 @@ function filterToQuery(filter, negate = false, res = {}) {
|
||||
function count(resource, filter, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let qs = {};
|
||||
if (filter) {
|
||||
if (filter && filter.ast) {
|
||||
filter = filter.evaluateExpressions();
|
||||
let ast = unpackTimestamps(filter.ast);
|
||||
|
||||
@@ -117,7 +117,7 @@ function query(resource, filter, limit, skip, projection, callback) {
|
||||
let ret;
|
||||
if (!callback) ret = [];
|
||||
let qs = {};
|
||||
if (filter) {
|
||||
if (filter && filter.ast) {
|
||||
filter = filter.evaluateExpressions();
|
||||
let ast = unpackTimestamps(filter.ast);
|
||||
|
||||
|
||||
+42
-9
@@ -18,11 +18,13 @@ const resources = {
|
||||
};
|
||||
|
||||
for (let [resource, update] of Object.entries(resources)) {
|
||||
router.head(`/${resource}`, async ctx => {
|
||||
router.head(`/${resource}`, async (ctx, next) => {
|
||||
let filter, limit;
|
||||
if (ctx.request.query.filter) filter = new Filter(ctx.request.query.filter);
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 1)) return next();
|
||||
|
||||
// Exclude temporary tasks and faults
|
||||
if (resource === "tasks" || resource === "faults") {
|
||||
let f = new Filter(["NOT", ["<", "expiry", Date.now() + 60000]]);
|
||||
@@ -34,12 +36,14 @@ for (let [resource, update] of Object.entries(resources)) {
|
||||
ctx.body = "";
|
||||
});
|
||||
|
||||
router.get(`/${resource}`, async ctx => {
|
||||
router.get(`/${resource}`, async (ctx, next) => {
|
||||
let filter, limit, skip;
|
||||
if (ctx.request.query.filter) filter = new Filter(ctx.request.query.filter);
|
||||
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
|
||||
if (ctx.request.query.skip) skip = +ctx.request.query.skip;
|
||||
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
// Exclude temporary tasks and faults
|
||||
if (resource === "tasks" || resource === "faults") {
|
||||
let f = new Filter(["NOT", ["<", "expiry", Date.now() + 60000]]);
|
||||
@@ -57,6 +61,8 @@ for (let [resource, update] of Object.entries(resources)) {
|
||||
|
||||
router.head(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
ctx.body = "";
|
||||
@@ -64,6 +70,8 @@ for (let [resource, update] of Object.entries(resources)) {
|
||||
|
||||
router.get(`/${resource}/:id`, async (ctx, next) => {
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
if (!ctx.state.authorizer.hasAccess(resource, 2)) return next();
|
||||
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
ctx.body = res[0];
|
||||
@@ -71,22 +79,47 @@ for (let [resource, update] of Object.entries(resources)) {
|
||||
|
||||
// TODO add PUT, PATCH routes
|
||||
if (update)
|
||||
router.delete(`/${resource}/:id`, async ctx => {
|
||||
let found = await apiFunctions.deleteResource(resource, ctx.params.id);
|
||||
if (found) ctx.status = 200;
|
||||
else ctx.status = 404;
|
||||
router.delete(`/${resource}/:id`, async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
if (!authorizer.hasAccess(resource, 2)) return next();
|
||||
let res = await apiFunctions.query(resource, filter);
|
||||
if (!res.length) return next();
|
||||
|
||||
const validate = authorizer.getValidator(resource, res[0]);
|
||||
if (!validate("delete")) return (ctx.status = 403);
|
||||
|
||||
await apiFunctions.deleteResource(resource, ctx.params.id);
|
||||
ctx.body = "";
|
||||
});
|
||||
}
|
||||
|
||||
router.post("/devices/:id/tasks", async ctx => {
|
||||
router.post("/devices/:id/tasks", async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
if (!authorizer.hasAccess("devices", 2)) return next();
|
||||
let devices = await apiFunctions.query("devices", filter);
|
||||
if (!devices.length) return next();
|
||||
|
||||
const validate = authorizer.getValidator("devices", devices[0]);
|
||||
for (let t of ctx.request.body)
|
||||
if (!validate("task", t)) return (ctx.status = 403);
|
||||
|
||||
let res = await apiFunctions.postTasks(ctx.params.id, ctx.request.body);
|
||||
// TODO 404 if no such device
|
||||
ctx.set("Connection-Request", res.connectionRequest);
|
||||
ctx.body = res.tasks;
|
||||
});
|
||||
|
||||
router.post("/devices/:id/tags", async ctx => {
|
||||
router.post("/devices/:id/tags", async (ctx, next) => {
|
||||
const authorizer = ctx.state.authorizer;
|
||||
let filter = new Filter(`DeviceID.ID = "${ctx.params.id}"`);
|
||||
if (!authorizer.hasAccess("devices", 2)) return next();
|
||||
let res = await apiFunctions.query("devices", filter);
|
||||
if (!res.length) return next();
|
||||
|
||||
const validate = authorizer.getValidator("devices", res[0]);
|
||||
if (!validate("tags", ctx.request.body)) return (ctx.status = 403);
|
||||
|
||||
await apiFunctions.updateTags(ctx.params.id, ctx.request.body);
|
||||
ctx.body = "";
|
||||
});
|
||||
|
||||
@@ -10,6 +10,13 @@ const cache = {};
|
||||
function parse(name, value) {
|
||||
if (/^ui\.overview\.charts\.[^.]+\.slices\.[^.]+\.filter$/.test(name))
|
||||
return new Filter(value);
|
||||
else if (/^permissions\.[^.]+\.[^.]+\.devices\.filter$/.test(name))
|
||||
return new Filter(value);
|
||||
else if (/^auth\.[^.]+\.users\.[^.]+\.roles$/.test(name))
|
||||
return value
|
||||
.split(",")
|
||||
.map(x => x.trim())
|
||||
.sort();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -5,17 +5,73 @@ const Router = require("koa-router");
|
||||
const koaStatic = require("koa-static");
|
||||
const koaCompress = require("koa-compress");
|
||||
const koaBody = require("koa-body");
|
||||
const koaJwt = require("koa-jwt");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
const config = require("./config");
|
||||
const api = require("./api");
|
||||
const Authorizer = require("../common/authorizer");
|
||||
|
||||
const koa = new Koa();
|
||||
const router = new Router();
|
||||
|
||||
const JWT_SECRET = config.get("server.jwtSecret");
|
||||
const JWT_COOKIE = "genieacs-ui-jwt";
|
||||
|
||||
function getPermissionSets(roles) {
|
||||
const allPermissions = config.get("permissions");
|
||||
const permissionSets = roles.map(r => Object.values(allPermissions[r] || {}));
|
||||
return permissionSets;
|
||||
}
|
||||
|
||||
koa.use(
|
||||
koaJwt({
|
||||
secret: JWT_SECRET,
|
||||
passthrough: true,
|
||||
cookie: JWT_COOKIE
|
||||
})
|
||||
);
|
||||
|
||||
koa.use(async (ctx, next) => {
|
||||
if (ctx.state.user && ctx.state.user.roles)
|
||||
ctx.state.authorizer = new Authorizer(
|
||||
getPermissionSets(ctx.state.user.roles)
|
||||
);
|
||||
else ctx.state.authorizer = new Authorizer([]);
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post("/login", async ctx => {
|
||||
const username = ctx.request.body.username;
|
||||
const password = ctx.request.body.password;
|
||||
|
||||
const user = config.get(`auth.simple.users.${username}`);
|
||||
|
||||
if (!user || user.password !== password) {
|
||||
ctx.status = 400;
|
||||
ctx.body = "Incorrect username or password";
|
||||
return;
|
||||
}
|
||||
|
||||
let token = jwt.sign({ username: username, roles: user.roles }, JWT_SECRET);
|
||||
ctx.cookies.set(JWT_COOKIE, token);
|
||||
ctx.body = JSON.stringify(token);
|
||||
});
|
||||
|
||||
router.post("/logout", async ctx => {
|
||||
ctx.cookies.set(JWT_COOKIE); // Delete cookie
|
||||
ctx.body = "";
|
||||
});
|
||||
|
||||
koa.use(koaBody());
|
||||
router.use("/api", api.routes(), api.allowedMethods());
|
||||
|
||||
router.get("/", async ctx => {
|
||||
let permissionSets = [];
|
||||
if (ctx.state.user && ctx.state.user.roles)
|
||||
permissionSets = getPermissionSets(ctx.state.user.roles);
|
||||
|
||||
ctx.body = `
|
||||
<html>
|
||||
<head>
|
||||
@@ -26,6 +82,10 @@ router.get("/", async ctx => {
|
||||
<body>
|
||||
<script>
|
||||
window.clientConfig = ${JSON.stringify(config.getClientConfig())};
|
||||
window.username = ${JSON.stringify(
|
||||
ctx.state.user ? ctx.state.user.username : ""
|
||||
)};
|
||||
window.permissionSets = ${JSON.stringify(permissionSets)};
|
||||
</script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in new issue
Block a user