Simpan kode pemulihan
Autentikasi dua faktor aktif. Bila ponsel hilang, masuk dengan salah satu kode di bawah (masing-masing hanya bisa dipakai sekali). Kode ini hanya ditampilkan sekarang: cetak atau simpan di tempat aman.
= e($c) ?>
= OTP_MAX_FAILS || login_locked()) { unset($_SESSION['pre_auth']); redirect(base_url('admin/login.php?reason=otp')); } if (preg_match('/^[A-Z2-7]{4}-?[A-Z2-7]{4}$/', $code)) { // One-time recovery code (lost phone) $hashes = json_decode((string)$row['recovery_codes'], true) ?: []; $i = array_search(recovery_hash($code), $hashes, true); if ($i !== false) { array_splice($hashes, (int)$i, 1); db()->prepare('UPDATE admins SET recovery_codes = ? WHERE id = ?')->execute([json_encode($hashes), $row['id']]); record_otp_attempt($row['username'], true); complete_login($row, 'kode pemulihan'); flash('info', 'Anda masuk dengan kode pemulihan. Sisa kode: ' . count($hashes) . '. Minta super admin me-reset OTP bila ponsel hilang.'); redirect(base_url('admin/')); } } elseif ($step = totp_verify(secret_decrypt((string)$row['otp_secret']), $code, $row['otp_last_step'] !== null ? (int)$row['otp_last_step'] : null)) { db()->prepare('UPDATE admins SET otp_last_step = ? WHERE id = ?')->execute([$step, $row['id']]); record_otp_attempt($row['username'], true); complete_login($row, 'OTP'); redirect(base_url('admin/')); } record_otp_attempt($row['username'], false); $left = OTP_MAX_FAILS - otp_failures($row['username']); $error = $left > 0 ? "Kode salah atau sudah dipakai. Sisa percobaan: $left." : 'Terlalu banyak kode salah.'; if ($left <= 0) { unset($_SESSION['pre_auth']); redirect(base_url('admin/login.php?reason=otp')); } } if ($mode === 'enroll') { app_key(); // fail early when the encryption key is not configured $_SESSION['pre_auth']['exp'] = time() + 2 * PRE_AUTH_TTL; // time to install the app if (empty($_SESSION['pre_auth']['secret'])) { $_SESSION['pre_auth']['secret'] = totp_new_secret(); } $secret = $_SESSION['pre_auth']['secret']; if ($_SERVER['REQUEST_METHOD'] === 'POST') { csrf_check(); if (otp_failures($row['username']) >= OTP_MAX_FAILS || login_locked()) { unset($_SESSION['pre_auth']); redirect(base_url('admin/login.php?reason=otp')); } $step = totp_verify($secret, input_str($_POST['code'] ?? ''), null); if ($step) { [$plain, $hashes] = recovery_codes_new(); db()->prepare('UPDATE admins SET otp_secret = ?, otp_enabled = 1, otp_last_step = ?, recovery_codes = ? WHERE id = ?') ->execute([secret_encrypt($secret), $step, json_encode($hashes), $row['id']]); record_otp_attempt($row['username'], true); complete_login($row, 'OTP, aktivasi'); audit('user.otp', 'Mengaktifkan autentikasi dua faktor (OTP)'); $_SESSION['recovery_once'] = $plain; redirect(base_url('admin/2fa.php?done=1')); } record_otp_attempt($row['username'], false); $error = 'Kode tidak cocok. Pastikan jam ponsel sudah otomatis, lalu ketik kode yang sedang tampil.'; } } } catch (RuntimeException $ex) { error_log('[serubi] 2fa: ' . $ex->getMessage()); $configError = 'Autentikasi dua faktor belum dapat dipakai karena konfigurasi server belum lengkap (security.app_key). Hubungi pengelola server.'; } ?>
Autentikasi dua faktor aktif. Bila ponsel hilang, masuk dengan salah satu kode di bawah (masing-masing hanya bisa dipakai sekali). Kode ini hanya ditampilkan sekarang: cetak atau simpan di tempat aman.
= e($c) ?>