Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filters for email token and backup code length #653

Merged
merged 17 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 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,14 @@ public static function codes_remaining_for_user( $user ) {
*/
public function authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php';

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

?>
<p class="two-factor-prompt"><?php esc_html_e( 'Enter a recovery code.', 'two-factor' ); ?></p><br/>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing too much blank space between the message and the input:

recover-codes

<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 esc_attr( self::NUMBER_OF_CODES ); ?>" />
</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' );
}

}
Loading