Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build
sql/queries/*
!sql/queries/samples
!sql/queries/README.md
.DS_Store
30 changes: 4 additions & 26 deletions api/v1/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,9 @@ const { Router } = require('express');
const router = Router();

const { formatAvatar, formatJobString, titleIdToString } = require('./utils/chars');
const { getJWTForAccountId } = require('./utils/accounts');
const { getJWTForAccountId, validateJWT } = require('./utils/accounts');

const validate = (req, res, next) => {
const token = req.headers.authorization.replace(/^Bearer\s/, '');
jwt.verify(
token,
process.env.JWT_SECRET,
{
algorithms: ['HS512'],
clockTolerance: 0,
ignoreExpiration: false,
maxAge: '30h',
},
(error, decoded) => {
if (!error) {
req.jwt = decoded;
next();
} else {
res.status(401).send();
}
}
);
};

router.get('/profile', validate, async (req, res) => {
router.get('/profile', validateJWT, async (req, res) => {
try {
const statement = `SELECT *, IF(accounts_sessions.charid IS NULL, 0, 1) AS \`online\` FROM chars
JOIN char_stats ON chars.charid = char_stats.charid
Expand Down Expand Up @@ -162,7 +140,7 @@ router.post('/register', (req, res) => {
}
});

router.put('/email', validate, async (req, res) => {
router.put('/email', validateJWT, async (req, res) => {
try {
const statement = 'UPDATE accounts SET `email` = ? WHERE id = ? AND `password` = PASSWORD(?);';
const result = await req.app.locals.query(statement, [req.headers.email, req.jwt.id, req.headers.oldpass]);
Expand All @@ -177,7 +155,7 @@ router.put('/email', validate, async (req, res) => {
}
});

router.put('/password', validate, async (req, res) => {
router.put('/password', validateJWT, async (req, res) => {
try {
const statement = 'UPDATE accounts SET `password` = PASSWORD(?) WHERE id = ? AND `password` = PASSWORD(?);';
const result = await req.app.locals.query(statement, [req.headers.newpass, req.jwt.id, req.headers.oldpass]);
Expand Down
86 changes: 60 additions & 26 deletions api/v1/misc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const parseMD = require('parse-md').default;
const fs = require('fs');
const { Router } = require('express');

const router = Router();
Expand All @@ -8,6 +6,9 @@ const axios = require('axios');
const scanner = require('portscanner');

const { getYells } = require('./utils/yells');
const { validateJWT } = require('./utils/accounts');

const MAX_NUMBER_OF_POSTS = 5;

router.get('/status', async (req, res) => {
try {
Expand Down Expand Up @@ -156,39 +157,72 @@ router.get('/config', async (req, res) => {
});

router.get('/news', async (req, res) => {
const maxAmountOfPosts = 5;
const newsFolder = './news';

const cache = await req.app.locals.cache.fetch(
{
key: req.originalUrl,
interval: 300000, // 5 minutes
},
() => {
let posts = [];
let currentDate = new Date();

const files = fs.readdirSync(newsFolder);
files.forEach(file => {
const fileContents = fs.readFileSync(`${newsFolder}/${file}`, 'utf8');
let post = parseMD(fileContents);
let date = Date.parse(post.metadata.date);

if (date > currentDate) {
// news that's dated in the future shouldn't show up
return;
}

posts.push(post);
});

posts.sort((p1, p2) => p2.metadata.date - p1.metadata.date);
posts = posts.slice(0, maxAmountOfPosts);
return posts;
async () => {
const statement = `
SELECT post_id, DATE_FORMAT(date, '%Y-%m-%dT%T.000Z') AS date, author, title, markdown
FROM web_posts
WHERE (expiration IS NULL OR expiration > UTC_TIMESTAMP) AND date < UTC_TIMESTAMP AND deleted = 0
ORDER BY date DESC
LIMIT ?;
`;
const posts = await req.app.locals.query(statement, [MAX_NUMBER_OF_POSTS]);
return posts.map(post => ({
content: post.markdown,
metadata: {
id: post.post_id,
title: post.title,
author: post.author,
date: post.date,
},
}));
}
);

res.send(cache);
});

router.post('/write', validateJWT, async (req, res) => {
try {
if (req.jwt.privileges.includes('WEB_SCRIBE')) {
const { author, date, expiration, title, markdown } = req.body;
const statement = 'INSERT INTO web_posts (`author_id`,`date`,`author`,`title`,`markdown`,`expiration`) VALUES (?, ?, ?, ?, ?, ?)';
const results = await req.app.locals.query(statement, [req.jwt.id, date, author, title, markdown, expiration]);
if (results.affectedRows) {
req.app.locals.cache.clear('/api/v1/misc/news');
res.send();
} else {
res.status(500).send();
}
} else {
res.status(401).send();
}
} catch {
res.status(500).send();
}
});

router.post('/trash', validateJWT, async (req, res) => {
try {
if (req.jwt.privileges.includes('WEB_SCRIBE')) {
const statement = 'UPDATE web_posts SET deleted = 1 WHERE post_id = ?';
const results = await req.app.locals.query(statement, [req.body.id]);
if (results.affectedRows) {
req.app.locals.cache.clear('/api/v1/misc/news');
res.send();
} else {
res.status(500).send();
}
} else {
res.status(401).send();
}
} catch {
res.status(500).send();
}
});

module.exports = router;
25 changes: 24 additions & 1 deletion api/v1/utils/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const jwt = require('jsonwebtoken');

const privileges = [
'PLAYER', // 1
'UNUSED1', // 2
'WEB_SCRIBE', // 2
'UNUSED2', // 4
'UNUSED3', // 8
'UNUSED4', // 16
Expand Down Expand Up @@ -49,6 +49,29 @@ const getJWTForAccountId = async (query, accid) => {
}
};

const validateJWT = (req, res, next) => {
const token = req.headers.authorization.replace(/^Bearer\s/, '');
jwt.verify(
token,
process.env.JWT_SECRET,
{
algorithms: ['HS512'],
clockTolerance: 0,
ignoreExpiration: false,
maxAge: '30h',
},
(error, decoded) => {
if (!error) {
req.jwt = decoded;
next();
} else {
res.status(401).send();
}
}
);
};

module.exports = {
getJWTForAccountId,
validateJWT,
};
4 changes: 4 additions & 0 deletions api/v1/utils/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ export default class Cache {
return cacheEntry.value;
}
}

clear(key: string): void {
delete this.store[key];
}
}
36 changes: 19 additions & 17 deletions api/v1/utils/db.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
module.exports = db => (statement, values = [], measure = true) =>
new Promise(async (resolve, reject) => {
const start = Date.now();
db.execute(statement, values, (error, value) => {
if (measure) {
const time = Date.now() - start;
if (time > 400) {
console.warn(`Query: ${statement}`);
console.warn(`Values: ${values}`);
console.warn(`Query took: ${time}ms`);
module.exports =
db =>
(statement, values = [], measure = true) =>
new Promise(async (resolve, reject) => {
const start = Date.now();
db.execute(statement, values, (error, value) => {
if (measure) {
const time = Date.now() - start;
if (time > 400) {
console.warn(`Query: ${statement}`);
console.warn(`Values: ${values}`);
console.warn(`Query took: ${time}ms`);
}
}
}

if (error) {
reject(error);
} else {
resolve(value);
}
if (error) {
reject(error);
} else {
resolve(value);
}
});
});
});
1 change: 1 addition & 0 deletions api/v1/utils/yells.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const hexToAscii = str => {
return strOut;
};

// prettier-ignore
const hexMap = {
'0': '30',
'1': '31',
Expand Down
Loading