mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
Role isolation, impersonation, account ownership, loading spinners, Docker fixes
- Enforce portal role isolation: admin/reseller/user can only auth on their own port - Admin/reseller impersonation: Login As with cookie handoff + Return banner in user panel - Account ownership: admin can reassign accounts to resellers, DNS NS follows - accounts/update: ownership change cascades package + NS to new owner - users.php endpoint: admin list/filter by role (reseller dropdown) - Docker launch fix: uDockerUpdateParams defined before call - Nova.loading() spinners: login, SSL, PHP switch/save, backup create, docker launch/actions - Logo consistency: gradient CPX text on all login pages, novacpx_logo_html() in all sidebars - BackupManager: fix DB class name, table name, column name - DNSManager: fix settings keys (ns1_hostname/ns2_hostname) - docker.php: resolve account_id from user uid for all actions - Auth: impersonate sets cookie + stores return_token for seamless round-trip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -33,13 +33,24 @@ class Auth {
|
||||
private function loginBySession(string $sessionId): bool {
|
||||
$db = DB::getInstance();
|
||||
$row = $db->fetchOne(
|
||||
"SELECT s.*, u.id as uid, u.username, u.email, u.role, u.status, u.reseller_id, u.theme
|
||||
"SELECT s.impersonator_id, s.expires_at, u.id as uid, u.username, u.email, u.role, u.status, u.reseller_id, u.theme
|
||||
FROM sessions s
|
||||
JOIN users u ON u.id = s.user_id
|
||||
WHERE s.id = ? AND s.expires_at > NOW() AND u.status = 'active'",
|
||||
[hash('sha256', $sessionId)]
|
||||
);
|
||||
if (!$row) return false;
|
||||
|
||||
// Reject session if user's role doesn't match the current portal
|
||||
// Exception: impersonation sessions always land on the user portal
|
||||
$portal = defined('CURRENT_PORTAL') ? CURRENT_PORTAL : 'user';
|
||||
$allowed = match($portal) {
|
||||
'admin' => ['admin'],
|
||||
'reseller' => ['reseller'],
|
||||
default => ['user'],
|
||||
};
|
||||
if (!in_array($row['role'], $allowed, true)) return false;
|
||||
|
||||
$this->user = $row;
|
||||
return true;
|
||||
}
|
||||
@@ -70,6 +81,15 @@ class Auth {
|
||||
);
|
||||
if (!$user || !password_verify($password, $user['password'])) return null;
|
||||
|
||||
// Portal role enforcement — each panel only accepts its own role
|
||||
$portal = defined('CURRENT_PORTAL') ? CURRENT_PORTAL : 'user';
|
||||
$allowed = match($portal) {
|
||||
'admin' => ['admin'],
|
||||
'reseller' => ['reseller'],
|
||||
default => ['user'],
|
||||
};
|
||||
if (!in_array($user['role'], $allowed, true)) return null;
|
||||
|
||||
// TOTP check
|
||||
if (!empty($user['totp_enabled'])) {
|
||||
if ($totpCode === null) {
|
||||
|
||||
Reference in New Issue
Block a user