mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/OATHAuth
synced 2024-11-30 19:14:31 +00:00
Add notification when user is running out of recovery codes
Bug: T131788
Change-Id: Ic4294dc4ca8eb238998af3ec6b69a771f1b17c17
(cherry picked from commit 8eb5725494
)
This commit is contained in:
parent
2ad451d2fa
commit
8b8b336613
|
@ -97,6 +97,10 @@
|
|||
"oathauth-notifications-enable-help": "Help",
|
||||
"oathauth-notifications-enable-helplink": "mw:Special:MyLanguage/Help:Two-factor authentication",
|
||||
"oathauth-notifications-enable-primary": "Check your two-factor authentication settings",
|
||||
"notification-body-oathauth-oathauth-recoverycodesleft": "{{GENDER:$2|You}} have $3 {{PLURAL:$3|recovery code|recovery codes}} left. You may want to consider disabling your two-factor and re-enabling it to generate new recovery codes, which will give you $4 {{PLURAL:$4|recovery code|recovery codes}} to use in future.",
|
||||
"oathauth-notifications-recoverycodesleft-help": "Help",
|
||||
"oathauth-notifications-recoverycodesleft-helplink": "mw:Special:MyLanguage/Help:Two-factor authentication",
|
||||
"oathauth-notifications-recoverycodesleft-primary": "Check your two-factor authentication settings",
|
||||
"oathauth-verify-enabled": "{{GENDER:$1|$1}} has two-factor authentication enabled.",
|
||||
"oathauth-verify-disabled": "{{GENDER:$1|$1}} does not have two-factor authentication enabled.",
|
||||
"oathauth-prefs-disabledgroups": "Disabled {{PLURAL:$1|group|groups}}:",
|
||||
|
|
|
@ -112,6 +112,10 @@
|
|||
"oathauth-notifications-enable-help": "Link text for the help link in the notification",
|
||||
"oathauth-notifications-enable-helplink": "{{notranslate}}",
|
||||
"oathauth-notifications-enable-primary": "Link text pointing a user where to check their 2FA settings",
|
||||
"notification-body-oathauth-oathauth-recoverycodesleft": "Notification body text for when the user is getting low on the number of recovery tokens left on their account.\n$2 - Name of user for GENDER\n$3 - Number of recovery codes that the user has left \n$4 - Number of recovery codes the user would have if they were regenerated",
|
||||
"oathauth-notifications-recoverycodesleft-help": "Link text for the help link in the notification\n{{identical|Help}}",
|
||||
"oathauth-notifications-recoverycodesleft-helplink": "{{notranslate}}",
|
||||
"oathauth-notifications-recoverycodesleft-primary": "Link text pointing a user where to check their 2FA settings",
|
||||
"oathauth-verify-enabled": "Notice that a user has 2FA enabled, shown on success at [[Special:VerifyOATHForUser]].\n$1 - Name of user",
|
||||
"oathauth-verify-disabled": "Notice that a user does not have 2FA enabled, shown on success at [[Special:VerifyOATHForUser]].\n$1 - Name of user",
|
||||
"oathauth-prefs-disabledgroups": "Label on Special:Preferences for groups in which the user's membership has been disabled for a lack of two-factor authentication.\n$1 - Number of groups",
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace MediaWiki\Extension\OATHAuth\Hook;
|
|||
use MediaWiki\Extension\Notifications\Hooks\BeforeCreateEchoEventHook;
|
||||
use MediaWiki\Extension\OATHAuth\Notifications\DisablePresentationModel;
|
||||
use MediaWiki\Extension\OATHAuth\Notifications\EnablePresentationModel;
|
||||
use MediaWiki\Extension\OATHAuth\Notifications\RecoveryCodeCountPresentationModel;
|
||||
|
||||
/**
|
||||
* Hooks from Echo extension,
|
||||
|
@ -25,6 +26,7 @@ class EchoHandler implements BeforeCreateEchoEventHook {
|
|||
public function onBeforeCreateEchoEvent(
|
||||
array &$notifications, array &$notificationCategories, array &$notificationIcons
|
||||
) {
|
||||
// message used: notification-header-oathauth-disable
|
||||
$notifications['oathauth-disable'] = [
|
||||
'category' => 'system',
|
||||
'group' => 'negative',
|
||||
|
@ -34,6 +36,7 @@ class EchoHandler implements BeforeCreateEchoEventHook {
|
|||
'user-locators' => [ 'EchoUserLocator::locateEventAgent' ],
|
||||
];
|
||||
|
||||
// message used: notification-header-oathauth-enable
|
||||
$notifications['oathauth-enable'] = [
|
||||
'category' => 'system',
|
||||
'group' => 'positive',
|
||||
|
@ -42,5 +45,15 @@ class EchoHandler implements BeforeCreateEchoEventHook {
|
|||
'canNotifyAgent' => true,
|
||||
'user-locators' => [ 'EchoUserLocator::locateEventAgent' ],
|
||||
];
|
||||
|
||||
// message used: notification-header-oathauth-recoverycodes-count
|
||||
$notifications['oathauth-recoverycodes-count'] = [
|
||||
'category' => 'system',
|
||||
'group' => 'negative',
|
||||
'section' => 'alert',
|
||||
'presentation-model' => RecoveryCodeCountPresentationModel::class,
|
||||
'canNotifyAgent' => true,
|
||||
'user-locators' => [ 'EchoUserLocator::locateEventAgent' ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ use EmptyBagOStuff;
|
|||
use Exception;
|
||||
use jakobo\HOTP\HOTP;
|
||||
use MediaWiki\Extension\OATHAuth\IAuthKey;
|
||||
use MediaWiki\Extension\OATHAuth\Notifications\Manager;
|
||||
use MediaWiki\Extension\OATHAuth\OATHAuthServices;
|
||||
use MediaWiki\Extension\OATHAuth\OATHUser;
|
||||
use MediaWiki\Logger\LoggerFactory;
|
||||
|
@ -50,6 +51,21 @@ class TOTPKey implements IAuthKey {
|
|||
/** @var string[] List of recovery codes */
|
||||
private $recoveryCodes = [];
|
||||
|
||||
/**
|
||||
* The upper threshold number of recovery codes that if a user has less than, we'll try and notify them...
|
||||
*/
|
||||
private const RECOVERY_CODES_NOTIFICATION_NUMBER = 2;
|
||||
|
||||
/**
|
||||
* Number of recovery codes to be generated
|
||||
*/
|
||||
public const RECOVERY_CODES_COUNT = 10;
|
||||
|
||||
/**
|
||||
* Length (in bytes) that recovery codes should be
|
||||
*/
|
||||
private const RECOVERY_CODE_LENGTH = 10;
|
||||
|
||||
/**
|
||||
* @return TOTPKey
|
||||
* @throws Exception
|
||||
|
@ -184,6 +200,18 @@ class TOTPKey implements IAuthKey {
|
|||
// This is saved below via OATHUserRepository::persist
|
||||
array_splice( $this->recoveryCodes, $i, 1 );
|
||||
|
||||
// TODO: Probably a better home for this...
|
||||
// It could go in OATHUserRepository::persist(), but then we start having to hard code checks
|
||||
// for Keys being TOTPKey...
|
||||
// And eventually we want to do T232336 to split them to their own 2FA method...
|
||||
if ( count( $this->recoveryCodes ) <= self::RECOVERY_CODES_NOTIFICATION_NUMBER ) {
|
||||
Manager::notifyRecoveryTokensRemaining(
|
||||
$user,
|
||||
self::RECOVERY_CODES_NOTIFICATION_NUMBER,
|
||||
self::RECOVERY_CODES_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
$logger->info( 'OATHAuth user {user} used a recovery token from {clientip}', [
|
||||
'user' => $user->getAccount(),
|
||||
'clientip' => $clientIP,
|
||||
|
@ -200,11 +228,11 @@ class TOTPKey implements IAuthKey {
|
|||
}
|
||||
|
||||
public function regenerateScratchTokens() {
|
||||
$scratchTokens = [];
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
$scratchTokens[] = Base32::encode( random_bytes( 10 ) );
|
||||
$codes = [];
|
||||
for ( $i = 0; $i < self::RECOVERY_CODES_COUNT; $i++ ) {
|
||||
$codes[] = Base32::encode( random_bytes( self::RECOVERY_CODE_LENGTH ) );
|
||||
}
|
||||
$this->recoveryCodes = $scratchTokens;
|
||||
$this->recoveryCodes = $codes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -80,4 +80,26 @@ class Manager {
|
|||
],
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a notification that the user has $tokenCount recovery tokens left
|
||||
*
|
||||
* @param OATHUser $oUser
|
||||
* @param int $tokenCount
|
||||
* @param int $generatedCount
|
||||
*/
|
||||
public static function notifyRecoveryTokensRemaining( OATHUser $oUser, int $tokenCount, int $generatedCount ) {
|
||||
if ( !self::isEnabled() ) {
|
||||
return;
|
||||
}
|
||||
Event::create( [
|
||||
// message used: notification-header-oathauth-recoverycodes-count
|
||||
'type' => 'oathauth-recoverycodes-count',
|
||||
'agent' => $oUser->getUser(),
|
||||
'extra' => [
|
||||
'codeCount' => $tokenCount,
|
||||
'generatedCount' => $generatedCount,
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
|
48
src/Notifications/RecoveryCodeCountPresentationModel.php
Normal file
48
src/Notifications/RecoveryCodeCountPresentationModel.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\OATHAuth\Notifications;
|
||||
|
||||
use MediaWiki\Extension\Notifications\Formatters\EchoEventPresentationModel;
|
||||
use MediaWiki\Extension\OATHAuth\Key\TOTPKey;
|
||||
use MediaWiki\SpecialPage\SpecialPage;
|
||||
use MediaWiki\Title\Title;
|
||||
|
||||
class RecoveryCodeCountPresentationModel extends EchoEventPresentationModel {
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getIconType() {
|
||||
return 'site';
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getPrimaryLink() {
|
||||
return [
|
||||
'url' => SpecialPage::getTitleFor( 'OATHManage' )->getLocalURL(),
|
||||
'label' => $this->msg( 'oathauth-notifications-recoverycodesleft-primary' )->text()
|
||||
];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getSecondaryLinks() {
|
||||
$link = $this->msg( 'oathauth-notifications-recoverycodesleft-helplink' )->inContentLanguage();
|
||||
$title = Title::newFromText( $link->plain() );
|
||||
if ( !$title ) {
|
||||
// Invalid title, skip
|
||||
return [];
|
||||
}
|
||||
return [ [
|
||||
'url' => $title->getLocalURL(),
|
||||
'label' => $this->msg( 'oathauth-notifications-recoverycodesleft-help' )->text(),
|
||||
'icon' => 'help',
|
||||
] ];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getBodyMessage() {
|
||||
$msg = $this->getMessageWithAgent( 'notification-body-oathauth-recoverycodesleft' );
|
||||
$msg->params( $this->event->getExtraParam( 'codeCount', 0 ) );
|
||||
$msg->params( $this->event->getExtraParam( 'generatedCount', TOTPKey::RECOVERY_CODES_COUNT ) );
|
||||
return $msg;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue