-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from Chefies/feat/auth-upw
feat: User-Password authentication
- Loading branch information
Showing
15 changed files
with
262 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
{} | ||
{ | ||
"projects": { | ||
"default": "chefies-test" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
from fastapi import FastAPI | ||
|
||
import cefies.internal.firestore # noqa: F401 -- Intended to initialize Firebase | ||
from cefies.routes import index_router | ||
from cefies.routes import index_router, auth_router | ||
|
||
app = FastAPI() | ||
app.include_router(index_router) | ||
app.include_router(auth_router) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import os | ||
import cefies.internal.firestore # noqa: F401L: Intended to initialize firebase | ||
|
||
from firebase_admin import storage | ||
|
||
assets_bucket = storage.bucket(os.getenv("STORAGE_BUCKET", "chefies-assets")) | ||
|
||
|
||
def upload_file(content: bytes | str, target_path: str): | ||
blob = assets_bucket.blob(target_path) | ||
blob.upload_from_string(content) | ||
|
||
return blob.public_url |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from typing import Annotated | ||
from pydantic import BaseModel, EmailStr, StringConstraints | ||
|
||
|
||
class Token(BaseModel): | ||
token: str | ||
|
||
|
||
class LoginData(BaseModel): | ||
email: EmailStr | ||
password: str | ||
|
||
|
||
class RegisterData(BaseModel): | ||
email: EmailStr | ||
password: Annotated[str, StringConstraints(min_length=8)] | ||
name: Annotated[str, StringConstraints(min_length=1)] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from cefies.models.db.recipe import Recipe | ||
from cefies.models.db.user import User |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from typing import List | ||
from fireo.typedmodels import TypedModel | ||
from cefies.models.db.user import User | ||
|
||
|
||
class Recipe(TypedModel): | ||
creator: User | ||
image: str | ||
ingredients: List[str] | ||
steps: List[str] | ||
title: str | ||
|
||
class Meta: | ||
collection_name = "recipes" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from fireo.typedmodels import TypedModel | ||
|
||
|
||
class User(TypedModel): | ||
name: str | ||
email: str | ||
avatar: str | ||
password: str | ||
|
||
class Meta: | ||
collection_name = "users" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from pydantic import BaseModel | ||
|
||
|
||
class MessageResponse(BaseModel): | ||
error: bool | ||
message: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
from cefies.routes.index import router as index_router | ||
from cefies.routes.auth import router as auth_router | ||
|
||
|
||
__all__ = ["index_router"] | ||
__all__ = [ | ||
"index_router", | ||
"auth_router", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from typing import Optional | ||
from fastapi import APIRouter, HTTPException, UploadFile, status | ||
|
||
from cefies.models.auth import LoginData, RegisterData, Token | ||
from cefies.models.db.user import User | ||
from cefies.models.response import MessageResponse | ||
from cefies.security import authenticate_user, create_access_token, get_password_hash | ||
|
||
|
||
router = APIRouter(prefix="/auth") | ||
|
||
|
||
@router.post("/login") | ||
async def login(data: LoginData): | ||
user = authenticate_user(data.email, data.password) | ||
if not user: | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Incorrect username or password", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) | ||
|
||
token = create_access_token(user.key) | ||
return Token(token=token) | ||
|
||
|
||
@router.post("/register") | ||
def register(data: RegisterData): | ||
existing_user = User.collection.filter(email=data.email).get() | ||
print(existing_user) | ||
if existing_user: | ||
raise HTTPException( | ||
status_code=status.HTTP_400_BAD_REQUEST, | ||
detail="Email has been used", | ||
) | ||
|
||
new_user = User() | ||
new_user.email = data.email | ||
new_user.name = data.name | ||
new_user.password = get_password_hash(data.password) | ||
# TODO: Upload file to object and set avatar | ||
new_user.avatar = "" | ||
new_user.save() | ||
|
||
return MessageResponse( | ||
error=False, | ||
message="Successfully registered", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,65 @@ | ||
from http.client import UNAUTHORIZED | ||
from typing import Annotated | ||
from fastapi import Depends, HTTPException | ||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | ||
import os | ||
from typing import Annotated, cast | ||
from fastapi import Depends, HTTPException, status | ||
from fastapi.security import OAuth2PasswordBearer | ||
import bcrypt | ||
|
||
from firebase_admin import auth | ||
from cefies.models.db.user import User | ||
from datetime import datetime, timedelta, timezone | ||
import jwt | ||
|
||
from cefies.models.firebase import DecodedIdToken | ||
SECRET_KEY = os.getenv("SECRET_KEY", "insecurekey000000000000000000000") | ||
ALGORITHM = "HS256" | ||
ACCESS_TOKEN_EXPIRE_MINUTES = 30 | ||
|
||
security = HTTPBearer() | ||
security = OAuth2PasswordBearer("/auth/login") | ||
|
||
|
||
def get_current_user(token: Annotated[HTTPAuthorizationCredentials, Depends(security)]): | ||
def authenticate_user(email: str, password: str): | ||
user: User | None = User.collection.filter(email=email).get() | ||
if not user: | ||
return None | ||
|
||
if not bcrypt.checkpw(password.encode(), user.password.encode()): | ||
return None | ||
|
||
return cast(User, user) | ||
|
||
|
||
def create_access_token(user_id: str, expires_delta: timedelta = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)): | ||
expire = datetime.now(timezone.utc) + expires_delta | ||
encoded_jwt = jwt.encode( | ||
{ | ||
"sub": user_id, | ||
"exp": expire, | ||
}, | ||
SECRET_KEY, | ||
algorithm=ALGORITHM, | ||
) | ||
return encoded_jwt | ||
|
||
|
||
def get_password_hash(password: str): | ||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() | ||
|
||
|
||
def get_current_user(token: Annotated[str, Depends(security)]): | ||
credentials_exception = HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Could not validate credentials", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) | ||
|
||
try: | ||
decoded_token: dict = auth.verify_id_token(token.credentials) | ||
except Exception: | ||
raise HTTPException(status_code=UNAUTHORIZED, detail="Invalid token") | ||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) | ||
user_id: str | None = payload.get("sub") | ||
if not user_id: | ||
raise credentials_exception | ||
except jwt.InvalidTokenError: | ||
raise credentials_exception | ||
|
||
user = User.collection.get(user_id) | ||
if not user: | ||
raise credentials_exception | ||
|
||
return DecodedIdToken(**decoded_token) | ||
return cast(User, user) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.