From 3f8d178d38686c1a5d71d94ebccdb61701469e95 Mon Sep 17 00:00:00 2001 From: stuppie Date: Mon, 31 Aug 2026 16:52:15 -0600 Subject: working on magic token and session token auth --- jb/api/magic_token.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 jb/api/magic_token.py (limited to 'jb/api/magic_token.py') diff --git a/jb/api/magic_token.py b/jb/api/magic_token.py new file mode 100644 index 0000000..136e1b4 --- /dev/null +++ b/jb/api/magic_token.py @@ -0,0 +1,41 @@ +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 -- cgit v1.2.3