Add Banned role
This commit is contained in:
176
BANNED_ROLE.md
Normal file
176
BANNED_ROLE.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# ⛔ Роль "Забанен"
|
||||||
|
|
||||||
|
## Что добавлено
|
||||||
|
|
||||||
|
### Новая роль "Забанен" (banned)
|
||||||
|
Роль для блокировки пользователей, которые нарушили правила или должны быть временно/постоянно отстранены от использования панели.
|
||||||
|
|
||||||
|
## 🚫 Ограничения роли
|
||||||
|
|
||||||
|
### Полная блокировка доступа
|
||||||
|
Пользователи с ролью "Забанен" **не имеют доступа** ни к каким функциям панели:
|
||||||
|
|
||||||
|
- ❌ Создание серверов
|
||||||
|
- ❌ Управление серверами
|
||||||
|
- ❌ Просмотр консоли
|
||||||
|
- ❌ Менеджер файлов
|
||||||
|
- ❌ Создание тикетов
|
||||||
|
- ❌ Просмотр тикетов
|
||||||
|
- ❌ Личный кабинет
|
||||||
|
- ❌ Любые другие функции
|
||||||
|
|
||||||
|
### Что происходит при попытке входа
|
||||||
|
При попытке доступа к любому endpoint API пользователь получает ошибку:
|
||||||
|
```
|
||||||
|
403 Forbidden: "Ваш аккаунт заблокирован"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Визуальное отображение
|
||||||
|
|
||||||
|
### В списке пользователей (Users.jsx)
|
||||||
|
- 🔴 **Красная иконка** пользователя
|
||||||
|
- **Текст роли**: "Забанен"
|
||||||
|
- **Описание**: "⛔ Пользователь заблокирован и не имеет доступа к панели"
|
||||||
|
|
||||||
|
### В личном кабинете (Profile.jsx)
|
||||||
|
- 🔴 **Красный бейдж** с текстом "Забанен"
|
||||||
|
- **Описание**: "⛔ Аккаунт заблокирован, доступ запрещён"
|
||||||
|
|
||||||
|
### В header (App.jsx)
|
||||||
|
- **Бейдж**: "Забанен" (красный цвет)
|
||||||
|
|
||||||
|
## 🔧 Как использовать
|
||||||
|
|
||||||
|
### Заблокировать пользователя
|
||||||
|
1. Войдите как администратор (none / none)
|
||||||
|
2. Нажмите кнопку "Пользователи" в header
|
||||||
|
3. Найдите нужного пользователя
|
||||||
|
4. В выпадающем списке выберите "Забанен"
|
||||||
|
5. Роль изменится автоматически
|
||||||
|
|
||||||
|
### Разблокировать пользователя
|
||||||
|
1. Войдите как администратор
|
||||||
|
2. Нажмите кнопку "Пользователи"
|
||||||
|
3. Найдите заблокированного пользователя
|
||||||
|
4. В выпадающем списке выберите другую роль:
|
||||||
|
- "Пользователь" - обычный доступ
|
||||||
|
- "Тех. поддержка" - доступ к тикетам
|
||||||
|
- "Администратор" - полный доступ
|
||||||
|
|
||||||
|
## 📋 Технические детали
|
||||||
|
|
||||||
|
### Backend (main.py)
|
||||||
|
|
||||||
|
#### Проверка в get_current_user()
|
||||||
|
```python
|
||||||
|
# Проверка на бан
|
||||||
|
if user.get("role") == "banned":
|
||||||
|
raise HTTPException(status_code=403, detail="Ваш аккаунт заблокирован")
|
||||||
|
```
|
||||||
|
|
||||||
|
Эта проверка выполняется **перед каждым запросом** к API, что гарантирует полную блокировку доступа.
|
||||||
|
|
||||||
|
#### Обновление роли
|
||||||
|
```python
|
||||||
|
new_role = data.get("role")
|
||||||
|
if new_role not in ["admin", "user", "support", "banned"]:
|
||||||
|
raise HTTPException(400, "Неверная роль")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
#### App.jsx
|
||||||
|
```javascript
|
||||||
|
const getRoleName = (role) => {
|
||||||
|
switch (role) {
|
||||||
|
case 'admin':
|
||||||
|
return 'Админ';
|
||||||
|
case 'support':
|
||||||
|
return 'Поддержка';
|
||||||
|
case 'banned':
|
||||||
|
return 'Забанен';
|
||||||
|
default:
|
||||||
|
return 'Пользователь';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Users.jsx
|
||||||
|
```javascript
|
||||||
|
<option value="banned">Забанен</option>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Profile.jsx
|
||||||
|
```javascript
|
||||||
|
case 'banned':
|
||||||
|
return 'bg-red-500/20 text-red-500 border-red-500/50';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Безопасность
|
||||||
|
|
||||||
|
### Защита на уровне API
|
||||||
|
- Проверка роли выполняется в функции `get_current_user()`
|
||||||
|
- Блокировка происходит **до** выполнения любого endpoint
|
||||||
|
- Невозможно обойти блокировку через API
|
||||||
|
|
||||||
|
### Защита на уровне UI
|
||||||
|
- Визуальное отображение статуса блокировки
|
||||||
|
- Красные индикаторы для предупреждения
|
||||||
|
- Понятные сообщения об ошибках
|
||||||
|
|
||||||
|
## ⚠️ Важные замечания
|
||||||
|
|
||||||
|
### Администраторы не могут быть заблокированы
|
||||||
|
Рекомендуется добавить проверку, чтобы администраторы не могли заблокировать сами себя или других администраторов.
|
||||||
|
|
||||||
|
### Логирование блокировок
|
||||||
|
Рекомендуется добавить логирование:
|
||||||
|
- Кто заблокировал пользователя
|
||||||
|
- Когда была выполнена блокировка
|
||||||
|
- Причина блокировки (опционально)
|
||||||
|
|
||||||
|
### Уведомления
|
||||||
|
Можно добавить:
|
||||||
|
- Email уведомление о блокировке
|
||||||
|
- Причину блокировки в профиле
|
||||||
|
- Дату окончания блокировки (для временных банов)
|
||||||
|
|
||||||
|
## 📊 Статистика
|
||||||
|
|
||||||
|
### Роли в системе
|
||||||
|
1. **Администратор** (admin) - полный доступ
|
||||||
|
2. **Тех. поддержка** (support) - доступ к тикетам
|
||||||
|
3. **Пользователь** (user) - доступ к своим серверам
|
||||||
|
4. **Забанен** (banned) - нет доступа ⛔
|
||||||
|
|
||||||
|
## ✅ Готово!
|
||||||
|
|
||||||
|
Роль "Забанен" полностью интегрирована в MC Panel. Администраторы могут блокировать пользователей, которые нарушают правила или должны быть отстранены от использования панели.
|
||||||
|
|
||||||
|
### Тестирование
|
||||||
|
|
||||||
|
1. **Создайте тестового пользователя**
|
||||||
|
- Зарегистрируйте нового пользователя
|
||||||
|
|
||||||
|
2. **Заблокируйте его**
|
||||||
|
- Войдите как админ
|
||||||
|
- Откройте "Пользователи"
|
||||||
|
- Измените роль на "Забанен"
|
||||||
|
|
||||||
|
3. **Попробуйте войти**
|
||||||
|
- Выйдите из админа
|
||||||
|
- Войдите как заблокированный пользователь
|
||||||
|
- Вы увидите ошибку "Ваш аккаунт заблокирован"
|
||||||
|
|
||||||
|
4. **Разблокируйте**
|
||||||
|
- Войдите как админ
|
||||||
|
- Измените роль обратно на "Пользователь"
|
||||||
|
|
||||||
|
## 🎯 Использование
|
||||||
|
|
||||||
|
Роль "Забанен" готова к использованию. Используйте её для:
|
||||||
|
- Блокировки нарушителей
|
||||||
|
- Временного отстранения пользователей
|
||||||
|
- Защиты панели от нежелательных действий
|
||||||
|
|
||||||
|
**Будьте осторожны с блокировками! 🚨**
|
||||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,5 +1,38 @@
|
|||||||
# 📝 История изменений MC Panel
|
# 📝 История изменений MC Panel
|
||||||
|
|
||||||
|
## Версия 2.2 - Роль "Забанен" (14.01.2026)
|
||||||
|
|
||||||
|
### ✨ Новые возможности
|
||||||
|
|
||||||
|
#### ⛔ Роль "Забанен"
|
||||||
|
- Новая роль для блокировки пользователей
|
||||||
|
- Полная блокировка доступа к панели
|
||||||
|
- Проверка на уровне API (функция get_current_user)
|
||||||
|
- Красные индикаторы в интерфейсе
|
||||||
|
- Сообщение об ошибке при попытке входа
|
||||||
|
|
||||||
|
#### 🎨 Визуальное отображение
|
||||||
|
- Красная иконка в списке пользователей
|
||||||
|
- Красный бейдж "Забанен" в header
|
||||||
|
- Красный бейдж в личном кабинете
|
||||||
|
- Предупреждающие сообщения
|
||||||
|
|
||||||
|
#### 🔐 Безопасность
|
||||||
|
- Проверка роли перед каждым запросом к API
|
||||||
|
- Невозможно обойти блокировку
|
||||||
|
- Ошибка 403: "Ваш аккаунт заблокирован"
|
||||||
|
|
||||||
|
### 📁 Новые файлы
|
||||||
|
- `BANNED_ROLE.md` - документация роли "Забанен"
|
||||||
|
|
||||||
|
### 🔧 Изменения в коде
|
||||||
|
- `backend/main.py` - добавлена проверка на бан в get_current_user()
|
||||||
|
- `frontend/src/App.jsx` - добавлена функция getRoleName()
|
||||||
|
- `frontend/src/components/Users.jsx` - добавлена опция "Забанен"
|
||||||
|
- `frontend/src/components/Profile.jsx` - добавлено отображение роли "Забанен"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Версия 2.1 - Личный кабинет (14.01.2026)
|
## Версия 2.1 - Личный кабинет (14.01.2026)
|
||||||
|
|
||||||
### ✨ Новые возможности
|
### ✨ Новые возможности
|
||||||
|
|||||||
@@ -123,7 +123,13 @@ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(securit
|
|||||||
if username not in users:
|
if username not in users:
|
||||||
raise HTTPException(status_code=401, detail="Пользователь не найден")
|
raise HTTPException(status_code=401, detail="Пользователь не найден")
|
||||||
|
|
||||||
return users[username]
|
user = users[username]
|
||||||
|
|
||||||
|
# Проверка на бан
|
||||||
|
if user.get("role") == "banned":
|
||||||
|
raise HTTPException(status_code=403, detail="Ваш аккаунт заблокирован")
|
||||||
|
|
||||||
|
return user
|
||||||
except JWTError:
|
except JWTError:
|
||||||
raise HTTPException(status_code=401, detail="Неверный токен")
|
raise HTTPException(status_code=401, detail="Неверный токен")
|
||||||
|
|
||||||
@@ -266,7 +272,7 @@ async def update_user_role(username: str, data: dict, user: dict = Depends(get_c
|
|||||||
raise HTTPException(404, "Пользователь не найден")
|
raise HTTPException(404, "Пользователь не найден")
|
||||||
|
|
||||||
new_role = data.get("role")
|
new_role = data.get("role")
|
||||||
if new_role not in ["admin", "user", "support"]:
|
if new_role not in ["admin", "user", "support", "banned"]:
|
||||||
raise HTTPException(400, "Неверная роль")
|
raise HTTPException(400, "Неверная роль")
|
||||||
|
|
||||||
users[username]["role"] = new_role
|
users[username]["role"] = new_role
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"arkonsad": {
|
"arkonsad": {
|
||||||
"username": "arkonsad",
|
"username": "arkonsad",
|
||||||
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
||||||
"role": "user",
|
"role": "banned",
|
||||||
"servers": [
|
"servers": [
|
||||||
"123"
|
"123"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ function App() {
|
|||||||
localStorage.setItem('theme', newTheme);
|
localStorage.setItem('theme', newTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRoleName = (role) => {
|
||||||
|
switch (role) {
|
||||||
|
case 'admin':
|
||||||
|
return 'Админ';
|
||||||
|
case 'support':
|
||||||
|
return 'Поддержка';
|
||||||
|
case 'banned':
|
||||||
|
return 'Забанен';
|
||||||
|
default:
|
||||||
|
return 'Пользователь';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUsernameChange = (newToken, newUsername) => {
|
const handleUsernameChange = (newToken, newUsername) => {
|
||||||
setToken(newToken);
|
setToken(newToken);
|
||||||
setUser({ ...user, username: newUsername });
|
setUser({ ...user, username: newUsername });
|
||||||
@@ -166,7 +179,7 @@ function App() {
|
|||||||
{user?.username}
|
{user?.username}
|
||||||
</span>
|
</span>
|
||||||
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
||||||
{user?.role === 'admin' ? 'Админ' : user?.role === 'support' ? 'Поддержка' : 'Пользователь'}
|
{getRoleName(user?.role)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
||||||
@@ -216,7 +229,7 @@ function App() {
|
|||||||
{user?.username}
|
{user?.username}
|
||||||
</span>
|
</span>
|
||||||
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
||||||
{user?.role === 'admin' ? 'Админ' : user?.role === 'support' ? 'Поддержка' : 'Пользователь'}
|
{getRoleName(user?.role)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
||||||
@@ -266,7 +279,7 @@ function App() {
|
|||||||
{user?.username}
|
{user?.username}
|
||||||
</span>
|
</span>
|
||||||
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
||||||
{user?.role === 'admin' ? 'Админ' : user?.role === 'support' ? 'Поддержка' : 'Пользователь'}
|
{getRoleName(user?.role)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
||||||
@@ -327,7 +340,7 @@ function App() {
|
|||||||
{user?.username}
|
{user?.username}
|
||||||
</span>
|
</span>
|
||||||
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
<span className={`ml-2 text-xs px-2 py-0.5 rounded ${currentTheme.accent} text-white`}>
|
||||||
{user?.role === 'admin' ? 'Админ' : user?.role === 'support' ? 'Поддержка' : 'Пользователь'}
|
{getRoleName(user?.role)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
<ThemeSelector currentTheme={theme} onThemeChange={handleThemeChange} />
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ export default function Profile({ token, user, theme, onUsernameChange }) {
|
|||||||
return 'Администратор';
|
return 'Администратор';
|
||||||
case 'support':
|
case 'support':
|
||||||
return 'Тех. поддержка';
|
return 'Тех. поддержка';
|
||||||
|
case 'banned':
|
||||||
|
return 'Забанен';
|
||||||
default:
|
default:
|
||||||
return 'Пользователь';
|
return 'Пользователь';
|
||||||
}
|
}
|
||||||
@@ -132,6 +134,8 @@ export default function Profile({ token, user, theme, onUsernameChange }) {
|
|||||||
return 'bg-blue-500/20 text-blue-500 border-blue-500/50';
|
return 'bg-blue-500/20 text-blue-500 border-blue-500/50';
|
||||||
case 'support':
|
case 'support':
|
||||||
return 'bg-purple-500/20 text-purple-500 border-purple-500/50';
|
return 'bg-purple-500/20 text-purple-500 border-purple-500/50';
|
||||||
|
case 'banned':
|
||||||
|
return 'bg-red-500/20 text-red-500 border-red-500/50';
|
||||||
default:
|
default:
|
||||||
return 'bg-gray-500/20 text-gray-500 border-gray-500/50';
|
return 'bg-gray-500/20 text-gray-500 border-gray-500/50';
|
||||||
}
|
}
|
||||||
@@ -280,6 +284,7 @@ export default function Profile({ token, user, theme, onUsernameChange }) {
|
|||||||
{stats?.role === 'admin' && 'Полный доступ ко всем функциям панели'}
|
{stats?.role === 'admin' && 'Полный доступ ко всем функциям панели'}
|
||||||
{stats?.role === 'support' && 'Доступ к системе тикетов и поддержке'}
|
{stats?.role === 'support' && 'Доступ к системе тикетов и поддержке'}
|
||||||
{stats?.role === 'user' && 'Доступ к своим серверам и тикетам'}
|
{stats?.role === 'user' && 'Доступ к своим серверам и тикетам'}
|
||||||
|
{stats?.role === 'banned' && '⛔ Аккаунт заблокирован, доступ запрещён'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function Users({ token }) {
|
|||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`p-2 rounded ${
|
<div className={`p-2 rounded ${
|
||||||
user.role === 'admin' ? 'bg-blue-600' : user.role === 'support' ? 'bg-purple-600' : 'bg-gray-700'
|
user.role === 'admin' ? 'bg-blue-600' : user.role === 'support' ? 'bg-purple-600' : user.role === 'banned' ? 'bg-red-600' : 'bg-gray-700'
|
||||||
}`}>
|
}`}>
|
||||||
{user.role === 'admin' ? (
|
{user.role === 'admin' ? (
|
||||||
<Shield className="w-6 h-6" />
|
<Shield className="w-6 h-6" />
|
||||||
@@ -113,7 +113,7 @@ export default function Users({ token }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold">{user.username}</h3>
|
<h3 className="text-lg font-semibold">{user.username}</h3>
|
||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
{user.role === 'admin' ? 'Администратор' : user.role === 'support' ? 'Тех. поддержка' : 'Пользователь'}
|
{user.role === 'admin' ? 'Администратор' : user.role === 'support' ? 'Тех. поддержка' : user.role === 'banned' ? 'Забанен' : 'Пользователь'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,6 +127,7 @@ export default function Users({ token }) {
|
|||||||
<option value="user">Пользователь</option>
|
<option value="user">Пользователь</option>
|
||||||
<option value="support">Тех. поддержка</option>
|
<option value="support">Тех. поддержка</option>
|
||||||
<option value="admin">Администратор</option>
|
<option value="admin">Администратор</option>
|
||||||
|
<option value="banned">Забанен</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteUser(user.username)}
|
onClick={() => deleteUser(user.username)}
|
||||||
@@ -178,6 +179,12 @@ export default function Users({ token }) {
|
|||||||
Тех. поддержка имеет доступ к системе тикетов
|
Тех. поддержка имеет доступ к системе тикетов
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{user.role === 'banned' && (
|
||||||
|
<p className="text-sm text-red-400">
|
||||||
|
⛔ Пользователь заблокирован и не имеет доступа к панели
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user