diff options
| author | stuppie | 2026-08-31 16:52:15 -0600 |
|---|---|---|
| committer | stuppie | 2026-08-31 16:52:15 -0600 |
| commit | 3f8d178d38686c1a5d71d94ebccdb61701469e95 (patch) | |
| tree | 435707789cc0d1eb3c8670e4ab4bbae11d773285 /jb/api/magic_token.py | |
| parent | 12f6fee851b68e86af658dfa17e4a0daed457dd1 (diff) | |
| download | amt-jb-3f8d178d38686c1a5d71d94ebccdb61701469e95.tar.gz amt-jb-3f8d178d38686c1a5d71d94ebccdb61701469e95.zip | |
working on magic token and session token auth
Diffstat (limited to 'jb/api/magic_token.py')
| -rw-r--r-- | jb/api/magic_token.py | 41 |
1 files changed, 41 insertions, 0 deletions
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 |
