From 6d2e401dceb5fa19b1f56fdb1f86da042be4c65a Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:05:18 +0800 Subject: [PATCH 1/4] feat: support locally auth --- server/.env.example | 6 +++ server/.env.local.example | 6 +++ server/auth/clients/__init__.py | 12 +++++ server/auth/clients/auth0.py | 92 +++++++++++++++++++++++++++++++++ server/auth/clients/base.py | 51 ++++++++++++++++++ server/auth/clients/local.py | 38 ++++++++++++++ server/auth/get_oauth_token.py | 22 -------- server/auth/get_user_info.py | 74 ++------------------------ server/auth/rate_limit.py | 14 ++--- server/auth/router.py | 90 +++++++------------------------- 10 files changed, 235 insertions(+), 170 deletions(-) create mode 100644 server/auth/clients/__init__.py create mode 100644 server/auth/clients/auth0.py create mode 100644 server/auth/clients/base.py create mode 100644 server/auth/clients/local.py delete mode 100644 server/auth/get_oauth_token.py diff --git a/server/.env.example b/server/.env.example index 3fd11573..09b1373b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -23,8 +23,14 @@ X_GITHUB_APP_ID=github_app_id X_GITHUB_APPS_CLIENT_ID=github_apps_client_id X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret +PETERCAT_AUTH0_ENABLED=False +# OPTIONAL - Local Authorization Configures + +PETERCAT_LOCAL_UID="petercat|001" +PETERCAT_LOCAL_UNAME="petercat" # OPTIONAL - AUTH0 Configures + API_IDENTIFIER=api_identifier AUTH0_DOMAIN=auth0_domain AUTH0_CLIENT_ID=auth0_client_id diff --git a/server/.env.local.example b/server/.env.local.example index 54648bc6..2e2460ab 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -23,6 +23,12 @@ X_GITHUB_APP_ID=github_app_id X_GITHUB_APPS_CLIENT_ID=github_apps_client_id X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret +# OPTIONAL - Local Authorization Configures +PETERCAT_LOCAL_UID="petercat|001" +PETERCAT_LOCAL_UNAME="petercat" + +# OPTIONAL - SKIP AUTH0 Authorization +PETERCAT_AUTH0_ENABLED=True # OPTIONAL - AUTH0 Configures API_IDENTIFIER=api_identifier diff --git a/server/auth/clients/__init__.py b/server/auth/clients/__init__.py new file mode 100644 index 00000000..a5ad37d3 --- /dev/null +++ b/server/auth/clients/__init__.py @@ -0,0 +1,12 @@ +from auth.clients.auth0 import Auth0Client +from auth.clients.base import BaseAuthClient +from auth.clients.local import LocalClient + +from petercat_utils import get_env_variable + +PETERCAT_AUTH0_ENABLED = get_env_variable("PETERCAT_AUTH0_ENABLED", "True") == "True" + +def get_auth_client() -> BaseAuthClient: + if PETERCAT_AUTH0_ENABLED: + return Auth0Client() + return LocalClient() diff --git a/server/auth/clients/auth0.py b/server/auth/clients/auth0.py new file mode 100644 index 00000000..ab329728 --- /dev/null +++ b/server/auth/clients/auth0.py @@ -0,0 +1,92 @@ +import httpx +import secrets + +from fastapi import Request +from auth.clients.base import BaseAuthClient +from petercat_utils import get_env_variable +from starlette.config import Config +from authlib.integrations.starlette_client import OAuth + +CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") +CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") +AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") +API_AUDIENCE = get_env_variable("API_IDENTIFIER") +API_URL = get_env_variable("API_URL") + +CALLBACK_URL = f"{API_URL}/api/auth/callback" + +config = Config( + environ={ + "AUTH0_CLIENT_ID": CLIENT_ID, + "AUTH0_CLIENT_SECRET": CLIENT_SECRET, + } +) + +class Auth0Client(BaseAuthClient): + _client: OAuth + + def __init__(self): + self._client = OAuth(config) + self._client.register( + name="auth0", + server_metadata_url=f"https://{AUTH0_DOMAIN}/.well-known/openid-configuration", + client_kwargs={"scope": "openid email profile"}, + ) + + async def login(self, request): + return await self._client.auth0.authorize_redirect( + request, redirect_uri=CALLBACK_URL + ) + + async def get_oauth_token(self): + url = f'https://{AUTH0_DOMAIN}/oauth/token' + headers = {"content-type": "application/x-www-form-urlencoded"} + data = { + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'audience': API_AUDIENCE, + 'grant_type': 'client_credentials' + } + async with httpx.AsyncClient() as client: + response = await client.post(url, data=data, headers=headers) + return response.json()['access_token'] + + async def get_user_info(self, request: Request) -> dict: + auth0_token = await self._client.auth0.authorize_access_token(request) + access_token = auth0_token["access_token"] + userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo" + headers = {"authorization": f"Bearer {access_token}"} + async with httpx.AsyncClient() as client: + user_info_response = await client.get(userinfo_url, headers=headers) + if user_info_response.status_code == 200: + user_info = user_info_response.json() + 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), + "agreement_accepted": user_info.get("agreement_accepted"), + } + return data + else: + return None + + async def get_access_token(self, user_id: str, provider="github"): + token = await self.get_oauth_token() + user_accesstoken_url = f"https://{AUTH0_DOMAIN}/api/v2/users/{user_id}" + + async with httpx.AsyncClient() as client: + headers = {"authorization": f"Bearer {token}"} + user_info_response = await client.get(user_accesstoken_url, headers=headers) + user = user_info_response.json() + identity = next( + ( + identity + for identity in user["identities"] + if identity["provider"] == provider + ), + None, + ) + return identity["access_token"] \ No newline at end of file diff --git a/server/auth/clients/base.py b/server/auth/clients/base.py new file mode 100644 index 00000000..9a34d0c5 --- /dev/null +++ b/server/auth/clients/base.py @@ -0,0 +1,51 @@ +import secrets + +from abc import abstractmethod +from fastapi import Request +from utils.random_str import random_str +from petercat_utils import get_client + + +class BaseAuthClient: + def __init__(self): + pass + + def generateAnonymousUser(self, clientId: str) -> tuple[str, dict]: + token = f"client|{clientId}" + seed = clientId[:4] + random_name = f"{seed}_{random_str(4)}" + data = { + "id": token, + "sub": token, + "nickname": random_name, + "name": random_name, + "picture": f"https://picsum.photos/seed/{seed}/100/100", + "sid": secrets.token_urlsafe(32), + "agreement_accepted": False, + } + + return token, data + + async def anonymouseLogin(self, request: Request) -> dict: + clientId = request.query_params.get("clientId") or random_str() + token, data = self.generateAnonymousUser(clientId = clientId) + supabase = get_client() + supabase.table("profiles").upsert(data).execute() + request.session["user"] = data + return data + + @abstractmethod + async def login(self, request: Request): + pass + + @abstractmethod + async def get_oauth_token(self) -> str: + pass + + @abstractmethod + async def get_user_info(self, request: Request) -> dict: + pass + + @abstractmethod + async def get_access_token(self, user_id: str, provider="github") -> str: + pass \ No newline at end of file diff --git a/server/auth/clients/local.py b/server/auth/clients/local.py new file mode 100644 index 00000000..f6b2a634 --- /dev/null +++ b/server/auth/clients/local.py @@ -0,0 +1,38 @@ +import secrets +from fastapi import Request +from fastapi.responses import RedirectResponse +from petercat_utils import get_client, get_env_variable +from auth.clients.base import BaseAuthClient + +PETERCAT_LOCAL_UID = get_env_variable("PETERCAT_LOCAL_UID") +PETERCAT_LOCAL_UNAME = get_env_variable("PETERCAT_LOCAL_UNAME") +WEB_URL = get_env_variable("WEB_URL") +WEB_LOGIN_SUCCESS_URL = f"{WEB_URL}/user/login" + +class LocalClient(BaseAuthClient): + def __init__(self): + pass + + async def login(self, request: Request): + data = await self.get_user_info() + supabase = get_client() + supabase.table("profiles").upsert(data).execute() + request.session["user"] = data + + return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) + + async def get_user_info(user_id): + token = PETERCAT_LOCAL_UID + username = PETERCAT_LOCAL_UNAME + seed = token[:4] + + return { + "id": token, + "sub": token, + "nickname": username, + "name": username, + "picture": f"https://picsum.photos/seed/{seed}/100/100", + "sid": secrets.token_urlsafe(32), + "agreement_accepted": False, + } + diff --git a/server/auth/get_oauth_token.py b/server/auth/get_oauth_token.py deleted file mode 100644 index 6e23c17b..00000000 --- a/server/auth/get_oauth_token.py +++ /dev/null @@ -1,22 +0,0 @@ -import httpx -from petercat_utils import get_env_variable - -AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") - -API_AUDIENCE = get_env_variable("API_IDENTIFIER") -CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") -CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") - -async def get_oauth_token(): - url = f'https://{AUTH0_DOMAIN}/oauth/token' - headers = {"content-type": "application/x-www-form-urlencoded"} - data = { - 'client_id': CLIENT_ID, - 'client_secret': CLIENT_SECRET, - 'audience': API_AUDIENCE, - 'grant_type': 'client_credentials' - } - async with httpx.AsyncClient() as client: - response = await client.post(url, data=data, headers=headers) - print(f"url={url}, response={response}") - return response.json()['access_token'] \ No newline at end of file diff --git a/server/auth/get_user_info.py b/server/auth/get_user_info.py index 7b587108..64fff60f 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,80 +1,14 @@ from fastapi import Request -import httpx -import secrets +from auth.clients import get_auth_client from core.models.user import User -from utils.random_str import random_str - -from .get_oauth_token import get_oauth_token -from petercat_utils import get_client, get_env_variable - +from petercat_utils import get_env_variable AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") - -async def getUserInfoByToken(token): - userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo" - headers = {"authorization": f"Bearer {token}"} - async with httpx.AsyncClient() as client: - user_info_response = await client.get(userinfo_url, headers=headers) - if user_info_response.status_code == 200: - user_info = user_info_response.json() - data = { - "id": user_info["sub"], - "nickname": user_info.get("nickname"), - "name": user_info.get("name"), - "picture": user_info.get("picture"), - "avatar": user_info.get("picture"), - "sub": user_info["sub"], - "sid": secrets.token_urlsafe(32), - "agreement_accepted": user_info.get("agreement_accepted"), - } - return data - else: - return None - - async def getUserAccessToken(user_id: str, provider="github"): - token = await get_oauth_token() - user_accesstoken_url = f"https://{AUTH0_DOMAIN}/api/v2/users/{user_id}" - - async with httpx.AsyncClient() as client: - headers = {"authorization": f"Bearer {token}"} - user_info_response = await client.get(user_accesstoken_url, headers=headers) - user = user_info_response.json() - identity = next( - ( - identity - for identity in user["identities"] - if identity["provider"] == provider - ), - None, - ) - return identity["access_token"] - - -async def generateAnonymousUser(clientId: str): - token = f"client|{clientId}" - seed = clientId[:4] - random_name = f"{seed}_{random_str(4)}" - data = { - "id": token, - "sub": token, - "nickname": random_name, - "name": random_name, - "picture": f"https://picsum.photos/seed/{seed}/100/100", - "sid": secrets.token_urlsafe(32), - "agreement_accepted": False, - } - - return token, data - - -async def getAnonymousUserInfoByToken(token: str): - supabase = get_client() - rows = supabase.table("profiles").select("*").eq("id", token).execute() - return rows.data[0] if (len(rows.data) > 0) else None - + auth_client = get_auth_client() + return await auth_client.get_access_token(user_id=user_id, provider=provider) async def get_user_id(request: Request): user_info = request.session.get("user") diff --git a/server/auth/rate_limit.py b/server/auth/rate_limit.py index cb820378..da57de95 100644 --- a/server/auth/rate_limit.py +++ b/server/auth/rate_limit.py @@ -1,22 +1,24 @@ -from typing import Annotated -from fastapi import Cookie, HTTPException +from typing import Optional +from fastapi import Depends, HTTPException from datetime import datetime, timedelta +from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient from petercat_utils import get_client, get_env_variable -from auth.get_user_info import getUserInfoByToken +from auth.get_user_info import get_user_id RATE_LIMIT_ENABLED = get_env_variable("RATE_LIMIT_ENABLED", "False") == 'True' RATE_LIMIT_REQUESTS = get_env_variable("RATE_LIMIT_REQUESTS") or 100 RATE_LIMIT_DURATION = timedelta(minutes=int(get_env_variable("RATE_LIMIT_DURATION") or 1)) -async def verify_rate_limit(petercat_user_token: Annotated[str | None, Cookie()] = None): +async def verify_rate_limit(user_id: Optional[str] = Depends(get_user_id), auth_client: BaseAuthClient = Depends(get_auth_client)): if not RATE_LIMIT_ENABLED: return - if not petercat_user_token: + if not user_id: raise HTTPException(status_code=403, detail="Must Login") - user = await getUserInfoByToken(petercat_user_token) + user = await auth_client.get_user_info(user_id) if user is None: raise HTTPException( diff --git a/server/auth/router.py b/server/auth/router.py index 240d9d40..af22dd78 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -1,47 +1,25 @@ -import secrets from typing import Annotated, Optional -from authlib.integrations.starlette_client import OAuth -from fastapi import APIRouter, Request, HTTPException, status, Depends +from fastapi import APIRouter, Request, HTTPException, Depends from fastapi.responses import RedirectResponse, JSONResponse from github import Github -from starlette.config import Config -from auth.get_user_info import generateAnonymousUser, getUserInfoByToken, get_user_id -from auth.get_user_info import ( - getUserAccessToken, -) + +from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient +from auth.get_user_info import get_user_id from core.dao.profilesDAO import ProfilesDAO from petercat_utils import get_client, get_env_variable -AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") - -API_AUDIENCE = get_env_variable("API_IDENTIFIER") -CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID") -CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET") - API_URL = get_env_variable("API_URL") +WEB_URL = get_env_variable("WEB_URL") + CALLBACK_URL = f"{API_URL}/api/auth/callback" LOGIN_URL = f"{API_URL}/api/auth/login" -WEB_URL = get_env_variable("WEB_URL") - WEB_LOGIN_SUCCESS_URL = f"{WEB_URL}/user/login" MARKET_URL = f"{WEB_URL}/market" -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", @@ -49,32 +27,9 @@ responses={404: {"description": "Not found"}}, ) - -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" - ) - token, data = await generateAnonymousUser(clientId) - - supabase = get_client() - supabase.table("profiles").upsert(data).execute() - request.session["user"] = data - return data - - @router.get("/login") -async def login(request: Request): - if CLIENT_ID is None: - return { - "message": "enviroments CLIENT_ID and CLIENT_SECRET required.", - } - redirect_response = await oauth.auth0.authorize_redirect( - request, redirect_uri=CALLBACK_URL - ) - return redirect_response - +async def login(request: Request, auth_client = Depends(get_auth_client)): + return await auth_client.login(request) @router.get("/logout") async def logout(request: Request): @@ -86,31 +41,21 @@ async def logout(request: Request): @router.get("/callback") -async def callback(request: Request): - auth0_token = await oauth.auth0.authorize_access_token(request) - user_info = await getUserInfoByToken(token=auth0_token["access_token"]) - +async def callback(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): + user_info = await auth_client.get_user_info(request) + print(f"user_info={user_info}") if 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), - "agreement_accepted": user_info.get("agreement_accepted"), - } - request.session["user"] = dict(data) + request.session["user"] = dict(user_info) supabase = get_client() - supabase.table("profiles").upsert(data).execute() + supabase.table("profiles").upsert(user_info).execute() return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) @router.get("/userinfo") -async def userinfo(request: Request): +async def userinfo(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): user = request.session.get("user") if not user: - data = await getAnonymousUser(request) + data = await auth_client.anonymouseLogin(request) return {"data": data, "status": 200} return {"data": user, "status": 200} @@ -155,7 +100,8 @@ async def get_user_repos(user_id: Optional[str] = Depends(get_user_id)): if not user_id: raise HTTPException(status_code=401, detail="User not found") try: - access_token = await getUserAccessToken(user_id=user_id) + client = get_auth_client() + access_token = await client.get_access_token(user_id=user_id) g = Github(access_token) user = g.get_user() repos = user.get_repos() From c4dbcb514e2530046a5dd88fe3594cb51829e084 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:09:22 +0800 Subject: [PATCH 2/4] feat: support locally auth --- server/auth/get_user_info.py | 11 ++++------- server/auth/router.py | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/server/auth/get_user_info.py b/server/auth/get_user_info.py index 64fff60f..168f6c07 100644 --- a/server/auth/get_user_info.py +++ b/server/auth/get_user_info.py @@ -1,15 +1,12 @@ -from fastapi import Request +from fastapi import Depends, Request from auth.clients import get_auth_client +from auth.clients.base import BaseAuthClient from core.models.user import User from petercat_utils import get_env_variable AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN") -async def getUserAccessToken(user_id: str, provider="github"): - auth_client = get_auth_client() - return await auth_client.get_access_token(user_id=user_id, provider=provider) - async def get_user_id(request: Request): user_info = request.session.get("user") try: @@ -21,7 +18,7 @@ async def get_user_id(request: Request): return None -async def get_user(request: Request) -> User | None: +async def get_user(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)) -> User | None: user_info = request.session.get("user") if user_info is None: return None @@ -29,5 +26,5 @@ async def get_user(request: Request) -> User | None: if user_info["sub"].startswith("client|"): return User(**user_info, anonymous=True) - access_token = await getUserAccessToken(user_id=user_info["sub"]) + access_token = await auth_client.get_access_token(user_id=user_info["sub"]) return User(**user_info, access_token=access_token, anonymous=False) diff --git a/server/auth/router.py b/server/auth/router.py index af22dd78..d9fc65db 100644 --- a/server/auth/router.py +++ b/server/auth/router.py @@ -43,7 +43,6 @@ async def logout(request: Request): @router.get("/callback") async def callback(request: Request, auth_client: BaseAuthClient = Depends(get_auth_client)): user_info = await auth_client.get_user_info(request) - print(f"user_info={user_info}") if user_info: request.session["user"] = dict(user_info) supabase = get_client() @@ -96,12 +95,11 @@ async def bot_generator( @router.get("/repos") -async def get_user_repos(user_id: Optional[str] = Depends(get_user_id)): +async def get_user_repos(user_id: Optional[str] = Depends(get_user_id), auth_client: BaseAuthClient = Depends(get_auth_client)): if not user_id: raise HTTPException(status_code=401, detail="User not found") try: - client = get_auth_client() - access_token = await client.get_access_token(user_id=user_id) + access_token = await auth_client.get_access_token(user_id=user_id) g = Github(access_token) user = g.get_user() repos = user.get_repos() From 178c432164dc96b3de2eb24a3b870f4c0856de04 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 17:19:12 +0800 Subject: [PATCH 3/4] feat: support locally auth --- server/.env.local.example | 1 + server/auth/clients/local.py | 6 +++++- server/main.py | 4 +--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/server/.env.local.example b/server/.env.local.example index 2e2460ab..390407c6 100644 --- a/server/.env.local.example +++ b/server/.env.local.example @@ -26,6 +26,7 @@ X_GITHUB_APPS_CLIENT_SECRET=github_apps_client_secret # OPTIONAL - Local Authorization Configures PETERCAT_LOCAL_UID="petercat|001" PETERCAT_LOCAL_UNAME="petercat" +PETERCAT_LOCAL_GITHUB_TOKEN="github_pat_xxxx" # OPTIONAL - SKIP AUTH0 Authorization PETERCAT_AUTH0_ENABLED=True diff --git a/server/auth/clients/local.py b/server/auth/clients/local.py index f6b2a634..da52f1fc 100644 --- a/server/auth/clients/local.py +++ b/server/auth/clients/local.py @@ -6,6 +6,7 @@ PETERCAT_LOCAL_UID = get_env_variable("PETERCAT_LOCAL_UID") PETERCAT_LOCAL_UNAME = get_env_variable("PETERCAT_LOCAL_UNAME") +PETERCAT_LOCAL_GITHUB_TOKEN = get_env_variable("PETERCAT_LOCAL_GITHUB_TOKEN") WEB_URL = get_env_variable("WEB_URL") WEB_LOGIN_SUCCESS_URL = f"{WEB_URL}/user/login" @@ -21,7 +22,7 @@ async def login(self, request: Request): return RedirectResponse(url=f"{WEB_LOGIN_SUCCESS_URL}", status_code=302) - async def get_user_info(user_id): + async def get_user_info(self, user_id): token = PETERCAT_LOCAL_UID username = PETERCAT_LOCAL_UNAME seed = token[:4] @@ -36,3 +37,6 @@ async def get_user_info(user_id): "agreement_accepted": False, } + + async def get_access_token(self, user_id): + return PETERCAT_LOCAL_GITHUB_TOKEN \ No newline at end of file diff --git a/server/main.py b/server/main.py index 793489de..6089da19 100644 --- a/server/main.py +++ b/server/main.py @@ -71,13 +71,11 @@ def home_page(): @app.get("/api/health_checker") def health_checker(): - supabase_url = get_env_variable("SUPABASE_URL") return { "ENVIRONMENT": ENVIRONMENT, "API_URL": API_URL, "WEB_URL": WEB_URL, - "CALLBACK_URL": CALLBACK_URL, - "supabase_url": supabase_url, + "CALLBACK_URL": CALLBACK_URL } From 719dab21a9e40088cac6b1cec17741ade5515e80 Mon Sep 17 00:00:00 2001 From: "raoha.rh" Date: Tue, 17 Dec 2024 18:05:37 +0800 Subject: [PATCH 4/4] feat: support locally auth --- assistant/package.json | 2 +- assistant/src/Chat/template/LoginCard.tsx | 4 ++-- assistant/src/Chat/template/index.tsx | 8 ++++++-- assistant/src/hooks/useUser.ts | 5 +++-- assistant/src/style.css | 24 +++++++++++------------ client/app/hooks/useUser.ts | 1 + client/components/User.tsx | 1 - client/package.json | 2 +- client/yarn.lock | 8 ++++---- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index 86c23827..952b86c3 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -1,6 +1,6 @@ { "name": "@petercatai/assistant", - "version": "1.0.20", + "version": "1.0.22", "description": "PeterCat Assistant Application", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", diff --git a/assistant/src/Chat/template/LoginCard.tsx b/assistant/src/Chat/template/LoginCard.tsx index 03ac5a6a..573997e7 100644 --- a/assistant/src/Chat/template/LoginCard.tsx +++ b/assistant/src/Chat/template/LoginCard.tsx @@ -4,8 +4,8 @@ import { Button } from 'antd'; import GitHubIcon from '../../icons/GitHubIcon'; import useUser from '../../hooks/useUser'; -const LoginCard = ({ apiDomain, token }: { apiDomain: string; token: string; }) => { - const { user, isLoading, actions } = useUser({ apiDomain, fingerprint: token }); +const LoginCard = ({ apiDomain, webDomain, token }: { apiDomain: string; webDomain?: string; token: string; }) => { + const { user, isLoading, actions } = useUser({ apiDomain, webDomain, fingerprint: token }); if (isLoading) { return diff --git a/assistant/src/Chat/template/index.tsx b/assistant/src/Chat/template/index.tsx index 0a81ec9f..49155493 100644 --- a/assistant/src/Chat/template/index.tsx +++ b/assistant/src/Chat/template/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import GitInsightCard from './GitInsightCard'; import LoginCard from './LoginCard'; -export const UITemplateRender = ({ templateId, apiDomain, token, cardData }: { templateId: string, apiDomain: string; token: string; cardData: any }) => { +export const UITemplateRender = ({ templateId, apiDomain, webDomain, token, cardData }: { templateId: string, apiDomain: string; webDomain?: string; token: string; cardData: any }) => { if (templateId === 'GIT_INSIGHT') { return ( + ); } return null; diff --git a/assistant/src/hooks/useUser.ts b/assistant/src/hooks/useUser.ts index c0bc06b5..c16113bd 100644 --- a/assistant/src/hooks/useUser.ts +++ b/assistant/src/hooks/useUser.ts @@ -5,7 +5,7 @@ import useSWR from 'swr'; import { popupCenter } from '../utils/popcenter'; import { useEffect } from 'react'; -function useUser({ apiDomain, fingerprint }: { apiDomain: string; fingerprint: string }) { +function useUser({ apiDomain, webDomain = 'https://petercat.ai', fingerprint }: { apiDomain: string; fingerprint: string; webDomain?: string }) { const { data: user, isLoading, mutate } = useSWR( ['user.info'], async () => getUserInfo(apiDomain, { clientId: fingerprint }), @@ -14,8 +14,9 @@ function useUser({ apiDomain, fingerprint }: { apiDomain: string; fingerprint: s const doLogin = () => { + console.log('call do Login', webDomain); popupCenter({ - url: 'https://petercat.ai/user/login', + url: `${webDomain}/user/login`, title: 'Login', w: 600, h: 400, diff --git a/assistant/src/style.css b/assistant/src/style.css index fb35752c..db155add 100644 --- a/assistant/src/style.css +++ b/assistant/src/style.css @@ -112,8 +112,8 @@ */ *:where(.petercat-lui,.petercat-lui *), -:where(.petercat-lui,.petercat-lui *)::before, -:where(.petercat-lui,.petercat-lui *)::after { +::before:where(.petercat-lui,.petercat-lui *), +::after:where(.petercat-lui,.petercat-lui *) { box-sizing: border-box; /* 1 */ border-width: 0; @@ -124,8 +124,8 @@ /* 2 */ } -:where(.petercat-lui,.petercat-lui *)::before, -:where(.petercat-lui,.petercat-lui *)::after { +::before:where(.petercat-lui,.petercat-lui *), +::after:where(.petercat-lui,.petercat-lui *) { --tw-content: ''; } @@ -378,8 +378,8 @@ progress:where(.petercat-lui,.petercat-lui *) { Correct the cursor style of increment and decrement buttons in Safari. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-inner-spin-button, -:where(.petercat-lui,.petercat-lui *) ::-webkit-outer-spin-button { +::-webkit-inner-spin-button:where(.petercat-lui,.petercat-lui *), +::-webkit-outer-spin-button:where(.petercat-lui,.petercat-lui *) { height: auto; } @@ -399,7 +399,7 @@ Correct the cursor style of increment and decrement buttons in Safari. Remove the inner padding in Chrome and Safari on macOS. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-search-decoration { +::-webkit-search-decoration:where(.petercat-lui,.petercat-lui *) { -webkit-appearance: none; } @@ -408,7 +408,7 @@ Remove the inner padding in Chrome and Safari on macOS. 2. Change font properties to `inherit` in Safari. */ -:where(.petercat-lui,.petercat-lui *) ::-webkit-file-upload-button { +::-webkit-file-upload-button:where(.petercat-lui,.petercat-lui *) { -webkit-appearance: button; /* 1 */ font: inherit; @@ -481,15 +481,15 @@ textarea:where(.petercat-lui,.petercat-lui *) { 2. Set the default placeholder color to the user's configured gray 400 color. */ -:where(.petercat-lui,.petercat-lui *) input::-moz-placeholder, :where(.petercat-lui,.petercat-lui *) textarea::-moz-placeholder { +input::-moz-placeholder:where(.petercat-lui,.petercat-lui *), textarea::-moz-placeholder:where(.petercat-lui,.petercat-lui *) { opacity: 1; /* 1 */ color: #9ca3af; /* 2 */ } -:where(.petercat-lui,.petercat-lui *) input::placeholder, -:where(.petercat-lui,.petercat-lui *) textarea::placeholder { +input::placeholder:where(.petercat-lui,.petercat-lui *), +textarea::placeholder:where(.petercat-lui,.petercat-lui *) { opacity: 1; /* 1 */ color: #9ca3af; @@ -545,7 +545,7 @@ video:where(.petercat-lui,.petercat-lui *) { /* Make elements with the HTML hidden attribute stay hidden by default */ -[hidden]:where(:not([hidden="until-found"])):where(.petercat-lui,.petercat-lui *) { +[hidden]:where(.petercat-lui,.petercat-lui *) { display: none; } diff --git a/client/app/hooks/useUser.ts b/client/app/hooks/useUser.ts index 53099261..2099af71 100644 --- a/client/app/hooks/useUser.ts +++ b/client/app/hooks/useUser.ts @@ -10,6 +10,7 @@ export const useUser = () => { const { data: fingerprint } = useFingerprint(); const { user, isLoading, actions } = useAssistUser({ apiDomain: API_DOMAIN, + webDomain: '', fingerprint: fingerprint?.visitorId!, }); diff --git a/client/components/User.tsx b/client/components/User.tsx index 0677a2b3..887a9068 100644 --- a/client/components/User.tsx +++ b/client/components/User.tsx @@ -1,6 +1,5 @@ 'use client'; import I18N from '@/app/utils/I18N'; -import { useRouter } from 'next/navigation'; import { Avatar, Button, diff --git a/client/package.json b/client/package.json index 9aeb5350..6b54fa7b 100644 --- a/client/package.json +++ b/client/package.json @@ -22,7 +22,7 @@ "@fullpage/react-fullpage": "^0.1.42", "@next/bundle-analyzer": "^13.4.19", "@nextui-org/react": "^2.2.9", - "@petercatai/assistant": "1.0.20", + "@petercatai/assistant": "1.0.22", "@sentry/nextjs": "^8.28.0", "@supabase/supabase-js": "^2.32.0", "@tanstack/react-query": "^5.17.19", diff --git a/client/yarn.lock b/client/yarn.lock index 7278dc7b..8efbf301 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2842,10 +2842,10 @@ resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.2.1.tgz#cb0d111ef700136f4580349ff0226bf25c853f23" integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw== -"@petercatai/assistant@1.0.20": - version "1.0.20" - resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.20.tgz#2d2dc1beb296c8524219a6de7eee1575cb3b4c92" - integrity sha512-csfRRsKB9FbBM+cMcCTQQowsuuFRVerSrxfMRTWoI1XHhBW3ormbt1XTeYKiubmwz4iKznR+2UCrZrCl75ckmA== +"@petercatai/assistant@1.0.22": + version "1.0.22" + resolved "https://registry.yarnpkg.com/@petercatai/assistant/-/assistant-1.0.22.tgz#a4113bf4eae9dc66ad0f0e2b33b1f579ca1252a2" + integrity sha512-E8uMZRK3bdD9Oh2mQhK6Zd2A+KV6dt/H2F/fnv/cBT6KOdywwDQIx94K/2fTcpZJXPsUCTMcOhl2877FNaJkxQ== dependencies: "@ant-design/icons" "^5.3.5" "@ant-design/pro-chat" "^1.9.0"