mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
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:
+29
-2
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user