2021-07-02 15:53:33 -04:00
< ? php
namespace BookStack\Http\Controllers\Auth ;
use BookStack\Actions\ActivityType ;
2021-08-02 11:35:37 -04:00
use BookStack\Auth\Access\LoginService ;
2021-07-02 15:53:33 -04:00
use BookStack\Auth\Access\Mfa\BackupCodeService ;
2021-08-02 11:35:37 -04:00
use BookStack\Auth\Access\Mfa\MfaSession ;
2021-07-02 15:53:33 -04:00
use BookStack\Auth\Access\Mfa\MfaValue ;
2021-08-02 11:35:37 -04:00
use BookStack\Exceptions\NotFoundException ;
2021-07-02 15:53:33 -04:00
use BookStack\Http\Controllers\Controller ;
use Exception ;
2021-08-02 11:35:37 -04:00
use Illuminate\Http\Request ;
use Illuminate\Validation\ValidationException ;
2021-07-02 15:53:33 -04:00
class MfaBackupCodesController extends Controller
{
2021-07-18 11:52:31 -04:00
use HandlesPartialLogins ;
2021-07-02 15:53:33 -04:00
protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-backup-codes' ;
/**
* Show a view that generates and displays backup codes
*/
public function generate ( BackupCodeService $codeService )
{
$codes = $codeService -> generateNewSet ();
session () -> put ( self :: SETUP_SECRET_SESSION_KEY , encrypt ( $codes ));
$downloadUrl = 'data:application/octet-stream;base64,' . base64_encode ( implode ( " \n \n " , $codes ));
return view ( 'mfa.backup-codes-generate' , [
'codes' => $codes ,
'downloadUrl' => $downloadUrl ,
]);
}
/**
* Confirm the setup of backup codes , storing them against the user .
* @ throws Exception
*/
public function confirm ()
{
if ( ! session () -> has ( self :: SETUP_SECRET_SESSION_KEY )) {
return response ( 'No generated codes found in the session' , 500 );
}
$codes = decrypt ( session () -> pull ( self :: SETUP_SECRET_SESSION_KEY ));
2021-07-18 11:52:31 -04:00
MfaValue :: upsertWithValue ( $this -> currentOrLastAttemptedUser (), MfaValue :: METHOD_BACKUP_CODES , json_encode ( $codes ));
2021-07-02 15:53:33 -04:00
$this -> logActivity ( ActivityType :: MFA_SETUP_METHOD , 'backup-codes' );
return redirect ( '/mfa/setup' );
}
2021-08-02 11:35:37 -04:00
/**
* Verify the MFA method submission on check .
* @ throws NotFoundException
* @ throws ValidationException
*/
public function verify ( Request $request , BackupCodeService $codeService , MfaSession $mfaSession , LoginService $loginService )
{
$user = $this -> currentOrLastAttemptedUser ();
$codes = MfaValue :: getValueForUser ( $user , MfaValue :: METHOD_BACKUP_CODES ) ? ? '[]' ;
$this -> validate ( $request , [
'code' => [
'required' ,
'max:12' , 'min:8' ,
function ( $attribute , $value , $fail ) use ( $codeService , $codes ) {
if ( ! $codeService -> inputCodeExistsInSet ( $value , $codes )) {
$fail ( trans ( 'validation.backup_codes' ));
}
}
]
]);
$updatedCodes = $codeService -> removeInputCodeFromSet ( $request -> get ( 'code' ), $codes );
MfaValue :: upsertWithValue ( $user , MfaValue :: METHOD_BACKUP_CODES , $updatedCodes );
$mfaSession -> markVerifiedForUser ( $user );
2021-08-02 17:02:25 -04:00
$loginService -> reattemptLoginFor ( $user );
2021-08-02 11:35:37 -04:00
if ( $codeService -> countCodesInSet ( $updatedCodes ) < 5 ) {
$this -> showWarningNotification ( 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.' );
}
return redirect () -> intended ();
}
2021-07-02 15:53:33 -04:00
}