diff --git a/languages/en-GB/2factor.json b/languages/en-GB/2factor.json index 0297510..4bf5649 100644 --- a/languages/en-GB/2factor.json +++ b/languages/en-GB/2factor.json @@ -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 has your password, and so your password is now compromised. You should change your password immediately.", + "failureInfo.cta": "Change your password →", + "admin.intro.one": "Two-Factor Authentication (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", diff --git a/lib/controllers.js b/lib/controllers.js index 86afb49..897a558 100644 --- a/lib/controllers.js +++ b/lib/controllers.js @@ -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; diff --git a/library.js b/library.js index 05572ce..fc64f35 100644 --- a/library.js +++ b/library.js @@ -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) => { diff --git a/static/templates/2fa-access-notification.tpl b/static/templates/2fa-access-notification.tpl new file mode 100644 index 0000000..8c61667 --- /dev/null +++ b/static/templates/2fa-access-notification.tpl @@ -0,0 +1,27 @@ +
+
+
+
+ +
+ +

[[2factor:failureInfo.lead]]

+ +
+ [[2factor:notification.failure]] +
+ +
+

[[2factor:failureInfo.when, {timeString}, {dateString}]]

+

[[2factor:failureInfo.explanation]]

+

[[2factor:failureInfo.remediation]]

+ + +
+
+
+
+ +