init
This commit is contained in:
0
backend/app/core/__init__.py
Normal file
0
backend/app/core/__init__.py
Normal file
36
backend/app/core/auth.py
Normal file
36
backend/app/core/auth.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.core.config import settings
|
||||
from backend.app.core.database import get_session
|
||||
from backend.app.core.security import decode_access_token
|
||||
from backend.app.models.rider import Rider
|
||||
|
||||
bearer_scheme = HTTPBearer()
|
||||
|
||||
|
||||
async def get_current_rider(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Rider:
|
||||
try:
|
||||
payload = decode_access_token(
|
||||
credentials.credentials,
|
||||
settings.JWT_SECRET_KEY,
|
||||
settings.JWT_ALGORITHM,
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
)
|
||||
|
||||
rider = await session.get(Rider, payload["sub"])
|
||||
if not rider:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Rider not found",
|
||||
)
|
||||
|
||||
return rider
|
||||
34
backend/app/core/config.py
Normal file
34
backend/app/core/config.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
|
||||
|
||||
# Database
|
||||
DATABASE_URL: str = "postgresql+asyncpg://velobrain:velobrain@localhost:5432/velobrain"
|
||||
|
||||
# Anthropic
|
||||
ANTHROPIC_API_KEY: str = ""
|
||||
|
||||
# Gemini
|
||||
GEMINI_API_KEY: str = ""
|
||||
GEMINI_MODEL: str = "gemini-2.5-pro"
|
||||
|
||||
# App
|
||||
APP_SECRET_KEY: str = "change-me-in-production"
|
||||
DEBUG: bool = True
|
||||
|
||||
# Auth / JWT
|
||||
JWT_SECRET_KEY: str = "change-me-jwt-secret"
|
||||
JWT_ALGORITHM: str = "HS256"
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440 # 24 hours
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN: str = ""
|
||||
TELEGRAM_BOT_USERNAME: str = ""
|
||||
|
||||
# Upload
|
||||
UPLOAD_DIR: str = "./uploads"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
17
backend/app/core/database.py
Normal file
17
backend/app/core/database.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from backend.app.core.config import settings
|
||||
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG)
|
||||
|
||||
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
async def get_session() -> AsyncSession:
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
71
backend/app/core/security.py
Normal file
71
backend/app/core/security.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from urllib.parse import parse_qs, unquote
|
||||
|
||||
import jwt
|
||||
|
||||
|
||||
def verify_telegram_login(data: dict, bot_token: str) -> bool:
|
||||
"""Verify data from Telegram Login Widget."""
|
||||
data = dict(data)
|
||||
check_hash = data.pop("hash", "")
|
||||
if not check_hash:
|
||||
return False
|
||||
|
||||
data_check_string = "\n".join(
|
||||
f"{k}={v}" for k, v in sorted(data.items())
|
||||
)
|
||||
secret_key = hashlib.sha256(bot_token.encode()).digest()
|
||||
computed = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
if int(data.get("auth_date", 0)) < time.time() - 86400:
|
||||
return False
|
||||
|
||||
return hmac.compare_digest(computed, check_hash)
|
||||
|
||||
|
||||
def verify_telegram_webapp(init_data: str, bot_token: str) -> dict | None:
|
||||
"""Verify Telegram WebApp initData and return parsed user dict."""
|
||||
parsed = parse_qs(init_data)
|
||||
data = {k: v[0] for k, v in parsed.items()}
|
||||
|
||||
check_hash = data.pop("hash", "")
|
||||
if not check_hash:
|
||||
return None
|
||||
|
||||
data_check_string = "\n".join(
|
||||
f"{k}={v}" for k, v in sorted(data.items())
|
||||
)
|
||||
secret_key = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
|
||||
computed = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
if not hmac.compare_digest(computed, check_hash):
|
||||
return None
|
||||
|
||||
user_raw = data.get("user")
|
||||
if not user_raw:
|
||||
return None
|
||||
|
||||
return json.loads(unquote(user_raw))
|
||||
|
||||
|
||||
def create_access_token(
|
||||
rider_id: str,
|
||||
telegram_id: int,
|
||||
secret: str,
|
||||
algorithm: str,
|
||||
expires_minutes: int,
|
||||
) -> str:
|
||||
payload = {
|
||||
"sub": rider_id,
|
||||
"tg_id": telegram_id,
|
||||
"exp": datetime.now(timezone.utc) + timedelta(minutes=expires_minutes),
|
||||
}
|
||||
return jwt.encode(payload, secret, algorithm=algorithm)
|
||||
|
||||
|
||||
def decode_access_token(token: str, secret: str, algorithm: str) -> dict:
|
||||
return jwt.decode(token, secret, algorithms=[algorithm])
|
||||
Reference in New Issue
Block a user