Skip to content

Commit

Permalink
Merge branch 'add/account-protection' into update/protect/account-pro…
Browse files Browse the repository at this point in the history
…tection-copy
  • Loading branch information
dkmyta committed Jan 30, 2025
2 parents 55eab44 + 98c2064 commit d328461
Show file tree
Hide file tree
Showing 8 changed files with 447 additions and 405 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public function __construct( ?Modules $modules = null, ?Password_Detection $pass

/**
* Initializes the configurations needed for the account protection module.
*
* @return void
*/
public function init(): void {
$this->register_hooks();
Expand All @@ -54,6 +56,8 @@ public function init(): void {

/**
* Register hooks for module activation and environment validation.
*
* @return void
*/
private function register_hooks(): void {
// Account protection activation/deactivation hooks
Expand All @@ -67,44 +71,44 @@ private function register_hooks(): void {

/**
* Register hooks for runtime operations.
*
* @return void
*/
private function register_runtime_hooks(): void {
// Validate password after successful login
add_action( 'wp_authenticate_user', array( $this->password_detection, 'login_form_password_detection' ), 10, 2 );

// Handle password detection login failure
add_action( 'wp_login_failed', array( $this->password_detection, 'handle_password_detection_validation_error' ), 10, 2 );

// Add password detection flow
add_action( 'login_form_password-detection', array( $this->password_detection, 'render_page' ), 10, 2 );

// Remove password detection usermeta after password reset and on profile password update
add_action( 'after_password_reset', array( $this->password_detection, 'delete_usermeta_after_password_reset' ), 10, 2 );
add_action( 'profile_update', array( $this->password_detection, 'delete_usermeta_on_profile_update' ), 10, 2 );

// Register AJAX resend password reset email action
add_action( 'wp_ajax_resend_password_reset', array( $this->password_detection, 'ajax_resend_password_reset_email' ) );
}

/**
* Activate the account protection on module activation.
*
* @return void
*/
public function on_account_protection_activation(): void {
// Activation logic can be added here
}

/**
* Deactivate the account protection on module deactivation.
*
* @return void
*/
public function on_account_protection_deactivation(): void {
// Remove password detection user meta on deactivation
// TODO: Run on Jetpack and Protect deactivation
$this->password_detection->delete_all_usermeta();
// Deactivation logic can be added here
}

/**
* Determines if the account protection module is enabled on the site.
*
* @return bool
*/
public function is_enabled() {
public function is_enabled(): bool {
return $this->modules->is_active( self::ACCOUNT_PROTECTION_MODULE_NAME );
}

Expand All @@ -113,7 +117,7 @@ public function is_enabled() {
*
* @return bool
*/
public function enable() {
public function enable(): bool {
// Return true if already enabled.
if ( $this->is_enabled() ) {
return true;
Expand Down
19 changes: 19 additions & 0 deletions projects/packages/account-protection/src/class-config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* Class used to define Config.
*
* @package automattic/jetpack-account-protection
*/

namespace Automattic\Jetpack\Account_Protection;

/**
* Class Config
*/
class Config {
public const TRANSIENT_PREFIX = 'password_detection';
public const ERROR_CODE = 'password_detection_validation_error';
public const ERROR_MESSAGE = 'Password validation failed.';
public const EMAIL_SENT_EXPIRATION = 600; // 10 minutes
public const MAX_RESEND_ATTEMPTS = 3;
}
114 changes: 114 additions & 0 deletions projects/packages/account-protection/src/class-email-service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/**
* Class used to define Email Service.
*
* @package automattic/jetpack-account-protection
*/

namespace Automattic\Jetpack\Account_Protection;

use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Jetpack_Options;

/**
* Class Email_Service
*/
class Email_Service {
/**
* Send the email using the API.
*
* @param \WP_User $user The user.
* @param string $auth_code The authentication code.
*
* @return bool True if the email was sent successfully, false otherwise.
*/
public function api_send_auth_email( \WP_User $user, string $auth_code ): bool {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();

if ( ! $blog_id || ! $is_connected ) {
return false;
}

$body = array(
'user_login' => $user->user_login,
'user_email' => $user->user_email,
'code' => $auth_code,
);

$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/jetpack-protect-send-verification-code', $blog_id ),
'2',
array(
'method' => 'POST',
),
$body,
'wpcom'
);

$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return false;
}

$body = json_decode( wp_remote_retrieve_body( $response ), true );

return $body['email_sent'] ?? false;
}

/**
* Resend email attempts.
*
* @param \WP_User $user The user.
* @param array $transient_data The transient data.
* @param string $token The token.
*
* @return bool True if the email was resent successfully, false otherwise.
*/
public function resend_auth_email( \WP_User $user, array $transient_data, string $token ): bool {
if ( $transient_data['resend_attempts'] >= Config::MAX_RESEND_ATTEMPTS ) {
return false;
}

$auth_code = $this->generate_auth_code();
$transient_data['auth_code'] = $auth_code;

if ( ! $this->api_send_auth_email( $user, $auth_code ) ) {
return false;
}

++$transient_data['resend_attempts'];

if ( ! set_transient( Config::TRANSIENT_PREFIX . "_{$token}", $transient_data, Config::EMAIL_SENT_EXPIRATION ) ) {
return false;
}

return true;
}

/**
* Generate an auth code.
*
* @return string The generated auth code.
*/
public function generate_auth_code(): string {
return (string) wp_rand( 100000, 999999 );
}

/**
* Mask an email address like d*****@g*****.com.
*
* @param string $email The email address to mask.
*
* @return string The masked email address.
*/
public function mask_email_address( string $email ): string {
$parts = explode( '@', $email );
$name = substr( $parts[0], 0, 1 ) . str_repeat( '*', strlen( $parts[0] ) - 1 );
$domain_parts = explode( '.', $parts[1] );
$domain = substr( $domain_parts[0], 0, 1 ) . str_repeat( '*', strlen( $domain_parts[0] ) - 1 );

return "{$name}@{$domain}.{$domain_parts[1]}";
}
}
Loading

0 comments on commit d328461

Please sign in to comment.