Add custom token support

This commit is contained in:
2026-09-13 01:01:48 +02:00
parent 1b77cdd165
commit 48229f15a2
12 changed files with 680 additions and 305 deletions
+105
View File
@@ -1624,3 +1624,108 @@ func TestUploadPageKeepsTheOneOffTokenField(t *testing.T) {
t.Error("the form does not carry the server-rendered size limit")
}
}
// A chosen passphrase has to work everywhere a generated token does: at the
// login form, on an upload, and as a session.
func TestChosenPassphraseWorksEndToEnd(t *testing.T) {
h := newHarness(t, nil)
const passphrase = "godot-friends-2026"
tok, err := auth.NewChosen("memorable", passphrase)
if err != nil {
t.Fatal(err)
}
tok.AllowVanity = true
if err := h.tokens.Add(tok); err != nil {
t.Fatal(err)
}
resp := h.postForm(t, "/login", url.Values{"token": {passphrase}, "persist": {"1"}}, nil)
resp.Body.Close()
if resp.StatusCode != http.StatusSeeOther {
t.Fatalf("login with a passphrase => %s", resp.Status)
}
cookie := findCookie(resp, tokenCookie)
if cookie == nil {
t.Fatal("no session was started")
}
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", strings.NewReader("x"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Vanity", "chosen-upload")
req.AddCookie(cookie)
up, err := h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
if up.StatusCode != http.StatusCreated {
t.Fatalf("upload with a passphrase session => %s", up.Status)
}
if res := decode[uploadResult](t, up); res.ID != "chosen-upload" {
t.Errorf("id = %q, want chosen-upload", res.ID)
}
}
// Deriving a passphrase is expensive by design, which makes an unverified
// credential an amplifier unless the work is charged for. Junk must not be
// able to buy unlimited derivations.
func TestUnverifiedCredentialsCannotForceUnlimitedDerivations(t *testing.T) {
h := newHarness(t, nil)
tok, err := auth.NewChosen("memorable", "a-chosen-passphrase")
if err != nil {
t.Fatal(err)
}
if err := h.tokens.Add(tok); err != nil {
t.Fatal(err)
}
h.authLimiter = newLimiter(1, 3)
// Distinct junk on every request, so the memo never answers.
for i := range 6 {
req, _ := http.NewRequest("GET", h.ts.URL+"/", nil)
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: fmt.Sprintf("junk-%d", i)})
resp, err := h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
// The page still renders; it just renders as anonymous.
if resp.StatusCode != http.StatusOK {
t.Fatalf("page %d => %s", i, resp.Status)
}
}
if h.tokens.Resolved("junk-5") {
t.Error("a derivation ran past the budget")
}
}
// The memo means a live session pays the derivation once, not per request.
func TestPassphraseSessionsAreMemoised(t *testing.T) {
h := newHarness(t, nil)
const passphrase = "a-chosen-passphrase"
tok, err := auth.NewChosen("memorable", passphrase)
if err != nil {
t.Fatal(err)
}
if err := h.tokens.Add(tok); err != nil {
t.Fatal(err)
}
if h.tokens.Resolved(passphrase) {
t.Fatal("resolved before anything verified it")
}
resp := h.get(t, "/", "")
resp.Body.Close()
req, _ := http.NewRequest("GET", h.ts.URL+"/", nil)
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: passphrase})
first, err := h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
first.Body.Close()
if !h.tokens.Resolved(passphrase) {
t.Error("the session was not memoised, so every request would derive again")
}
}