Finish promoting email views and add backend controller
This commit is contained in:
parent
6618e874e0
commit
f127abbc74
17
src/Mails.ts
Normal file
17
src/Mails.ts
Normal file
@ -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<string>('domain'),
|
||||||
|
);
|
@ -4,6 +4,10 @@ import User from "./models/User";
|
|||||||
import UserEmail from "./models/UserEmail";
|
import UserEmail from "./models/UserEmail";
|
||||||
import {Connection} from "mysql";
|
import {Connection} from "mysql";
|
||||||
import {Request} from "express";
|
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<P extends AuthProof> {
|
export default abstract class AuthGuard<P extends AuthProof> {
|
||||||
public abstract async getProofForSession(session: Express.Session): Promise<P | null>;
|
public abstract async getProofForSession(session: Express.Session): Promise<P | null>;
|
||||||
@ -48,7 +52,14 @@ export default abstract class AuthGuard<P extends AuthProof> {
|
|||||||
await callback();
|
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<string>('app.contact_email'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
throw new Error('Unable to register user.');
|
throw new Error('Unable to register user.');
|
||||||
}
|
}
|
||||||
} else if (registerCallback) {
|
} else if (registerCallback) {
|
||||||
|
69
src/helpers/BackendController.ts
Normal file
69
src/helpers/BackendController.ts
Normal file
@ -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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<string>('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<void> {
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
}
|
41
views/backend/accounts_approval.njk
Normal file
41
views/backend/accounts_approval.njk
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'layouts/base.njk' %}
|
||||||
|
|
||||||
|
{% set title = app.name + ' - Review accounts' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Accounts pending review</h1>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="shrink-col">#</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Main email</th>
|
||||||
|
<th>Registered at</th>
|
||||||
|
<th class="shrink-col">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in accounts %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.id }}</td>
|
||||||
|
<td>{{ user.name }}</td>
|
||||||
|
<td>{{ user.mainEmail.getOrFail().email }}</td>
|
||||||
|
<td>{{ user.created_at.toISOString() }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="max-content">
|
||||||
|
<a href="{{ route('approve-account', user.id) }}"
|
||||||
|
class="button success"><i data-feather="check"></i> Approve</a>
|
||||||
|
|
||||||
|
<a href="{{ route('reject-account', user.id) }}"
|
||||||
|
onclick="return confirm(`This will irrevocably delete the ${user.main_email} account.`)"
|
||||||
|
class="button danger"><i data-feather="x"></i> Reject</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
21
views/backend/index.njk
Normal file
21
views/backend/index.njk
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'layouts/base.njk' %}
|
||||||
|
|
||||||
|
{% set title = app.name + ' - Backend' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>App administration</h1>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="panel">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
{% if approval_mode %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ route('accounts-approval') }}">Accounts approval ({{ accounts_to_approve }})</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -4,63 +4,23 @@
|
|||||||
<mj-section>
|
<mj-section>
|
||||||
<mj-column>
|
<mj-column>
|
||||||
<mj-text mj-class="title">
|
<mj-text mj-class="title">
|
||||||
{% if type == 'register' %}
|
New user account on {{ app.name }}
|
||||||
Register an account on {{ app.name }}
|
|
||||||
{% else %}
|
|
||||||
Log in to {{ app.name }}
|
|
||||||
{% endif %}
|
|
||||||
</mj-text>
|
</mj-text>
|
||||||
<mj-text>
|
<mj-text>
|
||||||
{% if type == 'register' %}
|
A new user is pending review on {{ app.name }}.
|
||||||
Someone has requested an account registration for <strong>{{ mail_to }}</strong>. If it was not you,
|
|
||||||
please ignore this message.
|
Username: {{ username }}
|
||||||
{% else %}
|
|
||||||
Someone is attempting to log in to your account <strong>{{ mail_to }}</strong>.
|
|
||||||
{% endif %}
|
|
||||||
</mj-text>
|
</mj-text>
|
||||||
|
|
||||||
{% if type == 'register' %}
|
<mj-text><a href="{{ link | safe }}">Finalize my account registration</a></mj-text>
|
||||||
<mj-button href="{{ link | safe }}">Finalize my account registration</mj-button>
|
|
||||||
{% else %}
|
|
||||||
<mj-text>If it is not you, <strong>DO NOT CLICK ON THIS BUTTON</strong>.</mj-text>
|
|
||||||
{% endif %}
|
|
||||||
</mj-column>
|
</mj-column>
|
||||||
</mj-section>
|
</mj-section>
|
||||||
|
|
||||||
{% if type == 'login' %}
|
|
||||||
<mj-section background-color="#1b252d">
|
|
||||||
<mj-column>
|
|
||||||
<mj-text mj-class="important-line" padding-bottom="0px">
|
|
||||||
IP: <strong>{{ ip }}</strong>
|
|
||||||
</mj-text>
|
|
||||||
<mj-text mj-class="important-line">
|
|
||||||
Location: <strong>{{ geo }}</strong>
|
|
||||||
</mj-text>
|
|
||||||
</mj-column>
|
|
||||||
<mj-column>
|
|
||||||
<mj-button href="{{ link | safe }}" padding="20px 0" background-color="#caa200">
|
|
||||||
Authorize log in
|
|
||||||
</mj-button>
|
|
||||||
</mj-column>
|
|
||||||
</mj-section>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block text %}
|
{% block text %}
|
||||||
{% if type == 'register' %}
|
|
||||||
Hi!
|
Hi!
|
||||||
Someone requested an account registration for {{ mail_to }}. If it was not you,
|
A new user is pending review on {{ app.name }}.
|
||||||
please ignore this message.
|
|
||||||
|
|
||||||
To finalize your account registration, please follow this link: {{ link|safe }}
|
Username: {{ username }}
|
||||||
{% else %}
|
To review this account, please follow this link: {{ link|safe }}
|
||||||
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 %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user