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
@ -19,7 +20,13 @@ Fetching the upstream changes
- Fetch the the remotes `git fetch`. - Fetch the the remotes `git fetch`.
- Preview the changes `git log -p HEAD..upstream/master` - Preview the changes `git log -p HEAD..upstream/master`
- 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`

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,
}; };
} }