Add Google login functionality and update requirements
- Implement Google login endpoint with credential verification - Add GoogleLoginRequest schema for handling login requests - Update Settings class to include GOOGLE_CLIENT_ID - Add google-auth package to requirements
This commit is contained in:
parent
4fa2db2a3c
commit
08cc2bebbe
@ -13,6 +13,7 @@ class Settings(BaseSettings):
|
|||||||
SECRET_KEY: str = "change-me-to-a-random-secret-key"
|
SECRET_KEY: str = "change-me-to-a-random-secret-key"
|
||||||
ALGORITHM: str = "HS256"
|
ALGORITHM: str = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
|
||||||
|
GOOGLE_CLIENT_ID: str = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DATABASE_URL(self) -> str:
|
def DATABASE_URL(self) -> str:
|
||||||
|
|||||||
@ -1,10 +1,23 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from google.oauth2 import id_token
|
||||||
|
from google.auth.transport import requests as google_requests
|
||||||
from sqlalchemy.orm import Session, selectinload
|
from sqlalchemy.orm import Session, selectinload
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.models.user_profile import UserProfile
|
from app.models.user_profile import UserProfile
|
||||||
from app.schemas.user import LoginRequest, LoginResponse, UserCreate, UserResponse
|
from app.schemas.user import (
|
||||||
|
GoogleLoginRequest,
|
||||||
|
LoginRequest,
|
||||||
|
LoginResponse,
|
||||||
|
UserCreate,
|
||||||
|
UserResponse,
|
||||||
|
)
|
||||||
from app.security import create_access_token, hash_password, verify_password
|
from app.security import create_access_token, hash_password, verify_password
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||||
@ -70,3 +83,82 @@ def register(data: UserCreate, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/google-login", response_model=LoginResponse)
|
||||||
|
def google_login(data: GoogleLoginRequest, db: Session = Depends(get_db)):
|
||||||
|
try:
|
||||||
|
idinfo = id_token.verify_oauth2_token(
|
||||||
|
data.credential,
|
||||||
|
google_requests.Request(),
|
||||||
|
settings.GOOGLE_CLIENT_ID,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail={"message": "Kredensial Google tidak valid atau telah kedaluwarsa."},
|
||||||
|
)
|
||||||
|
|
||||||
|
email = idinfo.get("email")
|
||||||
|
name = idinfo.get("name", "")
|
||||||
|
picture = idinfo.get("picture", "")
|
||||||
|
|
||||||
|
if not email:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail={"message": "Tidak dapat mengambil email dari akun Google."},
|
||||||
|
)
|
||||||
|
|
||||||
|
user = db.query(User).options(selectinload(User.profile)).filter(
|
||||||
|
User.email == email,
|
||||||
|
User.deleted_at.is_(None),
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
base_username = email.split("@")[0]
|
||||||
|
username = base_username
|
||||||
|
suffix = 1
|
||||||
|
while db.query(User).filter(User.username == username).first():
|
||||||
|
suffix += 1
|
||||||
|
username = f"{base_username}{suffix}"
|
||||||
|
|
||||||
|
random_password = "".join(random.choices(string.ascii_letters + string.digits, k=32))
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
email=email,
|
||||||
|
username=username,
|
||||||
|
password=hash_password(random_password),
|
||||||
|
email_verified_at=datetime.now(timezone.utc),
|
||||||
|
status="ACTIVE",
|
||||||
|
)
|
||||||
|
db.add(user)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
|
profile = UserProfile(
|
||||||
|
user_id=user.id,
|
||||||
|
full_name=name,
|
||||||
|
timezone="Asia/Jakarta",
|
||||||
|
)
|
||||||
|
db.add(profile)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(user)
|
||||||
|
else:
|
||||||
|
if user.email_verified_at is None:
|
||||||
|
user.email_verified_at = datetime.now(timezone.utc)
|
||||||
|
if not user.profile:
|
||||||
|
profile = UserProfile(
|
||||||
|
user_id=user.id,
|
||||||
|
full_name=name,
|
||||||
|
timezone="Asia/Jakarta",
|
||||||
|
)
|
||||||
|
db.add(profile)
|
||||||
|
elif user.profile.full_name != name:
|
||||||
|
user.profile.full_name = name
|
||||||
|
db.commit()
|
||||||
|
db.refresh(user)
|
||||||
|
|
||||||
|
access_token = create_access_token({"sub": user.id})
|
||||||
|
return LoginResponse(
|
||||||
|
access_token=access_token,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|||||||
@ -56,3 +56,7 @@ class LoginResponse(BaseModel):
|
|||||||
access_token: str
|
access_token: str
|
||||||
token_type: str = "bearer"
|
token_type: str = "bearer"
|
||||||
user: UserDetailResponse
|
user: UserDetailResponse
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleLoginRequest(BaseModel):
|
||||||
|
credential: str
|
||||||
|
|||||||
@ -9,6 +9,7 @@ dnspython==2.8.0
|
|||||||
email-validator==2.3.0
|
email-validator==2.3.0
|
||||||
exceptiongroup==1.3.1
|
exceptiongroup==1.3.1
|
||||||
fastapi==0.139.0
|
fastapi==0.139.0
|
||||||
|
google-auth==2.56.2
|
||||||
fastapi-cli==0.0.29
|
fastapi-cli==0.0.29
|
||||||
fastapi-cloud-cli==0.22.1
|
fastapi-cloud-cli==0.22.1
|
||||||
fastar==0.11.0
|
fastar==0.11.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user