Compare commits

..

3 Commits

  1. 5
      api/ani/v1/routes/stats.js
  2. 34
      api/ani/v1/routes/user.js
  3. 8
      api/index.js
  4. 6
      api/util/auth/authenticate.js
  5. 18
      api/util/auth/authorize.js
  6. 15
      api/util/list.js
  7. 21
      api/util/startup/cache.js
  8. 24
      api/util/startup/cache/users.js
  9. 2
      db/ani/users.js
  10. 4
      package.json

@ -0,0 +1,5 @@
module.exports = (app, router) => {
router.get('/stats', (req, res) => res.json({
users: Object.keys(app.cache.users).length
}));
};

@ -4,8 +4,9 @@ const {sign} = require('jsonwebtoken');
module.exports = (app, router) => { module.exports = (app, router) => {
const Users = app.db.models.ani.users; const Users = app.db.models.ani.users;
router.get('/user', (req, res) => res.send("/user: /user/:id required.")); //router.get('/user', (req, res) => res.send("/user: /user/:id required."));
//router.use(app.util.list(router, '/user')); //list users router.use('/users', (req, res, next) => {req.listData = Object.keys(app.cache.users); next()}, app.util.list(router, '/users')); //list users
router.use('/user', (req, res, next) => {req.listData = Object.keys(app.cache.users); next()}, app.util.list(router, '/user'));
router.route('/user/:id') router.route('/user/:id')
.get(async (req, res) => { .get(async (req, res) => {
@ -21,27 +22,30 @@ module.exports = (app, router) => {
if (!req.body) {return res.status(400).send("Missing body!");} if (!req.body) {return res.status(400).send("Missing body!");}
if ( if (
!req.body.name || !req.body.discord || !req.body.permissions || !Array.isArray(req.body.permissions) !req.body.name || !req.body.discord || !req.body.permissions || !Array.isArray(req.body.permissions)
|| !req.body.name.match(/^[\w_ ]+$/gm) || req.body.name.length > 20 || !req.body.name.match(/^[\w_\- ]+$/gm) || req.body.name.length > 20
|| !req.params.id.match(/^[\w_]+$/gm) || req.params.id.length > 15 || !req.params.id.match(/^[\w_\-]+$/gm) || req.params.id.length > 15
|| !req.body.password || req.body.password.length > 30 || !req.body.password || req.body.password.length > 30
) {return res.status(400).send("Malformed body or missing body data. Make sure you have all the required parameters, and you don't have illegal characters present.");} ) {return res.status(400).send("Malformed body or missing body data. Make sure you have all the required parameters, and you don't have illegal characters present.");}
const newUser = new Users({ const newUser = new Users({
id: req.params.id.toLowerCase(), id: req.params.id.toLowerCase(),
name: req.body.name, name: req.body.name,
permissions: req.body.permissions, permissions: req.body.permissions.map(permission => permission.toLowerCase()),
discord: req.body.discord, discord: req.body.discord,
password: hashSync(req.body.password, 8) password: hashSync(req.body.password, 8)
}); });
return newUser.save() return newUser.save()
.then(() => res.json({ .then(() => {
app.cache.users[newUser.id] = {id: newUser.id, discord: newUser.discord};
return res.json({
message: "Successfully added user.", message: "Successfully added user.",
name: newUser.name, name: newUser.name,
discord: newUser.discord, discord: newUser.discord,
id: newUser.id, id: newUser.id,
permissions: newUser.permissions, permissions: newUser.permissions,
accessToken: sign({id: newUser.id}, app.auth.jwt_secret, {expiresIn: "15d"}) accessToken: sign({id: newUser.id}, app.auth.jwt_secret, {expiresIn: "15d"})
})) });
})
.catch(e => {console.error("Error trying to add new user", e); res.status(500).send("Something went wrong.");}); .catch(e => {console.error("Error trying to add new user", e); res.status(500).send("Something went wrong.");});
} }
catch (e) {console.error("Error trying to add new user", e); res.status(500).send("Something went wrong.");} catch (e) {console.error("Error trying to add new user", e); res.status(500).send("Something went wrong.");}
@ -66,17 +70,17 @@ module.exports = (app, router) => {
}); });
}) })
.get(app.auth.token, (req, res) => { .get(app.auth.token, (req, res) => {
if (!req.user) {return res.status(401).send("You have not been authenticated, and will not be able to access any sensitive routes.");} if (!req.authenticatedUser) {return res.status(401).send("You have not been authenticated, and will not be able to access any sensitive routes.");}
return res.json({ return res.json({
message: "You are authenticated, and your token is valid.", message: "You are authenticated, and your token is valid.",
name: req.user.name, name: req.authenticatedUser.name,
discord: req.user.discord, discord: req.authenticatedUser.discord,
id: req.user.id, id: req.authenticatedUser.id,
permissions: req.user.permissions permissions: req.authenticatedUser.permissions
}); });
}); });
router.use('/user/:id/permissions', async (req, res, next) => { router.use('/user/:id/permissions', app.auth.tokenPass, app.auth.permsPass('edit-users'), async (req, res, next) => {
if (!req.params.id) {return res.status(400).send("Missing ID!");} if (!req.params.id) {return res.status(400).send("Missing ID!");}
const user = await Users.findOne({id: req.params.id.toLowerCase()}); const user = await Users.findOne({id: req.params.id.toLowerCase()});
if (!user) {return res.status(404).send("That user doesn't exist!");} if (!user) {return res.status(404).send("That user doesn't exist!");}
@ -89,5 +93,9 @@ module.exports = (app, router) => {
req.user.permissions = permissions; req.user.permissions = permissions;
req.user.markModified('permissions'); req.user.markModified('permissions');
await req.user.save(); await req.user.save();
}, (req, res, next) => {
if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");}
if (req.unauthorized) {return res.status(401).send("You are not authorized to edit users!");}
return next();
})); }));
}; };

@ -34,12 +34,16 @@ server = app.listen(4062, async () => {
require('../db/build')(app); //place all models in memory to prevent double-compiling require('../db/build')(app); //place all models in memory to prevent double-compiling
app.auth.token = require('./util/baseAuthorize')(app); //jwt token validation app.auth.token = require('./util/auth/authenticate')(app); //jwt token validation
app.auth.tokenPass = require('./util/baseAuthorize')(app, true); //"next()" will run even if auth is not passed app.auth.tokenPass = require('./util/auth/authenticate')(app, true); //"next()" will run even if auth is not passed
app.auth.perms = require('./util/auth/authorize')(app); //permissions checking
app.auth.permsPass = require('./util/auth/authorize')(app, true);
app.util = {}; app.util = {};
app.util.list = require('./util/list'); app.util.list = require('./util/list');
await require('./util/startup/cache')(app);
require('./v1/index')(app); //initialize bot API branch require('./v1/index')(app); //initialize bot API branch
require('./ani/index')(app); //initialize ani API branch require('./ani/index')(app); //initialize ani API branch

@ -5,13 +5,13 @@ module.exports = (app, passAuth) => {
return (req, res, next) => { return (req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.authorization.split(" ")[0] === "JWT") { if (req.headers && req.headers.authorization && req.headers.authorization.split(" ")[0] === "JWT") {
verify(req.headers.authorization.split(' ')[1], app.auth.jwt_secret, (e, d) => { verify(req.headers.authorization.split(' ')[1], app.auth.jwt_secret, (e, d) => {
if (e) {req.user = undefined; return passAuth ? next() : res.status(401).send("You are not authenticated!");} if (e) {req.authenticatedUser = undefined; return passAuth ? next() : res.status(401).send("You are not authenticated!");}
Users.findOne({id: d.id}) Users.findOne({id: d.id})
.exec((err, user) => { .exec((err, user) => {
if (err) {res.status(500).send("Something went trying to authorize you!");} if (err) {res.status(500).send("Something went trying to authorize you!");}
else {req.user = user; next();} else {req.authenticatedUser = user; next();}
}); });
}); });
} } else {req.authenticatedUser = undefined; return passAuth ? next() : res.status(401).send("You are not authenticated; you did not send your request with any authentication headers.");}
}; };
}; };

@ -0,0 +1,18 @@
module.exports = (app, passAuth) => {
return requiredPermissions => {
if (!Array.isArray(requiredPermissions)) {requiredPermissions = [requiredPermissions];}
return (req, res, next) => {
if (!req.authenticatedUser) {return next();}
if (req.authenticatedUser.permissions.includes("admin")) {return next();}
else {
let hasAllPerms = true;
for (let permission of requiredPermissions) {
if (!req.authenticatedUser.permissions.includes(permission)) {hasAllPerms = false;}
}
if (!hasAllPerms) {req.unauthorized = true; return passAuth ? next() : res.status(401).send("You are not authorized to do that!");}
else {return next();}
}
};
};
};

@ -1,6 +1,6 @@
const {Router} = require("express"); const {Router} = require("express");
module.exports = (parentRouter, path, modifiable, bodyName, save) => { module.exports = (parentRouter, path, modifiable, bodyName, save, authHandler) => {
const router = Router(); const router = Router();
router.get("/", (req, res) => res.json(req.listData)); router.get("/", (req, res) => res.json(req.listData));
@ -14,18 +14,21 @@ module.exports = (parentRouter, path, modifiable, bodyName, save) => {
req.listData.push(req.body[bodyName]); req.listData.push(req.body[bodyName]);
return await save(req, res, req.listData, req.body[bodyName]) || res.json(req.listData); return await save(req, res, req.listData, req.body[bodyName]) || res.json(req.listData);
} }
router.post('/add', add); router.post('/add', authHandler, add);
router.post('/', add); router.post('/', authHandler, add);
router.post('/clear', async (req, res) => { router.post('/clear', authHandler, async (req, res) => {
req.listData = []; req.listData = [];
return await save(req, res, req.listData) || res.json(req.listData); return await save(req, res, req.listData) || res.json(req.listData);
}); });
router.delete('/remove', async (req, res) => { router.delete('/remove', authHandler, async (req, res) => {
if (!req.body[bodyName]) {return res.status(400).send(`Missing body param "${bodyName}".`);} if (!req.body[bodyName]) {return res.status(400).send(`Missing body param "${bodyName}".`);}
let index = req.listData.indexOf(req.body[bodyName]); let index = req.listData.indexOf(req.body[bodyName]);
if (index === -1) {return res.status(400).send("The server was unable to find an item matching the specified criteria.");} if (index === -1) {return res.status(400).send("The server was unable to find an item matching the specified criteria.");}
let length = req.listData.length; let length = req.listData.length;
req.listData = req.listData.length === 1 && req.listData[0] === req.body[bodyName] ? [] : req.listData.splice(req.listData.indexOf(req.body[bodyName]), 1);
if (req.listData.length === 1 && req.listData[0] === req.body[bodyName]) {req.listData = [];}
else {req.listData.splice(req.listData.indexOf(req.body[bodyName]), 1);}
return req.listData.length === length ? res.status(500).send("For some reason, that couldn't be removed.") : await save(req, res, req.listData) || res.json(req.listData); return req.listData.length === length ? res.status(500).send("For some reason, that couldn't be removed.") : await save(req, res, req.listData) || res.json(req.listData);
}); });
} }

@ -0,0 +1,21 @@
const gs = require('gradient-string');
const spinnies = require('dreidels');
const chalk = require('chalk');
module.exports = async app => {
app.cache = {
users: {}
};
return new Promise(async resolve => {
const loaders = [];
const spin = new spinnies();
let userCache = spin.add("ar", {text: "Caching Users..."});
loaders.push(require('./cache/users')(app, userCache));
await Promise.all(loaders);
console.log('');
resolve(0);
});
}; //robbery is cool :)

@ -0,0 +1,24 @@
const chalk = require('chalk');
module.exports = async (app, spinner) => {
const Users = app.db.models.ani.users;
return new Promise(async resolve => {
const st = new Date().getTime();
let amount = 1;
for await (const user of Users.find()) {
app.cache.users[user.id] = {id: user.id, discord: user.discord};
spinner.update({text: `${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${amount}`)} ${chalk.blueBright(`ani DB users.`)}`});
amount++;
}
const cacheTime = new Date().getTime() - st;
spinner.update({text: `${spinner.options.text.slice(0, 19).trim()} ${chalk.gray(`${cacheTime}ms >>`.padStart(8, '0').padStart(7, '0'))} ${spinner.options.text.slice(19).trim()}`});
spinner.status('non-spinnable');
resolve(0);
});
} //robbery is like, the best
// ...it's my own code don't @ me

@ -6,5 +6,5 @@ module.exports = (connection) => connection.model('users', new Schema({
discord: {type: String, unique: true}, discord: {type: String, unique: true},
permissions: [String], permissions: [String],
password: String, password: String,
apiToken: {type: String, unique: true} apiToken: String
})); }));

@ -5,9 +5,13 @@
"dependencies": { "dependencies": {
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"body-parser": "^1.20.1", "body-parser": "^1.20.1",
"chalk": "^4.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"dreidels": "^0.5.2",
"express": "^4.18.2", "express": "^4.18.2",
"fuzzysort": "^2.0.4",
"gradient-string": "^2.0.2",
"helmet": "^6.0.1", "helmet": "^6.0.1",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"mongoose": "^6.8.4" "mongoose": "^6.8.4"

Loading…
Cancel
Save