Add an admin account

This commit is contained in:
2025-08-25 11:20:04 +03:00
Unverified
parent cfaaad9b57
commit b8a7c20f96
5 changed files with 62 additions and 12 deletions
+1
View File
@@ -2,3 +2,4 @@ DATABASE_URL = "sqlite:///./data/database.db"
JWT_SECRET_KEY = "fromchat-jwt-secret" JWT_SECRET_KEY = "fromchat-jwt-secret"
JWT_ALGORITHM = "HS256" JWT_ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = 24 ACCESS_TOKEN_EXPIRE_HOURS = 24
OWNER_USERNAME = "denis0001-dev"
+50 -2
View File
@@ -3,6 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from routes.messaging import convert_message from routes.messaging import convert_message
from constants import OWNER_USERNAME
from dependencies import get_current_user, get_db from dependencies import get_current_user, get_db
from models import LoginRequest, RegisterRequest, User from models import LoginRequest, RegisterRequest, User
from utils import create_token, get_password_hash, verify_password from utils import create_token, get_password_hash, verify_password
@@ -16,14 +17,16 @@ def convert_user(user: User) -> dict:
"created_at": user.created_at.isoformat(), "created_at": user.created_at.isoformat(),
"last_seen": user.last_seen.isoformat(), "last_seen": user.last_seen.isoformat(),
"online": user.online, "online": user.online,
"username": user.username "username": user.username,
"admin": user.username == OWNER_USERNAME
} }
@router.get("/check_auth") @router.get("/check_auth")
def check_auth(current_user: User = Depends(get_current_user)): def check_auth(current_user: User = Depends(get_current_user)):
return { return {
"authenticated": True, "authenticated": True,
"username": current_user.username "username": current_user.username,
"admin": current_user.username == OWNER_USERNAME
} }
@@ -57,6 +60,16 @@ def register(request: RegisterRequest, db: Session = Depends(get_db)):
password = request.password.strip() password = request.password.strip()
confirm_password = request.confirm_password.strip() confirm_password = request.confirm_password.strip()
# Determine if owner already exists
owner_exists = db.query(User).filter(User.username == OWNER_USERNAME).first() is not None
# If owner not yet registered, only allow the owner to register
if not owner_exists and username != OWNER_USERNAME:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Регистрация временно закрыта до регистрации владельца"
)
# Validate input # Validate input
if not is_valid_username(username): if not is_valid_username(username):
raise HTTPException( raise HTTPException(
@@ -76,6 +89,13 @@ def register(request: RegisterRequest, db: Session = Depends(get_db)):
detail="Пароли не совпадают" detail="Пароли не совпадают"
) )
# After owner exists, disallow registering the reserved owner username via public registration
if owner_exists and username == OWNER_USERNAME:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Это имя пользователя зарезервировано"
)
existing_user = db.query(User).filter(User.username == username).first() existing_user = db.query(User).filter(User.username == username).first()
if existing_user: if existing_user:
raise HTTPException( raise HTTPException(
@@ -100,6 +120,34 @@ def register(request: RegisterRequest, db: Session = Depends(get_db)):
"message": "Регистрация прошла успешно Теперь вы можете войти." "message": "Регистрация прошла успешно Теперь вы можете войти."
} }
@router.delete("/admin/user/{user_id}")
def delete_user_as_owner(
user_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
# Only owner can delete users
if current_user.username != OWNER_USERNAME:
raise HTTPException(status_code=403, detail="Only owner can perform this action")
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Prevent deleting the owner account via API
if user.username == OWNER_USERNAME:
raise HTTPException(status_code=400, detail="Cannot delete owner account")
# Manually delete user's messages to satisfy FK constraints
from models import Message # local import to avoid circular
db.query(Message).filter(Message.user_id == user.id).delete()
db.delete(user)
db.commit()
return {"status": "success", "deleted_user_id": user_id}
@router.get("/logout") @router.get("/logout")
def logout( def logout(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
+3 -1
View File
@@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisco
from fastapi.security import HTTPAuthorizationCredentials from fastapi.security import HTTPAuthorizationCredentials
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from dependencies import get_current_user, get_db from dependencies import get_current_user, get_db
from constants import OWNER_USERNAME
from models import Message, SendMessageRequest, EditMessageRequest, ReplyMessageRequest, User from models import Message, SendMessageRequest, EditMessageRequest, ReplyMessageRequest, User
router = APIRouter() router = APIRouter()
@@ -105,7 +106,8 @@ async def delete_message(
if not message: if not message:
raise HTTPException(status_code=404, detail="Message not found") raise HTTPException(status_code=404, detail="Message not found")
if message.user_id != current_user.id: # Allow owner to delete any message
if current_user.username != OWNER_USERNAME and message.user_id != current_user.id:
raise HTTPException(status_code=403, detail="You can only delete your own messages") raise HTTPException(status_code=403, detail="You can only delete your own messages")
db.delete(message) db.delete(message)
+6 -8
View File
@@ -78,17 +78,15 @@ function bindEvents(): void {
export function show(message: Message, x: number, y: number): void { export function show(message: Message, x: number, y: number): void {
currentMessage = message; currentMessage = message;
// Only show edit and delete for own messages // Show delete for own messages and for owner on any message
const editItem = menu.querySelector('[data-action="edit"]') as HTMLElement; const editItem = menu.querySelector('[data-action="edit"]') as HTMLElement;
const deleteItem = menu.querySelector('[data-action="delete"]') as HTMLElement; const deleteItem = menu.querySelector('[data-action="delete"]') as HTMLElement;
if (message.username === currentUser?.username) { const isAuthor = message.username === currentUser?.username;
editItem.style.display = 'flex'; const isOwner = !!currentUser?.admin;
deleteItem.style.display = 'flex';
} else { editItem.style.display = isAuthor ? 'flex' : 'none';
editItem.style.display = 'none'; deleteItem.style.display = (isAuthor || isOwner) ? 'flex' : 'none';
deleteItem.style.display = 'none';
}
// Position the menu properly // Position the menu properly
menu.style.display = 'block'; menu.style.display = 'block';
+1
View File
@@ -81,6 +81,7 @@ export interface User {
last_seen: string; last_seen: string;
online: boolean; online: boolean;
username: string; username: string;
admin?: boolean;
bio?: string; bio?: string;
} }