diff --git a/scripts/remote.sh b/scripts/remote.sh index 96855cff..e63f4a67 100644 --- a/scripts/remote.sh +++ b/scripts/remote.sh @@ -1304,7 +1304,11 @@ cmd_disconnect() { # Server-side revoke first, local cleanup always — local deletion alone # does not stop the credential from continuing to authenticate # server-side (remote-connect lifecycle). - local revoke_ok=0 + local revoke_required=0 revoke_ok=0 + if [ -f "$cred_file" ] || + { [ -n "$credential_id" ] && [ "$credential_id" != "null" ]; }; then + revoke_required=1 + fi if [ -f "$cred_file" ] && [ -n "$endpoint" ] && [ "$endpoint" != "null" ] && [ -n "$credential_id" ] && [ "$credential_id" != "null" ]; then credential="$(python3 -c "import json,sys; print(json.load(open('$cred_file')).get('credential',''))" 2>/dev/null)" if [ -n "$credential" ] && [ -n "$server_instance_id" ] && [ "$server_instance_id" != "null" ] \ @@ -1335,11 +1339,13 @@ cmd_disconnect() { exit 1 fi - if [ "$revoke_ok" -eq 1 ]; then - echo "Revoking credential with server... ok." - else - echo "Revoking credential with server... failed." - echo "Local state cleared, but the server could not be reached to revoke this credential — if this device may be compromised, revoke it from the console/admin side directly." >&2 + if [ "$revoke_required" -eq 1 ]; then + if [ "$revoke_ok" -eq 1 ]; then + echo "Revoking credential with server... ok." + else + echo "Revoking credential with server... failed." + echo "Local state cleared, but the server could not be reached to revoke this credential — if this device may be compromised, revoke it from the console/admin side directly." >&2 + fi fi echo "Disconnected '$team'. Local sync state cleared; sends/reads continue locally." } diff --git a/tests/test_remote.bats b/tests/test_remote.bats index cbe02f35..d9311a2b 100644 --- a/tests/test_remote.bats +++ b/tests/test_remote.bats @@ -209,13 +209,26 @@ restart_mock_server() { run bash "$SCRIPTS/remote.sh" disconnect testteam [ "$status" -eq 0 ] [[ "$output" == *"Disconnected 'testteam'. Local sync state cleared"* ]] - # The binding is marked disconnected locally (no server round-trip is needed - # to disconnect in the register model). We do NOT assert on the "Revoking - # credential..." line: it is old-path output that runs with no credential - # present and is removed with the credential/E2EE cleanup. + [[ "$output" != *"Revoking credential"* ]] + [[ "$output" != *"revoke it from the console"* ]] + # The binding is marked disconnected locally. No server round-trip is needed + # because the current connect model does not create a server credential. [ "$(sqlite_mem "SELECT json_extract(CAST(readfile('$SCRIPTS/../teams/testteam/config.json') AS TEXT), '\$.remote_binding.disconnected_at');")" != "" ] } +@test "disconnect: a pulled no-auth team does not report a failed credential revoke" { + local pull_team_id="018f3f7e-2222-7000-8000-000000000002" + run bash "$SCRIPTS/remote.sh" pull --endpoint "$ENDPOINT" \ + --team-id "$pull_team_id" cloned + [ "$status" -eq 0 ] + + run bash "$SCRIPTS/remote.sh" disconnect cloned + [ "$status" -eq 0 ] + [[ "$output" == *"Disconnected 'cloned'. Local sync state cleared"* ]] + [[ "$output" != *"Revoking credential"* ]] + [[ "$output" != *"revoke it from the console"* ]] +} + @test "disconnect: fails for a team that isn't connected" { run bash "$SCRIPTS/remote.sh" disconnect testteam [ "$status" -ne 0 ]