diff --git a/nextcloud_mcp_server/auth/provision_routes.py b/nextcloud_mcp_server/auth/provision_routes.py index 54c4b52e..1ea34eec 100644 --- a/nextcloud_mcp_server/auth/provision_routes.py +++ b/nextcloud_mcp_server/auth/provision_routes.py @@ -6,21 +6,22 @@ chain OAuth (bearer token) with Login Flow v2 (app password) in a single user interaction. Flow: -1. GET /app/provision?redirect_uri=... → Initiates LFv2, renders polling page -2. The page opens Nextcloud's login URL in a popup window -3. User clicks "Grant access" in the popup -4. Page polls GET /app/provision/status?id=... for completion -5. On success, redirects to redirect_uri +1. GET /app/provision?redirect_uri=... → Initiates LFv2, redirects to NC login +2. User clicks "Grant access" on Nextcloud's login page +3. MCP server background task polls and stores app password +4. GET /app/provision/status?id=... → Returns completion status (JSON) +5. User returns to Astrolabe settings (via redirect_uri or navigation) """ import asyncio import logging +import os import secrets import time from urllib.parse import urlparse from starlette.requests import Request -from starlette.responses import HTMLResponse, JSONResponse +from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse from nextcloud_mcp_server.auth.login_flow import LoginFlowV2Client from nextcloud_mcp_server.auth.storage import get_shared_storage @@ -129,13 +130,15 @@ async def _poll_and_store(provision_id: str) -> None: ) -async def provision_page(request: Request) -> HTMLResponse: - """Render the Login Flow v2 provisioning page. +async def provision_page(request: Request) -> RedirectResponse | HTMLResponse: + """Initiate Login Flow v2 and redirect to Nextcloud's login page. GET /app/provision?redirect_uri=...&user_id=... - Initiates Login Flow v2, starts background polling, and returns an HTML - page that opens Nextcloud's login URL in a popup and polls for completion. + Initiates Login Flow v2, starts background polling, and redirects the + browser to Nextcloud's login/grant page. After the user grants access, + the background task stores the app password. The user then navigates + back to the redirect_uri (Astrolabe settings). """ _cleanup_expired_sessions() @@ -148,16 +151,13 @@ async def provision_page(request: Request) -> HTMLResponse: status_code=400, ) - # Check if user already has an app password + # Check if user already has an app password — skip straight to redirect if user_id: storage = await get_shared_storage() existing = await storage.get_app_password_with_scopes(user_id) if existing: logger.info(f"User {user_id} already has app password, skipping provision") - return HTMLResponse( - content=_render_redirect(redirect_uri), - status_code=200, - ) + return RedirectResponse(redirect_uri) # Initiate Login Flow v2 settings = get_settings() @@ -201,16 +201,18 @@ async def provision_page(request: Request) -> HTMLResponse: logger.info( f"Login Flow v2 web provision initiated (provision_id={provision_id}, " - f"user_id={user_id or 'unknown'})" + f"user_id={user_id or 'unknown'}), redirecting to NC login" ) - return HTMLResponse( - content=_render_provision_page( - provision_id=provision_id, - login_url=init_response.login_url, - redirect_uri=redirect_uri, - ) - ) + # Redirect to Nextcloud's Login Flow v2 login page. + # The login_url may use the internal Docker URL (http://app:80/...). + # Replace with the public Nextcloud URL for the browser. + login_url = init_response.login_url + public_issuer = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL", "") + if public_issuer and nextcloud_host and nextcloud_host in login_url: + login_url = login_url.replace(nextcloud_host, public_issuer.rstrip("/")) + + return RedirectResponse(login_url) async def provision_status(request: Request) -> JSONResponse: @@ -244,175 +246,6 @@ async def provision_status(request: Request) -> JSONResponse: # ── HTML rendering helpers ──────────────────────────────────────────────── -def _render_provision_page(provision_id: str, login_url: str, redirect_uri: str) -> str: - """Render the provisioning page HTML.""" - return f""" - - - - - Connecting to Nextcloud - Astrolabe - - - -
-

Connect to Nextcloud

-

Grant Astrolabe access to your Nextcloud account for background sync.

- -
- - Waiting for authorization... -
- - - -
- A popup window should open. Click "Grant access" in the - Nextcloud window to continue. -
-
- - - -""" - - def _render_error(message: str) -> str: """Render an error page.""" return f""" @@ -448,19 +281,3 @@ def _render_error(message: str) -> str: """ - - -def _render_redirect(redirect_uri: str) -> str: - """Render a page that immediately redirects (for already-provisioned users).""" - return f""" - - - - - Redirecting... - - -

Already connected. Redirecting...

- - -"""