Three phones on one account now produce one history, which is the main reason to have accounts beyond upload permission. GET /v1/runs returns the account's runs and says how many devices contributed; fetching and deleting resolve a run id against the caller's own devices, so an id from another account is not found rather than fetched from wherever it happens to live. The rule that needed stating: the empty account is never a group. Devices nobody has signed in on are unrelated devices that share the absence of an owner, and matching on "" would let any anonymous device read every other one's runs. Tested, along with sibling-device access working and cross-account access not. App side: authorization code with PKCE. The app is a public client - anything compiled into an APK can be read out with unzip and strings - and the redirect returns through a custom URI scheme that any app on the device may register, so an intercepted code is a real risk. PKCE makes a stolen code worthless: it can only be exchanged by presenting a verifier that never left the process. A callback whose state does not match is refused before the code is spent and before any network call, since that is exactly how someone gets a victim to complete the attacker's sign-in. Nothing from the IdP is retained. The ID token is used once to prove who is signing in and then discarded; the device credential authenticates everything afterwards. No access tokens to store, no refresh tokens to rotate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package runs
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Account scoping widens what a caller can read, so the test that matters is the one about what
|
|
// it must NOT widen: a run id from another account has to be invisible, not merely unlisted.
|
|
func TestAccountScopingDoesNotReachOtherAccounts(t *testing.T) {
|
|
s, _ := open(t, DefaultPolicy())
|
|
|
|
// Two devices on one account, one device belonging to somebody else.
|
|
mine := []string{"phone-a", "tablet-a"}
|
|
for i, d := range mine {
|
|
if _, err := s.Put(d, doc("run-"+d, AnonFull), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = i
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
if _, err := s.Put("phone-b", doc("run-secret", AnonFull), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := s.ListFor(mine)
|
|
if len(got) != 2 {
|
|
t.Fatalf("account history has %d runs, want 2", len(got))
|
|
}
|
|
for _, m := range got {
|
|
if m.ID == "run-secret" {
|
|
t.Fatal("another account's run appeared in the history")
|
|
}
|
|
}
|
|
|
|
// The decisive one: knowing the id is not enough.
|
|
if _, ok := s.OwnerOf(mine, "run-secret"); ok {
|
|
t.Fatal("a run id from another account resolved against this account's devices")
|
|
}
|
|
if owner, ok := s.OwnerOf(mine, "run-phone-a"); !ok || owner != "phone-a" {
|
|
t.Fatalf("own run did not resolve: owner=%q ok=%v", owner, ok)
|
|
}
|
|
// A sibling device's run must resolve — that is the point of the feature.
|
|
if owner, ok := s.OwnerOf(mine, "run-tablet-a"); !ok || owner != "tablet-a" {
|
|
t.Fatalf("sibling device's run did not resolve: owner=%q ok=%v", owner, ok)
|
|
}
|
|
}
|
|
|
|
func TestAccountHistoryIsNewestFirstAcrossDevices(t *testing.T) {
|
|
s, _ := open(t, DefaultPolicy())
|
|
if _, err := s.Put("phone", doc("older", AnonFull), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
if _, err := s.Put("tablet", doc("newer", AnonFull), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := s.ListFor([]string{"phone", "tablet"})
|
|
if len(got) != 2 || got[0].ID != "newer" {
|
|
t.Fatalf("not merged newest-first: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestEmptyDeviceSetSeesNothing(t *testing.T) {
|
|
s, _ := open(t, DefaultPolicy())
|
|
if _, err := s.Put("someone", doc("run-1", AnonFull), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := s.ListFor(nil); len(got) != 0 {
|
|
t.Fatalf("an empty device set returned %d runs", len(got))
|
|
}
|
|
if _, ok := s.OwnerOf(nil, "run-1"); ok {
|
|
t.Fatal("a run resolved against an empty device set")
|
|
}
|
|
}
|