Add custom token support
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user