galaxis-po/backend/app/core/security.py
zephyrdark 1dae2945c3
All checks were successful
Deploy to Production / deploy (push) Successful in 1m31s
feat: client-side password hashing and admin user auto-seeding
- Hash passwords with SHA-256 on frontend before transmission to prevent
  raw password exposure in network traffic and server logs
- Switch login endpoint from OAuth2 form-data to JSON body
- Auto-create admin user on startup from ADMIN_USERNAME/ADMIN_PASSWORD
  env vars, solving login failure after registration was disabled
- Update auth tests to match new SHA-256 + JSON login flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:21:36 +09:00

59 lines
1.7 KiB
Python

"""
Security utilities for authentication.
"""
import hashlib
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 sha256_hash(password: str) -> str:
"""SHA-256 hash a raw password (matches client-side hashing)."""
return hashlib.sha256(password.encode()).hexdigest()
def verify_password(sha256_password: str, hashed_password: str) -> bool:
"""Verify a SHA-256 hashed password against its bcrypt hash."""
return pwd_context.verify(sha256_password, hashed_password)
def get_password_hash(raw_password: str) -> str:
"""Hash a raw password: SHA-256 then bcrypt."""
return pwd_context.hash(sha256_hash(raw_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