From cf6c343fd5b421ae0c45cc0b10249f6660c1fa95 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Fri, 21 Aug 2026 19:59:40 -0400 Subject: [PATCH] refactor: use encoding/json/v2 --- internal/http/headers.go | 2 +- internal/ui/login/qr/msg.go | 87 ++++++++++++-------------------- internal/ui/login/qr/msg_test.go | 30 +++++++++++ 3 files changed, 64 insertions(+), 55 deletions(-) create mode 100644 internal/ui/login/qr/msg_test.go diff --git a/internal/http/headers.go b/internal/http/headers.go index dc9ea3f..f04b5fb 100644 --- a/internal/http/headers.go +++ b/internal/http/headers.go @@ -2,7 +2,7 @@ package http import ( "encoding/base64" - "encoding/json" + json "encoding/json/v2" "log/slog" stdHttp "net/http" diff --git a/internal/ui/login/qr/msg.go b/internal/ui/login/qr/msg.go index b9ff282..483fd75 100644 --- a/internal/ui/login/qr/msg.go +++ b/internal/ui/login/qr/msg.go @@ -6,7 +6,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" + json "encoding/json/v2" "errors" "strings" "time" @@ -90,60 +90,39 @@ func listen(conn *websocket.Conn) tview.Cmd { return errMsg(err) } - var payload struct { - Op string `json:"op"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } + return decodeMessage(data) + } +} - switch payload.Op { - case "hello": - var payload struct { - HeartbeatInterval int `json:"heartbeat_interval"` - TimeoutMS int `json:"timeout_ms"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } - return helloMsg{heartbeatInterval: payload.HeartbeatInterval, timeoutMS: payload.TimeoutMS} - case "nonce_proof": - var payload struct { - EncryptedNonce string `json:"encrypted_nonce"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } - return nonceProofMsg{encryptedNonce: payload.EncryptedNonce} - case "pending_remote_init": - var payload struct { - Fingerprint string `json:"fingerprint"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } - return pendingRemoteInitMsg{fingerprint: payload.Fingerprint} - case "pending_ticket": - var payload struct { - EncryptedUserPayload string `json:"encrypted_user_payload"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } - return pendingTicketMsg{encryptedUserPayload: payload.EncryptedUserPayload} - case "cancel": - return cancelMsg{} - case "pending_login": - var payload struct { - Ticket string `json:"ticket"` - } - if err := json.Unmarshal(data, &payload); err != nil { - return errMsg(err) - } - return pendingLoginMsg{ticket: payload.Ticket} - default: - return nil - } +func decodeMessage(data []byte) tview.Msg { + var payload struct { + Op string `json:"op"` + HeartbeatInterval int `json:"heartbeat_interval"` + TimeoutMS int `json:"timeout_ms"` + EncryptedNonce string `json:"encrypted_nonce"` + Fingerprint string `json:"fingerprint"` + EncryptedUserPayload string `json:"encrypted_user_payload"` + Ticket string `json:"ticket"` + } + if err := json.Unmarshal(data, &payload); err != nil { + return errMsg(err) + } + + switch payload.Op { + case "hello": + return helloMsg{heartbeatInterval: payload.HeartbeatInterval, timeoutMS: payload.TimeoutMS} + case "nonce_proof": + return nonceProofMsg{encryptedNonce: payload.EncryptedNonce} + case "pending_remote_init": + return pendingRemoteInitMsg{fingerprint: payload.Fingerprint} + case "pending_ticket": + return pendingTicketMsg{encryptedUserPayload: payload.EncryptedUserPayload} + case "cancel": + return cancelMsg{} + case "pending_login": + return pendingLoginMsg{ticket: payload.Ticket} + default: + return nil } } diff --git a/internal/ui/login/qr/msg_test.go b/internal/ui/login/qr/msg_test.go new file mode 100644 index 0000000..73fecfa --- /dev/null +++ b/internal/ui/login/qr/msg_test.go @@ -0,0 +1,30 @@ +package qr + +import "testing" + +func TestDecodeMessage(t *testing.T) { + tests := []struct { + data string + want any + }{ + {`{"op":"hello","heartbeat_interval":100,"timeout_ms":200}`, helloMsg{heartbeatInterval: 100, timeoutMS: 200}}, + {`{"op":"nonce_proof","encrypted_nonce":"nonce"}`, nonceProofMsg{encryptedNonce: "nonce"}}, + {`{"op":"pending_remote_init","fingerprint":"fingerprint"}`, pendingRemoteInitMsg{fingerprint: "fingerprint"}}, + {`{"op":"pending_ticket","encrypted_user_payload":"payload"}`, pendingTicketMsg{encryptedUserPayload: "payload"}}, + {`{"op":"cancel"}`, cancelMsg{}}, + {`{"op":"pending_login","ticket":"ticket"}`, pendingLoginMsg{ticket: "ticket"}}, + {`{"op":"unknown"}`, nil}, + } + + for _, test := range tests { + if got := decodeMessage([]byte(test.data)); got != test.want { + t.Errorf("decodeMessage(%s) = %#v, want %#v", test.data, got, test.want) + } + } +} + +func TestDecodeMessageRejectsDuplicateNames(t *testing.T) { + if _, ok := decodeMessage([]byte(`{"op":"cancel","op":"hello"}`)).(errMsg); !ok { + t.Fatal("duplicate name was accepted") + } +}