Compare commits
3 Commits
22328db33a
...
5e1c176ac6
Author | SHA1 | Date | |
---|---|---|---|
5e1c176ac6 | |||
4f12444a9d | |||
3b63f02dd7 |
@ -64,7 +64,7 @@ class UserDatabase extends AbstractUserDatabase {
|
|||||||
if (id.includes('temp'))
|
if (id.includes('temp'))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
const data = await this.db.findOne(this._userColllection, { _id: ObjectId(id) });
|
const data = await this.db.findOne(this._userColllection, { _id: new ObjectId(id) });
|
||||||
if (!data)
|
if (!data)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class UserDatabase extends AbstractUserDatabase {
|
|||||||
if (!force && this.cache.has(id))
|
if (!force && this.cache.has(id))
|
||||||
return this.cache.get(id);
|
return this.cache.get(id);
|
||||||
|
|
||||||
const data = await this.db.findOne(this._appCollection, { _id: ObjectId(id) });
|
const data = await this.db.findOne(this._appCollection, { _id: new ObjectId(id) });
|
||||||
if (!data)
|
if (!data)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -238,11 +238,11 @@ class UserDatabase extends AbstractUserDatabase {
|
|||||||
return false;
|
return false;
|
||||||
if (result.created + result.validFor < Date.now()) {
|
if (result.created + result.validFor < Date.now()) {
|
||||||
// Code existed but is no longer valid
|
// Code existed but is no longer valid
|
||||||
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
|
await this.db.deleteOne('registrationCodes', { _id: new ObjectId(result._id) });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Valid code
|
// Valid code
|
||||||
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
|
await this.db.deleteOne('registrationCodes', { _id: new ObjectId(result._id) });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ class UserDatabase extends AbstractUserDatabase {
|
|||||||
const { json } = user;
|
const { json } = user;
|
||||||
this.logger.debug(`Updating user ${inspect(json)}`);
|
this.logger.debug(`Updating user ${inspect(json)}`);
|
||||||
if (user._id)
|
if (user._id)
|
||||||
await this.db.updateOne(this._userColllection, { _id: ObjectId(user._id) }, json);
|
await this.db.updateOne(this._userColllection, { _id: new ObjectId(user._id) }, json);
|
||||||
else {
|
else {
|
||||||
const result = await this.db.insertOne(this._userColllection, json);
|
const result = await this.db.insertOne(this._userColllection, json);
|
||||||
user._id = result.insertedId;
|
user._id = result.insertedId;
|
||||||
@ -267,7 +267,7 @@ class UserDatabase extends AbstractUserDatabase {
|
|||||||
async updateApplication (app) {
|
async updateApplication (app) {
|
||||||
const { json } = app;
|
const { json } = app;
|
||||||
this.logger.debug(`Updating application ${inspect(json)}`);
|
this.logger.debug(`Updating application ${inspect(json)}`);
|
||||||
await this.db.updateOne(this._appCollection, { _id: ObjectId(json._id) }, json, true);
|
await this.db.updateOne(this._appCollection, { _id: new ObjectId(json._id) }, json, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ const { authenticator } = require('otplib');
|
|||||||
|
|
||||||
const { inspect } = require('node:util');
|
const { inspect } = require('node:util');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
const fs = require("node:fs");
|
||||||
|
|
||||||
// Populated by the constructor
|
// Populated by the constructor
|
||||||
const ServiceProfileLinks = {};
|
const ServiceProfileLinks = {};
|
||||||
@ -42,7 +43,7 @@ class UserEndpoint extends ApiEndpoint {
|
|||||||
|
|
||||||
// Applications
|
// Applications
|
||||||
[ 'get', '/applications', this.applications.bind(this) ],
|
[ 'get', '/applications', this.applications.bind(this) ],
|
||||||
[ 'post', '/applications', this.createApplication.bind(this) ],
|
[ 'post', '/applications', this.createApplication.bind(this), [ server.auth.createAuthoriser('applications:create', 5) ]],
|
||||||
[ 'delete', '/applications/:id', this.deleteApplication.bind(this) ],
|
[ 'delete', '/applications/:id', this.deleteApplication.bind(this) ],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -70,9 +71,14 @@ class UserEndpoint extends ApiEndpoint {
|
|||||||
|
|
||||||
const x = file.name.split('.');
|
const x = file.name.split('.');
|
||||||
const fileName = `${user.id}.${x[x.length - 1]}`;
|
const fileName = `${user.id}.${x[x.length - 1]}`;
|
||||||
const filesDir = path.resolve(this.server.serveFiles, 'avatars', fileName);
|
const avatarsDir = path.resolve(this.server.serveFiles, 'avatars');
|
||||||
|
const oldAvatar = path.join(avatarsDir, user.avatar);
|
||||||
|
|
||||||
file.mv(filesDir, async (err) => {
|
// Delete old avatar
|
||||||
|
if (fs.statSync(oldAvatar))
|
||||||
|
fs.unlinkSync(oldAvatar);
|
||||||
|
|
||||||
|
file.mv(path.join(avatarsDir, fileName), async (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.logger.error(`Error saving file: ${err.stack}`);
|
this.logger.error(`Error saving file: ${err.stack}`);
|
||||||
return res.status(500).end();
|
return res.status(500).end();
|
||||||
@ -235,6 +241,7 @@ class UserEndpoint extends ApiEndpoint {
|
|||||||
async createApplication (req, res) {
|
async createApplication (req, res) {
|
||||||
const { body, user } = req;
|
const { body, user } = req;
|
||||||
const { name, ...opts } = body;
|
const { name, ...opts } = body;
|
||||||
|
this.logger.info(`User ${user.username} (${user.id}) is creating an application`);
|
||||||
if (!name)
|
if (!name)
|
||||||
return res.status(400).send('Missing name');
|
return res.status(400).send('Missing name');
|
||||||
const application = await user.createApplication(name, opts);
|
const application = await user.createApplication(name, opts);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
const { AbstractUserDatabase } = require('../interfaces');
|
const { AbstractUserDatabase } = require('../interfaces');
|
||||||
const { Util } = require('../../util');
|
const { Util, PermissionManager } = require('../../util');
|
||||||
|
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
const MongoStore = require('connect-mongo');
|
const MongoStore = require('connect-mongo');
|
||||||
@ -177,6 +177,8 @@ class Authenticator {
|
|||||||
*/
|
*/
|
||||||
createAuthoriser (permission, level = 1) {
|
createAuthoriser (permission, level = 1) {
|
||||||
|
|
||||||
|
PermissionManager.ensurePermission(permission);
|
||||||
|
|
||||||
const func = async (req, res, next) => {
|
const func = async (req, res, next) => {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
// Request does not have a user bound to it, response already sent from #_authenticate
|
// Request does not have a user bound to it, response already sent from #_authenticate
|
||||||
@ -185,7 +187,7 @@ class Authenticator {
|
|||||||
// Has permission
|
// Has permission
|
||||||
if (user.hasPermission(permission, level))
|
if (user.hasPermission(permission, level))
|
||||||
return next();
|
return next();
|
||||||
return res.status(403).send('Access denied');
|
return res.status(403).send('Insufficient permissions');
|
||||||
};
|
};
|
||||||
|
|
||||||
return func.bind(this);
|
return func.bind(this);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const Argon2 = require('argon2');
|
const Argon2 = require('argon2');
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
const { Util } = require('../../util');
|
const { Util, PermissionManager } = require('../../util');
|
||||||
const UserApplicataion = require('./UserApplication');
|
const UserApplicataion = require('./UserApplication');
|
||||||
|
|
||||||
// Fields omitted in safeJson
|
// Fields omitted in safeJson
|
||||||
@ -9,23 +9,6 @@ const ProtectedFields = [ '_id', 'otpSecret', 'password' ];
|
|||||||
|
|
||||||
class User {
|
class User {
|
||||||
|
|
||||||
static defaultPermissions = {
|
|
||||||
developer: {
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
administrator: {
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
default: 0,
|
|
||||||
dingus: {
|
|
||||||
bingus: {
|
|
||||||
default: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static validTypes = [];
|
static validTypes = [];
|
||||||
|
|
||||||
constructor (db, data) {
|
constructor (db, data) {
|
||||||
@ -50,27 +33,11 @@ class User {
|
|||||||
throw new Error('Invalid user type');
|
throw new Error('Invalid user type');
|
||||||
this.type = data.type;
|
this.type = data.type;
|
||||||
|
|
||||||
// TODO: this
|
|
||||||
this.applications = {};
|
this.applications = {};
|
||||||
this._applications = data.applications || [];
|
this._applications = data.applications || [];
|
||||||
// Object.defineProperty(this.applications, 'json', {
|
|
||||||
// get: () => {
|
|
||||||
// const keys = Object.keys(this.applications);
|
|
||||||
// const obj = [];
|
|
||||||
// for (const key of keys) {
|
|
||||||
// obj[key] = this.applications[key].json;
|
|
||||||
// }
|
|
||||||
// return obj;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// const apps = Object.keys(data.applications);
|
|
||||||
// for (const id of apps) {
|
|
||||||
// const app = data.applications[id];
|
|
||||||
// this.applications[id] = new UserApplicataion(this, app);
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.externalProfiles = data.externalProfiles || {};
|
this.externalProfiles = data.externalProfiles || {};
|
||||||
this.permissions = { ...User.defaultPermissions, ...data.permissions };
|
this.permissions = PermissionManager.merge(data.permissions, PermissionManager.DefaultPermissions);
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
this._passwordHash = data.password || null;
|
this._passwordHash = data.password || null;
|
||||||
@ -169,7 +136,7 @@ class User {
|
|||||||
* @memberof User
|
* @memberof User
|
||||||
*/
|
*/
|
||||||
hasPermission (perm, level = 1) {
|
hasPermission (perm, level = 1) {
|
||||||
return Util.checkPermissions(this.permissions, perm, level);
|
return PermissionManager.checkPermissions(this.permissions, perm, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasExternalProfile (name) {
|
hasExternalProfile (name) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { Util } = require("../../util");
|
const { PermissionManager } = require("../../util");
|
||||||
|
|
||||||
// Fields omitted in safeJson
|
// Fields omitted in safeJson
|
||||||
// Should be keys used in the json, not the ones defined in the constructor
|
// Should be keys used in the json, not the ones defined in the constructor
|
||||||
@ -36,7 +36,7 @@ class UserApplicataion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPermission (perm, level = 1) {
|
hasPermission (perm, level = 1) {
|
||||||
return Util.checkPermissions(this.permissions, perm, level);
|
return PermissionManager.checkPermissions(this.permissions, perm, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
get json () {
|
get json () {
|
||||||
|
68
src/util/PermissionManager.js
Normal file
68
src/util/PermissionManager.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
class PermissionManager {
|
||||||
|
|
||||||
|
static DefaultPermissions = {};
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
throw new Error('This is a static class');
|
||||||
|
}
|
||||||
|
|
||||||
|
static ensurePermission (permission) {
|
||||||
|
|
||||||
|
if (!permission)
|
||||||
|
throw new Error('Missing permission');
|
||||||
|
|
||||||
|
const perms = permission.split(':');
|
||||||
|
let result = 0;
|
||||||
|
for (let i = perms.length - 1; i >= 0; i--) {
|
||||||
|
const perm = perms[i];
|
||||||
|
result = { [perm]: result };
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionManager.merge(PermissionManager.DefaultPermissions, result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkPermissions (perms, perm, level = 1) {
|
||||||
|
|
||||||
|
if (!perms || typeof perms !== 'object')
|
||||||
|
throw new Error('Missing perms object');
|
||||||
|
if (!perm || typeof perm !== 'string')
|
||||||
|
throw new Error('Missing perm string');
|
||||||
|
|
||||||
|
// Allow for checking of nested permissions, e.g. administrator:createUser
|
||||||
|
const resolveables = perm.split(':');
|
||||||
|
let selector = null;
|
||||||
|
|
||||||
|
// Iterate through nestedd permissions
|
||||||
|
for (const permission of resolveables) {
|
||||||
|
selector = perms[permission] || null;
|
||||||
|
if (selector === null)
|
||||||
|
return false;
|
||||||
|
if (typeof selector === 'number' && selector >= level || selector.default >= level)
|
||||||
|
return true;
|
||||||
|
perms = selector; // selector is a nested object and may contain the next permission
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static merge (to, from) {
|
||||||
|
const keys = Object.keys(from);
|
||||||
|
for (const key of keys) {
|
||||||
|
if (typeof to[key] === 'object') {
|
||||||
|
PermissionManager.merge(to[key], from[key]);
|
||||||
|
} else if (typeof from[key] === 'object') {
|
||||||
|
to[key] = { default: to[key] || 0 };
|
||||||
|
PermissionManager.merge(to[key], from[key]);
|
||||||
|
// eslint-disable-next-line no-undefined
|
||||||
|
} else if (to[key] === undefined) {
|
||||||
|
to[key] = from[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PermissionManager;
|
@ -81,32 +81,6 @@ class Util {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkPermissions (perms, perm, level = 1) {
|
|
||||||
|
|
||||||
if (!perms || typeof perms !== 'object')
|
|
||||||
throw new Error('Missing perms object');
|
|
||||||
if (!perm || typeof perm !== 'string')
|
|
||||||
throw new Error('Missing perm string');
|
|
||||||
|
|
||||||
// Allow for checking of nested permissions, e.g. administrator:createUser
|
|
||||||
const resolveables = perm.split(':');
|
|
||||||
let selector = null;
|
|
||||||
// Iterate through nestedd permissions
|
|
||||||
for (const permission of resolveables) {
|
|
||||||
selector = perms[permission] || null;
|
|
||||||
if (selector === null)
|
|
||||||
return false;
|
|
||||||
else if (typeof selector === 'number' && selector >= level)
|
|
||||||
return true;
|
|
||||||
perms = selector; // selector is a nested object and may contain the next permission
|
|
||||||
}
|
|
||||||
// If the last selector is a nested permission group, check if the group has a default level
|
|
||||||
if (typeof selector === 'object' && selector.default >= level)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOT CRYPTOGRAPHICALLY SAFE
|
* NOT CRYPTOGRAPHICALLY SAFE
|
||||||
*
|
*
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
Util: require('./Util')
|
Util: require('./Util'),
|
||||||
|
PermissionManager: require('./PermissionManager')
|
||||||
};
|
};
|
17
test/PermissionManager.test.js
Normal file
17
test/PermissionManager.test.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const { PermissionManager } = require('../src/util');
|
||||||
|
const { inspect } = require('node:util');
|
||||||
|
|
||||||
|
const log = (obj) => {
|
||||||
|
console.log(inspect(obj, { depth: 10 }));
|
||||||
|
};
|
||||||
|
// PermissionManager.ensurePermission('administrator');
|
||||||
|
PermissionManager.ensurePermission('administrator:removeuser:force:bruh:moment:broo');
|
||||||
|
PermissionManager.ensurePermission('developer:toggledebug');
|
||||||
|
PermissionManager.ensurePermission('developer');
|
||||||
|
|
||||||
|
// log(PermissionManager);
|
||||||
|
|
||||||
|
const perms = { developer: { default: 10 }, administrator: 10 };
|
||||||
|
log(PermissionManager.merge(perms, PermissionManager.DefaultPermissions));
|
||||||
|
// log(perms);
|
Loading…
Reference in New Issue
Block a user