Compare commits

..

No commits in common. "5e1c176ac634f8950483b2fb38cd621dd0321a6d" and "22328db33acb579b0cc50f16ba12d66b11fbf5c7" have entirely different histories.

9 changed files with 76 additions and 112 deletions

View File

@ -64,7 +64,7 @@ class UserDatabase extends AbstractUserDatabase {
if (id.includes('temp'))
return null;
const data = await this.db.findOne(this._userColllection, { _id: new ObjectId(id) });
const data = await this.db.findOne(this._userColllection, { _id: ObjectId(id) });
if (!data)
return null;
@ -85,7 +85,7 @@ class UserDatabase extends AbstractUserDatabase {
if (!force && this.cache.has(id))
return this.cache.get(id);
const data = await this.db.findOne(this._appCollection, { _id: new ObjectId(id) });
const data = await this.db.findOne(this._appCollection, { _id: ObjectId(id) });
if (!data)
return null;
@ -238,11 +238,11 @@ class UserDatabase extends AbstractUserDatabase {
return false;
if (result.created + result.validFor < Date.now()) {
// Code existed but is no longer valid
await this.db.deleteOne('registrationCodes', { _id: new ObjectId(result._id) });
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
return false;
}
// Valid code
await this.db.deleteOne('registrationCodes', { _id: new ObjectId(result._id) });
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
return true;
}
@ -256,7 +256,7 @@ class UserDatabase extends AbstractUserDatabase {
const { json } = user;
this.logger.debug(`Updating user ${inspect(json)}`);
if (user._id)
await this.db.updateOne(this._userColllection, { _id: new ObjectId(user._id) }, json);
await this.db.updateOne(this._userColllection, { _id: ObjectId(user._id) }, json);
else {
const result = await this.db.insertOne(this._userColllection, json);
user._id = result.insertedId;
@ -267,7 +267,7 @@ class UserDatabase extends AbstractUserDatabase {
async updateApplication (app) {
const { json } = app;
this.logger.debug(`Updating application ${inspect(json)}`);
await this.db.updateOne(this._appCollection, { _id: new ObjectId(json._id) }, json, true);
await this.db.updateOne(this._appCollection, { _id: ObjectId(json._id) }, json, true);
}

View File

@ -10,7 +10,6 @@ const { authenticator } = require('otplib');
const { inspect } = require('node:util');
const path = require('node:path');
const fs = require("node:fs");
// Populated by the constructor
const ServiceProfileLinks = {};
@ -43,7 +42,7 @@ class UserEndpoint extends ApiEndpoint {
// Applications
[ 'get', '/applications', this.applications.bind(this) ],
[ 'post', '/applications', this.createApplication.bind(this), [ server.auth.createAuthoriser('applications:create', 5) ]],
[ 'post', '/applications', this.createApplication.bind(this) ],
[ 'delete', '/applications/:id', this.deleteApplication.bind(this) ],
];
@ -71,14 +70,9 @@ class UserEndpoint extends ApiEndpoint {
const x = file.name.split('.');
const fileName = `${user.id}.${x[x.length - 1]}`;
const avatarsDir = path.resolve(this.server.serveFiles, 'avatars');
const oldAvatar = path.join(avatarsDir, user.avatar);
const filesDir = path.resolve(this.server.serveFiles, 'avatars', fileName);
// Delete old avatar
if (fs.statSync(oldAvatar))
fs.unlinkSync(oldAvatar);
file.mv(path.join(avatarsDir, fileName), async (err) => {
file.mv(filesDir, async (err) => {
if (err) {
this.logger.error(`Error saving file: ${err.stack}`);
return res.status(500).end();
@ -241,7 +235,6 @@ class UserEndpoint extends ApiEndpoint {
async createApplication (req, res) {
const { body, user } = req;
const { name, ...opts } = body;
this.logger.info(`User ${user.username} (${user.id}) is creating an application`);
if (!name)
return res.status(400).send('Missing name');
const application = await user.createApplication(name, opts);

View File

@ -1,6 +1,6 @@
/* eslint-disable camelcase */
const { AbstractUserDatabase } = require('../interfaces');
const { Util, PermissionManager } = require('../../util');
const { Util } = require('../../util');
const session = require('express-session');
const MongoStore = require('connect-mongo');
@ -177,8 +177,6 @@ class Authenticator {
*/
createAuthoriser (permission, level = 1) {
PermissionManager.ensurePermission(permission);
const func = async (req, res, next) => {
const { user } = req;
// Request does not have a user bound to it, response already sent from #_authenticate
@ -187,7 +185,7 @@ class Authenticator {
// Has permission
if (user.hasPermission(permission, level))
return next();
return res.status(403).send('Insufficient permissions');
return res.status(403).send('Access denied');
};
return func.bind(this);

View File

@ -1,6 +1,6 @@
const Argon2 = require('argon2');
const { ObjectId } = require('mongodb');
const { Util, PermissionManager } = require('../../util');
const { Util } = require('../../util');
const UserApplicataion = require('./UserApplication');
// Fields omitted in safeJson
@ -9,6 +9,23 @@ const ProtectedFields = [ '_id', 'otpSecret', 'password' ];
class User {
static defaultPermissions = {
developer: {
default: 0
},
administrator: {
default: 0
},
test: {
default: 0,
dingus: {
bingus: {
default: 10
}
}
}
};
static validTypes = [];
constructor (db, data) {
@ -33,11 +50,27 @@ class User {
throw new Error('Invalid user type');
this.type = data.type;
// TODO: this
this.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.permissions = PermissionManager.merge(data.permissions, PermissionManager.DefaultPermissions);
this.permissions = { ...User.defaultPermissions, ...data.permissions };
/** @private */
this._passwordHash = data.password || null;
@ -136,7 +169,7 @@ class User {
* @memberof User
*/
hasPermission (perm, level = 1) {
return PermissionManager.checkPermissions(this.permissions, perm, level);
return Util.checkPermissions(this.permissions, perm, level);
}
hasExternalProfile (name) {

View File

@ -1,4 +1,4 @@
const { PermissionManager } = require("../../util");
const { Util } = require("../../util");
// Fields omitted in safeJson
// Should be keys used in the json, not the ones defined in the constructor
@ -36,7 +36,7 @@ class UserApplicataion {
}
hasPermission (perm, level = 1) {
return PermissionManager.checkPermissions(this.permissions, perm, level);
return Util.checkPermissions(this.permissions, perm, level);
}
get json () {

View File

@ -1,68 +0,0 @@
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,6 +81,32 @@ 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
*

View File

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

View File

@ -1,17 +0,0 @@
/* 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);