fix: #80, notify user if 2fa challenge fails

This commit is contained in:
Julian Lam 2023-10-03 13:12:47 -04:00
parent b5a0d6aa3f
commit 948984010a
4 changed files with 66 additions and 0 deletions

View file

@ -7,6 +7,7 @@

"notification.backupCode.generated": "A set of backup codes have just been generated for this account.",
"notification.backupCode.used": "A backup code was just used to log into this account",
"notification.failure": "Someone attempted to access your account but could not pass the second factor.",

"user.force_2fa": "The administrator has required you to setup 2FA for your account for security purposes",

@ -58,6 +59,12 @@
"backup.generate.one": "These backup codes have been generated in order to help you re-gain access to your account in the event your authentication device (typically a mobile phone) is lost or damaged.",
"backup.generate.two": "Your codes are shown below, and will not be shown again. Write them down on a piece of paper and secure it!",

"failureInfo.lead": "If you are on this page, then you may be wondering what this notification means.",
"failureInfo.when": "We processed a login attempt at %1 on %2, and as your account is configured with a second factor, issued a second-factor challenge.",
"failureInfo.explanation": "The user who attempted this login did not successfully pass that second factor, which is why you have been notified. If this was you, then you can safely disregard this message.",
"failureInfo.remediation": "However — if this was not you, then it is likely that whoever attempted to access your account <strong>has your password, and so your password is now compromised</strong>. <a href=\"../me/edit/password\">You should change your password immediately</a>.",
"failureInfo.cta": "Change your password &rarr;",

"admin.intro.one": "<strong>Two-Factor Authentication</strong> (2FA) is a security protocol that works by adding a second layer of authentication before granting access. Typically, these two layers are \"something you know\" (e.g. your password), and \"something you have\" (e.g. a token generated by your mobile device). This plugin introduces the second layer of security allowing users to enable and pair their phones or other suitable devices to your forum's authentication protocols.",
"admin.intro.two": "While this plugin is active, users will be able to see a new profile menu item called \"Two-Factor Authentication\". They will be able to set up their tokens from this page.",
"admin.users.title": "Two-Factor Authentication Users",

View file

@ -75,6 +75,10 @@ Controllers.renderTotpChallenge = async (req, res, next) => {
}

const error = req.flash('error');
if (error.includes('[[2factor:login.failure]]')) {
const main = require('..');
main.handle2faFailure(uid);
}

if (!await parent.hasTotp(uid)) {
return next();
@ -233,4 +237,17 @@ Controllers.renderSettings = async (req, res) => {
});
};

Controllers.renderAccessNotificationHelp = (req, res, next) => {
const { when } = req.query;
if (!when) {
return next();
}

const date = new Date(parseInt(when, 10));
res.render('2fa-access-notification', {
timeString: date.toLocaleTimeString(date),
dateString: date.toLocaleDateString(date),
});
}

module.exports = Controllers;

View file

@ -41,6 +41,9 @@ plugin.init = async (params) => {
const controllers = require('./lib/controllers');
const middlewares = require('./lib/middlewares');

// Public-facing pages
hostHelpers.setupPageRoute(router, '/2factor/access-notification', controllers.renderAccessNotificationHelp);

// ACP
hostHelpers.setupAdminPageRoute(router, '/admin/plugins/2factor', [hostMiddleware.pluginHooks], controllers.renderAdminPage);

@ -413,6 +416,18 @@ plugin.adjustRelogin = async ({ req, res }) => {
}
};

plugin.handle2faFailure = async (uid) => {
const notification = await notifications.create({
bodyShort: '[[2factor:notification.failure]]',
bodyLong: '',
nid: `2factor.failure.${uid}-${Date.now()}`,
from: uid,
path: `/2factor/access-notification?when=${Date.now()}`,
});

await notifications.push(notification, [uid]);
}

plugin.integrations = {};

plugin.integrations.writeApi = async (data) => {

View file

@ -0,0 +1,27 @@
<div class="flex-fill">
<div class="mx-auto">
<div class="d-flex flex-column gap-3 justify-content-center text-center">
<div class="mx-auto p-4 bg-light border rounded">
<i class="text-secondary fa fa-fw fa-4x fa-lock"></i>
</div>

<p class="lead">[[2factor:failureInfo.lead]]</p>

<blockquote>
[[2factor:notification.failure]]
</blockquote>

<div class="text-start col-sm-6 offset-sm-3">
<p>[[2factor:failureInfo.when, {timeString}, {dateString}]]</p>
<p>[[2factor:failureInfo.explanation]]</p>
<p>[[2factor:failureInfo.remediation]]</p>

<div class="d-grid">
<a href="{config.relative_path}/me/edit/password" class="btn btn-primary">[[2factor:failureInfo.cta]]</a>
</div>
</div>
</div>
</div>
</div>