Download auth.py from MoBkr/dosemate: direct link, hf CLI and curl.
- Browser
- Download file 2.71 kB
-
https://huggingface.co/spaces/MoBkr/dosemate/resolve/main/auth.py
- Command line
-
hf download hf://spaces/MoBkr/dosemate/auth.py
-
curl -L -o auth.py https://huggingface.co/spaces/MoBkr/dosemate/resolve/main/auth.py
2.71 kB
| """Authentication utilities for the medication-adherence layer. | |
| Self-contained and additive: the existing chat endpoints never import this, | |
| so they stay fully public and unchanged. | |
| """ | |
| from datetime import datetime, timedelta | |
| from typing import Optional | |
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import OAuth2PasswordBearer | |
| from jose import JWTError, jwt | |
| import bcrypt | |
| from sqlalchemy.orm import Session | |
| import config | |
| from database import get_db, User | |
| # tokenUrl is only used by Swagger's "Authorize" button. | |
| oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login", auto_error=False) | |
| def _to_bytes(password: str) -> bytes: | |
| # bcrypt only considers the first 72 bytes; truncate explicitly to avoid errors. | |
| return password.encode("utf-8")[:72] | |
| def hash_password(password: str) -> str: | |
| return bcrypt.hashpw(_to_bytes(password), bcrypt.gensalt()).decode("utf-8") | |
| def verify_password(plain: str, hashed: str) -> bool: | |
| try: | |
| return bcrypt.checkpw(_to_bytes(plain), hashed.encode("utf-8")) | |
| except Exception: | |
| return False | |
| def create_access_token(user_id: int) -> str: | |
| expire = datetime.utcnow() + timedelta(minutes=config.JWT_EXPIRE_MIN) | |
| payload = {"sub": str(user_id), "exp": expire} | |
| return jwt.encode(payload, config.JWT_SECRET, algorithm=config.JWT_ALG) | |
| def decode_token(token: str) -> Optional[int]: | |
| try: | |
| payload = jwt.decode(token, config.JWT_SECRET, algorithms=[config.JWT_ALG]) | |
| sub = payload.get("sub") | |
| return int(sub) if sub is not None else None | |
| except (JWTError, ValueError): | |
| return None | |
| _credentials_exc = HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Not authenticated", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| def get_current_user( | |
| token: Optional[str] = Depends(oauth2_scheme), | |
| db: Session = Depends(get_db), | |
| ) -> User: | |
| """FastAPI dependency: resolves the Bearer token to a User or 401s.""" | |
| if not token: | |
| raise _credentials_exc | |
| user_id = decode_token(token) | |
| if user_id is None: | |
| raise _credentials_exc | |
| user = db.query(User).filter(User.id == user_id).first() | |
| if user is None: | |
| raise _credentials_exc | |
| return user | |
| def get_optional_user( | |
| token: Optional[str] = Depends(oauth2_scheme), | |
| db: Session = Depends(get_db), | |
| ) -> Optional[User]: | |
| """Like get_current_user but returns None instead of raising. | |
| Used by the /v2 conversation endpoints so chat works logged-in or out. | |
| """ | |
| if not token: | |
| return None | |
| user_id = decode_token(token) | |
| if user_id is None: | |
| return None | |
| return db.query(User).filter(User.id == user_id).first() | |