diff --git a/app/configs/config.py b/app/configs/config.py index ccbadcf..499742d 100644 --- a/app/configs/config.py +++ b/app/configs/config.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv import os # Load variabel dari file .env -load_dotenv() +load_dotenv(override=True) class Config: diff --git a/app/controllers/auth_controller.py b/app/controllers/auth_controller.py index b274f7e..393aecf 100644 --- a/app/controllers/auth_controller.py +++ b/app/controllers/auth_controller.py @@ -1,39 +1,73 @@ +import logging +import sys from flask import jsonify, request +from pydantic import ValidationError +from app.schemas.basic_response_schema import ResponseSchema +from app.schemas.google_login_schema import GoogleLoginSchema from schemas import LoginSchema -from services import UserService -from services import AuthService +from services import UserService, AuthService + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) class AuthController: - def __init__( - self, userService: UserService, authService: AuthService, googleAuth=None - ): + def __init__(self, userService: UserService, authService: AuthService): self.user_service = userService self.auth_service = authService - self.google_auth = googleAuth def login(self): data = request.get_json() dataSchema = LoginSchema(**data) response = self.auth_service.login(dataSchema) + if response.success: return jsonify(response.to_dict()), 200 return jsonify(response.to_dict()), 400 def google_login(self): - data = request.get_json() - id_token = data["token"] - response = self.google_auth.parse_id_token(id_token) - if response: - return jsonify({"message": response}) - return jsonify({"message": "google login failed"}) + """Handles Google Login via ID Token verification""" + try: + data = request.get_json() + + # Validasi data dengan Pydantic + validated_data = GoogleLoginSchema(**data) + id_token = validated_data.id_token + + # Verifikasi ID Token ke layanan AuthService + user_info = self.auth_service.verify_google_id_token(id_token) + if not user_info: + logger.error("Invalid Google ID Token") + response = ResponseSchema( + message="Invalid Google ID Token", data=None, meta=None + ) + return jsonify(response.model_dump()), 401 + + # Jika berhasil, kembalikan data user tanpa meta + response = ResponseSchema( + message="Login successful", + data=user_info, + meta=None, # Karena ini single data, tidak ada meta + ) + return jsonify(response.model_dump()), 200 + + except ValidationError as e: + logger.error(f"Validation error: {e}") + response = ResponseSchema(message="Invalid input", data=None, meta=None) + return jsonify(response.model_dump()), 400 + + except Exception as e: + logger.error(f"Error during Google login: {str(e)}", exc_info=True) + response = ResponseSchema( + message="Internal server error", data=None, meta=None + ) + return jsonify(response.model_dump()), 500 def register(self): - - return jsonify({"message": "register"}) + return jsonify({"message": "register"}), 200 def logout(self): - return jsonify({"message": "logout"}) + return jsonify({"message": "logout"}), 200 def test(self): return "test" diff --git a/app/di_container.py b/app/di_container.py index 8bdbb93..fb7cef5 100644 --- a/app/di_container.py +++ b/app/di_container.py @@ -9,7 +9,7 @@ class Container(containers.DeclarativeContainer): """Dependency Injection Container""" mongo = providers.Dependency() - google_auth = providers.Dependency() + # repository user_repository = providers.Factory(UserRepository, mongo.provided.db) @@ -18,6 +18,4 @@ class Container(containers.DeclarativeContainer): user_service = providers.Factory(UserService, user_repository) # controllers - auth_controller = providers.Factory( - AuthController, user_service, auth_service, google_auth - ) + auth_controller = providers.Factory(AuthController, user_service, auth_service) diff --git a/app/main.py b/app/main.py index d465713..73252af 100644 --- a/app/main.py +++ b/app/main.py @@ -1,36 +1,22 @@ +import sys + +from dotenv import load_dotenv from blueprints import default_blueprint from di_container import Container from configs import Config from flask import Flask from blueprints import auth_blueprint from database import init_db -from authlib.integrations.flask_client import OAuth -from flask_login import LoginManager def createApp() -> Flask: app = Flask(__name__) app.config.from_object(Config) + container = Container() app.container = container - login_manager = LoginManager(app) - oauth = OAuth(app) - - google = oauth.register( - name="google", - client_id=app.config["GOOGLE_CLIENT_ID"], - client_secret=app.config["GOOGLE_CLIENT_SECRET"], - access_token_url=app.config["GOOGLE_TOKEN_URI"], - authorize_url=app.config["GOOGLE_AUTH_URI"], - api_base_url=app.config["GOOGLE_BASE_URL"], - client_kwargs={"scope": "openid email profile"}, - ) - - if google is not None: - container.google_auth.override(google) - mongo = init_db(app) if mongo is not None: container.mongo.override(mongo) diff --git a/app/models/dto/response.py b/app/models/dto/response.py index 53a462f..975b0a9 100644 --- a/app/models/dto/response.py +++ b/app/models/dto/response.py @@ -1,22 +1,22 @@ -from pydantic import BaseModel -from typing import Generic, TypeVar, Optional +# from pydantic import BaseModel +# from typing import Generic, TypeVar, Optional -T = TypeVar("T") +# T = TypeVar("T") -class ApiResponse(BaseModel, Generic[T]): - success: bool - message: str - data: Optional[T] = None +# class ApiResponse(BaseModel, Generic[T]): +# success: bool +# message: str +# data: Optional[T] = None - def to_json(self) -> str: - """ - Convert the model to a properly formatted JSON string. - """ - return self.model_dump_json(indent=4) +# def to_json(self) -> str: +# """ +# Convert the model to a properly formatted JSON string. +# """ +# return self.model_dump_json(indent=4) - def to_dict(self) -> dict: - """ - Convert the model to a dictionary with proper key-value pairs. - """ - return self.model_dump() +# def to_dict(self) -> dict: +# """ +# Convert the model to a dictionary with proper key-value pairs. +# """ +# return self.model_dump() diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index bad0606..77d9edf 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -1 +1,2 @@ from .login_schema import LoginSchema +from .basic_response_schema import ResponseSchema, MetaSchema diff --git a/app/schemas/basic_response_schema.py b/app/schemas/basic_response_schema.py new file mode 100644 index 0000000..c44d071 --- /dev/null +++ b/app/schemas/basic_response_schema.py @@ -0,0 +1,17 @@ +from typing import Generic, TypeVar, Optional +from pydantic import BaseModel + +T = TypeVar("T") + + +class MetaSchema: + total_page: int + current_page: int + total_data: int + total_all_data: int + + +class ResponseSchema(BaseModel, Generic[T]): + message: str + data: Optional[T] = None + meta: Optional[MetaSchema] = None diff --git a/app/schemas/google_login_schema.py b/app/schemas/google_login_schema.py new file mode 100644 index 0000000..ddd22da --- /dev/null +++ b/app/schemas/google_login_schema.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class GoogleLoginSchema(BaseModel): + tokenId: str diff --git a/app/services/auth_service.py b/app/services/auth_service.py index bda34f2..852765d 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -1,12 +1,42 @@ +import sys from schemas import LoginSchema from repositories import UserRepository from models import ApiResponse +from google.oauth2 import id_token +from google.auth.transport import requests +from configs import Config class AuthService: def __init__(self, userRepository: UserRepository): self.user_repository = userRepository + def verify_google_id_token(self, id_token_str): + try: + payload = id_token.verify_oauth2_token( + id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID + ) + + print(f"output verify {payload}", file=sys.stderr) + + user_data = { + "_id": payload.get("sub"), + "email": payload.get("email"), + "name": payload.get("name"), + "picture": payload.get("picture"), + "given_name": payload.get("given_name"), + "family_name": payload.get("family_name"), + "locale": payload.get("locale"), + "email_verified": payload.get("email_verified", False), + "iat": payload.get("iat"), + "exp": payload.get("exp"), + } + + return payload + except Exception as e: + print(f"issue on the verify {e}", file=sys.stderr) + return None + def login(self, data: LoginSchema): try: diff --git a/app/services/oauth_service.py b/app/services/oauth_service.py index 7d914e2..e69de29 100644 --- a/app/services/oauth_service.py +++ b/app/services/oauth_service.py @@ -1,21 +0,0 @@ -from flask_login import ( - LoginManager, - UserMixin, - login_user, - logout_user, - login_required, - current_user, -) - - -google = oauth.remote_app( - "google", - consumer_key="YOUR_GOOGLE_CLIENT_ID", - consumer_secret="YOUR_GOOGLE_CLIENT_SECRET", - request_token_params={"scope": "email"}, - base_url="https://www.googleapis.com/oauth2/v1/", - request_token_url=None, - access_token_method="POST", - access_token_url="https://accounts.google.com/o/oauth2/token", - authorize_url="https://accounts.google.com/o/oauth2/auth", -)