speechify-clone/auth.py
ifedan-ed fb8e1340f1 Add auth, Nextcloud, voice lock, saved docs, progress saving, and all new TTS voices
- Auth system: JWT login/register with Cloudflare Turnstile, admin + user roles
- Admin panel: user management (role/active toggle, delete), Nextcloud config
- 6 TTS models: OpenAI TTS / TTS-HD, ElevenLabs, Gemini 2.5 Flash/Pro
- Voice lock: prevent model/voice changes while audio is playing
- Nextcloud browser: PROPFIND WebDAV, folder navigation, PDF loading
- Saved documents: save/load with PUT/POST, progress auto-saves every 2s
- Resume playback: remembers sentence position per user per document
- Better sentence highlighting with glow effect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 03:23:18 +02:00

58 lines
1.8 KiB
Python

from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, HTTPException, status, Cookie
from sqlalchemy.orm import Session
from models import get_db, User
SECRET_KEY = "speechify-jwt-secret-2025-change-in-prod"
ALGORITHM = "HS256"
TOKEN_EXPIRE_HOURS = 48
pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd.verify(plain, hashed)
def create_token(user_id: int, username: str, role: str) -> str:
expire = datetime.utcnow() + timedelta(hours=TOKEN_EXPIRE_HOURS)
return jwt.encode(
{"sub": str(user_id), "username": username, "role": role, "exp": expire},
SECRET_KEY,
algorithm=ALGORITHM,
)
def decode_token(token: str) -> Optional[dict]:
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTError:
return None
def get_current_user(
token: Optional[str] = Cookie(None, alias="auth_token"),
db: Session = Depends(get_db),
) -> User:
if not token:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
payload = decode_token(token)
if not payload:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.query(User).filter(User.id == int(payload["sub"])).first()
if not user or not user.is_active:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return user
def require_admin(user: User = Depends(get_current_user)) -> User:
if user.role != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin required")
return user