- Node.js: 22 → 24 (Active LTS) - PostgreSQL: 15 → 18 - FastAPI: 0.115.6 → 0.128.2 - Uvicorn: 0.34.0 → 0.40.0 - SQLAlchemy: 2.0.36 → 2.0.46 - Alembic: 1.14.0 → 1.18.3 - Pydantic: 2.10.4 → 2.12.5 - pandas: 2.2.3 → 2.3.3 - pykrx: 1.0.45 → 1.2.3 - React: 19.2.3 → 19.2.4 Breaking changes: - Migrate from python-jose to PyJWT for JWT handling - numpy downgraded to 1.26.4 for pykrx compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
53 lines
1.4 KiB
Python
53 lines
1.4 KiB
Python
"""
|
|
Security utilities for authentication.
|
|
"""
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Optional
|
|
|
|
import jwt
|
|
from jwt.exceptions import PyJWTError
|
|
from passlib.context import CryptContext
|
|
|
|
from app.core.config import get_settings
|
|
|
|
settings = get_settings()
|
|
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify a password against its hash."""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
|
|
def get_password_hash(password: str) -> str:
|
|
"""Hash a password."""
|
|
return pwd_context.hash(password)
|
|
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
"""Create a JWT access token."""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.now(timezone.utc) + expires_delta
|
|
else:
|
|
expire = datetime.now(timezone.utc) + timedelta(
|
|
minutes=settings.access_token_expire_minutes
|
|
)
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(
|
|
to_encode, settings.jwt_secret, algorithm=settings.jwt_algorithm
|
|
)
|
|
return encoded_jwt
|
|
|
|
|
|
def decode_access_token(token: str) -> Optional[dict]:
|
|
"""Decode and verify a JWT access token."""
|
|
try:
|
|
payload = jwt.decode(
|
|
token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]
|
|
)
|
|
return payload
|
|
except PyJWTError:
|
|
return None
|