Features #14-17: WordPress Manager, Backup, Cloudflare, TOTP 2FA

- WordPressManager.php: wp-cli wrapper for install/update/clone/delete
- BackupManager.php: tar+mysqldump, schedules, retention, rclone
- CloudflareManager.php: zone/record management, sync, cache purge
- TOTP.php: RFC 6238 pure-PHP with backup codes
- Auth.php: TOTP_REQUIRED two-step login flow
- 4 new API endpoints: wordpress, backup, cloudflare, totp
- DB migration 002: TOTP cols, CF cols, wordpress_installs, backups tables
- admin.js: full UI for all 4 features + TOTP login step
- admin/index.php: sidebar nav for WordPress, 2FA Manager, Cloudflare

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 21:13:59 +00:00
parent 62707d62ce
commit 135bbcb0b3
13 changed files with 1542 additions and 47 deletions
+29 -2
View File
@@ -1,8 +1,13 @@
<?php
if (!class_exists('TOTP')) require_once __DIR__ . '/TOTP.php';
class Auth {
private static ?Auth $instance = null;
private ?array $user = null;
// Returned by attempt() when password is correct but TOTP code still needed
public const TOTP_REQUIRED = 'TOTP_REQUIRED';
private function __construct() {}
public static function getInstance(): self {
@@ -54,14 +59,36 @@ class Auth {
return true;
}
public function attempt(string $username, string $password): ?string {
$db = DB::getInstance();
/**
* Returns null (bad credentials), self::TOTP_REQUIRED (need 2FA code), or session token string.
*/
public function attempt(string $username, string $password, ?string $totpCode = null): ?string {
$db = DB::getInstance();
$user = $db->fetchOne(
"SELECT * FROM users WHERE (username = ? OR email = ?) AND status = 'active'",
[$username, $username]
);
if (!$user || !password_verify($password, $user['password'])) return null;
// TOTP check
if (!empty($user['totp_enabled'])) {
if ($totpCode === null) {
$this->user = $user;
return self::TOTP_REQUIRED;
}
$verified = TOTP::verify($user['totp_secret'] ?? '', $totpCode);
if (!$verified && !empty($user['totp_backup_codes'])) {
$verified = TOTP::verifyBackupCode($totpCode, $user['totp_backup_codes']);
if ($verified) {
// Consume used backup code
$hashes = json_decode($user['totp_backup_codes'], true) ?? [];
$hashes = array_values(array_filter($hashes, fn($h) => !password_verify(strtoupper($totpCode), $h)));
$db->execute("UPDATE users SET totp_backup_codes=? WHERE id=?", [json_encode($hashes), $user['id']]);
}
}
if (!$verified) return null;
}
// Create session
$token = bin2hex(random_bytes(32));
$sessionId = hash('sha256', $token);