mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor(quartz/relay): move auth identity from policy into connection scope
Authentication state (who is logged in on a connection) is connection scope, not a policy decision. It was stored on FullAuthPolicy, which forced the AuthScopedPolicy marker, the PolicyStack union, and a downcast in RequestContext just to route it back out as scope. Now the engine owns it: RelaySession holds the authenticatedUsers set behind the (now public) requestContext; the data plane and policies read it through RequestContext. The policy stays pure decision — - onConnect(scope, send): a per-connection policy captures the read-only scope to gate on; shared singletons ignore it. - onAuthenticated(): Boolean: the policy's vote on whether to record the verified pubkey (default false, so blind-accept policies never record an unverified identity). FullAuthPolicy runs authorize() then votes true. - RelaySession.handleAuth performs the single, engine-side commit after the whole chain approves and a verifying policy votes to record. FullAuthPolicy keeps all auth logic (challenge, accept(AuthCmd), gating, authorize) and gains a protected authenticatedUsers accessor over the scope for subclasses (restricted content / filter rewrite). Deletes AuthScopedPolicy, PolicyStack.authenticatedUsers, and the RequestContext downcast.
This commit is contained in:
+4
-2
@@ -151,8 +151,10 @@ non-storage relay: a single shared `EventSource` instance can serve
|
||||
caller-relative results (trust/relevance scored from the viewer's perspective,
|
||||
"for-you" feeds), restricted content (a pubkey's DMs returned only to that
|
||||
pubkey), or per-connection tenancy — without smuggling auth state through a
|
||||
side channel. `ctx.authenticatedUsers` reads live from the connection's policy,
|
||||
so a REQ that arrives after the AUTH sees the freshly authenticated pubkey(s).
|
||||
side channel. `ctx.authenticatedUsers` is a live view of the engine-owned
|
||||
connection scope, so a REQ that arrives after the AUTH sees the freshly
|
||||
recorded pubkey(s). (The engine records them on a successful NIP-42 AUTH; the
|
||||
policy reads the same scope to gate.)
|
||||
|
||||
For per-connection state richer than the pubkey (e.g. a backend session token
|
||||
minted in `FullAuthPolicy.authorize`), downcast `ctx.policy` to your own policy
|
||||
|
||||
+31
-16
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.server
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.ClosedMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage
|
||||
@@ -76,15 +77,24 @@ class RelaySession(
|
||||
private val subscriptions = LargeCache<String, Job>()
|
||||
|
||||
/**
|
||||
* The per-connection context handed to the [store] on every REQ/COUNT so a
|
||||
* source can see who is asking. [RequestContext.authenticatedUsers] reads
|
||||
* live from [policy], so a REQ that arrives after a NIP-42 AUTH sees the
|
||||
* freshly authenticated pubkey(s).
|
||||
* The authenticated-identity store for this connection. The engine is the
|
||||
* only writer (committed in [handleAuth] on a successful NIP-42 AUTH); the
|
||||
* policy and the data plane read it through [requestContext].
|
||||
*/
|
||||
private val requestContext =
|
||||
private val authenticatedUsers = mutableSetOf<HexKey>()
|
||||
|
||||
/**
|
||||
* The per-connection scope. Handed to the [policy] at connect (so gating
|
||||
* policies can read the authenticated users) and to the [store] on every
|
||||
* REQ/COUNT (so a source can see who is asking). [RequestContext.authenticatedUsers]
|
||||
* is a live view of [authenticatedUsers], so a REQ after a NIP-42 AUTH sees
|
||||
* the freshly recorded pubkey(s).
|
||||
*/
|
||||
val requestContext: RequestContext =
|
||||
object : RequestContext {
|
||||
override val connectionId = id
|
||||
override val policy = this@RelaySession.policy
|
||||
override val authenticatedUsers: Set<HexKey> get() = this@RelaySession.authenticatedUsers
|
||||
}
|
||||
|
||||
/** NIP-77 negentropy state for this connection. */
|
||||
@@ -223,16 +233,21 @@ class RelaySession(
|
||||
|
||||
// The whole policy chain validated the AUTH. onAuthenticated runs any
|
||||
// post-verification I/O (e.g. exchanging the verified event for a
|
||||
// backend token) AND is where a policy commits the authentication, so a
|
||||
// throw here cleanly fails the login — nothing was committed to undo.
|
||||
try {
|
||||
policy.onAuthenticated(cmd.event.pubKey, cmd.event)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
send(OkMessage.rejected(cmd.event.id, MachineReadablePrefix.ERROR, e.message ?: "authentication failed"))
|
||||
return
|
||||
}
|
||||
// backend token) and votes on whether to record the identity. A throw
|
||||
// here cleanly fails the login — the engine records nothing.
|
||||
val record =
|
||||
try {
|
||||
policy.onAuthenticated(cmd.event.pubKey, cmd.event)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
send(OkMessage.rejected(cmd.event.id, MachineReadablePrefix.ERROR, e.message ?: "authentication failed"))
|
||||
return
|
||||
}
|
||||
|
||||
// Single, engine-side commit into the connection scope — after the full
|
||||
// chain approved and a verifying policy voted to record.
|
||||
if (record) authenticatedUsers.add(cmd.event.pubKey)
|
||||
|
||||
send(OkMessage(cmd.event.id, true, ""))
|
||||
}
|
||||
@@ -299,7 +314,7 @@ class RelaySession(
|
||||
}
|
||||
|
||||
init {
|
||||
policy.onConnect(::send)
|
||||
policy.onConnect(requestContext, ::send)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+11
-11
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.server.backend
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.policies.AuthScopedPolicy
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.policies.IRelayPolicy
|
||||
|
||||
/**
|
||||
@@ -30,11 +29,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.server.policies.IRelayPolicy
|
||||
* shared source can tailor its answer to the caller without smuggling state in
|
||||
* through a side channel.
|
||||
*
|
||||
* This is what makes NIP-42 useful on a non-storage relay: the engine already
|
||||
* knows the authenticated pubkey(s) (the connection's [IRelayPolicy] records
|
||||
* them), and [RequestContext] is the path that carries them to the code that
|
||||
* actually produces the events. With it, the same `EventSource` instance can
|
||||
* serve:
|
||||
* This is the connection scope: the engine owns it (one per
|
||||
* [com.vitorpamplona.quartz.nip01Core.relay.server.RelaySession]) and records
|
||||
* the authenticated pubkey(s) into it on a successful NIP-42 AUTH. That is what
|
||||
* makes NIP-42 useful on a non-storage relay — [RequestContext] is the path
|
||||
* that carries the caller's identity to the code that produces the events. With
|
||||
* it, the same `EventSource` instance can serve:
|
||||
*
|
||||
* - **caller-relative results** — trust/relevance scored from
|
||||
* [authenticatedUsers]'s perspective ("for-you" feeds, follow-aware search);
|
||||
@@ -60,10 +60,10 @@ interface RequestContext {
|
||||
val policy: IRelayPolicy
|
||||
|
||||
/**
|
||||
* The pubkeys that have authenticated on this connection via NIP-42, read
|
||||
* live from [policy]. Empty when the connection is unauthenticated or the
|
||||
* policy does not track auth (i.e. is not an [AuthScopedPolicy]). This
|
||||
* accessor encapsulates that downcast so a source needn't repeat it.
|
||||
* The pubkeys that have authenticated on this connection via NIP-42. Empty
|
||||
* when the connection is unauthenticated. Backed by the engine-owned scope
|
||||
* and read live, so a REQ that arrives after a successful AUTH sees the
|
||||
* freshly recorded pubkey(s).
|
||||
*/
|
||||
val authenticatedUsers: Set<HexKey> get() = (policy as? AuthScopedPolicy)?.authenticatedUsers ?: emptySet()
|
||||
val authenticatedUsers: Set<HexKey>
|
||||
}
|
||||
|
||||
-40
@@ -1,40 +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.quartz.nip01Core.relay.server.policies
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
/**
|
||||
* Opt-in mixin for the policies that actually track NIP-42 authentication, so
|
||||
* the auth concept stays out of the universal [IRelayPolicy]. A relay that does
|
||||
* no auth never implements this; only [FullAuthPolicy] (and [PolicyStack], which
|
||||
* unions any auth-tracking members) do.
|
||||
*
|
||||
* The engine surfaces it to the data plane through
|
||||
* [com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext.authenticatedUsers],
|
||||
* which downcasts the connection's policy to this interface — so a source sees
|
||||
* the authenticated pubkey(s) without the base policy interface knowing about
|
||||
* NIP-42.
|
||||
*/
|
||||
interface AuthScopedPolicy {
|
||||
/** The pubkeys that have authenticated on this connection via NIP-42. */
|
||||
val authenticatedUsers: Set<HexKey>
|
||||
}
|
||||
+37
-19
@@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
@@ -40,11 +41,13 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
*
|
||||
* Implements the full NIP-42 challenge/verify handshake: [onConnect] sends the
|
||||
* [challenge] and [accept] (AuthCmd) validates the returned event (expiration,
|
||||
* freshness, challenge match, relay match). Crucially, [accept] does NOT mutate
|
||||
* state — the pubkey is recorded in [authenticatedUsers] only by [onAuthenticated],
|
||||
* which the engine calls once [accept] *and* the rest of the policy chain have
|
||||
* approved the AUTH. That single, late commit is why there is no rollback to
|
||||
* reason about: a rejected AUTH simply never reaches it.
|
||||
* freshness, challenge match, relay match). This policy runs the auth *logic*
|
||||
* but does not *own* the authenticated-identity store: the engine-owned
|
||||
* connection [scope] holds it. [accept] does NOT mutate state, and
|
||||
* [onAuthenticated] only votes `true` (after [authorize]) — the engine performs
|
||||
* the single recording into the scope once the whole chain has approved. Gating
|
||||
* decisions read [scope].`authenticatedUsers`. A rejected AUTH simply never
|
||||
* reaches [onAuthenticated], so there is no rollback to reason about.
|
||||
*
|
||||
* To bridge to an external auth system, override [authorize] (a `suspend` hook)
|
||||
* and do the post-verification I/O there — e.g. exchange the verified event for
|
||||
@@ -53,18 +56,34 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
*/
|
||||
open class FullAuthPolicy(
|
||||
val relay: NormalizedRelayUrl,
|
||||
) : IRelayPolicy,
|
||||
AuthScopedPolicy {
|
||||
) : IRelayPolicy {
|
||||
/** The challenge string sent to this client for NIP-42 authentication. */
|
||||
val challenge: String = RandomInstance.randomChars(32)
|
||||
|
||||
/** Set of pubkeys that have successfully authenticated on this session. */
|
||||
override val authenticatedUsers = mutableSetOf<HexKey>()
|
||||
/**
|
||||
* The engine-owned connection scope, captured at [onConnect]. Read-only
|
||||
* here: this policy reads [RequestContext.authenticatedUsers] to gate, while
|
||||
* the engine is the only writer. Held safely because a [FullAuthPolicy] is
|
||||
* built fresh per connection.
|
||||
*/
|
||||
private lateinit var scope: RequestContext
|
||||
|
||||
/** Returns true if at least one pubkey has authenticated. */
|
||||
/**
|
||||
* The pubkeys authenticated on this connection, read from the engine-owned
|
||||
* scope. Exposed to subclasses so they can gate or rewrite on the caller's
|
||||
* identity (restricted content, caller-relative filters) — the same set the
|
||||
* data plane sees via [RequestContext.authenticatedUsers].
|
||||
*/
|
||||
protected val authenticatedUsers: Set<HexKey> get() = scope.authenticatedUsers
|
||||
|
||||
/** Returns true if at least one pubkey has authenticated on this connection. */
|
||||
fun isAuthenticated(): Boolean = authenticatedUsers.isNotEmpty()
|
||||
|
||||
override fun onConnect(send: (Message) -> Unit) {
|
||||
override fun onConnect(
|
||||
scope: RequestContext,
|
||||
send: (Message) -> Unit,
|
||||
) {
|
||||
this.scope = scope
|
||||
send(AuthMessage(challenge))
|
||||
}
|
||||
|
||||
@@ -91,19 +110,18 @@ open class FullAuthPolicy(
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the authentication. The engine calls this only after [accept] and
|
||||
* the whole policy chain have approved the AUTH, so this is the single point
|
||||
* where the pubkey is recorded. It runs [authorize] first (which may throw
|
||||
* to reject) and records the pubkey only on success — the connection is
|
||||
* never left authenticated behind a failing `OK`. `final`: override
|
||||
* [authorize], not this.
|
||||
* Votes to record the authentication. The engine calls this only after
|
||||
* [accept] and the whole policy chain have approved the AUTH. It runs
|
||||
* [authorize] first (which may throw to reject — the engine then records
|
||||
* nothing) and returns `true` so the engine records [pubKey] into the
|
||||
* connection scope. `final`: override [authorize], not this.
|
||||
*/
|
||||
final override suspend fun onAuthenticated(
|
||||
pubKey: HexKey,
|
||||
event: RelayAuthEvent,
|
||||
) {
|
||||
): Boolean {
|
||||
authorize(pubKey, event)
|
||||
authenticatedUsers.add(pubKey)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+27
-10
@@ -28,13 +28,24 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
|
||||
/**
|
||||
* Defines custom behavior for this relay.
|
||||
*/
|
||||
interface IRelayPolicy {
|
||||
fun onConnect(send: (Message) -> Unit)
|
||||
/**
|
||||
* Called once when the connection opens. [scope] is the engine-owned,
|
||||
* read-only connection scope (id + authenticated users) — a per-connection
|
||||
* policy may retain it to make later auth-aware decisions; shared singleton
|
||||
* policies must ignore it and stay stateless. [send] pushes a message to the
|
||||
* client (e.g. a NIP-42 AUTH challenge).
|
||||
*/
|
||||
fun onConnect(
|
||||
scope: RequestContext,
|
||||
send: (Message) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Evaluates whether an incoming EVENT command should be accepted.
|
||||
@@ -70,24 +81,30 @@ interface IRelayPolicy {
|
||||
|
||||
/**
|
||||
* Called once an AUTH command has been [accept]ed by this policy *and* the
|
||||
* rest of the policy chain, before the success `OK` is sent. This is where
|
||||
* a policy commits the authentication and/or runs post-verification side
|
||||
* effects that need network or disk I/O — e.g. exchanging the verified
|
||||
* NIP-42 event for a backend session token — without leaking that logic into
|
||||
* the transport layer.
|
||||
* rest of the policy chain, before the success `OK` is sent. Run any
|
||||
* post-verification side effects that need network or disk I/O here — e.g.
|
||||
* exchanging the verified NIP-42 event for a backend session token — without
|
||||
* leaking that logic into the transport layer.
|
||||
*
|
||||
* The engine — not the policy — owns the authenticated-identity store. The
|
||||
* return value is this policy's vote on whether [pubKey] should be recorded
|
||||
* as authenticated on the connection: return `true` only if this policy
|
||||
* actually verified the identity. The default returns `false`, so a policy
|
||||
* that does not authenticate (e.g. a pass-through or a blind-accept) never
|
||||
* causes an unverified pubkey to be recorded.
|
||||
*
|
||||
* Because it runs only after the whole chain approved the AUTH, throwing
|
||||
* here cleanly fails the login: the AUTH becomes `OK false` and, since the
|
||||
* commit lives here too, the connection is never left authenticated. The
|
||||
* default implementation does nothing.
|
||||
* here cleanly fails the login: the AUTH becomes `OK false` and the engine
|
||||
* records nothing.
|
||||
*
|
||||
* @param pubKey The pubkey being authenticated.
|
||||
* @param event The verified NIP-42 auth event.
|
||||
* @return `true` to have the engine record [pubKey] as authenticated.
|
||||
*/
|
||||
suspend fun onAuthenticated(
|
||||
pubKey: HexKey,
|
||||
event: RelayAuthEvent,
|
||||
) {}
|
||||
): Boolean = false
|
||||
|
||||
/**
|
||||
* Inspects a raw inbound message before it is parsed. Return a reason
|
||||
|
||||
+5
-1
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.AuthCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext
|
||||
|
||||
/**
|
||||
* Convenience base that accepts everything by default. Subclasses
|
||||
@@ -37,7 +38,10 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
* instantiate this directly.
|
||||
*/
|
||||
open class PassThroughPolicy : IRelayPolicy {
|
||||
override fun onConnect(send: (Message) -> Unit) {}
|
||||
override fun onConnect(
|
||||
scope: RequestContext,
|
||||
send: (Message) -> Unit,
|
||||
) {}
|
||||
|
||||
override fun accept(cmd: EventCmd): PolicyResult<EventCmd> = PolicyResult.Accepted(cmd)
|
||||
|
||||
|
||||
+11
-10
@@ -28,20 +28,19 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
|
||||
class PolicyStack(
|
||||
vararg policies: IRelayPolicy,
|
||||
) : IRelayPolicy,
|
||||
AuthScopedPolicy {
|
||||
) : IRelayPolicy {
|
||||
val policies = policies.toList()
|
||||
|
||||
/** Union of the authenticated pubkeys across the auth-tracking members. */
|
||||
override val authenticatedUsers: Set<HexKey>
|
||||
get() = policies.filterIsInstance<AuthScopedPolicy>().flatMapTo(mutableSetOf()) { it.authenticatedUsers }
|
||||
|
||||
override fun onConnect(send: (Message) -> Unit) {
|
||||
policies.forEach { it.onConnect(send) }
|
||||
override fun onConnect(
|
||||
scope: RequestContext,
|
||||
send: (Message) -> Unit,
|
||||
) {
|
||||
policies.forEach { it.onConnect(scope, send) }
|
||||
}
|
||||
|
||||
override fun accept(cmd: EventCmd) = runPolicies(cmd) { p, c -> p.accept(c) }
|
||||
@@ -55,8 +54,10 @@ class PolicyStack(
|
||||
override suspend fun onAuthenticated(
|
||||
pubKey: HexKey,
|
||||
event: RelayAuthEvent,
|
||||
) {
|
||||
policies.forEach { it.onAuthenticated(pubKey, event) }
|
||||
): Boolean {
|
||||
// Run every member (side effects) and record iff any one verified the
|
||||
// identity. `fold` keeps the call on the left so no member is skipped.
|
||||
return policies.fold(false) { recorded, p -> p.onAuthenticated(pubKey, event) || recorded }
|
||||
}
|
||||
|
||||
override fun acceptMessage(message: String): String? = policies.firstNotNullOfOrNull { it.acceptMessage(message) }
|
||||
|
||||
+5
-1
@@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.AuthCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.server.backend.RequestContext
|
||||
|
||||
/**
|
||||
* Verifies the Schnorr signature + id hash of every incoming
|
||||
@@ -43,7 +44,10 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
open class VerifyEventsAndAuthPolicy(
|
||||
private val verifyEvents: Boolean,
|
||||
) : IRelayPolicy {
|
||||
override fun onConnect(send: (Message) -> Unit) { }
|
||||
override fun onConnect(
|
||||
scope: RequestContext,
|
||||
send: (Message) -> Unit,
|
||||
) { }
|
||||
|
||||
override fun accept(cmd: EventCmd) =
|
||||
if (!verifyEvents || cmd.event.verify()) {
|
||||
|
||||
+6
-6
@@ -168,7 +168,7 @@ class NostrServerAuthTest {
|
||||
assertEquals(1, okMessages.size)
|
||||
assertTrue(okMessages[0].contains(",true,"))
|
||||
assertTrue((session.policy as FullAuthPolicy).isAuthenticated())
|
||||
assertTrue(session.policy.authenticatedUsers.contains(pubkey))
|
||||
assertTrue(session.requestContext.authenticatedUsers.contains(pubkey))
|
||||
|
||||
server.close()
|
||||
}
|
||||
@@ -313,7 +313,7 @@ class NostrServerAuthTest {
|
||||
assertTrue(okMessages[0].contains(",true,"))
|
||||
assertTrue(okMessages[1].contains(",true,"))
|
||||
|
||||
val authedPubkeys = (session.policy as FullAuthPolicy).authenticatedUsers
|
||||
val authedPubkeys = session.requestContext.authenticatedUsers
|
||||
assertEquals(2, authedPubkeys.size)
|
||||
assertTrue(authedPubkeys.contains(pubkey))
|
||||
assertTrue(authedPubkeys.contains(pubkey2))
|
||||
@@ -596,7 +596,7 @@ class NostrServerAuthTest {
|
||||
// still-authenticated connection would be an auth bypass.
|
||||
val authPolicy = session.policy as FullAuthPolicy
|
||||
assertFalse(authPolicy.isAuthenticated())
|
||||
assertFalse(authPolicy.authenticatedUsers.contains(pubkey))
|
||||
assertFalse(session.requestContext.authenticatedUsers.contains(pubkey))
|
||||
|
||||
server.close()
|
||||
}
|
||||
@@ -653,7 +653,7 @@ class NostrServerAuthTest {
|
||||
assertEquals(1, ok.size)
|
||||
assertTrue(ok[0].contains(",false,"))
|
||||
assertFalse(auth.isAuthenticated())
|
||||
assertFalse(auth.authenticatedUsers.contains(pubkey))
|
||||
assertFalse(session.requestContext.authenticatedUsers.contains(pubkey))
|
||||
|
||||
// And a privileged REQ is still gated.
|
||||
session.receive("""["REQ","sub1",{"kinds":[1]}]""")
|
||||
@@ -686,12 +686,12 @@ class NostrServerAuthTest {
|
||||
val msg = OptimizedJsonMapper.fromJsonToMessage(collector.messages[0]) as AuthMessage
|
||||
|
||||
session.receive(authJson(authEvent(challenge = msg.challenge)))
|
||||
assertTrue(auth.authenticatedUsers.contains(pubkey))
|
||||
assertTrue(session.requestContext.authenticatedUsers.contains(pubkey))
|
||||
|
||||
// Second AUTH for the SAME pubkey, rejected downstream.
|
||||
session.receive(authJson(authEvent(challenge = msg.challenge)))
|
||||
assertTrue(auth.isAuthenticated())
|
||||
assertTrue(auth.authenticatedUsers.contains(pubkey))
|
||||
assertTrue(session.requestContext.authenticatedUsers.contains(pubkey))
|
||||
|
||||
server.close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user