From 93a2bf3efa580cae76211ecb0b6bd2f2cf507586 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 2 May 2023 15:16:32 +1000 Subject: [PATCH 1/8] Move inline JS to external files. --- js/admin-backup-codes.js | 28 +++++++ js/admin-totp.js | 76 ++++++++++++++++++ providers/class-two-factor-backup-codes.php | 47 +++++------ providers/class-two-factor-totp.php | 86 +++------------------ 4 files changed, 133 insertions(+), 104 deletions(-) create mode 100644 js/admin-backup-codes.js create mode 100644 js/admin-totp.js diff --git a/js/admin-backup-codes.js b/js/admin-backup-codes.js new file mode 100644 index 00000000..35d52960 --- /dev/null +++ b/js/admin-backup-codes.js @@ -0,0 +1,28 @@ +(function($){ + + // Backup Codes generation + $( '.button-two-factor-backup-codes-generate' ).click( function() { + wp.apiRequest( { + method: 'POST', + path: , + data: { + user_id: ID ); ?> + } + } ).then( function( response ) { + var $codesList = $( '.two-factor-backup-codes-unused-codes' ); + + $( '.two-factor-backup-codes-wrapper' ).show(); + $codesList.html( '' ); + + // Append the codes. + for ( i = 0; i < response.codes.length; i++ ) { + $codesList.append( '
  • ' + response.codes[ i ] + '
  • ' ); + } + + // Update counter. + $( '.two-factor-backup-codes-count' ).html( response.i18n.count ); + $( '#two-factor-backup-codes-download-link' ).attr( 'href', response.download_link ); + } ); + } ); + +})(jQuery); \ No newline at end of file diff --git a/js/admin-totp.js b/js/admin-totp.js new file mode 100644 index 00000000..67207755 --- /dev/null +++ b/js/admin-totp.js @@ -0,0 +1,76 @@ +(function($){ + + // TOTP QR Setup + var qr_generator = 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 ); + }; + + // Run now if the document is loaded, otherwise on DOMContentLoaded. + if ( document.readyState === 'complete' ) { + qr_generator(); + } else { + window.addEventListener( 'DOMContentLoaded', qr_generator ); + } + + // TOTP Setup + $('.totp-submit').click( function( e ) { + e.preventDefault(); + var key = $('#two-factor-totp-key').val(), + code = $('#two-factor-totp-authcode').val(); + + wp.apiRequest( { + method: 'POST', + path: , + data: { + user_id: ID ); ?>, + key: key, + code: code, + } + } ).fail( function( response, status ) { + var errorMessage = response.responseJSON.message || status, + $error = $( '#totp-setup-error' ); + + if ( ! $error.length ) { + $error = $('

    ').insertAfter( $('.totp-submit') ); + } + + $error.find('p').text( errorMessage ); + + $('#two-factor-totp-authcode').val(''); + } ).then( function( response ) { + $( '#two-factor-totp-options' ).html( response.html ); + } ); + } ); + + // TOTP Reset + $( '.button.reset-totp-key' ).click( function( e ) { + e.preventDefault(); + + wp.apiRequest( { + method: 'DELETE', + path: , + data: { + user_id: ID ); ?>, + } + } ).then( function( response ) { + $( '#two-factor-totp-options' ).html( response.html ); + } ); + } ); + +})(jQuery); \ 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..b18d57ed 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( 'jquery', 'wp-api-request' ), + TWO_FACTOR_VERSION, + true + ); + } + /** * Whether this Two Factor provider is configured and codes are available for the user specified. * @@ -154,6 +172,8 @@ 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 ); ?>

    @@ -181,33 +201,6 @@ public function user_options( $user ) {

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

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

    @@ -343,39 +325,6 @@ public function user_two_factor_options( $user ) {

    - -

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

    From bf6df016db8975127bc4ac1c8f9ebe90ff368d14 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 2 May 2023 15:35:24 +1000 Subject: [PATCH 2/8] Use a proper wrapper div for backup codes. --- providers/class-two-factor-backup-codes.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/providers/class-two-factor-backup-codes.php b/providers/class-two-factor-backup-codes.php index b18d57ed..16aa73f8 100644 --- a/providers/class-two-factor-backup-codes.php +++ b/providers/class-two-factor-backup-codes.php @@ -176,7 +176,7 @@ public function user_options( $user ) { $count = self::codes_remaining_for_user( $user ); ?> -

    +

    -

    - Date: Tue, 2 May 2023 15:46:17 +1000 Subject: [PATCH 3/8] Backup Codes: Migrate to vanilla JS. --- js/admin-backup-codes.js | 34 +++++++++++++-------- providers/class-two-factor-backup-codes.php | 4 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/js/admin-backup-codes.js b/js/admin-backup-codes.js index 35d52960..d81f3575 100644 --- a/js/admin-backup-codes.js +++ b/js/admin-backup-codes.js @@ -1,28 +1,38 @@ -(function($){ +(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 - $( '.button-two-factor-backup-codes-generate' ).click( function() { + 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: , + path: 'two-factor/1.0/generate-backup-codes', data: { - user_id: ID ); ?> + user_id: userId } } ).then( function( response ) { - var $codesList = $( '.two-factor-backup-codes-unused-codes' ); - - $( '.two-factor-backup-codes-wrapper' ).show(); - $codesList.html( '' ); + codeList.innerHTML = ''; // Append the codes. for ( i = 0; i < response.codes.length; i++ ) { - $codesList.append( '
  • ' + response.codes[ i ] + '
  • ' ); + codeList.innerHTML += '
  • ' + response.codes[ i ] + '
  • '; } + // Display the section. + codeWrapper.style.display = 'block'; + // Update counter. - $( '.two-factor-backup-codes-count' ).html( response.i18n.count ); - $( '#two-factor-backup-codes-download-link' ).attr( 'href', response.download_link ); + codesCountDiv.innerHTML = response.i18n.count; + + // Update link. + downloadButton.href = response.download_link; } ); } ); -})(jQuery); \ No newline at end of file +})(); \ 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 16aa73f8..fbbb6926 100644 --- a/providers/class-two-factor-backup-codes.php +++ b/providers/class-two-factor-backup-codes.php @@ -176,7 +176,7 @@ public function user_options( $user ) { $count = self::codes_remaining_for_user( $user ); ?> -
    +

    - +

    From 97c19ab030cbeebf2da58a7d6884699b020438a3 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 2 May 2023 16:26:00 +1000 Subject: [PATCH 4/8] Migrate TOTP to vanialla JS. --- js/admin-totp.js | 78 +++++++++++++++++------------ providers/class-two-factor-totp.php | 6 +-- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/js/admin-totp.js b/js/admin-totp.js index 67207755..f178bfe5 100644 --- a/js/admin-totp.js +++ b/js/admin-totp.js @@ -1,7 +1,9 @@ -(function($){ +(function(){ + const totpSetup = document.getElementById( 'two-factor-totp-options' ), + userId = totpSetup.dataset.userid || 0; // TOTP QR Setup - var qr_generator = function() { + const renderQRCode = function() { var link = document.querySelector( '#two-factor-qr-code a' ); if ( ! link ) { return; @@ -21,56 +23,68 @@ link.innerHTML = qr.createSvgTag( 5 ); }; - // Run now if the document is loaded, otherwise on DOMContentLoaded. - if ( document.readyState === 'complete' ) { - qr_generator(); - } else { - window.addEventListener( 'DOMContentLoaded', qr_generator ); - } - // TOTP Setup - $('.totp-submit').click( function( e ) { + const totpSetupHandler = function( e ) { e.preventDefault(); - var key = $('#two-factor-totp-key').val(), - code = $('#two-factor-totp-authcode').val(); + + 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: , + path: 'two-factor/1.0/totp', data: { - user_id: ID ); ?>, - key: key, - code: code, + user_id: userId, + key: totpKey, + code: totpCodeInput.value, } } ).fail( function( response, status ) { - var errorMessage = response.responseJSON.message || status, - $error = $( '#totp-setup-error' ); + let errorMessage = response.responseJSON.message || status, + errorDiv = totpSetup.querySelector( '.totp-setup-error' ); - if ( ! $error.length ) { - $error = $('

    ').insertAfter( $('.totp-submit') ); + if ( ! errorDiv ) { + totpSetupSubmit.outerHTML += '

    '; + errorDiv = totpSetup.querySelector( '.totp-setup-error' ); } - $error.find('p').text( errorMessage ); - - $('#two-factor-totp-authcode').val(''); + errorDiv.querySelector( 'p' ).textContent = errorMessage; + totpCodeInput.value = ''; } ).then( function( response ) { - $( '#two-factor-totp-options' ).html( response.html ); + totpSetup.innerHTML = response.html; } ); - } ); + }; - // TOTP Reset - $( '.button.reset-totp-key' ).click( function( e ) { + const totpResetHandler = function( e ) { e.preventDefault(); wp.apiRequest( { method: 'DELETE', - path: , + path: 'two-factor/1.0/totp', data: { - user_id: ID ); ?>, + user_id: userId, } } ).then( function( response ) { - $( '#two-factor-totp-options' ).html( response.html ); + 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 ); + } -})(jQuery); \ No newline at end of file + // 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-totp.php b/providers/class-two-factor-totp.php index a1398aab..1ca918d3 100644 --- a/providers/class-two-factor-totp.php +++ b/providers/class-two-factor-totp.php @@ -282,7 +282,7 @@ public function user_two_factor_options( $user ) { wp_enqueue_script( 'two-factor-totp' ); ?> -
    +

    - Loading... +

    @@ -322,7 +322,7 @@ public function user_two_factor_options( $user ) { ?> - +

    From e42fd1a2018ccbcbe06f057a1081c5fff80589f0 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 2 May 2023 16:34:47 +1000 Subject: [PATCH 5/8] Remove JS dep. --- providers/class-two-factor-backup-codes.php | 2 +- providers/class-two-factor-totp.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/class-two-factor-backup-codes.php b/providers/class-two-factor-backup-codes.php index fbbb6926..83c1ee84 100644 --- a/providers/class-two-factor-backup-codes.php +++ b/providers/class-two-factor-backup-codes.php @@ -142,7 +142,7 @@ public function enqueue_assets( $hook_suffix ) { wp_register_script( 'two-factor-backup-codes', plugins_url( 'js/admin-backup-codes.js', __DIR__ ), - array( 'jquery', 'wp-api-request' ), + array( 'wp-api-request' ), TWO_FACTOR_VERSION, true ); diff --git a/providers/class-two-factor-totp.php b/providers/class-two-factor-totp.php index 1ca918d3..1cc92e2f 100644 --- a/providers/class-two-factor-totp.php +++ b/providers/class-two-factor-totp.php @@ -141,7 +141,7 @@ public function enqueue_assets( $hook_suffix ) { wp_register_script( 'two-factor-totp', plugins_url( 'js/admin-totp.js', __DIR__ ), - array( 'two-factor-qr-code-generator', 'jquery', 'wp-api-request' ), + array( 'two-factor-qr-code-generator', 'wp-api-request' ), TWO_FACTOR_VERSION, true ); From 6b2499fc8904b53fff7939089c62509b8b441af8 Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Tue, 2 May 2023 16:51:55 +1000 Subject: [PATCH 6/8] Fix poor test. --- tests/providers/class-two-factor-backup-codes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/providers/class-two-factor-backup-codes.php b/tests/providers/class-two-factor-backup-codes.php index a926044e..d4d067bd 100644 --- a/tests/providers/class-two-factor-backup-codes.php +++ b/tests/providers/class-two-factor-backup-codes.php @@ -163,7 +163,7 @@ public function test_user_options() { $this->provider->user_options( $user ); $buffer = ob_get_clean(); - $this->assertStringContainsString( '

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

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