Skip to content

Commit

Permalink
Merge pull request #4 from QuizCast/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
PinsaraPerera authored Dec 12, 2024
2 parents 535795d + 870f0bf commit 2cb88bf
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.venv
.env
test_supabase.py
__pycache__
__pycache__
startup.sh
83 changes: 83 additions & 0 deletions app/api/endpoints/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import List, Union
from fastapi import APIRouter, HTTPException, Form, Response
from app.schemas import auth_schema, user_schema
from fastapi.responses import JSONResponse
from app.crud import user_crud
from app.db.db import supabase

router = APIRouter(
prefix="/authentication",
tags=["Authentication"],
)

@router.post("/signup", response_model=user_schema.UserResponse)
async def signup(cred: auth_schema.SignUp):
try:
auth_response = supabase.auth.sign_up({
'email': cred.email,
'password': cred.password,
})

if auth_response.user is None:
raise HTTPException(status_code=400, detail="Signup failed")

# Add user to the database and return user

user = user_crud.create_user(
user_schema.UserCreate(
name=cred.name,
email=cred.email,
is_active=cred.is_active,
img_url=cred.img_url
)
)

return user

except Exception as e:
raise HTTPException(status_code=400, detail=str(e))

@router.post("/login", response_model=auth_schema.LoginResponse)
async def login(cred: auth_schema.Login):
try:
auth_response = supabase.auth.sign_in_with_password({
'email': cred.email,
'password': cred.password,
})

if auth_response.user is None:
raise HTTPException(status_code=400, detail="Login failed")

access_token = auth_response.session.access_token
json_response = JSONResponse(content={"message": "Login successful"})
json_response.set_cookie(
key="access_token",
value=f"Bearer {access_token}",
httponly=True,
secure=True,
samesite="Lax"
)

user = user_crud.get_user_by_email(auth_response.user.email)

response = auth_schema.LoginResponse(
access_token=access_token,
token_type="bearer",
email=user["email"],
user_id=user["id"],
name=user["name"],
)

return response

except Exception as e:
raise HTTPException(status_code=400, detail=str(e))

@router.post("/logout")
async def logout(response: Response):
try:
response.delete_cookie(key="access_token")
response = JSONResponse(content={"message": "Logout successful"})
return response
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
10 changes: 7 additions & 3 deletions app/api/endpoints/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from app.core.config import SUPABASE_URL, SUPABASE_KEY
from app.db.db import supabase
from app.crud import user_crud
from app.utils.autherization import get_current_user

router = APIRouter(
prefix="/user",
tags=["users"],
)


@router.post("/users", response_model=List[user_schema.UserResponse])
async def get_users():
return user_crud.get_users()
Expand All @@ -19,6 +19,10 @@ async def get_users():
async def create_user(user: user_schema.UserCreate):
return user_crud.create_user(user)

@router.put("/update_user", response_model=user_schema.UserResponse)
@router.put("/update_user", response_model=user_schema.UserResponse, dependencies=[Depends(get_current_user)])
async def update_user(user: user_schema.UserUpdate):
return user_crud.update_user(user)
return user_crud.update_user(user)

@router.get("/get_user/{email}", response_model=user_schema.UserResponse, dependencies=[Depends(get_current_user)])
async def get_user_by_email(email: str):
return user_crud.get_user_by_email(email)
3 changes: 2 additions & 1 deletion app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")
SUPABASE_JWT_SECRET = os.getenv("SUPABASE_JWT_SECRET")
7 changes: 7 additions & 0 deletions app/crud/user_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ def update_user(user: user_schema.UserUpdate) -> user_schema.UserResponse:
return response.data[0]
except Exception as e:
return{"error": f"Failed to update user: {str(e)}"}

def get_user_by_email(email: str) -> user_schema.UserResponse:
try:
user = supabase.table("users").select("*").eq("email", email).execute()
return user.data[0]
except Exception as e:
return{"error": f"Failed to retrieve user: {str(e)}"}

4 changes: 2 additions & 2 deletions app/db/db.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from supabase import create_client, Client
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET, SUPABASE_JWT_SECRET


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

# Initialize the Supabase client
Expand Down
5 changes: 4 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.endpoints import users, quizEntry
from app.api.endpoints import users, quizEntry, auth
from app.db.db import supabase
from app.utils.autherization import auth_middleware


app = FastAPI()
Expand All @@ -20,9 +21,11 @@
allow_methods=["*"],
allow_headers=["*"],
)
app.middleware("http")(auth_middleware)

app.include_router(users.router)
app.include_router(quizEntry.router)
app.include_router(auth.router)


@app.get("/")
Expand Down
20 changes: 20 additions & 0 deletions app/schemas/auth_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Optional, List
from pydantic import BaseModel

class Login(BaseModel):
email: str
password: str

class SignUp(BaseModel):
name: str
email: str
password: str
img_url: Optional[str] = "None"
is_active: Optional[bool] = True

class LoginResponse(BaseModel):
access_token: str
token_type: str
email: str
user_id: int
name: str
32 changes: 32 additions & 0 deletions app/utils/autherization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# auth.py
from fastapi import Request, HTTPException, status, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from app.core.config import SUPABASE_JWT_SECRET

security = HTTPBearer()

async def auth_middleware(request: Request, call_next):
token = request.cookies.get("access_token")
if token and token.startswith("Bearer "):
token = token.split(" ")[1]
request.headers.__dict__["_list"].append(
(b"authorization", f"Bearer {token}".encode())
)
response = await call_next(request)
return response

def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
token = credentials.credentials
if token.startswith("Bearer "):
token = token.split(" ")[1]
payload = jwt.decode(token, SUPABASE_JWT_SECRET, algorithms=['HS256'], options={"verify_aud": False})
user_id = payload.get('sub')
if user_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials")
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired")
except jwt.PyJWTError as e:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ jinja2
python-jose
supabase
email-validator
pyjwt

0 comments on commit 2cb88bf

Please sign in to comment.