mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
Merge remote-tracking branch 'origin/claude/relay-auth-cache-91pa8f' into claude/relay-auth-cache-91pa8f
This commit is contained in:
+17
-1
@@ -149,8 +149,24 @@ class RelayAuthPermissionLedger(
|
||||
/**
|
||||
* Remembers a "log in" answer for [relayUrl] until the app is restarted, so the relay's next
|
||||
* reconnect doesn't ask again. Nothing is written to disk — see [RelayAuthSessionGrants].
|
||||
*
|
||||
* Refused, returning false, while [globalPolicy] is [RelayAuthPolicy.NEVER]: that is the
|
||||
* switch-it-all-off answer, and a session grant outranks the policy (see [RelayAuthResolver]),
|
||||
* so recording one here would quietly re-enable the very thing the user just turned off. The
|
||||
* settings screen clears existing grants when the policy is set to NEVER; this stops a *new*
|
||||
* one being written afterwards — which the undo on "forget this login" otherwise did, because
|
||||
* its snackbar carries an action label and so sits on screen indefinitely, long enough for the
|
||||
* policy to change underneath it.
|
||||
*
|
||||
* Only the policy needs this guard. A stored override arriving in the same window is
|
||||
* self-protecting: it is ranked *above* the grant, so an ALLOW or DENY written meanwhile
|
||||
* decides the relay either way. So is the block list, which outranks everything.
|
||||
*/
|
||||
fun grantForSession(relayUrl: String) = sessionGrants.grant(relayUrl)
|
||||
fun grantForSession(relayUrl: String): Boolean {
|
||||
if (globalPolicy() == RelayAuthPolicy.NEVER) return false
|
||||
sessionGrants.grant(relayUrl)
|
||||
return true
|
||||
}
|
||||
|
||||
/** Forgets this session's grant for [relayUrl], so the next challenge is decided from scratch. */
|
||||
fun revokeSessionGrant(relayUrl: String) = sessionGrants.revoke(relayUrl)
|
||||
|
||||
+13
-2
@@ -155,18 +155,29 @@ fun RelayAuthSettingsScreen(
|
||||
|
||||
val removedLabel = stringResource(R.string.relay_auth_exception_removed_undo)
|
||||
val sessionForgottenLabel = stringResource(R.string.relay_auth_session_forgotten_undo)
|
||||
val sessionUndoBlockedLabel = stringResource(R.string.relay_auth_session_undo_blocked)
|
||||
val undoLabel = stringResource(R.string.relay_auth_undo)
|
||||
|
||||
fun forgetSessionGrant(url: String) {
|
||||
ledger.revokeSessionGrant(url)
|
||||
scope.launch {
|
||||
val display = url.normalizeRelayUrlOrNull()?.displayUrl() ?: url
|
||||
val result =
|
||||
snackbarHostState.showSnackbar(
|
||||
message = sessionForgottenLabel.format(url.normalizeRelayUrlOrNull()?.displayUrl() ?: url),
|
||||
message = sessionForgottenLabel.format(display),
|
||||
actionLabel = undoLabel,
|
||||
withDismissAction = true,
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) ledger.grantForSession(url)
|
||||
// An action label makes Material3 show this indefinitely, so the undo can be tapped long
|
||||
// after the fact — including after the policy above was switched to "Never log in", which
|
||||
// clears every grant. The ledger refuses to write a new one in that state; report that
|
||||
// instead of leaving a tapped undo looking like it silently did nothing.
|
||||
if (result == SnackbarResult.ActionPerformed && !ledger.grantForSession(url)) {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = sessionUndoBlockedLabel.format(display),
|
||||
withDismissAction = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1202,6 +1202,7 @@
|
||||
<string name="relay_auth_session_row_desc">Logged in until you restart Amethyst</string>
|
||||
<string name="relay_auth_forget_session">Forget this login</string>
|
||||
<string name="relay_auth_session_forgotten_undo">%1$s will ask again the next time it needs you.</string>
|
||||
<string name="relay_auth_session_undo_blocked">Not restored. “Never log in” is on, so Amethyst won\'t log in to %1$s.</string>
|
||||
<string name="relay_auth_blocked_section">Blocked by your block list</string>
|
||||
<string name="relay_auth_blocked_row_desc">Amethyst never logs in to blocked relays.</string>
|
||||
<string name="relay_auth_no_blocked">Nothing blocked. Relays you block will never be logged in to, whatever you set here.</string>
|
||||
|
||||
+56
-1
@@ -53,9 +53,10 @@ class RelayAuthSessionGrantsTest {
|
||||
grants: RelayAuthSessionGrants = RelayAuthSessionGrants(),
|
||||
store: RelayAuthPermissionStore = InMemoryRelayAuthPermissionStore(),
|
||||
blocked: Set<String> = emptySet(),
|
||||
policy: () -> RelayAuthPolicy = { RelayAuthPolicy.CUSTOM },
|
||||
) = RelayAuthPermissionLedger(
|
||||
store = store,
|
||||
globalPolicy = { RelayAuthPolicy.CUSTOM },
|
||||
globalPolicy = policy,
|
||||
sessionGrants = grants,
|
||||
isBlocked = { it in blocked },
|
||||
)
|
||||
@@ -234,4 +235,58 @@ class RelayAuthSessionGrantsTest {
|
||||
grants.clear()
|
||||
assertEquals(emptySet<String>(), grants.grants.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aGrantIsRefusedWhileThePolicyIsNever() =
|
||||
runTest {
|
||||
val grants = RelayAuthSessionGrants()
|
||||
val ledger = ledger(grants = grants, policy = { RelayAuthPolicy.NEVER })
|
||||
|
||||
assertFalse(ledger.grantForSession(relay))
|
||||
assertFalse(grants.isGranted(relay))
|
||||
assertEquals(RelayAuthVerdict.DENY, ledger.decide(askable(relay)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun undoingAForgetAfterSwitchingToNeverDoesNotResurrectTheGrant() =
|
||||
runTest {
|
||||
// The settings screen's undo snackbar carries an action label, so Material3 leaves it up
|
||||
// indefinitely — the user can switch the whole policy off and only then tap undo.
|
||||
val grants = RelayAuthSessionGrants()
|
||||
var policy = RelayAuthPolicy.CUSTOM
|
||||
val ledger = ledger(grants = grants, policy = { policy })
|
||||
|
||||
assertTrue(ledger.grantForSession(relay))
|
||||
assertEquals(RelayAuthVerdict.ALLOW, ledger.decide(askable(relay)))
|
||||
|
||||
// "Forget this login", then "Never log in" — which also clears what is already granted.
|
||||
ledger.revokeSessionGrant(relay)
|
||||
policy = RelayAuthPolicy.NEVER
|
||||
grants.clear()
|
||||
|
||||
// ...and only now, undo.
|
||||
assertFalse(ledger.grantForSession(relay))
|
||||
assertEquals(RelayAuthVerdict.DENY, ledger.decide(askable(relay)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theGuardOnlyAppliesToNever() =
|
||||
runTest {
|
||||
assertTrue(ledger(policy = { RelayAuthPolicy.CUSTOM }).grantForSession(relay))
|
||||
assertTrue(ledger(policy = { RelayAuthPolicy.ALWAYS }).grantForSession(relay))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aStoredDecisionTakenDuringTheUndoWindowNeedsNoGuardBecauseItOutranksTheGrant() =
|
||||
runTest {
|
||||
// Why the guard is narrowed to the policy: an override written while the snackbar was up
|
||||
// is ranked above the grant, so restoring the grant cannot undo the user's newer answer.
|
||||
val ledger = ledger()
|
||||
|
||||
ledger.revokeSessionGrant(relay)
|
||||
ledger.setDecision(relay, RelayAuthDecision.DENY)
|
||||
|
||||
assertTrue(ledger.grantForSession(relay))
|
||||
assertEquals(RelayAuthVerdict.DENY, ledger.decide(askable(relay)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user