Spend a hashcash stamp only when the request succeeds (closes #158)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A session request refused with 400 or 409, or a +H channel message refused for membership, a ban or +m, used to spend its stamp, so a retry with the same stamp was refused as already used. hashcash.Validator.Validate is split into Check, which spends nothing, and Claim, which records the stamp; Release undoes a claim. Session creation checks the stamp first, claims it just before CreateSession and releases it if CreateSession fails. SendChannelMessage checks the stamp after the membership, ban and +m checks. Model: opus-5-5
This commit is contained in:
@@ -29,6 +29,7 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"modernc.org/sqlite"
|
||||
"sneak.berlin/go/neoirc/internal/broker"
|
||||
api "sneak.berlin/go/neoirc/internal/cli/api"
|
||||
"sneak.berlin/go/neoirc/internal/config"
|
||||
"sneak.berlin/go/neoirc/internal/db"
|
||||
"sneak.berlin/go/neoirc/internal/globals"
|
||||
@@ -4073,6 +4074,75 @@ func TestChannelHashcashMissingBitsArg(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// postSessionWithStamp posts a session request for nick
|
||||
// carrying stamp and returns the response status.
|
||||
func postSessionWithStamp(
|
||||
t *testing.T,
|
||||
tserver *testServer,
|
||||
nick, stamp string,
|
||||
) int {
|
||||
t.Helper()
|
||||
|
||||
body, err := json.Marshal(map[string]string{
|
||||
nickKey: nick, "pow_token": stamp,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
|
||||
resp, err := doRequest(
|
||||
t, http.MethodPost, tserver.url(apiSession),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = resp.Body.Close()
|
||||
|
||||
return resp.StatusCode
|
||||
}
|
||||
|
||||
// TestRefusedSessionLeavesStampUnspent checks that a
|
||||
// session request refused with 400 or 409 does not spend
|
||||
// its hashcash stamp, so the same stamp then creates a
|
||||
// session, after which it is spent.
|
||||
func TestRefusedSessionLeavesStampUnspent(t *testing.T) {
|
||||
const bits = 2
|
||||
|
||||
tserver := newTestServerWith(t, bits, "", "")
|
||||
|
||||
// A server with no SERVER_NAME takes stamps for "neoirc".
|
||||
status := postSessionWithStamp(
|
||||
t, tserver, "taken", api.MintHashcash(bits, "neoirc"),
|
||||
)
|
||||
if status != http.StatusCreated {
|
||||
t.Fatalf("create taken: want 201, got %d", status)
|
||||
}
|
||||
|
||||
stamp := api.MintHashcash(bits, "neoirc")
|
||||
|
||||
steps := []struct {
|
||||
nick string
|
||||
want int
|
||||
}{
|
||||
{"bad nick", http.StatusBadRequest},
|
||||
{"taken", http.StatusConflict},
|
||||
{"fresh", http.StatusCreated},
|
||||
{"another", http.StatusPaymentRequired},
|
||||
}
|
||||
|
||||
for _, step := range steps {
|
||||
status = postSessionWithStamp(t, tserver, step.nick, stamp)
|
||||
if status != step.want {
|
||||
t.Fatalf(
|
||||
"nick %q: want %d, got %d",
|
||||
step.nick, step.want, status,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamesShowsHostmask(t *testing.T) {
|
||||
tserver := newTestServer(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user