From 28190b8d690a7bdd62e60bcc5c57273df32f7381 Mon Sep 17 00:00:00 2001 From: James Morrison Date: Tue, 10 Dec 2024 13:12:55 +0000 Subject: [PATCH] Added check against HIBP API when setting a password. --- includes/classes/Authentication/Passwords.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/includes/classes/Authentication/Passwords.php b/includes/classes/Authentication/Passwords.php index 9e43702..21ba2a5 100644 --- a/includes/classes/Authentication/Passwords.php +++ b/includes/classes/Authentication/Passwords.php @@ -17,6 +17,11 @@ class Passwords { use Singleton; + /** + * Stores the Have I Been Pwned API URL + */ + const API_URL = 'https://api.pwnedpasswords.com/range/'; + /** * Setup hooks * @@ -307,6 +312,11 @@ public function validate_strong_password( $errors, $user_data ) { return $errors; } + // Validate the password against the Have I Been Pwned API. + if ( ! $this->is_password_secure( $password ) && is_wp_error( $errors ) ) { + $errors->add( 'password_reset_error', __( 'ERROR: The password entered may have been included in a data breach and is not considered safe to use. Please choose another.', 'tenup' ) ); + } + // Should a strong password be enforced for this user? if ( $user_id ) { @@ -374,4 +384,44 @@ public function enforce_for_user( $user_id ) { return $enforce; } + + /** + * Check if password is secure by querying the Have I Been Pwned API. + * + * @param string $password Password to validate. + * + * @return bool True if password is ok, false if it shows up in a breach. + */ + protected function is_password_secure( $password ): bool { + $hash = strtoupper( sha1( $password ) ); + $prefix = substr( $hash, 0, 5 ); + $suffix = substr( $hash, 5 ); + + $response = wp_remote_get( self::API_URL . $prefix ); + + // Allow for a failed request to the HIPB API. + if ( is_wp_error( $response ) ) { + return true; + } + + $body = wp_remote_retrieve_body( $response ); + + // Allow for a failed request to the HIPB API. + if ( is_wp_error( $body ) ) { + return true; + } + + $lines = explode( "\r\n", $body ); + + foreach ( $lines as $line ) { + $parts = explode( ':', $line ); + + // If the suffix is found in the response, the password may be in a breach. + if ( $parts[0] === $suffix ) { + return false; + } + } + + return true; + } }