diff --git a/js/admin-backup-codes.js b/js/admin-backup-codes.js new file mode 100644 index 00000000..d81f3575 --- /dev/null +++ b/js/admin-backup-codes.js @@ -0,0 +1,38 @@ +(function(){ + const backupCodes = document.getElementById( 'two-factor-backup-codes' ), + generateCodesButton = backupCodes.querySelector( '.button-two-factor-backup-codes-generate' ), + userId = backupCodes.dataset.userid || 0; + + // Backup Codes generation + generateCodesButton.addEventListener( 'click', function(e) { + const codesCountDiv = backupCodes.querySelector( '.two-factor-backup-codes-count' ), + codeWrapper = backupCodes.querySelector( '.two-factor-backup-codes-wrapper' ), + codeList = backupCodes.querySelector( '.two-factor-backup-codes-unused-codes' ), + downloadButton = backupCodes.querySelector( '.button-two-factor-backup-codes-download' ); + + wp.apiRequest( { + method: 'POST', + path: 'two-factor/1.0/generate-backup-codes', + data: { + user_id: userId + } + } ).then( function( response ) { + codeList.innerHTML = ''; + + // Append the codes. + for ( i = 0; i < response.codes.length; i++ ) { + codeList.innerHTML += '
  • ' + response.codes[ i ] + '
  • '; + } + + // Display the section. + codeWrapper.style.display = 'block'; + + // Update counter. + codesCountDiv.innerHTML = response.i18n.count; + + // Update link. + downloadButton.href = response.download_link; + } ); + } ); + +})(); \ No newline at end of file diff --git a/js/admin-totp.js b/js/admin-totp.js new file mode 100644 index 00000000..f178bfe5 --- /dev/null +++ b/js/admin-totp.js @@ -0,0 +1,90 @@ +(function(){ + const totpSetup = document.getElementById( 'two-factor-totp-options' ), + userId = totpSetup.dataset.userid || 0; + + // TOTP QR Setup + const renderQRCode = function() { + var link = document.querySelector( '#two-factor-qr-code a' ); + if ( ! link ) { + return; + } + + /* + * 0 = Automatically select the version, to avoid going over the limit of URL + * length. + * L = Least amount of error correction, because it's not needed when scanning + * on a monitor, and it lowers the image size. + */ + var qr = qrcode( 0, 'L' ); + + qr.addData( link.href ); + qr.make(); + + link.innerHTML = qr.createSvgTag( 5 ); + }; + + // TOTP Setup + const totpSetupHandler = function( e ) { + e.preventDefault(); + + const totpKey = document.getElementById( 'two-factor-totp-key' ).value, + totpCodeInput = document.getElementById( 'two-factor-totp-authcode' ), + totpSetupSubmit = totpSetup.querySelector( '.totp-submit' ); + + wp.apiRequest( { + method: 'POST', + path: 'two-factor/1.0/totp', + data: { + user_id: userId, + key: totpKey, + code: totpCodeInput.value, + } + } ).fail( function( response, status ) { + let errorMessage = response.responseJSON.message || status, + errorDiv = totpSetup.querySelector( '.totp-setup-error' ); + + if ( ! errorDiv ) { + totpSetupSubmit.outerHTML += '

    '; + errorDiv = totpSetup.querySelector( '.totp-setup-error' ); + } + + errorDiv.querySelector( 'p' ).textContent = errorMessage; + totpCodeInput.value = ''; + } ).then( function( response ) { + totpSetup.innerHTML = response.html; + } ); + }; + + const totpResetHandler = function( e ) { + e.preventDefault(); + + wp.apiRequest( { + method: 'DELETE', + path: 'two-factor/1.0/totp', + data: { + user_id: userId, + } + } ).then( function( response ) { + totpSetup.innerHTML = response.html; + + // And render the QR. + renderQRCode(); + } ); + }; + + // Render the QR now if the document is loaded, otherwise on DOMContentLoaded. + if ( document.readyState === 'complete' ) { + renderQRCode(); + } else { + window.addEventListener( 'DOMContentLoaded', renderQRCode ); + } + + // Add the Click handlers. + totpSetup.addEventListener( 'click', function( e ) { + if ( e.target.closest( '.totp-submit' ) ) { + totpSetupHandler( e ); + } else if ( e.target.closest( '.reset-totp-key' ) ) { + totpResetHandler( e ); + } + } ); +})(); \ No newline at end of file diff --git a/providers/class-two-factor-backup-codes.php b/providers/class-two-factor-backup-codes.php index bebbb94c..83c1ee84 100644 --- a/providers/class-two-factor-backup-codes.php +++ b/providers/class-two-factor-backup-codes.php @@ -40,6 +40,9 @@ protected function __construct() { add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); add_action( 'admin_notices', array( $this, 'admin_notices' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); + return parent::__construct(); } @@ -130,6 +133,21 @@ public function get_alternative_provider_label() { return __( 'Use a recovery code', 'two-factor' ); } + /** + * Enqueue scripts + * + * @codeCoverageIgnore + */ + public function enqueue_assets( $hook_suffix ) { + wp_register_script( + 'two-factor-backup-codes', + plugins_url( 'js/admin-backup-codes.js', __DIR__ ), + array( 'wp-api-request' ), + TWO_FACTOR_VERSION, + true + ); + } + /** * Whether this Two Factor provider is configured and codes are available for the user specified. * @@ -154,9 +172,11 @@ public function is_available_for_user( $user ) { * @param WP_User $user WP_User object of the logged-in user. */ public function user_options( $user ) { + wp_enqueue_script( 'two-factor-backup-codes' ); + $count = self::codes_remaining_for_user( $user ); ?> -

    +

    -

    - - get_user_totp_key( $user->ID ); - wp_enqueue_script( 'two-factor-qr-code-generator' ); + wp_enqueue_script( 'two-factor-totp' ); ?> -
    +

    - Loading... +

    @@ -301,32 +309,6 @@ public function user_two_factor_options( $user ) { } - -

    @@ -340,42 +322,9 @@ public function user_two_factor_options( $user ) { ?> - +

    - -

    @@ -384,23 +333,6 @@ public function user_two_factor_options( $user ) { -

    diff --git a/tests/providers/class-two-factor-backup-codes.php b/tests/providers/class-two-factor-backup-codes.php index a926044e..b08491ab 100644 --- a/tests/providers/class-two-factor-backup-codes.php +++ b/tests/providers/class-two-factor-backup-codes.php @@ -163,9 +163,9 @@ public function test_user_options() { $this->provider->user_options( $user ); $buffer = ob_get_clean(); - $this->assertStringContainsString( '

    ', $buffer ); + $this->assertStringContainsString( '

    assertStringContainsString( '