Skip to content

Commit 154ca81

Browse files
authored
Merge pull request #95 from ant-xuexiao/feat/chengyue-bot
feat: add auth0 related service
2 parents d84ad62 + e6499c7 commit 154ca81

File tree

9 files changed

+142
-14
lines changed

9 files changed

+142
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ yarn-error.log*
3131

3232
# local env files
3333
.env*.local
34+
.env*.pre
3435
.env
3536
*.env
3637
# vercel

client/app/hooks/useUser.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
getUserInfo
3+
} from '@/app/services/UserController';
4+
import { useQuery } from '@tanstack/react-query';
5+
6+
7+
8+
function useUser() {
9+
const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN;
10+
return useQuery({
11+
queryKey: [`user.userinfo`],
12+
queryFn: async () => getUserInfo(),
13+
retry: false,
14+
});
15+
}
16+
17+
export default useUser;

client/app/services/UserController.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import axios from 'axios';
2+
3+
4+
// Get the public bot profile by id
5+
export async function getUserInfo() {
6+
7+
const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN;
8+
const response = await axios.get(`${apiDomain}/api/auth/userinfo`);
9+
return response.data.data;
10+
}

client/components/User.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { useUser } from '@auth0/nextjs-auth0/client';
21
import { Avatar, Button, Link } from '@nextui-org/react';
2+
import useUser from '../app/hooks/useUser';
33

44
export default function Profile() {
5-
const { user } = useUser();
5+
const { data: user } = useUser();
6+
const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN;
67

78
if (!user) {
89
return (
9-
<Button as={Link} color="primary" href="/api/auth/login" variant="flat">
10+
<Button as={Link} color="primary" href={`${apiDomain}/api/auth/login`} variant="flat">
1011
Login
1112
</Button>
1213
);

client/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export async function middleware(req: NextRequest) {
66
const res = NextResponse.next()
77

88
const session = await getSession();
9-
9+
1010
if (!session?.user) {
1111
return NextResponse.redirect(new URL('/api/auth/login', req.url));
1212
}

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
python-dotenv
2+
python-jose
3+
six
4+
jose

server/main.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,35 @@
33
import uvicorn
44
from fastapi import FastAPI
55
from fastapi.responses import StreamingResponse
6-
from fastapi.middleware.cors import CORSMiddleware
6+
from starlette.middleware.sessions import SessionMiddleware
7+
78
from agent import stream
89

910
from uilts.env import get_env_variable
1011
from data_class import ChatData
1112

1213
# Import fastapi routers
13-
from routers import bot, health_checker, github, rag
14+
from routers import bot, health_checker, github, rag, auth
1415

1516
open_api_key = get_env_variable("OPENAI_API_KEY")
1617
is_dev = bool(get_env_variable("IS_DEV"))
17-
18+
session_secret_key = get_env_variable("FASTAPI_SECRET_KEY")
1819
app = FastAPI(
1920
title="Bo-meta Server",
2021
version="1.0",
2122
description="Agent Chat APIs"
2223
)
2324

2425
app.add_middleware(
25-
CORSMiddleware,
26-
allow_origins=["*"],
27-
allow_credentials=True,
28-
allow_methods=["*"],
29-
allow_headers=["*"],
30-
expose_headers=["*"],
26+
SessionMiddleware,
27+
secret_key = session_secret_key,
3128
)
3229

3330
app.include_router(health_checker.router)
3431
app.include_router(github.router)
3532
app.include_router(rag.router)
3633
app.include_router(bot.router)
34+
app.include_router(auth.router)
3735

3836
@app.post("/api/chat/stream", response_class=StreamingResponse)
3937
def run_agent_chat(input_data: ChatData):

server/routers/auth.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from fastapi import APIRouter,Cookie, Depends, Security, Request, HTTPException, status, Response
2+
from uilts.env import get_env_variable
3+
from fastapi_auth0 import Auth0, Auth0User
4+
from fastapi.responses import RedirectResponse, JSONResponse
5+
import httpx
6+
from db.supabase.client import get_client
7+
import secrets
8+
9+
AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN")
10+
11+
API_AUDIENCE = get_env_variable("API_IDENTIFIER")
12+
CLIENT_ID = get_env_variable("AUTH0_CLIENT_ID")
13+
CLIENT_SECRET = get_env_variable("AUTH0_CLIENT_SECRET")
14+
15+
API_URL = get_env_variable("API_URL")
16+
CALLBACK_URL = f"{API_URL}/api/auth/callback"
17+
LOGIN_URL = f"{API_URL}/api/auth/login"
18+
19+
WEB_URL = get_env_variable("WEB_URL")
20+
21+
22+
auth = Auth0(domain=AUTH0_DOMAIN, api_audience=API_AUDIENCE, scopes={'read': 'get list'})
23+
24+
router = APIRouter(
25+
prefix="/api/auth",
26+
tags=["auth"],
27+
responses={404: {"description": "Not found"}},
28+
)
29+
30+
async def getUserInfoByToken(token):
31+
userinfo_url = f"https://{AUTH0_DOMAIN}/userinfo"
32+
33+
34+
headers = {"authorization": f"Bearer {token}"}
35+
async with httpx.AsyncClient() as client:
36+
user_info_response = await client.get(userinfo_url, headers=headers)
37+
if user_info_response.status_code == 200:
38+
user_info = user_info_response.json()
39+
data = {
40+
"id": user_info["sub"],
41+
"nickname": user_info.get("nickname"),
42+
"name": user_info.get("name"),
43+
"picture": user_info.get("picture"),
44+
"sub": user_info["sub"],
45+
"sid": secrets.token_urlsafe(32)
46+
}
47+
return data
48+
else :
49+
return {}
50+
51+
async def getTokenByCode(code):
52+
token_url = f"https://{AUTH0_DOMAIN}/oauth/token"
53+
headers = {"content-type": "application/x-www-form-urlencoded"}
54+
data = {
55+
"grant_type": "authorization_code",
56+
"client_id": CLIENT_ID,
57+
"client_secret": CLIENT_SECRET,
58+
"code": code,
59+
"redirect_uri": CALLBACK_URL,
60+
}
61+
async with httpx.AsyncClient() as client:
62+
response = await client.post(token_url, headers=headers, data=data)
63+
token_response = response.json()
64+
65+
if "access_token" not in token_response:
66+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Failed to get access token")
67+
return token_response['access_token']
68+
69+
70+
@router.get("/login")
71+
def login():
72+
redirect_uri = f"https://{AUTH0_DOMAIN}/authorize?audience={API_AUDIENCE}&response_type=code&client_id={CLIENT_ID}&redirect_uri={CALLBACK_URL}&scope=openid profile email&state=STATE"
73+
return RedirectResponse(redirect_uri)
74+
75+
@router.get("/callback")
76+
async def callback(request: Request, response: Response):
77+
code = request.query_params.get("code")
78+
if not code:
79+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization code")
80+
token = await getTokenByCode(code)
81+
data = await getUserInfoByToken(token)
82+
83+
supabase = get_client()
84+
supabase.table("profiles").upsert(data).execute()
85+
response = RedirectResponse(url=f'{WEB_URL}', status_code=302) # 303 See Other 确保正确处理 POST 到 GET 的重定向
86+
response.set_cookie(key="petercat", value=token, httponly=True, secure=True, samesite='Lax')
87+
return response
88+
89+
@router.get("/userinfo")
90+
async def userinfo(petercat: str = Cookie(None)):
91+
if not petercat:
92+
return RedirectResponse(url=LOGIN_URL, status_code=303)
93+
data = await getUserInfoByToken(petercat)
94+
if data :
95+
return { "data": data, "status": 200}
96+
else:
97+
return RedirectResponse(url=LOGIN_URL, status_code=303)

server/routers/bot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends, HTTPException, Query, Body, Path
1+
from fastapi import APIRouter, Query, Body, Path
22
from db.supabase.client import get_client
33
from type_class.bot import BotUpdateRequest, BotCreateRequest
44
from typing import Optional

0 commit comments

Comments
 (0)