taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

commit ebb1f9a89b279fd6fdf0297c9c52cdf3a08ba1cf
parent ee5c19e9edf7e4e0959becc99e97606d9e6de041
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Tue, 22 Apr 2025 13:27:17 +0200

update

Diffstat:
M.gitignore | 2+-
Mcmd/mailbox-server/main.go | 4++--
Mpkg/rest/mailbox.go | 32++++++++++++++++++++++++--------
3 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1 +1 @@ -mailbox-server +./mailbox-server diff --git a/cmd/mailbox-server/main.go b/cmd/mailbox-server/main.go @@ -28,7 +28,7 @@ import ( var m mailbox.Mailbox -func handleRequests() { +func handleRequests(m *mailbox.Mailbox) { log.Fatal(http.ListenAndServe(m.Cfg.Section("mailbox").Key("bind_to").MustString("localhost:11000"), m.Router)) } @@ -42,5 +42,5 @@ func main() { } m := mailbox.Mailbox{} m.Initialize(cfgfile) - handleRequests() + handleRequests(&m) } diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go @@ -28,6 +28,7 @@ import ( "log" "net/http" "os" + "strings" "time" gnunetutil "git.gnunet.org/gnunet-go.git/pkg/util" @@ -57,9 +58,14 @@ type Mailbox struct { Merchant merchant.Merchant } -type identityMessage struct { +type IdentityMessage struct { + // Internal ID primary key (GORM thingy). + // Not returned in JSON + ID int64 `json:"-"` + // Public DH key used to encrypt the body. Must be fresh // and only used once (ephemeral). + // FIXME: Do we want to use HPKE DHKEM X25519 + ChaChaPoly? EphemeralKey string `json:"ephemeral_key"` // Encrypted message. Must be exactly 256-32 bytes long. @@ -171,6 +177,9 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) return } + // Add ETag of first message ID + etag := entries[0].ID + w.Header().Add("ETag", fmt.Sprintf("%d", etag)) for _, entry := range entries { eph, err := gnunetutil.DecodeStringToBinary(entry.EphemeralKey, 32) if err != nil { @@ -182,17 +191,15 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - entry.Read = true w.Write(eph) w.Write(body) } - m.Db.Save(&entries) w.WriteHeader(http.StatusOK) } func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - var msg identityMessage + var msg IdentityMessage var entry inboxEntry if r.Body == nil { http.Error(w, "No request body", 400) @@ -211,7 +218,7 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { return } err = m.Db.First(&entry, "h_mailbox = ? AND ephemeral_key = ? AND body = ?", vars["h_mailbox"], msg.EphemeralKey, msg.Body).Error - // FIXME + // FIXME actual cost cost, _ := talerutil.ParseAmount("KUDOS:1") if err != nil { entry.HMailbox = vars["h_mailbox"] @@ -261,6 +268,15 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusInternalServerError) return } + etag_expected := r.Header.Get("If-Match") + if etag_expected == "" { + http.Error(w, "If-Match header missing", 400) + return + } + if strings.Contains(etag_expected, ",") { + http.Error(w, "If-Match contains multiple values", 400) + return + } pkey, err := gnunetutil.DecodeStringToBinary(vars["mailbox"], 32) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -281,7 +297,7 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) size := make([]byte, 4) binary.BigEndian.PutUint32(size, 64+4+4) purp := make([]byte, 4) - binary.BigEndian.PutUint32(purp, 23) // FIXME purpose + binary.BigEndian.PutUint32(purp, 23) // FIXME purpose in GANA signed_msg.Write(size) signed_msg.Write(purp) signed_msg.Write(checksum) @@ -292,7 +308,7 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) h := sha512.New() h.Write(pkey) h_mailbox := gnunetutil.EncodeBinaryToString(h.Sum(nil)) - err = m.Db.Where("h_mailbox = ? AND read = ? LIMIT ?", h_mailbox, true, msg.Count).Find(&entries).Error + err = m.Db.Where("h_mailbox = ? AND ID >= ? LIMIT ?", h_mailbox, etag_expected, msg.Count).Find(&entries).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) return @@ -316,7 +332,7 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) h_all.Write(eph) h_all.Write(body) } - if 0 != bytes.Compare(h_all.Sum(nil), checksum) { + if !bytes.Equal(h_all.Sum(nil), checksum) { w.WriteHeader(http.StatusNotFound) return }