mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:17:08 +00:00
Merge branch '61-user-authentication' into devel
This commit is contained in:
@@ -62,8 +62,22 @@ const allMeta = (key, value) => {
|
||||
return { all: [ meta(key, value) ] };
|
||||
};
|
||||
|
||||
// These routes do not require authentication
|
||||
app.map({
|
||||
'*': { all: [ meta() ] }, // Create the req.meta object
|
||||
'/login': {
|
||||
post: [ mw.user.login ]
|
||||
},
|
||||
'/logout': {
|
||||
get: [ mw.user.logout ],
|
||||
post: [ mw.user.logout ]
|
||||
}
|
||||
});
|
||||
|
||||
app.use(mw.auth.authentify);
|
||||
|
||||
// We must be authenticated before we can access these
|
||||
app.map({
|
||||
'/project': {
|
||||
get: [ mw.project.list ], // Get list of projects
|
||||
},
|
||||
@@ -93,7 +107,7 @@ app.map({
|
||||
},
|
||||
'/project/:project/line/:line': {
|
||||
// get: [ mw.line.get ],
|
||||
patch: [ mw.line.patch ],
|
||||
patch: [ mw.auth.access.write, mw.line.patch ],
|
||||
},
|
||||
|
||||
'/project/:project/sequence/': {
|
||||
@@ -101,30 +115,30 @@ app.map({
|
||||
},
|
||||
'/project/:project/sequence/:sequence': {
|
||||
// get: [ mw.sequence.get ],
|
||||
patch: [ mw.sequence.patch ],
|
||||
patch: [ mw.auth.access.write, mw.sequence.patch ],
|
||||
},
|
||||
|
||||
'/project/:project/plan/': {
|
||||
get: [ mw.plan.list ],
|
||||
put: [ mw.plan.put ],
|
||||
post: [ mw.plan.post ]
|
||||
put: [ mw.auth.access.write, mw.plan.put ],
|
||||
post: [ mw.auth.access.write, mw.plan.post ]
|
||||
},
|
||||
'/project/:project/plan/:sequence': {
|
||||
// get: [ mw.plan.get ],
|
||||
patch: [ mw.plan.patch ],
|
||||
delete: [ mw.plan.delete ]
|
||||
patch: [ mw.auth.access.write, mw.plan.patch ],
|
||||
delete: [ mw.auth.access.write, mw.plan.delete ]
|
||||
},
|
||||
//
|
||||
'/project/:project/event/': {
|
||||
get: [ mw.event.cache.get, mw.event.list, mw.event.cache.save ],
|
||||
post: [ mw.event.post ],
|
||||
put: [ mw.event.put ],
|
||||
delete: [ mw.event.delete ],
|
||||
post: [ mw.auth.access.write, mw.event.post ],
|
||||
put: [ mw.auth.access.write, mw.event.put ],
|
||||
delete: [ mw.auth.access.write, mw.event.delete ],
|
||||
':type/': {
|
||||
':id/': {
|
||||
// get: [ mw.event.get ],
|
||||
put: [ mw.event.put ],
|
||||
delete: [ mw.event.delete ]
|
||||
put: [ mw.auth.access.write, mw.event.put ],
|
||||
delete: [mw.auth.access.write, mw.event.delete ]
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -134,7 +148,7 @@ app.map({
|
||||
},
|
||||
'/project/:project/configuration/:path(*)?': {
|
||||
get: [ mw.configuration.get ],
|
||||
// post: [ mw.label.post ],
|
||||
// post: [ mw.auth.access.admin, mw.label.post ],
|
||||
},
|
||||
'/project/:project/info/:path(*)': {
|
||||
get: [ mw.info.get ],
|
||||
@@ -165,7 +179,7 @@ app.map({
|
||||
'gis/:featuretype(line|point)': {
|
||||
get: [ mw.gis.navdata.get ]
|
||||
}
|
||||
}
|
||||
},
|
||||
//
|
||||
// '/user': {
|
||||
// get: [ mw.user.get ],
|
||||
@@ -177,12 +191,6 @@ app.map({
|
||||
// // delete: [ mw.user.delete ]
|
||||
// },
|
||||
//
|
||||
// '/login': {
|
||||
// post: [ mw.user.login ]
|
||||
// },
|
||||
// '/logout': {
|
||||
// post: [ mw.user.logout ]
|
||||
// }
|
||||
});
|
||||
|
||||
// Generic error handler. Stops stack dumps
|
||||
|
||||
31
lib/www/server/api/middleware/auth/access.js
Normal file
31
lib/www/server/api/middleware/auth/access.js
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
async function read (req, res, next) {
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
next({status: 403, message: "Access denied"});
|
||||
}
|
||||
}
|
||||
|
||||
async function write (req, res, next) {
|
||||
if (req.user && (req.user.role == "user" || req.user.role == "admin")) {
|
||||
next();
|
||||
} else {
|
||||
next({status: 403, message: "Access denied"});
|
||||
}
|
||||
}
|
||||
|
||||
async function admin (req, res, next) {
|
||||
if (req.user && req.user.role == "admin") {
|
||||
next();
|
||||
} else {
|
||||
next({status: 403, message: "Access denied"});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
read,
|
||||
write,
|
||||
admin
|
||||
};
|
||||
92
lib/www/server/api/middleware/auth/authentify.js
Normal file
92
lib/www/server/api/middleware/auth/authentify.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const dns = require('dns');
|
||||
const { Netmask } = require('netmask');
|
||||
const cfg = require('../../../lib/config');
|
||||
const jwt = require('../../../lib/jwt');
|
||||
|
||||
async function authorisedIP (req, res) {
|
||||
const validIPs = cfg._("global.users.login.ip") || {};
|
||||
for (const key in validIPs) {
|
||||
const block = new Netmask(key);
|
||||
if (block.contains(req.ip)) {
|
||||
const payload = Object.assign({
|
||||
ip: req.ip,
|
||||
autologin: true
|
||||
}, validIPs[key]);
|
||||
jwt.issue(payload, req, res);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function authorisedHost (req, res) {
|
||||
const validHosts = cfg._("global.users.login.host") || {};
|
||||
for (const key in validHosts) {
|
||||
try {
|
||||
const ip = await dns.promises.resolve(key);
|
||||
if (ip == req.ip) {
|
||||
const payload = Object.assign({
|
||||
ip: req.ip,
|
||||
host: key,
|
||||
autologin: true
|
||||
}, validHosts[key]);
|
||||
jwt.issue(payload, req, res);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code != "ENODATA") {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function auth (req, res, next) {
|
||||
|
||||
if (res.headersSent) {
|
||||
// Nothing to do, this request must have been
|
||||
// handled already by another middleware.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a valid JWT (already decoded by a previous
|
||||
// middleware).
|
||||
if (req.user) {
|
||||
if (!req.user.autologin) {
|
||||
// If this is not an automatic login, check if the token is in the
|
||||
// second half of its lifetime. If so, reissue a new one, valid for
|
||||
// another cfg.jwt.options.expiresIn seconds.
|
||||
if (req.user.exp) {
|
||||
const ttl = req.user.exp - Date.now()/1000;
|
||||
if (ttl < cfg.jwt.options.expiresIn/2) {
|
||||
const credentials = cfg._("global.users.login.user").find(i => i.name == req.user.name && i.role == req.user.role);
|
||||
if (credentials) {
|
||||
// Refresh token
|
||||
payload = Object.assign({}, credentials);
|
||||
delete payload.hash;
|
||||
jwt.issue(Object.assign({}, credentials), req, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP is known to us
|
||||
if (await authorisedIP(req, res)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the hostname is known to us
|
||||
if (await authorisedHost(req, res)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
next({status: 401, message: "Not authorised"});
|
||||
}
|
||||
|
||||
module.exports = auth;
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
exports.jwt = require('./jwt');
|
||||
// exports.access = require('./access');
|
||||
exports.authentify = require('./authentify');
|
||||
exports.access = require('./access');
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
exports.login = require('./login');
|
||||
exports.logout = require('./logout');
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
const crypto = require('crypto');
|
||||
const cfg = require('../../../lib/config');
|
||||
const jwt = require('../../../lib/jwt');
|
||||
|
||||
async function login (req, res, next) {
|
||||
if (req.body) {
|
||||
const {user, password} = req.body;
|
||||
if (user && password) {
|
||||
const hash = crypto
|
||||
.pbkdf2Sync(password, 'Dougal'+user, 10712, 48, 'sha512')
|
||||
.toString('base64');
|
||||
for (const credentials of cfg._("global.users.login.user") || []) {
|
||||
if (credentials.name == user && credentials.hash == hash) {
|
||||
const payload = Object.assign({}, credentials);
|
||||
delete payload.hash;
|
||||
jwt.issue(payload, req, res);
|
||||
res.status(204).send();
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
next({status: 401, message: "Unauthorised"});
|
||||
}
|
||||
}
|
||||
next({status: 400, message: "Bad request"});
|
||||
}
|
||||
|
||||
module.exports = login;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
async function logout (req, res, next) {
|
||||
res.clearCookie("JWT");
|
||||
res.status(204).send();
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = logout;
|
||||
|
||||
Reference in New Issue
Block a user