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;