diff --git a/apps/node-agent/internal/node/pairing.go b/apps/node-agent/internal/node/pairing.go index 024f7ae..8918412 100644 --- a/apps/node-agent/internal/node/pairing.go +++ b/apps/node-agent/internal/node/pairing.go @@ -81,6 +81,13 @@ func OpenPairing(store *Store, dir, version string, inventory func() Inventory) if (p.state.Phase == "inviting" || p.state.Phase == "pending") && p.state.Invitation == nil { return nil, errors.New("incomplete invitation") } + if (p.state.Phase == "paired" || p.state.Phase == "revoked") && p.state.Invitation != nil { + next := p.state + next.Invitation = nil + if e := p.save(next); e != nil { + return nil, e + } + } return p, nil } func savePrivateJSON(path string, value any) error { @@ -186,6 +193,8 @@ func (p *Pairing) invite(address string) (map[string]any, error) { if e := p.save(PairState{Schema: PairSchema, Phase: "inviting", Invitation: i, Revocations: p.state.Revocations}); e != nil { return nil, e } + p.connection = "offline" + p.lastSeen = 0 id, _ := p.store.Public() code, _ := json.Marshal(map[string]any{"schema": PairSchema, "node_id": id, "id": i.ID, "endpoint": i.Endpoint, "expires_at": i.ExpiresAt, "secret": secret}) return map[string]any{"code": "MCN1." + base64.RawURLEncoding.EncodeToString(code), "expires_at": i.ExpiresAt}, nil diff --git a/apps/node-agent/internal/node/pairing_test.go b/apps/node-agent/internal/node/pairing_test.go index fbc74b3..0d4f30d 100644 --- a/apps/node-agent/internal/node/pairing_test.go +++ b/apps/node-agent/internal/node/pairing_test.go @@ -103,6 +103,9 @@ func TestPairingDurableCommitConflictAndReplay(t *testing.T) { if _, e = restored.invite("192.168.10.4"); e == nil { t.Fatal("paired Node offered another invitation") } + if restored.state.Invitation != nil { + t.Fatal("consumed invitation retained") + } raw, _ := json.Marshal(restored.status()) if strings.Contains(string(raw), i["secret"].(string)) || strings.Contains(string(raw), "client_pem") { t.Fatal("status leaked trust") diff --git a/apps/node-agent/internal/node/pairing_transport.go b/apps/node-agent/internal/node/pairing_transport.go index 6c4ac3a..717abaf 100644 --- a/apps/node-agent/internal/node/pairing_transport.go +++ b/apps/node-agent/internal/node/pairing_transport.go @@ -138,6 +138,7 @@ func (p *Pairing) remoteHandler() http.Handler { } next := p.state next.Phase = "paired" + next.Invitation = nil if p.save(next) != nil { http.Error(w, "State unavailable", 503) return diff --git a/apps/node-agent/packaging/build_deb.py b/apps/node-agent/packaging/build_deb.py index c9e5949..c99cb93 100644 --- a/apps/node-agent/packaging/build_deb.py +++ b/apps/node-agent/packaging/build_deb.py @@ -13,7 +13,7 @@ import tarfile ROOT = Path(__file__).resolve().parents[1] -VERSION = "0.5.0" +VERSION = "0.5.1" BRAND_SHA256 = "8bfee8ca9f98e0db48d98aae3af4b32493b8593e18b064a0239d513d824182af" diff --git a/apps/node-agent/ui/src/CoreConnectionView.tsx b/apps/node-agent/ui/src/CoreConnectionView.tsx index 52c1c48..7ee0b9e 100644 --- a/apps/node-agent/ui/src/CoreConnectionView.tsx +++ b/apps/node-agent/ui/src/CoreConnectionView.tsx @@ -57,7 +57,7 @@ export function CoreConnectionView({ failure }: { failure: (error: unknown) => v