"""Authentication service for Google OAuth.""" import os from typing import Any from authlib.integrations.flask_client import OAuth from flask import Flask, make_response, request from app.services.token_service import TokenService class AuthService: """Service for handling Google OAuth authentication.""" def __init__(self, app: Flask | None = None) -> None: """Initialize the authentication service.""" self.oauth = OAuth() self.google = None self.token_service = TokenService() if app: self.init_app(app) def init_app(self, app: Flask) -> None: """Initialize the service with Flask app.""" self.oauth.init_app(app) # Configure Google OAuth self.google = self.oauth.register( name="google", client_id=os.getenv("GOOGLE_CLIENT_ID"), client_secret=os.getenv("GOOGLE_CLIENT_SECRET"), server_metadata_url="https://accounts.google.com/.well-known/openid_configuration", client_kwargs={"scope": "openid email profile"}, ) def get_login_url(self, redirect_uri: str) -> str: """Generate Google OAuth login URL.""" if not self.google: msg = "Google OAuth not configured" raise RuntimeError(msg) return self.google.authorize_redirect(redirect_uri).location def handle_callback(self) -> tuple[dict[str, Any], Any]: """Handle OAuth callback and exchange code for token.""" if not self.google: msg = "Google OAuth not configured" raise RuntimeError(msg) token = self.google.authorize_access_token() user_info = token.get("userinfo") if user_info: user_data = { "id": user_info["sub"], "email": user_info["email"], "name": user_info["name"], "picture": user_info.get("picture"), } # Generate JWT tokens access_token = self.token_service.generate_access_token(user_data) refresh_token = self.token_service.generate_refresh_token(user_data) # Create response and set HTTP-only cookies response = make_response({ "message": "Login successful", "user": user_data, }) response.set_cookie( "access_token", access_token, httponly=True, secure=True, samesite="Lax", max_age=15 * 60, # 15 minutes ) response.set_cookie( "refresh_token", refresh_token, httponly=True, secure=True, samesite="Lax", max_age=7 * 24 * 60 * 60, # 7 days ) return user_data, response msg = "Failed to get user information from Google" raise ValueError(msg) def get_current_user(self) -> dict[str, Any] | None: """Get current user from access token.""" access_token = request.cookies.get("access_token") if not access_token: return None return self.token_service.get_user_from_access_token(access_token) def refresh_tokens(self) -> Any: """Refresh access token using refresh token.""" refresh_token = request.cookies.get("refresh_token") if not refresh_token: return None payload = self.token_service.verify_token(refresh_token) if not payload or not self.token_service.is_refresh_token(payload): return None # For refresh, we need to get user data (in a real app, from database) # For now, we'll extract what we can from the refresh token user_data = { "id": payload["user_id"], "email": "", # Would need to fetch from database "name": "", # Would need to fetch from database } # Generate new access token new_access_token = self.token_service.generate_access_token(user_data) response = make_response({"message": "Token refreshed"}) response.set_cookie( "access_token", new_access_token, httponly=True, secure=True, samesite="Lax", max_age=15 * 60, # 15 minutes ) return response def logout(self) -> Any: """Clear authentication cookies.""" response = make_response({"message": "Logged out successfully"}) response.set_cookie("access_token", "", expires=0) response.set_cookie("refresh_token", "", expires=0) return response def is_authenticated(self) -> bool: """Check if user is authenticated.""" return self.get_current_user() is not None