mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor: delete the unused AuthDecisionResolver
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rado2dnqpbCuCUyCd3trQz
This commit is contained in:
-68
@@ -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<RelayAuthVerdict>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
-117
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user