Adding a minimal backend API route that decrypts the web's JWE and returns the email

This commit is contained in:
Keith Stevens
2023-01-28 16:56:34 +09:00
parent 76ef08dfe4
commit 62d00d5bd4
3 changed files with 43 additions and 23 deletions
+32 -22
View File
@@ -1,39 +1,49 @@
import json
from typing import Union
import jwt
from fastapi import APIRouter, Depends, HTTPException, status
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from fastapi import APIRouter, Depends, Security
from fastapi.security import APIKeyCookie
from jose import jwe
from oasst_backend.config import settings
from pydantic import BaseModel
router = APIRouter()
oauth2_scheme = APIKeyCookie(name="next-auth.session-token")
SECRET_KEY = "O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98="
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = APIKeyCookie(name=settings.AUTH_COOKIE_NAME)
class TokenData(BaseModel):
sub: Union[str, None] = None
"""
A minimal re-creation of the web's token type. To be expanded later.
"""
email: Union[str, None] = None
async def get_current_user(token: str = Depends(oauth2_scheme)):
print("get_current_user")
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
async def get_current_user(token: str = Security(oauth2_scheme)):
"""
Decrypts the user's JSON Web Token using HKDF encryption and returns the
TokenData.
"""
# We first generate a key from the auth secret.
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=settings.AUTH_LENGTH,
salt=settings.AUTH_SALT,
info=settings.AUTH_INFO,
)
print(token)
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_signature": False})
print(payload)
sub: str = payload.get("sub")
if sub is None:
raise credentials_exception
return TokenData(sub=sub)
key = hkdf.derive(settings.AUTH_SECRET)
# Next we decrypt the JWE token.
payload = jwe.decrypt(token, key)
# Finally we have the real token JSON payload and can do whatever we want.
content = json.loads(payload)
email = content["email"]
return TokenData(email=email)
@router.get("/check", response_model=str)
async def auth_check(token_data: TokenData = Depends(get_current_user)):
return token_data.sub
"""Returns the user's email if it can be decrypted."""
return token_data.email
+9
View File
@@ -63,6 +63,15 @@ class Settings(BaseSettings):
API_V1_STR: str = "/api/v1"
OFFICIAL_WEB_API_KEY: str = "1234"
# Encryption fields for handling the web generated JSON Web Tokens.
# These fields need to be shared with the web's auth settings in order to
# correctly decrypt the web tokens.
AUTH_INFO: bytes = b"NextAuth.js Generated Encryption Key"
AUTH_SALT: bytes = b""
AUTH_LENGTH: int = 32
AUTH_SECRET: bytes = b"O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98="
AUTH_COOKIE_NAME: str = "next-auth.session-token"
POSTGRES_HOST: str = "localhost"
POSTGRES_PORT: str = "5432"
POSTGRES_USER: str = "postgres"
+2 -1
View File
@@ -1,4 +1,5 @@
alembic==1.8.1
cryptography==39.0.0
fastapi==0.88.0
fastapi-limiter==0.1.5
fastapi-utils==0.2.1
@@ -6,8 +7,8 @@ loguru==0.6.0
numpy==1.22.4
psycopg2-binary==2.9.5
pydantic==1.9.1
pyjwt
python-dotenv==0.21.0
python-jose[cryptography]==3.3.0
redis
scipy==1.8.1
SQLAlchemy==1.4.41