From 4a1204ae577ee53e8aa9bbd9a0e344cc90849543 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 16:24:24 +0000 Subject: [PATCH] refactor: delete the unused AuthDecisionResolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AuthDecisionResolver has never had a production caller. Searching every commit that touched amethyst/src/main for the symbol outside the object's own file returns nothing, and `git log -S` against AuthCoordinator.kt is likewise empty: it arrived unused and stayed that way, kept alive only by its own test. The live equivalent is the per-account block in AuthCoordinator, which covers every branch it modelled — ALLOW/DENY/ASK, and the full UserAuthChoice mapping including the setDecision writes behind "always allow" and "never allow". Two things made it worse than merely dead. It folded every logged-in account into a single verdict, whereas the coordinator decides per account because one socket is shared and an answer given for @a must not reveal @b. And its "no verdicts -> authenticate" branch encoded the old any-account-allows fold that was deliberately removed to fix the over-AUTH bug; there is no random-key fallback any more. Left in place it reads as a template for policy the codebase has since rejected. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Rado2dnqpbCuCUyCd3trQz --- .../authCommand/model/AuthDecisionResolver.kt | 68 ---------- .../model/AuthDecisionResolverTest.kt | 117 ------------------ 2 files changed, 185 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolver.kt delete mode 100644 amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolverTest.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolver.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolver.kt deleted file mode 100644 index 5223e51add..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolver.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.service.relayClient.authCommand.model - -import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision -import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict - -/** - * Combines every logged-in account's [RelayAuthVerdict] into a single decision for whether to - * authenticate with a relay — and, when the user is asked, what to remember. Pulled out of - * [AuthCoordinator] so the policy is unit-testable without the relay client, signers, or Compose. - */ -object AuthDecisionResolver { - /** - * @param shouldAuth whether to sign the NIP-42 auth challenge. - * @param remember a per-relay override to persist ([RelayAuthDecision.ALLOW]/[RelayAuthDecision.DENY]), - * or null to leave the relay's stored decision untouched. - */ - data class Outcome( - val shouldAuth: Boolean, - val remember: RelayAuthDecision? = null, - ) - - /** - * Resolves the combined verdict: - * - **No verdicts** (no ledgers watching) => auto-authenticate; the caller has no policy to apply. - * - **Any [RelayAuthVerdict.ALLOW]** => authenticate without asking. - * - **Any [RelayAuthVerdict.ASK]** (and none allow) => call [prompt] and act on the user's choice, - * remembering ALLOW/DENY for Always-allow / Block. - * - **Otherwise** (all DENY) => do not authenticate. - * - * [prompt] is only invoked in the ASK case, so the no-op paths never build a dialog. - */ - suspend fun resolve( - verdicts: List, - prompt: suspend () -> UserAuthChoice, - ): Outcome = - when { - verdicts.isEmpty() -> Outcome(shouldAuth = true) - verdicts.any { it == RelayAuthVerdict.ALLOW } -> Outcome(shouldAuth = true) - verdicts.any { it == RelayAuthVerdict.ASK } -> - when (prompt()) { - UserAuthChoice.ALLOW_ONCE -> Outcome(shouldAuth = true) - UserAuthChoice.ALWAYS_ALLOW -> Outcome(shouldAuth = true, remember = RelayAuthDecision.ALLOW) - UserAuthChoice.BLOCK -> Outcome(shouldAuth = false, remember = RelayAuthDecision.DENY) - UserAuthChoice.DISMISS -> Outcome(shouldAuth = false) - } - else -> Outcome(shouldAuth = false) - } -} diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolverTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolverTest.kt deleted file mode 100644 index 4f92e7450b..0000000000 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/AuthDecisionResolverTest.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.service.relayClient.authCommand.model - -import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision -import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Test - -class AuthDecisionResolverTest { - /** A prompt that must never be called; fails the test if the resolver asks the user. */ - private val neverPrompt: suspend () -> UserAuthChoice = { error("prompt() should not be called") } - - @Test - fun noVerdictsAutoAuthenticatesWithoutPrompting() = - runTest { - val outcome = AuthDecisionResolver.resolve(emptyList(), neverPrompt) - assertTrue(outcome.shouldAuth) - assertNull(outcome.remember) - } - - @Test - fun anyAllowAuthenticatesWithoutPrompting() = - runTest { - val outcome = - AuthDecisionResolver.resolve( - listOf(RelayAuthVerdict.DENY, RelayAuthVerdict.ALLOW, RelayAuthVerdict.ASK), - neverPrompt, - ) - assertTrue(outcome.shouldAuth) - assertNull(outcome.remember) - } - - @Test - fun allDenyDoesNotAuthenticateAndDoesNotPrompt() = - runTest { - val outcome = - AuthDecisionResolver.resolve( - listOf(RelayAuthVerdict.DENY, RelayAuthVerdict.DENY), - neverPrompt, - ) - assertFalse(outcome.shouldAuth) - assertNull(outcome.remember) - } - - @Test - fun askAllowOnceAuthenticatesButRemembersNothing() = - runTest { - val outcome = - AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.ALLOW_ONCE } - assertTrue(outcome.shouldAuth) - assertNull(outcome.remember) - } - - @Test - fun askAlwaysAllowAuthenticatesAndRemembersAllow() = - runTest { - val outcome = - AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.ALWAYS_ALLOW } - assertTrue(outcome.shouldAuth) - assertEquals(RelayAuthDecision.ALLOW, outcome.remember) - } - - @Test - fun askBlockDoesNotAuthenticateAndRemembersDeny() = - runTest { - val outcome = - AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.BLOCK } - assertFalse(outcome.shouldAuth) - assertEquals(RelayAuthDecision.DENY, outcome.remember) - } - - @Test - fun askDismissDoesNotAuthenticateAndRemembersNothing() = - runTest { - val outcome = - AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.DISMISS } - assertFalse(outcome.shouldAuth) - assertNull(outcome.remember) - } - - @Test - fun askIsOnlyReachedWhenNoAccountAllows() = - runTest { - // ALLOW present alongside ASK must short-circuit to auth without prompting. - var prompted = false - val outcome = - AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK, RelayAuthVerdict.ALLOW)) { - prompted = true - UserAuthChoice.BLOCK - } - assertTrue(outcome.shouldAuth) - assertFalse(prompted) - } -}