261 lines
7.1 KiB
TypeScript
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;
|