Skip to content

Commit

Permalink
Add filters for email token and backup code length
Browse files Browse the repository at this point in the history
  • Loading branch information
kasparsd authored Jan 9, 2025
2 parents 3c85963 + 3c50342 commit abd8289
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 7 deletions.
31 changes: 28 additions & 3 deletions providers/class-two-factor-backup-codes.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,25 @@ public function user_options( $user ) {
<?php
}

/**
* Get the backup code length for a user.
*
* @param WP_User $user User object.
*
* @return int Number of characters.
*/
private function get_backup_code_length( $user ) {
/**
* Customize the character count of the backup codes.
*
* @var int $code_length Length of the backup code.
* @var WP_User $user User object.
*/
$code_length = (int) apply_filters( 'two_factor_backup_code_length', 8, $user );

return $code_length;
}

/**
* Generates backup codes & updates the user meta.
*
Expand All @@ -239,8 +258,10 @@ public function generate_codes( $user, $args = '' ) {
$codes_hashed = (array) get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
}

$code_length = $this->get_backup_code_length( $user );

for ( $i = 0; $i < $num_codes; $i++ ) {
$code = $this->get_code();
$code = $this->get_code( $code_length );
$codes_hashed[] = wp_hash_password( $code );
$codes[] = $code;
unset( $code );
Expand Down Expand Up @@ -326,11 +347,15 @@ public static function codes_remaining_for_user( $user ) {
*/
public function authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php';

$code_length = $this->get_backup_code_length( $user );
$code_placeholder = str_repeat( 'X', $code_length );

?>
<p class="two-factor-prompt"><?php esc_html_e( 'Enter a recovery code.', 'two-factor' ); ?></p><br/>
<p class="two-factor-prompt"><?php esc_html_e( 'Enter a recovery code.', 'two-factor' ); ?></p>
<p>
<label for="authcode"><?php esc_html_e( 'Recovery Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="1234 5678" autocomplete="one-time-code" data-digits="8" />
<input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $code_placeholder ); ?>" data-digits="<?php echo esc_attr( $code_length ); ?>" />
</p>
<?php
submit_button( __( 'Submit', 'two-factor' ) );
Expand Down
36 changes: 33 additions & 3 deletions providers/class-two-factor-email.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ public function get_alternative_provider_label() {
return __( 'Send a code to your email', 'two-factor' );
}

/**
* Get the email token length.
*
* @return int Email token string length.
*/
private function get_token_length() {
/**
* Number of characters in the email token.
*
* @param int $token_length Number of characters in the email token.
*/
$token_length = (int) apply_filters( 'two_factor_email_token_length', 8 );

return $token_length;
}

/**
* Generate the user token.
*
Expand All @@ -72,7 +88,7 @@ public function get_alternative_provider_label() {
* @return string
*/
public function generate_token( $user_id ) {
$token = $this->get_code();
$token = $this->get_code( $this->get_token_length() );

update_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, time() );
update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) );
Expand Down Expand Up @@ -146,10 +162,21 @@ public function user_token_ttl( $user_id ) {
* Number of seconds the token is considered valid
* after the generation.
*
* @deprecated 0.11.0 Use {@see 'two_factor_email_token_ttl'} instead.
*
* @param integer $token_ttl Token time-to-live in seconds.
* @param integer $user_id User ID.
*/
return (int) apply_filters( 'two_factor_token_ttl', $token_ttl, $user_id );
$token_ttl = (int) apply_filters_deprecated( 'two_factor_token_ttl', array( $token_ttl, $user_id ), '0.11.0', 'two_factor_email_token_ttl' );

/**
* Number of seconds the token is considered valid
* after the generation.
*
* @param integer $token_ttl Token time-to-live in seconds.
* @param integer $user_id User ID.
*/
return (int) apply_filters( 'two_factor_email_token_ttl', $token_ttl, $user_id );
}

/**
Expand Down Expand Up @@ -259,12 +286,15 @@ public function authentication_page( $user ) {
$this->generate_and_email_token( $user );
}

$token_length = $this->get_token_length();
$token_placeholder = str_repeat( 'X', $token_length );

require_once ABSPATH . '/wp-admin/includes/template.php';
?>
<p class="two-factor-prompt"><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p>
<p>
<label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-email-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" autocomplete="one-time-code" placeholder="1234 5678" data-digits="8" />
<input type="text" inputmode="numeric" name="two-factor-email-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" autocomplete="one-time-code" placeholder="<?php echo esc_attr( $token_placeholder ); ?>" data-digits="<?php echo esc_attr( $token_length ); ?>" />
<?php submit_button( __( 'Log In', 'two-factor' ) ); ?>
</p>
<p class="two-factor-email-resend">
Expand Down
4 changes: 3 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ Here is a list of action and filter hooks provided by the plugin:
- `two_factor_providers` filter overrides the available two-factor providers such as email and time-based one-time passwords. Array values are PHP classnames of the two-factor providers.
- `two_factor_enabled_providers_for_user` filter overrides the list of two-factor providers enabled for a user. First argument is an array of enabled provider classnames as values, the second argument is the user ID.
- `two_factor_user_authenticated` action which receives the logged in `WP_User` object as the first argument for determining the logged in user right after the authentication workflow.
- `two_factor_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated.
- `two_factor_email_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated.
- `two_factor_email_token_length` filter overrides the default 8 character count for email tokens.
- `two_factor_backup_code_length` filter overrides the default 8 character count for backup codes. Providers the `WP_User` of the associated user as the second argument.

== Frequently Asked Questions ==

Expand Down
21 changes: 21 additions & 0 deletions tests/providers/class-two-factor-backup-codes.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,25 @@ public function test_delete_code() {
$this->provider->delete_code( $user, $backup_codes[0] );
$this->assertEquals( 1, $this->provider->codes_remaining_for_user( $user ) );
}

public function test_backup_code_length_filter() {
$user = new WP_User( self::factory()->user->create() );

$code_default = $this->provider->generate_codes( $user, array( 'number' => 1 ) );

add_filter(
'two_factor_backup_code_length',
function() {
return 7;
}
);

$code_custom_length = $this->provider->generate_codes( $user, array( 'number' => 1 ) );

$this->assertNotEquals( strlen( $code_custom_length[0] ), strlen( $code_default[0] ), 'Backup code length can be adjusted via filter' );

$this->assertEquals( 7, strlen( $code_custom_length[0] ), 'Backup code length matches the filtered length' );

remove_all_filters( 'two_factor_backup_code_length' );
}
}
63 changes: 63 additions & 0 deletions tests/providers/class-two-factor-email.php
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,67 @@ public function test_tokens_can_expire() {
);
}

public function test_custom_token_length() {
$user_id = self::factory()->user->create();

$default_token = $this->provider->generate_token( $user_id );

add_filter(
'two_factor_email_token_length',
function() {
return 15;
}
);

$custom_token = $this->provider->generate_token( $user_id );

$this->assertNotEquals( strlen( $default_token ), strlen( $custom_token ), 'Token length is different due to filter' );
$this->assertEquals( 15, strlen( $custom_token ), 'Token length matches the filter value' );

remove_all_filters( 'two_factor_email_token_length' );
}

/**
* Test the email token TTL.
*
* @expectedDeprecated two_factor_token_ttl
*/
public function test_email_token_ttl() {
$this->assertEquals(
15 * MINUTE_IN_SECONDS,
$this->provider->user_token_ttl( 123 ),
'The email token matches the default TTL'
);

add_filter(
'two_factor_email_token_ttl',
function() {
return 42;
}
);

$this->assertEquals(
42,
$this->provider->user_token_ttl( 123 ),
'The email token ttl can be filtered'
);

remove_all_filters( 'two_factor_email_token_ttl' );

add_filter(
'two_factor_token_ttl',
function() {
return 66;
}
);

$this->assertEquals(
66,
$this->provider->user_token_ttl( 123 ),
'The email token matches can be filtered with the deprecated filter'
);

remove_all_filters( 'two_factor_token_ttl' );
}

}

0 comments on commit abd8289

Please sign in to comment.