__('Permission denied.', 'acemedia-login-block')]); return; } $force_new = isset($_POST['force_new']) && $_POST['force_new'] === 'true'; $backup_codes = get_user_meta($user_id, '_acemedia_2fa_backup_codes', true); if ($force_new || !is_array($backup_codes) || empty($backup_codes)) { // Generate new codes with high entropy $codes = []; for ($i = 0; $i < 10; $i++) { $codes[] = strtoupper(bin2hex(random_bytes(4))); // 8 characters of high entropy } // Store codes with metadata $hashed_codes = array_map(function($code) { return [ 'hash' => wp_hash_password($code), 'created' => time(), 'used' => false ]; }, $codes); update_user_meta($user_id, '_acemedia_2fa_backup_codes', $hashed_codes); wp_send_json_success(['codes' => $codes]); } else { // Codes are single-use and hashed — never expose them (or any hash-derived // hint) again after generation. Report how many remain instead. $remaining = count(array_filter($backup_codes, function($code_data) { return empty($code_data['used']); })); wp_send_json_success([ 'codes' => [], 'remaining' => $remaining, 'message' => __('Backup codes were already generated. Regenerate to get a new set.', 'acemedia-login-block'), ]); } } /** * Helper function for base32 encoding */ public static function base32_encode($data) { $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $binary = ''; foreach (str_split($data) as $char) { $binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT); } $binary = str_pad($binary, ceil(strlen($binary) / 40) * 40, '0'); $result = ''; foreach (str_split($binary, 5) as $chunk) { $result .= $alphabet[bindec($chunk)]; } return $result; } } // Initialize the class new Backup_Codes();