Compare commits

..

3 Commits

Author SHA1 Message Date
0b7f194ff4
misc fixes n cleanup 2022-11-24 13:20:55 +02:00
dc71a19fa8
registration codes 2022-11-24 13:20:43 +02:00
f5538b6a95
endpoint load order 2022-11-24 13:20:26 +02:00
8 changed files with 55 additions and 15 deletions

View File

@ -27,6 +27,7 @@ By default any OAuth callbacks are expected at `/api/login/<methodname>`, where
**Endpoints** **Endpoints**
Any all endpoints should go in the `/endpoints` directory and are expected to inherit from the Endpoint superclass. Any all endpoints should go in the `/endpoints` directory and are expected to inherit from the Endpoint superclass.
Endpoints have a loadOrder property that can be adjusted, lower values are loaded first.
## Main components ## Main components
**Controller:** `/src/controller/Controller.js` **Controller:** `/src/controller/Controller.js`

View File

@ -1,7 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const fs = require('node:fs'); const fs = require('node:fs');
const {Endpoint: BaseEndpoint} = require('../interfaces') const { Endpoint: BaseEndpoint } = require('../interfaces');
const { Util } = require('../../util'); const { Util } = require('../../util');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
@ -33,13 +33,13 @@ class Registry {
const { server, endpoints, logger } = this; const { server, endpoints, logger } = this;
return (async function read (pth) { (function read (pth) {
const dir = fs.readdirSync(pth, { withFileTypes: true }); const dir = fs.readdirSync(pth, { withFileTypes: true });
for (let file of dir) { for (let file of dir) {
if (file.isDirectory()) await read(path.join(pth, file.name)); if (file.isDirectory()) read(path.join(pth, file.name));
else { else {
file = path.join(pth, file.name); file = path.join(pth, file.name);
@ -51,10 +51,9 @@ class Registry {
continue; continue;
} }
const endpoint = new Endpoint(server); const endpoint = new Endpoint(server);
if(!(endpoint instanceof BaseEndpoint)) throw new Error(`Attempt to load invalid endpoint at ${file.name}`) if (!(endpoint instanceof BaseEndpoint)) throw new Error(`Attempt to load invalid endpoint at ${file.name}`);
endpoint.init();
if (endpoints.has(endpoint.resolveable)) throw new Error(`Error registering endpoint ${endpoint.resolveable}: an endpoint with that resolveable already exists`); if (endpoints.has(endpoint.resolveable)) throw new Error(`Error registering endpoint ${endpoint.resolveable}: an endpoint with that resolveable already exists`);
endpoints.set(endpoint.resolveable, endpoint); endpoints.set(endpoint.resolveable, endpoint);
logger.info(`Created endpoint ${endpoint.name}`); logger.info(`Created endpoint ${endpoint.name}`);
@ -64,6 +63,9 @@ class Registry {
}(this.path)); }(this.path));
const sorted = this.endpoints.sort((a, b) => a.loadOrder - b.loadOrder);
for (const ep of sorted.values()) ep.init();
} }
} }

View File

@ -5,6 +5,7 @@ const { inspect } = require('node:util');
const { AbstractUserDatabase } = require("../interfaces/"); const { AbstractUserDatabase } = require("../interfaces/");
const { User } = require("../structures"); const { User } = require("../structures");
const UserApplicataion = require("../structures/UserApplication"); const UserApplicataion = require("../structures/UserApplication");
const { Util } = require("../../util");
// MongoDB based user db // MongoDB based user db
class UserDatabase extends AbstractUserDatabase { class UserDatabase extends AbstractUserDatabase {
@ -185,6 +186,35 @@ class UserDatabase extends AbstractUserDatabase {
} }
/**
* Create code for letting users register when registration is disabled
*
* @param {number} [validFor=1] Amount of days the code is valid
* @memberof UserDatabase
*/
async createRegistrationCode (validFor = 1) {
const string = Util.randomString();
const now = Date.now();
const obj = { code: string, validFor: validFor * 24 * 60 * 60 * 1000, created: now };
await this.db.insertOne('registrationCodes', obj);
return obj;
}
async consumeRegistrationCode (code) {
const result = await this.db.findOne('registrationCodes', { code });
// Invalid conditions
if (!result) return false;
if (result.created + result.validFor < Date.now()) {
// Code existed but is no longer valid
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
return false;
}
// Valid code
await this.db.deleteOne('registrationCodes', { _id: ObjectId(result._id) });
return true;
}
/** /**
* Updates user entry * Updates user entry
* *

View File

@ -9,7 +9,8 @@ class Api404 extends ApiEndpoint {
super(server, { super(server, {
name: 'api404', name: 'api404',
path: '/*' path: '/*',
loadOrder: 10
}); });
this.methods = [ this.methods = [

View File

@ -13,8 +13,9 @@ class RegisterEndpoint extends ApiEndpoint {
[ 'post', this.register.bind(this), [ this.notLoggedIn.bind(this) ]] [ 'post', this.register.bind(this), [ this.notLoggedIn.bind(this) ]]
]; ];
this.subpaths = [ this.subpaths = [
[ '/finalise', 'post', this.finaliseRegistration.bind(this), [ this.loggedIn.bind(this) ]], [ '/finalise', 'post', this.finaliseRegistration.bind(this), [ server.auth.authenticate ]],
[ '/toggle', 'post', this.toggleRegistration.bind(this), [ server.authenticator.createAuthoriser('administrator', 5) ]] [ '/toggle', 'post', this.toggleRegistration.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]],
[ '/code', 'get', this.registrationCode.bind(this), [ server.auth.createAuthoriser('administrator', 5) ]]
]; ];
this.middleware = [ ]; this.middleware = [ ];
@ -69,9 +70,10 @@ class RegisterEndpoint extends ApiEndpoint {
} }
loggedIn (req, res, next) { async registrationCode (req, res) {
if (!req.user) return res.status(400).end(); const code = await this.userdb.createRegistrationCode();
next(); delete code._id;
res.json(code);
} }
notLoggedIn (req, res, next) { notLoggedIn (req, res, next) {

View File

@ -1,7 +1,7 @@
const { Util } = require('../../util'); const { Util } = require('../../util');
class Endpoint { class Endpoint {
constructor (server, { path, name }) { constructor (server, { path, name, loadOrder = 5 }) {
if (!server) Util.fatal(new Error('Missing server object in endpoint')); if (!server) Util.fatal(new Error('Missing server object in endpoint'));
this.server = server; this.server = server;
@ -25,6 +25,11 @@ class Endpoint {
this.logger = server.createLogger(this); this.logger = server.createLogger(this);
this.debug = false; this.debug = false;
// Used to sort the endpoints from smallest to highest before initialisation
// Useful when needing to have certain endpoints register before or after some other endpoint
// E.g. 404 pages should be initialised last by having a loadOrder of 10
this.loadOrder = loadOrder;
} }
init () { init () {

View File

@ -130,7 +130,7 @@ class Authenticator {
// we don't care about that, we just want the key, which will always be the last element // we don't care about that, we just want the key, which will always be the last element
const authHeader = req.get('Authorization') || req.get('Authorisation'); const authHeader = req.get('Authorization') || req.get('Authorisation');
if (!authHeader) { if (!authHeader) {
res.status(400).send('Invalid auth header'); res.status(401).send('Invalid auth header');
return false; return false;
} }

View File

@ -59,7 +59,6 @@ class Util {
} }
static createEncryptionKey (secret, salt, len = 32) { static createEncryptionKey (secret, salt, len = 32) {
if (!secret) throw new Error('Missing secret'); if (!secret) throw new Error('Missing secret');