PlainSQL / backend /app /auth /jwt_auth.py
LalitChaudhari3's picture
feat: synchronize text-to-sql-bot codebase with Hugging Face Space repository, including Docker build configurations
6086e71
Raw
History Blame Contribute Delete
3.57 kB
"""
JWT Authentication Service β€” Token creation, verification, and password hashing.
Production-grade auth with bcrypt password hashing and configurable expiry.
"""
import bcrypt
import structlog
from datetime import datetime, timedelta, timezone
from typing import Optional
from jose import jwt, JWTError
logger = structlog.get_logger()
class AuthService:
"""
JWT-based authentication service.
Handles token creation, verification, and password hashing.
"""
def __init__(self, secret_key: str, algorithm: str = "HS256", expiry_hours: int = 8):
self.secret_key = secret_key
self.algorithm = algorithm
self.expiry_hours = expiry_hours
# ── Password Hashing ─────────────────────────────────
@staticmethod
def hash_password(password: str) -> str:
"""Hash a plaintext password using bcrypt."""
pwd_bytes = password.encode("utf-8")
salt = bcrypt.gensalt()
return bcrypt.hashpw(pwd_bytes, salt).decode("utf-8")
@staticmethod
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a plaintext password against a bcrypt hash."""
return bcrypt.checkpw(
plain_password.encode("utf-8"),
hashed_password.encode("utf-8"),
)
# ── Token Management ─────────────────────────────────
def create_access_token(
self,
user_id: str,
username: str,
role: str,
tenant_id: str = "default",
extra_claims: Optional[dict] = None,
) -> str:
"""
Create a JWT access token.
Payload includes:
- sub: user_id
- username: display name
- role: admin | analyst | viewer
- tenant_id: multi-tenant isolation
- exp: expiration timestamp
- iat: issued-at timestamp
"""
now = datetime.now(timezone.utc)
payload = {
"sub": user_id,
"username": username,
"role": role,
"tenant_id": tenant_id,
"exp": now + timedelta(hours=self.expiry_hours),
"iat": now,
"type": "access",
}
if extra_claims:
payload.update(extra_claims)
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
logger.info("token_created", user_id=user_id, role=role, tenant_id=tenant_id)
return token
def verify_token(self, token: str) -> dict:
"""
Verify and decode a JWT token.
Raises JWTError if invalid or expired.
"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return payload
except JWTError as e:
logger.warning("token_verification_failed", error=str(e))
raise
def create_api_key_token(self, key_name: str, tenant_id: str, role: str = "analyst") -> str:
"""
Create a long-lived API key token (90 days).
Used for programmatic access.
"""
now = datetime.now(timezone.utc)
payload = {
"sub": f"apikey:{key_name}",
"username": key_name,
"role": role,
"tenant_id": tenant_id,
"exp": now + timedelta(days=90),
"iat": now,
"type": "api_key",
}
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)