curiousroamers/src/server/routes/admin.ts

261 lines
7.1 KiB
TypeScript

import express, {RequestHandler} from 'express';
import sequelize from './../dbobject';
import Article, {articleI} from './../models/article';
import Tag from './../models/tag';
import User from './../models/user';
import UniqueLink from './../models/uniquelink';
import argon2 from 'argon2';
import {authorize,
authorizeEmailVerification,
checkNoAuth,
generateAccessToken} from './../auth';
/* eslint-disable */
const router = express.Router();
/* eslint-enable */
/* eslint-disable */
declare global {
namespace Express {
interface Request {
article: articleI;
}
}
}
/* eslint-enable */
/* Homepage route */
router.get('/', authorize, async (_req, res) => {
const articles = await Article.findAll({
attributes: [
'title',
'id',
'createdAt',
],
});
res.render('admin/index', {articles});
});
/* Login routes */
/**
* @param {string} type Type of unique link for error message
* @param {number} expiration Duration of expiration of the link in minute
* @return {RequestHandler} middleware
*/
function verifyUniqueLink(type: string, expiration: number): RequestHandler {
return async (req, res, next) => {
const linkCheck = await UniqueLink.findOne({
where: {id: req.params.uuid},
});
if (linkCheck == null) {
req.flash('error', 'Page does not exists');
return res.status(404).redirect('/');
}
if ((Date.now() - linkCheck.createdAt.getTime()) / (1000 * 60) >
expiration) {
if (type == 'signup') {
req.flash('error', `The ressource has expired, please contact
an admin to create a new signup link`);
} else {
req.flash('error', `The ressource has expired, please create a
new ` + type + ' link');
}
await linkCheck.destroy();
return res.status(410).redirect('/');
}
return next();
};
}
router.get('/verifyemail/:id/:uuid', verifyUniqueLink('email verification',
60*24*30), authorizeEmailVerification, async (req, res) => {
try {
const user = await User.findOne({
where: {id: req.params.id},
rejectOnEmpty: true,
});
user.verifiedemail = true;
await user.save();
await UniqueLink.destroy({where: {id: req.params.uuid}});
res.status(204).redirect('/admin');
} catch (e) {
console.log(e);
req.flash('error', `Server error, please try again, if that
persists contact your web administrator`);
res.status(500).redirect('/admin');
}
});
router.post('/changepassword/:id/:uuid', verifyUniqueLink('password reset',
30), checkNoAuth, async (req, res) => {
try {
const user = await User.findOne({
where: {id: req.params.id},
rejectOnEmpty: true,
});
const hash = await argon2.hash(req.body.password);
user.password = hash;
await user.save();
await UniqueLink.destroy({where: {id: req.params.uuid}});
res.status(204).redirect('/admin/signin');
} catch (e) {
console.log(e);
req.flash('error', `Server error, please try again, if that
persists contact your web administrator`);
res.status(500).redirect('/admin');
}
});
router.post('/signup/:uuid', verifyUniqueLink('signup', 30), checkNoAuth,
async (req, res) => {
try {
if (await User.findOne({where: {name: req.body.name}}) != null) {
req.flash('error', 'Username already in use');
return res.status(409).redirect('/admin/signup/' +
req.params.uuid);
}
if (await User.findOne({where: {email: req.body.email}}) != null) {
req.flash('error', 'Email already in use');
return res.status(409).redirect('/admin/signup/' +
req.params.uuid);
}
const hash = await argon2.hash(req.body.password);
const user = User.build({
name: req.body.name,
email: req.body.email,
verifiedemail: false,
password: hash,
});
await user.save();
await UniqueLink.destroy({where: {id: req.params.uuid}});
res.status(201).redirect('/admin/signin');
} catch (e) {
console.log(e);
req.flash('error', `Server error, please try again, if that
persists contact your web administrator`);
res.status(500).redirect('/admin');
}
});
router.post('/signin', checkNoAuth, async (req, res) => {
let user: any = await User.findOne({where: {name: req.body.name}});
const email = await User.findOne({where: {email: req.body.name}});
if (!user && !email) {
req.flash('error', 'Name or email does not match');
return res.redirect('/admin/signin');
}
if (!user) {
user = email;
}
if (await argon2.verify(user.password, req.body.password)) {
const tokenData = {
sub: user.id,
name: user.name,
email: user.email,
};
const accessToken = generateAccessToken(tokenData);
const farFuture = new Date(new Date().getTime() +
(1000 * 60 * 60 * 24 * 365 * 100));
res.cookie('accessToken', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
expires: farFuture,
});
res.redirect('/admin');
} else {
req.flash('error', 'Password does not match');
return res.redirect('/admin/signin');
}
});
router.delete('/signout', async (_req, res) => {
res.cookie('accessToken', '', {expires: new Date()});
res.redirect('/');
});
router.get('/signin', checkNoAuth, (_req, res) => {
res.render('admin/signin');
});
router.get('/signup/:uuid', checkNoAuth, (req, res) => {
res.render('admin/signup', {uuid: req.params.uuid});
});
router.get('/changepassword/:id/:uuid', checkNoAuth, (_req, res) => {
res.render('admin/changepassword');
});
/* Article routes */
router.get('/new', authorize, (_req, res) => {
res.render('admin/new', {article: Article.build(), tags: []});
});
router.get('/edit/:id', authorize, async (req, res) => {
const article = await Article.findByPk(req.params.id,
{rejectOnEmpty: true});
const tags = await Tag.findAll({
where: {articleId: article.id},
attributes: [
'name',
],
});
res.render('admin/edit', {article, tags});
});
router.post('/', authorize, async (req, _res, next) => {
req.article = Article.build();
next();
}, saveArticleAndRedirect('new'));
router.put('/:id', authorize, async (req, _res, next) => {
req.article = await Article.findByPk(req.params.id, {rejectOnEmpty: true});
next();
}, saveArticleAndRedirect('edit'));
router.delete('/:id', authorize, async (req, res) => {
await Article.destroy({where: {id: req.params.id}, cascade: true});
res.sendStatus(204);
});
/**
* @param {string} path Path of redirection
* @return {RequestHandler} middleware
*/
function saveArticleAndRedirect(path: string): RequestHandler {
return async (req, res) => {
let article: any = req.article;
article.title = req.body.title;
article.description = req.body.description;
article.markdown = req.body.markdown;
try {
await sequelize.transaction(async (t) => {
let tagCount = 0;
while (req.body.articletags[tagCount] != '') {
tagCount++;
}
Tag.destroy({where: {articleId: article.id},
transaction: t});
article = await article.save();
for (let i = 0; i < tagCount; i++) {
await article.createTag({
articleId: article.id,
main: (i == 0) ? true : false,
name: req.body.articletags[i],
});
}
});
res.redirect(`/blog/post/${article.slug}`);
} catch (e) {
console.log(e);
req.flash('error', 'The operation has failed, please try again');
res.render(`admin/${path}`, {article: article});
}
};
}
export default router;