feat(05-10): oauth_initiate returns 200 JSON {url} instead of 302 redirect
- Remove response_class=RedirectResponse from @router.get decorator
- Replace both RedirectResponse(status_code=302) returns with JSONResponse({url})
- Frontend can now inject Bearer header before navigating to OAuth URL (T-05-10-01)
- Update test_connect_google_drive to expect 200 JSON (regression fix)
This commit is contained in:
+11
-5
@@ -311,22 +311,28 @@ async def _upsert_cloud_connection(
|
||||
# ── GET /api/cloud/oauth/initiate/{provider} ──────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/oauth/initiate/{provider}", response_class=RedirectResponse)
|
||||
@router.get("/oauth/initiate/{provider}")
|
||||
async def oauth_initiate(
|
||||
provider: str,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_regular_user),
|
||||
):
|
||||
) -> dict:
|
||||
"""Start the OAuth flow for Google Drive or OneDrive.
|
||||
|
||||
Generates a CSRF state token, stores it in Redis with TTL 1800 (30 min),
|
||||
and redirects the browser to the provider's authorization URL.
|
||||
and returns the provider's authorization URL as JSON so the frontend can
|
||||
navigate using fetch() with the Bearer header (plan 05-10 fix).
|
||||
|
||||
Returns: {"url": "<authorization_url>"}
|
||||
|
||||
Security:
|
||||
- state token is secrets.token_urlsafe(32) — 256 bits of entropy (T-05-05-01)
|
||||
- Redis key is single-use: deleted in the callback handler (T-05-05-02)
|
||||
- Only google_drive and onedrive are accepted (T-05-05-06)
|
||||
- Endpoint requires get_regular_user — no unauthenticated access (T-05-10-01)
|
||||
"""
|
||||
from fastapi.responses import JSONResponse # already available via fastapi
|
||||
|
||||
if provider not in VALID_OAUTH_PROVIDERS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -359,7 +365,7 @@ async def oauth_initiate(
|
||||
prompt="consent",
|
||||
state=state_token,
|
||||
)
|
||||
return RedirectResponse(url=authorization_url, status_code=302)
|
||||
return JSONResponse({"url": authorization_url})
|
||||
|
||||
elif provider == "onedrive":
|
||||
import msal # lazy import
|
||||
@@ -375,7 +381,7 @@ async def oauth_initiate(
|
||||
redirect_uri=redirect_uri,
|
||||
state=state_token,
|
||||
)
|
||||
return RedirectResponse(url=auth_url, status_code=302)
|
||||
return JSONResponse({"url": auth_url})
|
||||
|
||||
|
||||
# ── GET /api/cloud/oauth/callback/{provider} ──────────────────────────────────
|
||||
|
||||
@@ -178,7 +178,11 @@ async def test_factory_returns_correct_backend():
|
||||
# ── CLOUD-01: OAuth connect / WebDAV connect ──────────────────────────────────
|
||||
|
||||
async def test_connect_google_drive(async_client, db_session, monkeypatch):
|
||||
"""GET /api/cloud/oauth/initiate/google_drive redirects to Google's OAuth URL."""
|
||||
"""GET /api/cloud/oauth/initiate/google_drive returns 200 JSON {url} pointing to Google OAuth.
|
||||
|
||||
Updated in plan 05-10: endpoint now returns JSON instead of 302 redirect
|
||||
so the frontend can inject the Bearer Authorization header before navigating.
|
||||
"""
|
||||
from main import app
|
||||
|
||||
auth = await _create_user_and_token(db_session, role="user")
|
||||
@@ -187,15 +191,23 @@ async def test_connect_google_drive(async_client, db_session, monkeypatch):
|
||||
fake_redis = FakeRedis()
|
||||
app.state.redis = fake_redis
|
||||
|
||||
resp = await async_client.get(
|
||||
"/api/cloud/oauth/initiate/google_drive",
|
||||
headers=auth["headers"],
|
||||
follow_redirects=False,
|
||||
mock_flow = MagicMock()
|
||||
mock_flow.authorization_url.return_value = (
|
||||
"https://accounts.google.com/o/oauth2/auth?scope=drive&state=test",
|
||||
"test",
|
||||
)
|
||||
|
||||
assert resp.status_code == 302
|
||||
location = resp.headers.get("location", "")
|
||||
assert "accounts.google.com" in location
|
||||
with patch("google_auth_oauthlib.flow.Flow.from_client_config", return_value=mock_flow):
|
||||
resp = await async_client.get(
|
||||
"/api/cloud/oauth/initiate/google_drive",
|
||||
headers=auth["headers"],
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "url" in data
|
||||
assert "accounts.google.com" in data["url"]
|
||||
|
||||
# Clean up
|
||||
app.state.redis = None
|
||||
|
||||
Reference in New Issue
Block a user