Compare commits

..

No commits in common. "master" and "1.5.8" have entirely different histories.

4 changed files with 84 additions and 10 deletions

View File

@ -10,12 +10,34 @@ from passlib.context import CryptContext
from ..models import users, token from ..models import users, token
from ..dependencies import database, cookie from ..dependencies import database, cookie
from authlib.integrations.starlette_client import OAuth
import httpx, os
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30 ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth = OAuth()
oauth.register(
name="google",
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
authorize_url="https://accounts.google.com/o/oauth2/auth",
access_token_url="https://oauth2.googleapis.com/token",
client_kwargs={"scope": "openid email profile"},
)
oauth.register(
name="facebook",
client_id=os.environ["FACEBOOK_CLIENT_ID"],
client_secret=os.environ["FACEBOOK_CLIENT_SECRET"],
authorize_url="https://www.facebook.com/v12.0/dialog/oauth",
access_token_url="https://graph.facebook.com/v12.0/oauth/access_token",
client_kwargs={"scope": "email public_profile"},
)
oauth2_scheme = cookie.OAuth2PasswordBearerWithCookie(tokenUrl="token") oauth2_scheme = cookie.OAuth2PasswordBearerWithCookie(tokenUrl="token")
def create_access_token(data: dict, expires_delta: timedelta | None = None): def create_access_token(data: dict, expires_delta: timedelta | None = None):
@ -25,6 +47,38 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt return encoded_jwt
async def authenticate_oauth(provider: str, token: str):
"""Validate OAuth token and get user info."""
if provider == "google":
url = f"https://www.googleapis.com/oauth2/v3/userinfo?access_token={token}"
elif provider == "facebook":
url = f"https://graph.facebook.com/me?fields=id,name,email,picture&access_token={token}"
else:
raise HTTPException(status_code=400, detail="Unsupported provider")
async with httpx.AsyncClient() as client:
response = await client.get(url)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Invalid OAuth token")
user_info = response.json()
email = user_info.get("email")
if not email:
raise HTTPException(status_code=400, detail="Email not provided by provider")
user_repository = users.UserRepository(database=database.database)
user = user_repository.find_one_by({'email': email})
if not user:
user = users.User(
username=email,
email=email,
profile_picture=user_info.get("picture", {}).get("data", {}).get("url", ""),
status=1,
)
user_repository.save(user)
return user
def verify_password(plain_password, hashed_password): def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password) return pwd_context.verify(plain_password, hashed_password)

View File

@ -84,7 +84,8 @@ async def read_events(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="`skip` should be >= 0 and `limit` should be > 0 and greater than `skip`.", detail="`skip` should be >= 0 and `limit` should be > 0 and greater than `skip`.",
) )
skip = limit * skip limit = limit + skip
# Initialize filters # Initialize filters
filters = [] filters = []
@ -165,7 +166,7 @@ async def search_events(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="`skip` should be >= 0 and `limit` should be > 0 and greater than `skip`.", detail="`skip` should be >= 0 and `limit` should be > 0 and greater than `skip`.",
) )
skip = limit * skip limit = limit + skip
# Initialize filters # Initialize filters
filters = [{"status": {"$eq": status}}] filters = [{"status": {"$eq": status}}]

View File

@ -1,7 +1,7 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Annotated from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status, APIRouter, Form from fastapi import Depends, FastAPI, HTTPException, status, APIRouter
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from ..dependencies import users_token, permissions_checker from ..dependencies import users_token, permissions_checker
@ -11,21 +11,38 @@ from ..models import token, users
router = APIRouter() router = APIRouter()
ACCESS_TOKEN_EXPIRE_MINUTES = 30 ACCESS_TOKEN_EXPIRE_MINUTES = 30
@router.post("/oauth/{provider}", tags=["token"])
async def oauth_login(provider: str, token: str):
"""Handles OAuth login via Google/Facebook."""
user = await users_token.authenticate_oauth(provider, token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication"
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = users_token.create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
content = {"roles": user.roles, "message": "OAuth login successful"}
response = JSONResponse(content=content)
response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
return response
@router.post("/token", tags=["token"]) @router.post("/token", tags=["token"])
async def login_for_access_token( async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()], form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
remember_me: bool = Form(False)):
user = users_token.authenticate_user(form_data.username, form_data.password) user = users_token.authenticate_user(form_data.username, form_data.password)
expires_access_token_time = ACCESS_TOKEN_EXPIRE_MINUTES
if remember_me:
expires_access_token_time=120
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password", detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
access_token_expires = timedelta(minutes=expires_access_token_time) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = users_token.create_access_token( access_token = users_token.create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires data={"sub": user.username}, expires_delta=access_token_expires
) )

View File

@ -5,4 +5,6 @@ python-jose[cryptography]
passlib[bcrypt] passlib[bcrypt]
python-multipart python-multipart
fastapi-mail fastapi-mail
redis redis
authlib
httpx