diff --git a/.env.example b/.env.example index e69de29..f8ad228 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,12 @@ +MONGO_URI= +FLASK_ENV= +DEBUG= + +SECRET_KEY= + +GOOGLE_PROJECT_ID= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_AUHT_URI= +GOOGLE_TOKEN_URI= +GOOGLE_AUTH_PROVIDER_X509_CERT_URL= diff --git a/app/blueprints/auth.py b/app/blueprints/auth.py index 4611ac5..5834846 100644 --- a/app/blueprints/auth.py +++ b/app/blueprints/auth.py @@ -20,6 +20,12 @@ def login(auth_controller: AuthController = Provide[Container.auth_controller]): return auth_controller.login() +@auth_blueprint.route("/login/google", methods=["POST"]) +@inject +def google_login(auth_controller: AuthController = Provide[Container.auth_controller]): + return auth_controller.google_login() + + @auth_blueprint.route("/logout", methods=["DELETE"]) @inject def logout(auth_controller: AuthController = Provide[Container.auth_controller]): diff --git a/app/configs/config.py b/app/configs/config.py index 9ffc32b..ccbadcf 100644 --- a/app/configs/config.py +++ b/app/configs/config.py @@ -1,18 +1,27 @@ -import os from dotenv import load_dotenv +import os -load_dotenv() # Load environment variables from .env +# Load variabel dari file .env +load_dotenv() class Config: - MONGO_URI = os.getenv( - "MONGO_URI", "mongodb://localhost:27017/quiz_app" - ) # Default value if not set + FLASK_ENV = os.getenv("FLASK_ENV", "development") + DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "t") - FLASK_ENV = os.getenv("FLASK_ENV", "development") # Default to development + SECRET_KEY = os.getenv("SECRET_KEY", "your_secret_key") + MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:27017/yourdb") - DEBUG = os.getenv("DEBUG", "True").lower() in [ - "true", - "1", - "t", - ] # Convert string to boolean + GOOGLE_PROJECT_ID = os.getenv("GOOGLE_PROJECT_ID") + GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID") + GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET") + GOOGLE_AUTH_URI = os.getenv( + "GOOGLE_AUTH_URI", "https://accounts.google.com/o/oauth2/auth" + ) + GOOGLE_TOKEN_URI = os.getenv( + "GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token" + ) + GOOGLE_AUTH_PROVIDER_X509_CERT_URL = os.getenv("GOOGLE_AUTH_PROVIDER_X509_CERT_URL") + + GOOGLE_SCOPE = "email profile" + GOOGLE_BASE_URL = "https://www.googleapis.com/oauth2/v1/" diff --git a/app/controllers/auth_controller.py b/app/controllers/auth_controller.py index 937da6f..b274f7e 100644 --- a/app/controllers/auth_controller.py +++ b/app/controllers/auth_controller.py @@ -5,9 +5,12 @@ from services import AuthService class AuthController: - def __init__(self, userService: UserService, authService: AuthService): + def __init__( + self, userService: UserService, authService: AuthService, googleAuth=None + ): self.user_service = userService self.auth_service = authService + self.google_auth = googleAuth def login(self): data = request.get_json() @@ -17,6 +20,14 @@ class AuthController: 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"}) + def register(self): return jsonify({"message": "register"}) diff --git a/app/database/db.py b/app/database/db.py index 0ed8e64..90f04e0 100644 --- a/app/database/db.py +++ b/app/database/db.py @@ -5,11 +5,9 @@ from configs import Config def init_db(app: Flask) -> PyMongo: try: - app.config["MONGO_URI"] = Config.MONGO_URI - mongo = PyMongo(app) # Initialize PyMongo with the app - print(f"Connecting to MongoDB: {Config.MONGO_URI}") + mongo = PyMongo(app) - mongo.cx.server_info() # Ping the MongoDB server + mongo.cx.server_info() print("✅ MongoDB connection successful!") return mongo diff --git a/app/di_container.py b/app/di_container.py index 69613f1..8bdbb93 100644 --- a/app/di_container.py +++ b/app/di_container.py @@ -9,6 +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) @@ -17,4 +18,6 @@ class Container(containers.DeclarativeContainer): user_service = providers.Factory(UserService, user_repository) # controllers - auth_controller = providers.Factory(AuthController, user_service, auth_service) + auth_controller = providers.Factory( + AuthController, user_service, auth_service, google_auth + ) diff --git a/app/main.py b/app/main.py index 95ae22b..d465713 100644 --- a/app/main.py +++ b/app/main.py @@ -4,15 +4,33 @@ 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) @@ -22,11 +40,10 @@ def createApp() -> Flask: # Register Blueprints app.register_blueprint(default_blueprint) app.register_blueprint(auth_blueprint, url_prefix="/api") - # app.register_blueprint(user_blueprint, url_prefix="/api") return app if __name__ == "__main__": app = createApp() - app.run(debug=Config.DEBUG) + app.run(host="0.0.0.0", debug=Config.DEBUG) diff --git a/app/services/oauth_service.py b/app/services/oauth_service.py new file mode 100644 index 0000000..7d914e2 --- /dev/null +++ b/app/services/oauth_service.py @@ -0,0 +1,21 @@ +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", +) diff --git a/requirement.txt b/requirement.txt index fc5baee..b4f1c12 100644 --- a/requirement.txt +++ b/requirement.txt @@ -5,4 +5,8 @@ python-dotenv==1.0.1 dependency-injector==4.46.0 pytest==8.3.4 pydantic==2.10.6 -email-validator==2.2.0 \ No newline at end of file +email-validator==2.2.0 +Flask-Bcrypt==1.0.1 +flask-jwt-extended==4.7.1 +Flask-Login==0.6.3 +authlib==1.5.1 \ No newline at end of file