From b555779befb8f8211e6a579d87e68aa47b88780b Mon Sep 17 00:00:00 2001 From: HumbleTurtle Date: Sat, 1 Aug 2026 03:59:52 -0300 Subject: [PATCH] Clear stale worker identity on project attach --- resources/scripts/napseer_mcp_server.py | 35 +++++++++++++++++-------- tests/test_project_attach_oauth.py | 10 ++++++- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index 3a166bc..65716ea 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -7059,17 +7059,20 @@ def operator_project_attach(args=None): if not project_id: raise RuntimeError("OAuth authorization did not return a selected project_id") token_expires_at = iso_timestamp_after_seconds(int(result.get("expires_in") or 0)) - save_auth({ - "base_url": result.get("api_base_url") or BASE_URL, - "token": result.get("access_token"), - "refresh_token": result.get("refresh_token"), - "token_expires_at": token_expires_at, - "refresh_expires_at": result.get("refresh_expires_at"), - "project_id": project_id, - "account_mode": "operator_project", - "oauth_client_id": "nap-cli", - "oauth_scope": result.get("scope"), - }) + replace_public_auth_state( + { + "base_url": result.get("api_base_url") or BASE_URL, + "token": result.get("access_token"), + "refresh_token": result.get("refresh_token"), + "token_expires_at": token_expires_at, + "refresh_expires_at": result.get("refresh_expires_at"), + "project_id": project_id, + "account_mode": "operator_project", + "oauth_client_id": "nap-cli", + "oauth_scope": result.get("scope"), + }, + clear_keys=WORKER_BINDING_AUTH_KEYS, + ) try: project = request_json("GET", f"/v1/projects/{project_id}") save_auth_file_credentials({ @@ -13921,6 +13924,16 @@ PROJECT_BINDING_AUTH_KEYS = { "service_registration_id", } +WORKER_BINDING_AUTH_KEYS = { + "worker_id", + "agent_id", + "worker_name", + "worker_capabilities", + "claimed_account_id", + "claimed_at", + "service_registration_id", +} + def replace_public_auth_state(updates, *, clear_keys=()): current_public = load_public_auth_file() diff --git a/tests/test_project_attach_oauth.py b/tests/test_project_attach_oauth.py index 057ef1b..b431313 100644 --- a/tests/test_project_attach_oauth.py +++ b/tests/test_project_attach_oauth.py @@ -25,6 +25,7 @@ def run(): test_state = tempfile.TemporaryDirectory() mod.AUTH_PATH = pathlib.Path(test_state.name) / "auth.json" saves = [] + replacements = [] def save_auth(updates): saves.append(dict(updates)) @@ -40,7 +41,11 @@ def run(): } mod.save_auth = save_auth mod.save_auth_file_credentials = save_auth - mod.replace_public_auth_state = lambda updates, clear_keys=(): save_auth(updates) + def replace_public_auth_state(updates, clear_keys=()): + replacements.append(set(clear_keys)) + save_auth(updates) + + mod.replace_public_auth_state = replace_public_auth_state mod.request_json = lambda method, path: { "id": PROJECT_ID, "slug": "existing-project", @@ -54,12 +59,15 @@ def run(): assert saves[0]["refresh_token"] == "oauth-refresh-token" assert saves[0]["project_id"] == PROJECT_ID assert saves[0]["account_mode"] == "operator_project" + assert replacements[0] == mod.WORKER_BINDING_AUTH_KEYS + assert "project_id" not in replacements[0] assert "napseer.projects.read" in saves[0]["oauth_scope"] assert saves[1]["project_slug"] == "existing-project" assert result["project_id"] == PROJECT_ID assert result["project"]["name"] == "Existing Project" saves.clear() + replacements.clear() mod.oauth_loopback_authorize = lambda args: { "api_base_url": "https://api.example.test", "access_token": "account-token",