feat: Implement OAuth2 authentication with Google and GitHub

- Added OAuth2 endpoints for Google and GitHub authentication.
- Created OAuth service to handle provider interactions and user info retrieval.
- Implemented user OAuth repository for managing user OAuth links in the database.
- Updated auth service to support linking existing users and creating new users via OAuth.
- Added CORS middleware to allow frontend access.
- Created tests for OAuth endpoints and service functionality.
- Introduced environment configuration for OAuth client IDs and secrets.
- Added logging for OAuth operations and error handling.
This commit is contained in:
JSC
2025-07-26 14:38:13 +02:00
parent 52ebc59293
commit 51423779a8
14 changed files with 1119 additions and 37 deletions

View File

@@ -54,8 +54,6 @@ async def register(
samesite=settings.COOKIE_SAMESITE,
)
# Return only user data, tokens are now in cookies
return auth_response.user
except HTTPException:
raise
except Exception as e:
@@ -64,6 +62,8 @@ async def register(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Registration failed",
) from e
else:
return auth_response.user
@router.post("/login")
@@ -101,8 +101,6 @@ async def login(
samesite=settings.COOKIE_SAMESITE,
)
# Return only user data, tokens are now in cookies
return auth_response.user
except HTTPException:
raise
except Exception as e:
@@ -111,6 +109,8 @@ async def login(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Login failed",
) from e
else:
return auth_response.user
@router.get("/me")
@@ -156,7 +156,6 @@ async def refresh_token(
samesite=settings.COOKIE_SAMESITE,
)
return {"message": "Token refreshed successfully"}
except HTTPException:
raise
except Exception as e:
@@ -165,6 +164,8 @@ async def refresh_token(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Token refresh failed",
) from e
else:
return {"message": "Token refreshed successfully"}
@router.post("/logout")
@@ -176,7 +177,7 @@ async def logout(
) -> dict[str, str]:
"""Logout endpoint - clears cookies and revokes refresh token."""
user = None
# Try to get user from access token first
if access_token:
try:
@@ -188,7 +189,7 @@ async def logout(
logger.info("Found user from access token: %s", user.email)
except (HTTPException, Exception) as e:
logger.info("Access token validation failed: %s", str(e))
# If no user found, try refresh token
if not user and refresh_token:
try:
@@ -200,14 +201,14 @@ async def logout(
logger.info("Found user from refresh token: %s", user.email)
except (HTTPException, Exception) as e:
logger.info("Refresh token validation failed: %s", str(e))
# If we found a user, revoke their refresh token
if user:
await auth_service.revoke_refresh_token(user)
logger.info("Successfully revoked refresh token for user: %s", user.email)
else:
logger.info("No user found, skipping token revocation")
# Always clear both cookies regardless of token validity
response.delete_cookie(
key="access_token",
@@ -221,5 +222,5 @@ async def logout(
secure=settings.COOKIE_SECURE,
samesite=settings.COOKIE_SAMESITE,
)
return {"message": "Successfully logged out"}