Spaces:
Sleeping
Sleeping
sachin1801
help page revamp according to requirements, removed tutorial page, search filter improved on history, login with email pass created
068e060 | """Authentication service with password hashing and JWT sessions.""" | |
| from datetime import datetime, timedelta | |
| from typing import Optional | |
| import hashlib | |
| import hmac | |
| import base64 | |
| import secrets | |
| import json | |
| from webapp.app.config import settings | |
| # Try to import bcrypt and jose, fall back to simpler methods if not available | |
| try: | |
| import bcrypt | |
| BCRYPT_AVAILABLE = True | |
| except ImportError: | |
| BCRYPT_AVAILABLE = False | |
| try: | |
| from jose import jwt, JWTError | |
| JOSE_AVAILABLE = True | |
| except ImportError: | |
| JOSE_AVAILABLE = False | |
| # Secret key for JWT (in production, use environment variable) | |
| SECRET_KEY = getattr(settings, 'secret_key', 'splicing-predictor-secret-key-change-in-production') | |
| ALGORITHM = "HS256" | |
| ACCESS_TOKEN_EXPIRE_DAYS = 30 | |
| def hash_password(password: str) -> str: | |
| """Hash a password using bcrypt (or fallback).""" | |
| if BCRYPT_AVAILABLE: | |
| return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') | |
| else: | |
| # Fallback: SHA256 with salt (less secure, but works without dependencies) | |
| salt = secrets.token_hex(16) | |
| hash_obj = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt.encode('utf-8'), 100000) | |
| return f"pbkdf2:{salt}:{hash_obj.hex()}" | |
| def verify_password(plain_password: str, hashed_password: str) -> bool: | |
| """Verify a password against its hash.""" | |
| if BCRYPT_AVAILABLE and not hashed_password.startswith('pbkdf2:'): | |
| try: | |
| return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8')) | |
| except Exception: | |
| return False | |
| elif hashed_password.startswith('pbkdf2:'): | |
| # Fallback verification | |
| try: | |
| _, salt, expected_hash = hashed_password.split(':') | |
| hash_obj = hashlib.pbkdf2_hmac('sha256', plain_password.encode('utf-8'), salt.encode('utf-8'), 100000) | |
| return hmac.compare_digest(hash_obj.hex(), expected_hash) | |
| except Exception: | |
| return False | |
| return False | |
| def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: | |
| """Create a JWT access token.""" | |
| to_encode = data.copy() | |
| expire = datetime.utcnow() + (expires_delta or timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS)) | |
| to_encode.update({"exp": expire}) | |
| if JOSE_AVAILABLE: | |
| return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) | |
| else: | |
| # Fallback: Simple base64 encoded JSON with signature | |
| payload = json.dumps(to_encode, default=str) | |
| payload_b64 = base64.urlsafe_b64encode(payload.encode()).decode() | |
| signature = hmac.new(SECRET_KEY.encode(), payload_b64.encode(), hashlib.sha256).hexdigest() | |
| return f"{payload_b64}.{signature}" | |
| def decode_access_token(token: str) -> Optional[dict]: | |
| """Decode and verify a JWT access token.""" | |
| try: | |
| if JOSE_AVAILABLE: | |
| payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) | |
| return payload | |
| else: | |
| # Fallback: Verify simple token | |
| parts = token.split('.') | |
| if len(parts) != 2: | |
| return None | |
| payload_b64, signature = parts | |
| expected_sig = hmac.new(SECRET_KEY.encode(), payload_b64.encode(), hashlib.sha256).hexdigest() | |
| if not hmac.compare_digest(signature, expected_sig): | |
| return None | |
| payload = json.loads(base64.urlsafe_b64decode(payload_b64)) | |
| # Check expiration | |
| if 'exp' in payload: | |
| exp = datetime.fromisoformat(payload['exp']) if isinstance(payload['exp'], str) else datetime.utcfromtimestamp(payload['exp']) | |
| if exp < datetime.utcnow(): | |
| return None | |
| return payload | |
| except Exception: | |
| return None | |
| def generate_session_token() -> str: | |
| """Generate a secure session token.""" | |
| return secrets.token_urlsafe(32) | |