first commit

This commit is contained in:
debu-2 2026-10-02 11:22:40 +05:30
commit efa5080897
127 changed files with 18610 additions and 0 deletions

85
backend/app/auth.py Normal file
View file

@ -0,0 +1,85 @@
import uuid
from datetime import datetime, timedelta, timezone
import jwt
from fastapi import Cookie, Depends, HTTPException, status
from pwdlib import PasswordHash
from sqlalchemy import select
from sqlalchemy.orm import Session
from .config import settings
from .database import get_db
from .models import UserAccount
COOKIE_NAME = "urmi_session"
ADMIN_COOKIE_NAME = "urmi_admin_session"
password_hash = PasswordHash.recommended()
def normalize_email(email: str) -> str:
return email.strip().casefold()
def create_token(user_id: uuid.UUID, purpose: str = "session", minutes: int | None = None) -> str:
now = datetime.now(timezone.utc)
return jwt.encode(
{
"sub": str(user_id),
"purpose": purpose,
"iat": now,
"exp": now + timedelta(minutes=minutes or settings.access_token_minutes),
},
settings.auth_secret,
algorithm="HS256",
)
def decode_token(token: str, purpose: str = "session") -> dict:
try:
payload = jwt.decode(token, settings.auth_secret, algorithms=["HS256"])
if payload.get("purpose") != purpose:
raise ValueError("invalid token purpose")
return payload
except (jwt.PyJWTError, ValueError) as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session") from exc
def current_user(
urmi_session: str | None = Cookie(default=None, alias=COOKIE_NAME),
db: Session = Depends(get_db),
) -> UserAccount:
if not urmi_session:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required")
payload = decode_token(urmi_session)
try:
user_id = uuid.UUID(payload["sub"])
except (KeyError, ValueError) as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session") from exc
user = db.scalar(select(UserAccount).where(UserAccount.id == user_id))
if not user or user.status != "active":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Account unavailable")
return user
def create_admin_token(username: str) -> str:
now = datetime.now(timezone.utc)
return jwt.encode(
{
"sub": username,
"purpose": "admin_session",
"iat": now,
"exp": now + timedelta(minutes=settings.access_token_minutes),
},
settings.auth_secret,
algorithm="HS256",
)
def current_admin(urmi_admin_session: str | None = Cookie(default=None, alias=ADMIN_COOKIE_NAME)) -> str:
if not urmi_admin_session:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin authentication required")
payload = decode_token(urmi_admin_session, purpose="admin_session")
username = payload.get("sub")
if username != settings.admin_username:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid admin session")
return username