ID; $ip = AO_Request_Context::ip(); $ip_key = self::SENDS_PREFIX . 'ip_' . md5( $ip ); $now = time(); $user_win = get_transient( $user_key ); $ip_win = '' !== $ip ? get_transient( $ip_key ) : false; $user_win = ( is_array( $user_win ) && $user_win['expires'] > $now ) ? $user_win : [ 'count' => 0, 'expires' => $now + self::SEND_WINDOW, 'last' => 0 ]; $ip_win = ( is_array( $ip_win ) && $ip_win['expires'] > $now ) ? $ip_win : [ 'count' => 0, 'expires' => $now + self::SEND_WINDOW ]; if ( $now - (int) ( $user_win['last'] ?? 0 ) < self::COOLDOWN || $user_win['count'] >= self::MAX_SENDS_PER_USER || ( '' !== $ip && $ip_win['count'] >= self::MAX_SENDS_PER_IP ) ) { return 'limited'; } // --- 2. Create and store the code (hash only) --- $code = str_pad( (string) random_int( 0, 999999 ), 6, '0', STR_PAD_LEFT ); set_transient( self::CODE_PREFIX . $user->ID, [ 'hash' => wp_hash_password( $code ), 'expires' => $now + self::TTL, 'guesses' => 0, ], self::TTL ); // --- 3. Email it --- $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); $sent = wp_mail( $user->user_email, /* translators: %s: site name. */ sprintf( __( '[%s] Your one-time sign-in code', 'admin-optimizer' ), $site ), sprintf( /* translators: 1: user display name, 2: the code, 3: minutes the code is valid for. */ __( "Hello %1\$s,\n\nYour one-time sign-in code is: %2\$s\n\nIt can be used once and expires in %3\$d minutes. We are sending it because your authenticator app could not be verified.\n\nIf you did not try to sign in, ignore this email and consider changing your password.", 'admin-optimizer' ), $user->display_name, $code, (int) ( self::TTL / MINUTE_IN_SECONDS ) ) ); if ( ! $sent ) { delete_transient( self::CODE_PREFIX . $user->ID ); $this->error( 'Email one-time code could not be sent.', [ 'user_id' => (int) $user->ID ], 'email_otp_send_failed' ); return 'failed'; } ++$user_win['count']; $user_win['last'] = $now; ++$ip_win['count']; set_transient( $user_key, $user_win, max( 1, $user_win['expires'] - $now ) ); if ( '' !== $ip ) { set_transient( $ip_key, $ip_win, max( 1, $ip_win['expires'] - $now ) ); } $this->warning( 'Email one-time code sent because the authenticator secret could not be read.', [ 'user_id' => (int) $user->ID ], 'email_otp_sent' ); return 'sent'; } /** * Check a submitted code. A correct code is consumed; a wrong one counts as a guess. * * @param \WP_User $user User being signed in. * @param string $code Submitted 6-digit code. * * @return bool */ public function verify( \WP_User $user, string $code ): bool { $key = self::CODE_PREFIX . $user->ID; $record = get_transient( $key ); if ( ! is_array( $record ) || (int) ( $record['expires'] ?? 0 ) <= time() || (int) ( $record['guesses'] ?? 0 ) >= self::MAX_GUESSES ) { delete_transient( $key ); return false; } if ( wp_check_password( $code, (string) $record['hash'], $user->ID ) ) { delete_transient( $key ); return true; } ++$record['guesses']; if ( $record['guesses'] >= self::MAX_GUESSES ) { delete_transient( $key ); } else { set_transient( $key, $record, max( 1, (int) $record['expires'] - time() ) ); } return false; } /** * Tell the account owner their account was just signed in with an emailed code, and log it. * * @param \WP_User $user User who signed in. * * @return void */ public function notify_used( \WP_User $user ): void { $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); wp_mail( $user->user_email, /* translators: %s: site name. */ sprintf( __( '[%s] Your account was signed in with an emailed code', 'admin-optimizer' ), $site ), sprintf( /* translators: 1: user display name, 2: IP address, 3: site URL. */ __( "Hello %1\$s,\n\nYour account was just signed in using a one-time code sent to this address, because your authenticator app could not be verified. The request came from IP address %2\$s.\n\nIf this was you, sign in to %3\$s and reset your two-factor authentication from your profile page. If it was not you, change your password immediately and tell a site administrator.", 'admin-optimizer' ), $user->display_name, AO_Request_Context::ip(), home_url( '/' ) ) ); $this->warning( 'Account signed in with an emailed one-time code.', [ 'user_id' => (int) $user->ID ], 'email_otp_used' ); } }