Add KeepOrg sync server: accounts, nested groups, versioned KDBX sync
Why: collaborative KeePass use needs one arbiter of "who wrote last" that never sees a master password, plus access control that follows an org chart. What: stdlib-only Go server. Metadata is a single JSON document with one writer and atomic commit; KDBX payloads are versioned blobs on disk. Access comes from a group tree where a grant on a group reaches every member below it. Login is password+TOTP, invite links, or OIDC through a browser-approved device flow. Pushes carry If-Match and are arbitrated by a per-database merge strategy (server-wins by default). Updates are pushed to clients over SSE. Proved: go test ./internal/... covers RFC 6238 vectors, ACL inheritance and isolation, store rollback, blob pruning, every push verdict, and an end-to-end HTTP pass over login, conflict, force, read-only grants, invite redemption, TOTP enrolment, device login and the event stream.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"cc.tiredsysadmin.keeporg/internal/store"
|
||||
)
|
||||
|
||||
func TestDecidePushRequiresIfMatch(t *testing.T) {
|
||||
v := decidePush(3, "", store.StrategyServerWins)
|
||||
if v.Accept || v.Status != http.StatusPreconditionRequired {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecidePushAcceptsCurrentVersion(t *testing.T) {
|
||||
for _, m := range []string{`"3"`, "3", ` "3" `} {
|
||||
if v := decidePush(3, m, store.StrategyServerWins); !v.Accept {
|
||||
t.Fatalf("If-Match %q rejected: %+v", m, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecidePushRejectsStaleVersion(t *testing.T) {
|
||||
v := decidePush(4, `"3"`, store.StrategyServerWins)
|
||||
if v.Accept || v.Status != http.StatusConflict || v.Code != "conflict" {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceOnlyAllowedForClientWins(t *testing.T) {
|
||||
if v := decidePush(4, "*", store.StrategyClientWins); !v.Accept {
|
||||
t.Fatalf("client-wins force rejected: %+v", v)
|
||||
}
|
||||
for _, s := range []store.Strategy{store.StrategyServerWins, store.StrategyManual} {
|
||||
v := decidePush(4, "*", s)
|
||||
if v.Accept || v.Code != "force_not_allowed" {
|
||||
t.Fatalf("force under %s = %+v", s, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecidePushRejectsJunkIfMatch(t *testing.T) {
|
||||
v := decidePush(1, `"abc"`, store.StrategyServerWins)
|
||||
if v.Accept || v.Status != http.StatusBadRequest {
|
||||
t.Fatalf("verdict = %+v", v)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user