Compare commits

...

3 Commits

Author SHA1 Message Date
5e1c176ac6
new permission manager 2023-02-07 21:15:04 +02:00
4f12444a9d
new keyworkd for objectid 2023-02-07 21:14:31 +02:00
3b63f02dd7
delete old pfp file 2023-02-07 21:13:53 +02:00
9 changed files with 112 additions and 76 deletions

View File

@ -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);
} }

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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 () {

View 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;

View File

@ -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
* *

View File

@ -1,3 +1,4 @@
module.exports = { module.exports = {
Util: require('./Util') Util: require('./Util'),
PermissionManager: require('./PermissionManager')
}; };

View 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);