Compare commits

...

5 Commits

Author SHA1 Message Date
b50f603ee2
endpoint 404 2022-11-24 00:07:06 +02:00
f4a04bc67b
misc improvements 2022-11-24 00:06:53 +02:00
a7dac9f5f7
bugfix 2022-11-24 00:06:36 +02:00
ede532df4c
mongodb auth db 2022-11-24 00:06:02 +02:00
b5820dafde
update readme 2022-11-24 00:05:29 +02:00
8 changed files with 56 additions and 19 deletions

View File

@ -1,6 +1,7 @@
# Navy's webserver framework # Navy's webserver framework
A template repository for creating Node.js based webservers with sharding. A template repository for creating Node.js based webservers with sharding.
Main repository: https://git.corgi.wtf/Navy.gif/webserver-framework Main repository: https://git.corgi.wtf/Navy.gif/webserver-framework
Frontend: https://git.corgi.wtf/Navy.gif/webserver-framework-frontend
## Technologies ## Technologies
- Argon2 - Argon2
@ -21,6 +22,12 @@ Fetching the upstream changes
- Merge changes to the current branch `git merge upstream/master`. - Merge changes to the current branch `git merge upstream/master`.
(Use `git remote -v` to list remotes) (Use `git remote -v` to list remotes)
**OAuth callbacks**
By default any OAuth callbacks are expected at `/api/login/<methodname>`, where methodname is the name of the OAuth strategy. E.g. by default this framework has a discord strategy defined in Server.addAuthStrategies which expects a callback to `/api/login/discord`.
**Endpoints**
Any all endpoints should go in the `/endpoints` directory and are expected to inherit from the Endpoint superclass.
## Main components ## Main components
**Controller:** `/src/controller/Controller.js` **Controller:** `/src/controller/Controller.js`
Master process, orchestrates the whole program. Takes care of starting up the shards and communication with them. Master process, orchestrates the whole program. Takes care of starting up the shards and communication with them.

View File

@ -11,6 +11,7 @@ MONGO_PORT=27017
MONGO_USER= MONGO_USER=
MONGO_PASS= MONGO_PASS=
MONGO_DB=framework-proto MONGO_DB=framework-proto
MONGO_AUTH_DB=auth
# Encryption secret # Encryption secret
CRYPTO_SECRET=verySecretSecret CRYPTO_SECRET=verySecretSecret

View File

@ -34,7 +34,7 @@ class Server extends EventEmitter {
const { MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB, const { MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB,
MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB, MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB,
NODE_ENV, SECRET, CRYPTO_SECRET, CRYPTO_SALT } = process.env; NODE_ENV, SECRET, CRYPTO_SECRET, CRYPTO_SALT, MONGO_AUTH_DB } = process.env;
const { http: httpOpts, databases, validUserTypes } = options; const { http: httpOpts, databases, validUserTypes } = options;
// This key never leaves memory and is exclusively used on the server, the salt can stay static // This key never leaves memory and is exclusively used on the server, the salt can stay static
@ -66,7 +66,7 @@ class Server extends EventEmitter {
// Mariadb isn't strictly necessary here for anything, it's just here pre-emptively // Mariadb isn't strictly necessary here for anything, it's just here pre-emptively
this.mariadb = new MariaDB(this, { options: databases.mariadb, MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB }); this.mariadb = new MariaDB(this, { options: databases.mariadb, MARIA_HOST, MARIA_USER, MARIA_PORT, MARIA_PASS, MARIA_DB });
// Mongo is used for session and user storage // Mongo is used for session and user storage
this.mongodb = new MongoDB(this, { options: databases.mongodb, MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB }); this.mongodb = new MongoDB(this, { options: databases.mongodb, MONGO_HOST, MONGO_USER, MONGO_PORT, MONGO_PASS, MONGO_DB, MONGO_AUTH_DB });
this.userDatabase = new UserDatabase(this, this.mongodb, { validUserTypes }); this.userDatabase = new UserDatabase(this, this.mongodb, { validUserTypes });
// Alias // Alias
this.users = this.userDatabase; this.users = this.userDatabase;
@ -102,9 +102,6 @@ class Server extends EventEmitter {
}, },
crossOriginResourcePolicy: { crossOriginResourcePolicy: {
policy: 'cross-origin' policy: 'cross-origin'
},
crossOriginEmbedderPolicy: {
policy: 'require-corp'
} }
})); }));
this.app.use(express.json({ limit: '10mb' })); this.app.use(express.json({ limit: '10mb' }));

View File

@ -156,7 +156,7 @@ class UserDatabase extends AbstractUserDatabase {
} }
this.logger.info(`Creating new user from Discord profile: ${profile.username} (${profile.id})`); this.logger.info(`Creating new user from Discord profile: ${profile.username} (${profile.id})`);
user = this._createUser({ type: 'user', name: profile.username }); user = this._createUser({ type: 'user', username: profile.username });
user.addExternalProfile('discord', profile); user.addExternalProfile('discord', profile);
await user.save(); await user.save();
this.cache.set(user.id, user); this.cache.set(user.id, user);

View File

@ -0,0 +1,27 @@
const { ApiEndpoint } = require("../../interfaces");
// Just so requests to /api/.. don't get sent the index.html file on invalid path
// Could've also just defined this quick and dirty in the Server class,
// but I feel this is better for readability
class Api404 extends ApiEndpoint {
constructor (server) {
super(server, {
name: 'api404',
path: '/*'
});
this.methods = [
[ 'get', this.get.bind(this) ]
];
}
async get (req, res) {
res.status(404).end();
}
}
module.exports = Api404;

View File

@ -18,7 +18,7 @@ class LogoutEndpoint extends ApiEndpoint {
logout (req, res) { logout (req, res) {
req.session.destroy(); req.session.destroy();
res.clearCookie(this.server.authenticator.cookieName); res.clearCookie(this.server.auth.cookieName);
res.end(); res.end();
} }

View File

@ -22,7 +22,7 @@ class UsersEndpoint extends ApiEndpoint {
const { amount, page } = query; const { amount, page } = query;
if (!page) return res.status(400).send('Missing page number'); if (!page) return res.status(400).send('Missing page number');
const users = await this.server.users.fetchUsers(page, amount); const users = await this.server.users.fetchUsers(page, amount);
res.json(users.map(user => user.json)); res.json(users.map(user => user.safeJson));
} }
async user (req, res) { async user (req, res) {
@ -30,7 +30,7 @@ class UsersEndpoint extends ApiEndpoint {
const { userid } = params; const { userid } = params;
const user = await this.server.users.fetchUser(userid); const user = await this.server.users.fetchUser(userid);
if (!user) return res.status(404).end(); if (!user) return res.status(404).end();
res.json(user); res.json(user.safeJson);
} }
async userApplications (req, res) { async userApplications (req, res) {
@ -39,7 +39,7 @@ class UsersEndpoint extends ApiEndpoint {
const user = await this.server.users.fetchUser(userid); const user = await this.server.users.fetchUser(userid);
if (!user) return res.status(404).send('Could not find the user'); if (!user) return res.status(404).send('Could not find the user');
const applications = await user.fetchApplications(); const applications = await user.fetchApplications();
res.json(Object.values(applications).map(app => app.json)); res.json(Object.values(applications).map(app => app.safeJson));
} }
} }

View File

@ -3,6 +3,9 @@ const { ObjectId } = require('mongodb');
const { Util } = require('../../util'); const { Util } = require('../../util');
const UserApplicataion = require('./UserApplication'); const UserApplicataion = require('./UserApplication');
// Fields omitted in safeJson
const ProtectedFields = [ '_id', '_otpSecret', '_passwordHash' ];
class User { class User {
static defaultPermissions = { static defaultPermissions = {
@ -29,6 +32,7 @@ class User {
this._db = db; this._db = db;
this.temporary = data.temporary || false; this.temporary = data.temporary || false;
this.disabled = data.disabled || false;
this._id = data._id || null; this._id = data._id || null;
if (this.temporary) this._tempId = `temp-${Date.now()}`; if (this.temporary) this._tempId = `temp-${Date.now()}`;
@ -69,6 +73,7 @@ class User {
this._2fa = data.twoFactor || false; this._2fa = data.twoFactor || false;
this.cachedTimestamp = Date.now(); this.cachedTimestamp = Date.now();
this.createdTimestamp = data.createdTimestamp || Date.now();
} }
@ -171,21 +176,21 @@ class User {
otpSecret: this._otpSecret, otpSecret: this._otpSecret,
twoFactor: this._2fa, twoFactor: this._2fa,
applications: this._applications, applications: this._applications,
createdTimestamp: this.createdTimestamp,
disabled: this.disabled,
}; };
} }
get safeJson () { get safeJson () {
const { json } = this;
for (const key of ProtectedFields) delete json[key];
return { return {
...json,
id: this.id, id: this.id,
username: this.username,
displayName: this.displayName,
type: this.type,
permissions: this.permissions,
externalProfiles: Object.values(this.externalProfiles).map(prof => { externalProfiles: Object.values(this.externalProfiles).map(prof => {
return { id: prof.id, provider: prof.provider, username: prof.username }; return { id: prof.id, provider: prof.provider, username: prof.username };
}), }),
twoFactor: this._2fa,
applications: this._applications,
}; };
} }