Coverage for app / core / security.py: 100%
31 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
1import secrets
2import hashlib
3from datetime import datetime, timedelta, timezone
4from typing import Optional
6from jose import jwt
7from passlib.context import CryptContext
9from app.core.config import settings
11pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
14def create_api_key() -> str:
15 """Generate a new API key."""
16 return secrets.token_urlsafe(32)
19def hash_api_key(api_key: str) -> str:
20 """Hash an API key for storage."""
21 salted = f"{settings.api_key_salt}{api_key}"
22 return hashlib.sha256(salted.encode()).hexdigest()
25def verify_api_key(plain_key: str, hashed_key: str) -> bool:
26 """Verify an API key against its hash."""
27 return hash_api_key(plain_key) == hashed_key
30def create_magic_link_token() -> str:
31 """Generate a magic link token."""
32 return secrets.token_urlsafe(32)
35def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
36 """Create a JWT access token."""
37 to_encode = data.copy()
38 if expires_delta:
39 expire = datetime.now(timezone.utc) + expires_delta
40 else:
41 expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)
42 to_encode.update({"exp": expire})
43 encoded_jwt = jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
44 return encoded_jwt
47def decode_access_token(token: str) -> Optional[dict]:
48 """Decode and verify a JWT access token."""
49 try:
50 payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
51 return payload
52 except jwt.JWTError:
53 return None