Files
classquiz-ai/classquiz/routers/users.py
T
2022-04-07 20:52:01 +02:00

145 lines
6.3 KiB
Python

import os
from email_validator import validate_email, EmailNotValidError
from fastapi import APIRouter, Response
from fastapi.background import BackgroundTasks
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import OAuth2PasswordRequestForm
from classquiz.auth import *
from classquiz.config import redis
from classquiz.cache import clear_cache_for_account
from classquiz.db.models import *
from classquiz.emails import send_register_email
router = APIRouter()
route_user = User.get_pydantic(
exclude={"id": ..., "verified": ..., "verify_key": ..., "created_at": ..., "usersessions": ...})
@router.post("/create", response_model=User,
response_model_include={"id": ..., "verified": ..., "email": ...})
async def create_user(user: route_user, background_task: BackgroundTasks) -> User | JSONResponse:
user = User(**user.dict(), id=uuid.uuid4())
try:
validate_email(user.email)
except EmailNotValidError as e:
raise HTTPException(status_code=400, detail=str(e))
user.verify_key = str(os.urandom(16).hex())
res = await User.objects.filter((User.email == user.email) | (User.username == user.username)).all()
if len(res) != 0:
raise HTTPException(status_code=409, detail="User already exists")
user.password = get_password_hash(user.password)
if len(user.username) == 32:
return JSONResponse({"details": "Username mustn't be 32 characters long"}, 400)
await user.save()
background_task.add_task(send_register_email, email=user.email)
await redis.delete("global_user_count")
return user
@router.post("/token/cookie", response_model=Token)
async def login_for_cookie_access_token(request: Request, response: Response,
form_data: OAuth2PasswordRequestForm = Depends()):
user = await authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
)
session_key = os.urandom(32).hex()
user_session = UserSession(user=user, session_key=session_key, ip_address=request.client.host,
user_agent=request.headers.get("User-Agent"), id=uuid.uuid4())
await user_session.save()
# await user_session.save()
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
access_token = create_access_token(
data={"sub": user.email}, expires_delta=access_token_expires
)
await redis.set(access_token, user.email, ex=settings.access_token_expire_minutes * 60)
response.set_cookie(key="access_token", value=f"Bearer {access_token}",
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="rememberme", value="")
response.set_cookie(key="rememberme_token", value=session_key, httponly=True, samesite='strict')
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/token/rememberme")
async def rememberme_token(request: Request, response: Response):
rememberme_cookie = request.cookies.get("rememberme_token")
if rememberme_cookie is None:
raise HTTPException(status_code=400, detail="No rememberme cookie")
user_session: UserSession | None = await UserSession.objects.filter(session_key=rememberme_cookie).select_related(
UserSession.user).get_or_none()
if (user_session is None) or (user_session.user is None):
raise HTTPException(status_code=401, detail="No user session")
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes * 60)
access_token = create_access_token(
data={"sub": user_session.user.email}, expires_delta=access_token_expires
)
response.set_cookie(key="access_token", value=f"Bearer {access_token}",
httponly=True, samesite='strict', max_age=settings.access_token_expire_minutes * 60)
response.set_cookie(key="expiry", value="", max_age=settings.access_token_expire_minutes * 60)
await user_session.update(last_seen=datetime.now())
@router.get("/logout")
async def logout(request: Request, response: Response):
remember_token = request.cookies.get("rememberme_token")
if remember_token is not None:
await UserSession.objects.filter(session_key=remember_token).delete()
response.delete_cookie("access_token")
response.delete_cookie("expiry")
response.delete_cookie("rememberme")
response.delete_cookie("rememberme_token")
response.status_code = 302
response.headers["Location"] = "/"
return response
@router.get("/check")
async def check_token(user: User = Depends(get_current_user)):
return {"email": user.email}
@router.get("/verify/{verify_key}")
async def verify_user(verify_key: str):
user = await User.objects.filter(verify_key=verify_key).get_or_none()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
user.verified = True
user.verify_key = None
await user.update()
return RedirectResponse(url="/account/login?verified=true")
@router.put("/password/update")
async def change_password(password_data: UpdatePassword, response: Response, user: User = Depends(get_current_user)):
if not verify_password(password_data.old_password, user.password):
raise HTTPException(status_code=400, detail="Incorrect password")
user.password = get_password_hash(password_data.new_password)
await user.update()
await clear_cache_for_account(user)
await UserSession.objects.filter(user=user).delete()
response.delete_cookie("access_token")
response.delete_cookie("expiry")
response.delete_cookie("rememberme")
response.delete_cookie("rememberme_token")
return {"message": "Password updated successfully"}
@router.delete("/signout-everywhere")
async def signout_everywhere(response: Response, user: User = Depends(get_current_user)):
await UserSession.objects.filter(user=user).delete()
await clear_cache_for_account(user)
response.delete_cookie("access_token")
response.delete_cookie("expiry")
response.delete_cookie("rememberme")
response.delete_cookie("rememberme_token")
return {"message": "Signout everywhere"}