Skip to content

Commit 2cb88bf

Browse files
Merge pull request #4 from QuizCast/dev
Dev
2 parents 535795d + 870f0bf commit 2cb88bf

File tree

10 files changed

+160
-8
lines changed

10 files changed

+160
-8
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.venv
22
.env
33
test_supabase.py
4-
__pycache__
4+
__pycache__
5+
startup.sh

app/api/endpoints/auth.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from typing import List, Union
2+
from fastapi import APIRouter, HTTPException, Form, Response
3+
from app.schemas import auth_schema, user_schema
4+
from fastapi.responses import JSONResponse
5+
from app.crud import user_crud
6+
from app.db.db import supabase
7+
8+
router = APIRouter(
9+
prefix="/authentication",
10+
tags=["Authentication"],
11+
)
12+
13+
@router.post("/signup", response_model=user_schema.UserResponse)
14+
async def signup(cred: auth_schema.SignUp):
15+
try:
16+
auth_response = supabase.auth.sign_up({
17+
'email': cred.email,
18+
'password': cred.password,
19+
})
20+
21+
if auth_response.user is None:
22+
raise HTTPException(status_code=400, detail="Signup failed")
23+
24+
# Add user to the database and return user
25+
26+
user = user_crud.create_user(
27+
user_schema.UserCreate(
28+
name=cred.name,
29+
email=cred.email,
30+
is_active=cred.is_active,
31+
img_url=cred.img_url
32+
)
33+
)
34+
35+
return user
36+
37+
except Exception as e:
38+
raise HTTPException(status_code=400, detail=str(e))
39+
40+
@router.post("/login", response_model=auth_schema.LoginResponse)
41+
async def login(cred: auth_schema.Login):
42+
try:
43+
auth_response = supabase.auth.sign_in_with_password({
44+
'email': cred.email,
45+
'password': cred.password,
46+
})
47+
48+
if auth_response.user is None:
49+
raise HTTPException(status_code=400, detail="Login failed")
50+
51+
access_token = auth_response.session.access_token
52+
json_response = JSONResponse(content={"message": "Login successful"})
53+
json_response.set_cookie(
54+
key="access_token",
55+
value=f"Bearer {access_token}",
56+
httponly=True,
57+
secure=True,
58+
samesite="Lax"
59+
)
60+
61+
user = user_crud.get_user_by_email(auth_response.user.email)
62+
63+
response = auth_schema.LoginResponse(
64+
access_token=access_token,
65+
token_type="bearer",
66+
email=user["email"],
67+
user_id=user["id"],
68+
name=user["name"],
69+
)
70+
71+
return response
72+
73+
except Exception as e:
74+
raise HTTPException(status_code=400, detail=str(e))
75+
76+
@router.post("/logout")
77+
async def logout(response: Response):
78+
try:
79+
response.delete_cookie(key="access_token")
80+
response = JSONResponse(content={"message": "Logout successful"})
81+
return response
82+
except Exception as e:
83+
raise HTTPException(status_code=400, detail=str(e))

app/api/endpoints/users.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
from app.core.config import SUPABASE_URL, SUPABASE_KEY
55
from app.db.db import supabase
66
from app.crud import user_crud
7+
from app.utils.autherization import get_current_user
78

89
router = APIRouter(
910
prefix="/user",
1011
tags=["users"],
1112
)
1213

13-
1414
@router.post("/users", response_model=List[user_schema.UserResponse])
1515
async def get_users():
1616
return user_crud.get_users()
@@ -19,6 +19,10 @@ async def get_users():
1919
async def create_user(user: user_schema.UserCreate):
2020
return user_crud.create_user(user)
2121

22-
@router.put("/update_user", response_model=user_schema.UserResponse)
22+
@router.put("/update_user", response_model=user_schema.UserResponse, dependencies=[Depends(get_current_user)])
2323
async def update_user(user: user_schema.UserUpdate):
24-
return user_crud.update_user(user)
24+
return user_crud.update_user(user)
25+
26+
@router.get("/get_user/{email}", response_model=user_schema.UserResponse, dependencies=[Depends(get_current_user)])
27+
async def get_user_by_email(email: str):
28+
return user_crud.get_user_by_email(email)

app/core/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55

66
SUPABASE_URL = os.getenv("SUPABASE_URL")
77
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
8-
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")
8+
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")
9+
SUPABASE_JWT_SECRET = os.getenv("SUPABASE_JWT_SECRET")

app/crud/user_crud.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,11 @@ def update_user(user: user_schema.UserUpdate) -> user_schema.UserResponse:
2525
return response.data[0]
2626
except Exception as e:
2727
return{"error": f"Failed to update user: {str(e)}"}
28+
29+
def get_user_by_email(email: str) -> user_schema.UserResponse:
30+
try:
31+
user = supabase.table("users").select("*").eq("email", email).execute()
32+
return user.data[0]
33+
except Exception as e:
34+
return{"error": f"Failed to retrieve user: {str(e)}"}
2835

app/db/db.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from supabase import create_client, Client
2-
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET
2+
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET, SUPABASE_JWT_SECRET
33

44

5-
if not all([SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET]):
5+
if not all([SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET, SUPABASE_JWT_SECRET]):
66
raise EnvironmentError("One or more Supabase environment variables are missing")
77

88
# Initialize the Supabase client

app/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from fastapi import FastAPI
22
from fastapi.middleware.cors import CORSMiddleware
3-
from app.api.endpoints import users, quizEntry
3+
from app.api.endpoints import users, quizEntry, auth
44
from app.db.db import supabase
5+
from app.utils.autherization import auth_middleware
56

67

78
app = FastAPI()
@@ -20,9 +21,11 @@
2021
allow_methods=["*"],
2122
allow_headers=["*"],
2223
)
24+
app.middleware("http")(auth_middleware)
2325

2426
app.include_router(users.router)
2527
app.include_router(quizEntry.router)
28+
app.include_router(auth.router)
2629

2730

2831
@app.get("/")

app/schemas/auth_schema.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Optional, List
2+
from pydantic import BaseModel
3+
4+
class Login(BaseModel):
5+
email: str
6+
password: str
7+
8+
class SignUp(BaseModel):
9+
name: str
10+
email: str
11+
password: str
12+
img_url: Optional[str] = "None"
13+
is_active: Optional[bool] = True
14+
15+
class LoginResponse(BaseModel):
16+
access_token: str
17+
token_type: str
18+
email: str
19+
user_id: int
20+
name: str

app/utils/autherization.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# auth.py
2+
from fastapi import Request, HTTPException, status, Depends
3+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
4+
import jwt
5+
from app.core.config import SUPABASE_JWT_SECRET
6+
7+
security = HTTPBearer()
8+
9+
async def auth_middleware(request: Request, call_next):
10+
token = request.cookies.get("access_token")
11+
if token and token.startswith("Bearer "):
12+
token = token.split(" ")[1]
13+
request.headers.__dict__["_list"].append(
14+
(b"authorization", f"Bearer {token}".encode())
15+
)
16+
response = await call_next(request)
17+
return response
18+
19+
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
20+
try:
21+
token = credentials.credentials
22+
if token.startswith("Bearer "):
23+
token = token.split(" ")[1]
24+
payload = jwt.decode(token, SUPABASE_JWT_SECRET, algorithms=['HS256'], options={"verify_aud": False})
25+
user_id = payload.get('sub')
26+
if user_id is None:
27+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
28+
return payload
29+
except jwt.ExpiredSignatureError:
30+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired")
31+
except jwt.PyJWTError as e:
32+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ jinja2
88
python-jose
99
supabase
1010
email-validator
11+
pyjwt

0 commit comments

Comments
 (0)