From f127abbc745c266d979be22d0ebcf9fc1fed6eb8 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 20 Jul 2020 17:32:32 +0200 Subject: [PATCH] Finish promoting email views and add backend controller --- src/Mails.ts | 17 +++++ src/auth/AuthGuard.ts | 13 +++- src/helpers/BackendController.ts | 69 +++++++++++++++++++++ views/backend/accounts_approval.njk | 41 ++++++++++++ views/backend/index.njk | 21 +++++++ views/mails/pending_account_review.mjml.njk | 58 +++-------------- 6 files changed, 169 insertions(+), 50 deletions(-) create mode 100644 src/Mails.ts create mode 100644 src/helpers/BackendController.ts create mode 100644 views/backend/accounts_approval.njk create mode 100644 views/backend/index.njk diff --git a/src/Mails.ts b/src/Mails.ts new file mode 100644 index 0000000..9a4635c --- /dev/null +++ b/src/Mails.ts @@ -0,0 +1,17 @@ +import config from "config"; +import {MailTemplate} from "./Mail"; + +export const MAGIC_LINK_MAIL = new MailTemplate( + 'magic_link', + data => data.type === 'register' ? 'Registration' : 'Login magic link' +); + +export const ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE: MailTemplate = new MailTemplate( + 'account_review_notice', + data => `Your account was ${data.approved ? 'approved' : 'rejected'}.` +); + +export const PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE: MailTemplate = new MailTemplate( + 'pending_account_review', + () => 'A new account is pending review on ' + config.get('domain'), +); diff --git a/src/auth/AuthGuard.ts b/src/auth/AuthGuard.ts index 51fb21e..934d74b 100644 --- a/src/auth/AuthGuard.ts +++ b/src/auth/AuthGuard.ts @@ -4,6 +4,10 @@ import User from "./models/User"; import UserEmail from "./models/UserEmail"; import {Connection} from "mysql"; import {Request} from "express"; +import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails"; +import Mail from "../Mail"; +import Controller from "../Controller"; +import config from "config"; export default abstract class AuthGuard

{ public abstract async getProofForSession(session: Express.Session): Promise

; @@ -48,7 +52,14 @@ export default abstract class AuthGuard

{ await callback(); } - if (!user) { + if (user) { + if (User.isApprovalMode()) { + await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { + username: user!.name, + link: Controller.route('accounts-approval'), + }).send(config.get('app.contact_email')); + } + } else { throw new Error('Unable to register user.'); } } else if (registerCallback) { diff --git a/src/helpers/BackendController.ts b/src/helpers/BackendController.ts new file mode 100644 index 0000000..a421f5c --- /dev/null +++ b/src/helpers/BackendController.ts @@ -0,0 +1,69 @@ +import config from "config"; +import Controller from "../Controller"; +import {REQUIRE_ADMIN_MIDDLEWARE, REQUIRE_AUTH_MIDDLEWARE} from "../auth/AuthComponent"; +import User from "../auth/models/User"; +import {Request, Response} from "express"; +import {NotFoundHttpError} from "../HttpError"; +import Mail from "../Mail"; +import {ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE} from "../Mails"; + +export default class BackendController extends Controller { + getRoutesPrefix(): string { + return '/backend'; + } + + routes(): void { + this.get('/', this.getIndex, 'backend', REQUIRE_AUTH_MIDDLEWARE, REQUIRE_ADMIN_MIDDLEWARE); + if (User.isApprovalMode()) { + this.get('/accounts-approval', this.getAccountApproval, 'accounts-approval', REQUIRE_AUTH_MIDDLEWARE, REQUIRE_ADMIN_MIDDLEWARE); + this.post('/accounts-approval/approve/:id', this.postApproveAccount, 'approve-account', REQUIRE_AUTH_MIDDLEWARE, REQUIRE_ADMIN_MIDDLEWARE); + this.post('/accounts-approval/reject/:id', this.postRejectAccount, 'reject-account', REQUIRE_AUTH_MIDDLEWARE, REQUIRE_ADMIN_MIDDLEWARE); + } + } + + public async getIndex(req: Request, res: Response): Promise { + res.render('backend/index', { + approval_mode: User.isApprovalMode(), + accounts_to_approve: User.isApprovalMode() ? await User.select().count() : 0, + }); + } + + public async getAccountApproval(req: Request, res: Response): Promise { + const accounts = await User.select().where('approved', 0).with('mainEmail').get(); + res.render('backend/accounts_approval', { + accounts: User.isApprovalMode() ? accounts : 0, + }); + } + + public async postApproveAccount(req: Request, res: Response): Promise { + const account = await User.select().where('id', req.params.id).with('mainEmail').first(); + if (!account) throw new NotFoundHttpError('User', req.url); + const email = await account.mainEmail.get(); + + account.approved = true; + await account.save(); + + await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { + approved: true, + link: config.get('base_url') + Controller.route('auth'), + }).send(email!.email!); + + req.flash('success', `Account successfully approved.`); + res.redirectBack(Controller.route('accounts-approval')); + } + + public async postRejectAccount(req: Request, res: Response): Promise { + const account = await User.select().where('id', req.params.id).with('mainEmail').first(); + if (!account) throw new NotFoundHttpError('User', req.url); + const email = await account.mainEmail.get(); + + await account.delete(); + + await new Mail(ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE, { + approved: false, + }).send(email!.email!); + + req.flash('success', `Account successfully deleted.`); + res.redirectBack(Controller.route('accounts-approval')); + } +} \ No newline at end of file diff --git a/views/backend/accounts_approval.njk b/views/backend/accounts_approval.njk new file mode 100644 index 0000000..3f72fe6 --- /dev/null +++ b/views/backend/accounts_approval.njk @@ -0,0 +1,41 @@ +{% extends 'layouts/base.njk' %} + +{% set title = app.name + ' - Review accounts' %} + +{% block body %} +

Accounts pending review

+ +
+ + + + + + + + + + + + {% for user in accounts %} + + + + + + + + {% endfor %} + +
#NameMain emailRegistered atAction
{{ user.id }}{{ user.name }}{{ user.mainEmail.getOrFail().email }}{{ user.created_at.toISOString() }} + +
+
+{% endblock %} \ No newline at end of file diff --git a/views/backend/index.njk b/views/backend/index.njk new file mode 100644 index 0000000..14299da --- /dev/null +++ b/views/backend/index.njk @@ -0,0 +1,21 @@ +{% extends 'layouts/base.njk' %} + +{% set title = app.name + ' - Backend' %} + +{% block body %} +

App administration

+ +
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/views/mails/pending_account_review.mjml.njk b/views/mails/pending_account_review.mjml.njk index 208fa4e..b79dea1 100644 --- a/views/mails/pending_account_review.mjml.njk +++ b/views/mails/pending_account_review.mjml.njk @@ -4,63 +4,23 @@ - {% if type == 'register' %} - Register an account on {{ app.name }} - {% else %} - Log in to {{ app.name }} - {% endif %} + New user account on {{ app.name }} - {% if type == 'register' %} - Someone has requested an account registration for {{ mail_to }}. If it was not you, - please ignore this message. - {% else %} - Someone is attempting to log in to your account {{ mail_to }}. - {% endif %} + A new user is pending review on {{ app.name }}. + + Username: {{ username }} - {% if type == 'register' %} - Finalize my account registration - {% else %} - If it is not you, DO NOT CLICK ON THIS BUTTON. - {% endif %} + Finalize my account registration - - {% if type == 'login' %} - - - - IP: {{ ip }} - - - Location: {{ geo }} - - - - - Authorize log in - - - - {% endif %} {% endblock %} {% block text %} - {% if type == 'register' %} - Hi! - Someone requested an account registration for {{ mail_to }}. If it was not you, - please ignore this message. + Hi! + A new user is pending review on {{ app.name }}. - To finalize your account registration, please follow this link: {{ link|safe }} - {% else %} - Hi! - Someone is attempting to log in to your account {{ mail_to }}. - - If it is not you, DO NOT FOLLOW THIS LINK. - - IP: {{ ip }} - Location: {{ geo }} - To authorize this log in, please follow this link: {{ link|safe }} - {% endif %} + Username: {{ username }} + To review this account, please follow this link: {{ link|safe }} {% endblock %} \ No newline at end of file