import hashlib import secrets from fastapi import HTTPException, status from jb.decorators import REDIS MAGIC_TOKEN_PREFIX = "auth:magic:" MAGIC_TOKEN_TTL: int = 5 * 60 # 5 minutes, in seconds def redis_token_key(token: str) -> str: # Redis never contains a usable credential, even if its keys are exposed. digest = hashlib.sha256(token.encode("utf-8")).hexdigest() return f"{MAGIC_TOKEN_PREFIX}{digest}" def create_magic_token(user_email: str) -> str: """Create a short-lived, single-use token for a user. The raw token can then be sent by email. """ if not user_email or not user_email.strip(): raise ValueError("user_email must not be empty") token = secrets.token_urlsafe(32) REDIS.set( redis_token_key(token), user_email, ex=MAGIC_TOKEN_TTL, ) return token def consume_magic_token(token: str) -> str: user_email = REDIS.getdel(redis_token_key(token)) if user_email is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired magic token", ) return user_email