From dca3cf6f8419250d03f8fbf8a648aaa22b2f20e6 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Thu, 22 Aug 2024 16:47:52 +0800 Subject: [PATCH 1/2] fix: authorization --- server/auth/get_user_info.py | 25 ++++----- server/requirements.txt | 2 + server/routers/auth.py | 106 +++++++++++++++++------------------ 3 files changed, 64 insertions(+), 69 deletions(-) diff --git a/server/auth/get_user_info.py b/server/auth/get_user_info.py index 7198ad98..0fc1687a 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,5 +1,4 @@ -from typing import Annotated -from fastapi import Cookie +from fastapi import Request import httpx import secrets import random @@ -49,11 +48,12 @@ async def generateAnonymousUser(clientId: str): seed = clientId[:4] random_name = f"{seed}_{random_str(4)}" data = { + "anonymous": True, "id": token, + "sub": token, "nickname": random_name, "name": random_name, "picture": f"https://picsum.photos/seed/{seed}/100/100", - "sub": seed, "sid": secrets.token_urlsafe(32) } @@ -64,25 +64,24 @@ async def getAnonymousUserInfoByToken(token: str): rows = supabase.table("profiles").select("*").eq("id", token).execute() return rows.data[0] if (len(rows.data) > 0) else None -async def get_user_id(petercat_user_token: Annotated[str | None, Cookie()] = None): +async def get_user_id(request: Request): + user_info = request.session.get('user') try: - if petercat_user_token is None: + if user_info is None: return None - user_info = await getUserInfoByToken(petercat_user_token) - return user_info['id'] + return user_info['sub'] except Exception: return None -async def get_user_access_token(petercat_user_token: Annotated[str | None, Cookie()] = None): +async def get_user_access_token(request: Request): try: - if petercat_user_token is None: - return None - user_info = await getUserInfoByToken(petercat_user_token) + user_info = request.session.get('user') if user_info is None: return None - access_token = await getUserAccessToken(user_id=user_info['id']) - print(f"get_user_access_token: user_info={user_info}, access_token={access_token}") + + access_token = await getUserAccessToken(user_id=user_info['sub']) return access_token except Exception: return None + diff --git a/server/requirements.txt b/server/requirements.txt index a8ccc8dd..27df0de5 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -12,6 +12,8 @@ python-multipart httpx[socks] load_dotenv supabase +starlette==0.13.6 +Authlib==0.14.3 boto3>=1.34.84 PyJWT pydantic>=2.7.0 diff --git a/server/routers/auth.py b/server/routers/auth.py index 851b4949..d8e117ee 100644 --- a/server/routers/auth.py +++ b/server/routers/auth.py @@ -1,10 +1,11 @@ -from typing import Annotated -from fastapi import APIRouter, Cookie, Request, HTTPException, status, Response +from fastapi import APIRouter, Request, HTTPException, status from fastapi.responses import RedirectResponse -import httpx +import secrets from petercat_utils import get_client, get_env_variable +from starlette.config import Config +from authlib.integrations.starlette_client import OAuth -from auth.get_user_info import generateAnonymousUser, getAnonymousUserInfoByToken, getUserInfoByToken +from auth.get_user_info import generateAnonymousUser AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") @@ -18,6 +19,19 @@ WEB_URL = get_env_variable("WEB_URL") +config = Config(environ={ + "AUTH0_CLIENT_ID": CLIENT_ID, + "AUTH0_CLIENT_SECRET": CLIENT_SECRET, +}) + +oauth = OAuth(config) +oauth.register( + name="auth0", + server_metadata_url=f'https://{AUTH0_DOMAIN}/.well-known/openid-configuration', + client_kwargs={ + 'scope': 'openid email profile' + } +) router = APIRouter( prefix="/api/auth", @@ -25,26 +39,7 @@ responses={404: {"description": "Not found"}}, ) -async def getTokenByCode(code): - token_url = f"https://{AUTH0_DOMAIN}/oauth/token" - headers = {"content-type": "application/x-www-form-urlencoded"} - data = { - "grant_type": "authorization_code", - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "code": code, - "redirect_uri": CALLBACK_URL, - } - async with httpx.AsyncClient() as client: - response = await client.post(token_url, headers=headers, data=data) - token_response = response.json() - print(f"token_response={token_response}") - - if "access_token" not in token_response: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Failed to get access token") - return token_response['access_token'] - -async def getAnonymousUser(request: Request, response: Response): +async def getAnonymousUser(request: Request): clientId = request.query_params.get("clientId") if not clientId: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing clientId") @@ -52,43 +47,42 @@ async def getAnonymousUser(request: Request, response: Response): supabase = get_client() supabase.table("profiles").upsert(data).execute() - response.set_cookie(key="petercat_user_token", value=token, httponly=True, secure=True, samesite='Lax') - return { "data": data, "status": 200} + request.session['user'] = data + return data @router.get("/login") -def login(): - redirect_uri = f"https://{AUTH0_DOMAIN}/authorize?audience={API_AUDIENCE}&response_type=code&client_id={CLIENT_ID}&redirect_uri={CALLBACK_URL}&scope=openid%20profile%20email%20read%3Ausers%20read%3Auser_idp_tokens&state=STATE" - return RedirectResponse(redirect_uri) +async def login(request: Request): + return await oauth.auth0.authorize_redirect(request, redirect_uri=CALLBACK_URL) + +@router.get('/logout') +async def logout(request: Request): + request.session.pop('user', None) + return RedirectResponse(url='/') @router.get("/callback") -async def callback(request: Request, response: Response): +async def callback(request: Request): print(f"auth_callback: {request.query_params}") - code = request.query_params.get("code") - if not code: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization code") - token = await getTokenByCode(code) - if not token: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization token") - data = await getUserInfoByToken(token) - if data is None: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization token") - supabase = get_client() - supabase.table("profiles").upsert(data).execute() - print(f"auth_callback: {data}, token={token}") - response = RedirectResponse(url=f'{WEB_URL}', status_code=302) - response.set_cookie(key="petercat_user_token", value=token, httponly=True, secure=False, samesite='Lax') - - return response + auth0_token = await oauth.auth0.authorize_access_token(request) + user_info = auth0_token.get('userinfo') + if user_info: + request.session['user'] = dict(user_info) + data = { + "id": user_info["sub"], + "nickname": user_info.get("nickname"), + "name": user_info.get("name"), + "picture": user_info.get("picture"), + "sub": user_info["sub"], + "sid": secrets.token_urlsafe(32) + } + supabase = get_client() + supabase.table("profiles").upsert(data).execute() + return RedirectResponse(url=f'{WEB_URL}', status_code=302) @router.get("/userinfo") -async def userinfo(request: Request, response: Response, petercat_user_token: Annotated[str | None, Cookie()] = None): - print(f"petercat_user_token: {petercat_user_token}") - if not petercat_user_token: - return await getAnonymousUser(request, response) - data = await getAnonymousUserInfoByToken(petercat_user_token) if petercat_user_token.startswith("client|") else await getUserInfoByToken(petercat_user_token) - if data is None: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Failed to get access token") - if data : +async def userinfo(request: Request): + user = request.session.get('user') + + if not user: + data = await getAnonymousUser(request) return { "data": data, "status": 200} - else: - return RedirectResponse(url=LOGIN_URL, status_code=303) + return { "data": user, "status": 200} From 6f1ce2f20b0a03462d3454bb6ca762d1f57f27fa Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Thu, 22 Aug 2024 16:50:50 +0800 Subject: [PATCH 2/2] fix: authorization --- server/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/requirements.txt b/server/requirements.txt index 27df0de5..9f00d685 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -12,8 +12,7 @@ python-multipart httpx[socks] load_dotenv supabase -starlette==0.13.6 -Authlib==0.14.3 +authlib==0.14.3 boto3>=1.34.84 PyJWT pydantic>=2.7.0