fix: address PR #589 review feedback for Login Flow v2

- Fix data loss in nc_auth_update_scopes: remove premature
  delete_app_password call; old password stays valid until upsert
  replaces it on successful re-provisioning
- Replace assert with proper error return in nc_auth_check_status
- Add lazy singleton for RefreshTokenStorage in auth_tools,
  scope_authorization, and context to avoid per-call re-initialization
- Centralize _is_login_flow_mode() to get_settings().enable_login_flow
  and remove duplicate definitions and per-call os.getenv reads
- Add dev-only comment to TOKEN_ENCRYPTION_KEY in docker-compose.yml
- Gate OIDC build steps in CI behind matrix.needs-playwright
- Add diagnostic step reporting Playwright skip count in CI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-02-28 10:08:55 +01:00
parent 87ec3c4f5b
commit e28af5453b
6 changed files with 67 additions and 72 deletions
+13 -8
View File
@@ -1,7 +1,6 @@
"""Helper functions for accessing context in MCP tools."""
import logging
import os
from httpx import BasicAuth
from mcp.server.fastmcp import Context
@@ -11,6 +10,7 @@ from nextcloud_mcp_server.auth.context_helper import (
get_session_client_from_context,
)
from nextcloud_mcp_server.auth.scope_authorization import ProvisioningRequiredError
from nextcloud_mcp_server.auth.storage import RefreshTokenStorage
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.config import (
DeploymentMode,
@@ -82,7 +82,7 @@ async def get_client(ctx: Context) -> NextcloudClient:
# Login Flow v2 multi-user mode: app password is REQUIRED for NC API access
# OAuth token is only used for MCP session identity, not NC API calls
if hasattr(lifespan_ctx, "nextcloud_host") and _is_login_flow_mode():
if hasattr(lifespan_ctx, "nextcloud_host") and settings.enable_login_flow:
return await _get_client_from_login_flow(ctx, lifespan_ctx.nextcloud_host)
# BasicAuth mode - use shared client (no token exchange)
@@ -254,9 +254,16 @@ def _get_client_from_basic_auth(ctx: Context) -> NextcloudClient:
)
def _is_login_flow_mode() -> bool:
"""Check if Login Flow v2 multi-user mode is active."""
return os.getenv("ENABLE_LOGIN_FLOW", "false").lower() == "true"
_login_flow_storage_instance = None
async def _get_login_flow_storage():
"""Get initialized storage instance for login flow (lazy singleton)."""
global _login_flow_storage_instance
if _login_flow_storage_instance is None:
_login_flow_storage_instance = RefreshTokenStorage.from_env()
await _login_flow_storage_instance.initialize()
return _login_flow_storage_instance
async def _get_client_from_login_flow(
@@ -277,7 +284,6 @@ async def _get_client_from_login_flow(
Raises:
ProvisioningRequiredError: If no stored app password exists
"""
from nextcloud_mcp_server.auth.storage import RefreshTokenStorage # noqa: PLC0415
from nextcloud_mcp_server.server.oauth_tools import ( # noqa: PLC0415
extract_user_id_from_token,
)
@@ -288,8 +294,7 @@ async def _get_client_from_login_flow(
"Cannot determine user identity from MCP token."
)
storage = RefreshTokenStorage.from_env()
await storage.initialize()
storage = await _get_login_flow_storage()
app_data = await storage.get_app_password_with_scopes(user_id)
if not app_data: