taler-mailbox

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

commit 0f39be86e2f1fba35263ffb0381b06b82f518b09
parent 1d34b9880ad327f9c97d644af8dc17aade3c2c71
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Sun, 15 Feb 2026 11:16:19 +0100

Implement untested GORM replacement for versioned database

Diffstat:
Mcmd/mailbox-server/main.go | 20+++++++++++---------
Mgo.mod | 1+
Mgo.sum | 2++
Apkg/rest/db.go | 387+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/rest/mailbox.go | 249+++++++++++++++++++++++++++++++++++++++++--------------------------------------
5 files changed, 529 insertions(+), 130 deletions(-)

diff --git a/cmd/mailbox-server/main.go b/cmd/mailbox-server/main.go @@ -19,15 +19,17 @@ package main import ( + "database/sql" "flag" "fmt" - "gopkg.in/ini.v1" - "gorm.io/driver/postgres" "log" "net/http" "os" "path" + "gopkg.in/ini.v1" + + _ "github.com/lib/pq" "github.com/schanzen/taler-go/pkg/merchant" "rsc.io/getopt" mailbox "taler.net/taler-mailbox/pkg/rest" @@ -87,13 +89,13 @@ func main() { log.Printf("Failed to read config: %v", err) os.Exit(1) } - psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", - iniCfg.Section("mailbox-pq").Key("host").MustString("localhost"), - iniCfg.Section("mailbox-pq").Key("port").MustInt64(5432), - iniCfg.Section("mailbox-pq").Key("user").MustString("taler-mailbox"), - iniCfg.Section("mailbox-pq").Key("password").MustString("secret"), - iniCfg.Section("mailbox-pq").Key("db_name").MustString("taler-mailbox")) - db := postgres.Open(psqlconn) + psqlconn := iniCfg.Section("taldir-pq").Key("connection_string").MustString("postgres:///taler-directory") + + db, err := sql.Open("postgres", psqlconn) + if err != nil { + log.Panic(err) + } + defer db.Close() merchURL := iniCfg.Section("mailbox").Key("merchant_baseurl_private").MustString("http://merchant.mailbox/instances/myInstance") merchToken := iniCfg.Section("mailbox").Key("merchant_token").MustString("secretAccessToken") merch := merchant.NewMerchant(merchURL, merchToken) diff --git a/go.mod b/go.mod @@ -19,6 +19,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/lib/pq v1.11.2 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/sync v0.19.0 // indirect diff --git a/go.sum b/go.sum @@ -17,6 +17,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/pkg/rest/db.go b/pkg/rest/db.go @@ -0,0 +1,387 @@ +// This file is part of taler-mailbox, the Taler Mailbox implementation. +// Copyright (C) 2026 Martin Schanzenbach +// +// taler-mailbox is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// taler-mailbox is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// SPDX-License-Identifier: AGPL3.0-or-later + +package mailbox + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" +) + +type MailboxMetadata struct { + // ORM + Serial int64 `json:"-"` + + // ORM helper hash of signing key + HashedSigningKey string `json:"-"` + + // The mailbox signing key. + // Note that $H_MAILBOX == H(singingKey). + // Note also how this key cannot be updated + // as it identifies the mailbox. + SigningKey string `json:"signing_key"` + + // Type of key. + // Optional, as currently only + // EdDSA keys are supported. + SigningKeyType string `json:"signing_key_type"` + + // The mailbox encryption key. + // This is an HPKE public key + // in the X25519 format for use + // in a X25519-DHKEM (RFC 9180). + // Base32 crockford-encoded. + EncryptionKey string `json:"encryption_key"` + + // Type of key. + // Optional, as currently only + // X25519 keys are supported. + EncryptionKeyType string `json:"encryption_key_type"` + + // Expiration of this mapping (UNIX Epoch seconds). + Expiration int64 `json:"expiration" gorm:"embedded;embeddedPrefix:expiration_"` + + // Keyoxide claim proof + KoxClaimProof string `json:"kox_claim_proof,omitempty"` +} + +type PendingMailboxRegistration struct { + // ORM + Serial int64 `json:"-"` + + // Hash of the inbox for this entry + HashedSigningKey string // Requested registration duration + + // Registration duration (in Seconds) + Duration int64 + + // The order ID associated with this registration + OrderID string `json:"-"` +} + +type InboxEntry struct { + // ORM + Serial int64 `json:"-"` + + // Encrypted message. Must be exactly 256-32 bytes long. + Body []byte + + // Hash of the inbox for this entry + HashedSigningKey string +} + + +func InsertInboxEntryIntoDatabase(db *sql.DB, e *InboxEntry) error { + query := `INSERT INTO taler_mailbox.inbox_entries + VALUES (DEFAULT, $1, $2);` + rows, err := db.Query(query, e.HashedSigningKey, e.Body) + if err != nil { + return err + } + defer rows.Close() + return nil +} + + +func InsertPendingRegistrationIntoDatabase(db *sql.DB, pr *PendingMailboxRegistration) error { + query := `INSERT INTO taler_mailbox.pending_mailbox_registrations + VALUES (DEFAULT, $1, $2, $3);` + rows, err := db.Query(query, pr.HashedSigningKey, pr.Duration, pr.OrderID) + if err != nil { + return err + } + defer rows.Close() + return nil +} + + +func InsertMailboxRegistrationIntoDatabase(db *sql.DB, mb *MailboxMetadata) error { + query := `INSERT INTO taler_mailbox.mailbox_metadata + VALUES (DEFAULT, $1, $2, $3, $4, $5, $6, $7);` + rows, err := db.Query(query, mb.HashedSigningKey, mb.SigningKey, mb.SigningKeyType, mb.EncryptionKey, mb.EncryptionKeyType, mb.Expiration, mb.KoxClaimProof) + if err != nil { + return err + } + defer rows.Close() + return nil +} + +func UpdatePendingRegistrationOrderIdInDatabase(db *sql.DB, pr *PendingMailboxRegistration) error { + query := `UPDATE taler_mailbox.pending_mailbox_registrations + SET + "order_id" = $2 + WHERE "hashed_signing_key" = $1;` + rows, err := db.Query(query, pr.HashedSigningKey, pr.OrderID) + if err != nil { + return err + } + defer rows.Close() + return nil +} + + +func UpdateMailboxExpirationInDatabase(db *sql.DB, mb *MailboxMetadata) error { + query := `UPDATE taler_mailbox.mailbox_metadata + SET + "expiration" = $2 + WHERE "hashed_signing_key" = $1;` + rows, err := db.Query(query, mb.HashedSigningKey, mb.Expiration) + if err != nil { + return err + } + defer rows.Close() + return nil +} + +func GetPendingRegistrationFromDatabaseBySigningKey(db *sql.DB, pr *PendingMailboxRegistration, hashedKey string) error { + query := `SELECT + "serial", + "hashed_signing_key", + "duration", + "order_id" + FROM taler_mailbox.pending_mailbox_registrations + WHERE + "hashed_signing_key"=$1 + LIMIT 1 + ;` + // Execute Query + rows, err := db.Query(query, hashedKey) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + fmt.Printf("error val %v\n", rows.Err()) + return errors.New("Mailbox metadata does not exist") + } + return rows.Scan( + &pr.Serial, + &pr.HashedSigningKey, + &pr.Duration, + &pr.OrderID, + ) +} + + +func GetMailboxMetadataFromDatabaseBySigningKey(db *sql.DB, mb *MailboxMetadata, hashedKey string) error { + query := `SELECT + "serial", + "hashed_signing_key", + "signing_key" + "signing_key_type" + "encryption_key" + "encryption_key_type" + "expiration" + FROM taler_mailbox.mailbox_metadata + WHERE + "hashed_signing_key"=$1 + LIMIT 1 + ;` + // Execute Query + rows, err := db.Query(query, hashedKey) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + fmt.Printf("error val %v\n", rows.Err()) + return errors.New("Mailbox metadata does not exist") + } + return rows.Scan( + &mb.Serial, + &mb.HashedSigningKey, + &mb.SigningKey, + &mb.SigningKeyType, + &mb.EncryptionKey, + &mb.EncryptionKeyType, + &mb.Expiration, + ) +} + +func GetInboxEntryFromDatabaseBySigningKeyAndBody(db *sql.DB, e *InboxEntry, hashedKey string, body []byte) error { + query := `SELECT + "serial", + "hashed_signing_key", + "body" + FROM taler_mailbox.inbox_entries + WHERE + "hashed_signing_key"=$1 AND + "body"=$2 + ;` + // Execute Query + rows, err := db.Query(query, hashedKey, body) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + fmt.Printf("error val %v\n", rows.Err()) + return errors.New("Inbox entry does not exist") + } + return rows.Scan( + &e.Serial, + &e.HashedSigningKey, + &e.Body, + ) +} + +func GetInboxEntryFromDatabaseBySerial(db *sql.DB, e *InboxEntry, hashedKey string, serial int64) error { + query := `SELECT + "serial", + "hashed_signing_key", + "body" + FROM taler_mailbox.inbox_entries + WHERE + "serial"=$1 AND + "hashed_signing_key"=$2 + ;` + // Execute Query + rows, err := db.Query(query, serial, hashedKey) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + fmt.Printf("error val %v\n", rows.Err()) + return errors.New("Inbox entry does not exist") + } + return rows.Scan( + &e.Serial, + &e.HashedSigningKey, + &e.Body, + ) +} + +func DeletePendingRegistrationFromDatabase(db *sql.DB, pr *PendingMailboxRegistration) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_mailbox.pending_mailbox_registrations + WHERE + "serial" = $1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, pr.Serial) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + + +// DeleteInboxEntryFromSerialFromDatabase Deletes all entries starting from given serial +func DeleteInboxEntryFromSerialFromDatabase(db *sql.DB, e *InboxEntry) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_mailbox.inbox_entries + WHERE + "hashed_signing_key" = $1 AND + "serial" >= $2 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, e.HashedSigningKey, e.Serial) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + + +// DeleteStaleRegstrationsFromDatabase purges stale registrations +func DeleteStaleRegistrationsFromDatabase(db *sql.DB, registrationExpiration time.Time) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_mailbox.mailbox_metadata + WHERE + "expiration" < $1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, registrationExpiration.Unix()) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +// DeleteStalePendingRegstrationsFromDatabase purges stale registrations +func DeleteStalePendingRegistrationsFromDatabase(db *sql.DB, registrationExpiration time.Time) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_mailbox.pending_mailbox_registrations + WHERE + "created_at" < $1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, registrationExpiration.Unix()) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + + diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go @@ -22,6 +22,7 @@ package mailbox import ( "crypto/ed25519" "crypto/sha512" + "database/sql" "encoding/binary" "encoding/json" "errors" @@ -39,8 +40,6 @@ import ( tos "github.com/schanzen/taler-go/pkg/rest" talerutil "github.com/schanzen/taler-go/pkg/util" "gopkg.in/ini.v1" - "gorm.io/gorm" - "gorm.io/gorm/logger" "taler.net/taler-mailbox/internal/gana" "taler.net/taler-mailbox/internal/util" ) @@ -76,8 +75,8 @@ type MailboxConfig struct { // Configuration Ini *ini.File - // DB connection - DB gorm.Dialector + // The database connection to use + DB *sql.DB // Merchant connection Merchant merchant.Merchant @@ -92,8 +91,8 @@ type Mailbox struct { // The main router Router *mux.Router - // The main DB handle - DB *gorm.DB + // The database connection to use + DB *sql.DB // Our configuration from the ini Cfg MailboxConfig @@ -185,56 +184,6 @@ type VersionResponse struct { FreeMessageQuota string `json:"free_message_quota"` } -type MailboxMetadata struct { - // ORM - gorm.Model `json:"-"` - - // ORM helper hash of signing key - HashedSigningKey string `json:"-"` - - // The mailbox signing key. - // Note that $H_MAILBOX == H(singingKey). - // Note also how this key cannot be updated - // as it identifies the mailbox. - SigningKey string `json:"signing_key"` - - // Type of key. - // Optional, as currently only - // EdDSA keys are supported. - SigningKeyType string `json:"signing_key_type"` - - // The mailbox encryption key. - // This is an HPKE public key - // in the X25519 format for use - // in a X25519-DHKEM (RFC 9180). - // Base32 crockford-encoded. - EncryptionKey string `json:"encryption_key"` - - // Type of key. - // Optional, as currently only - // X25519 keys are supported. - EncryptionKeyType string `json:"encryption_key_type"` - - // Expiration of this mapping. - Expiration Timestamp `json:"expiration" gorm:"embedded;embeddedPrefix:expiration_"` - - // Keyoxide claim proof - KoxClaimProof string `json:"kox_claim_proof,omitempty"` -} - -type PendingMailboxRegistration struct { - // ORM - gorm.Model `json:"-"` - - // Hash of the inbox for this entry - HashedSigningKey string // Requested registration duration - - Duration time.Duration - - // The order ID associated with this registration - OrderID string `json:"-"` -} - type MailboxRegistrationRequest struct { // Keys to add/update for a mailbox. @@ -261,17 +210,6 @@ type MailboxRateLimitedResponse struct { Hint string `json:"hint"` } -type InboxEntry struct { - // ORM - gorm.Model `json:"-"` - - // Encrypted message. Must be exactly 256-32 bytes long. - Body []byte - - // Hash of the inbox for this entry - HashedSigningKey string -} - func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { dpStr := m.Cfg.Ini.Section("mailbox").Key("delivery_period").MustString("72h") dp, err := time.ParseDuration(dpStr) @@ -292,6 +230,67 @@ func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) { w.Write(response) } +// Get Hash-salted alias from database +func GetMessagesCountFromDatabase(db *sql.DB, hashedKey string) (int64, error) { + query := `SELECT COUNT(*) AS num_messages + FROM taler_mailbox.inbox_entries + WHERE + "hashed_signing_key" = $1 + ;` + // Execute Query + rows, err := db.Query(query, hashedKey) + if err != nil { + return 0, err + } + defer rows.Close() + if !rows.Next() { + return 0, nil + } + var res int64 + err = rows.Scan( + &res, + ) + if err != nil { + return 0, err + } + return res, nil +} + + +// Get Hash-salted alias from database +func GetMessagesFromDatabase(db *sql.DB, hashedKey string, limit int) ([]InboxEntry, error) { + query := `SELECT + "serial", + "hashed_signing_key", + "body" + FROM taler_mailbox.inbox_entries + WHERE + "hashed_signing_key" = $1 + LIMIT $2 + ;` + // Execute Query + rows, err := db.Query(query, hashedKey, limit) + if err != nil { + return []InboxEntry{}, err + } + defer rows.Close() + var entries = make([]InboxEntry, 0) + for rows.Next() { + var e InboxEntry + err = rows.Scan( + &e.Serial, + &e.HashedSigningKey, + &e.Body, + ) + if err != nil { + return entries, err + } + entries = append(entries, e) + } + return entries, nil +} + + func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) //to, toSet := vars["timeout_ms"] @@ -300,7 +299,7 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { // FIXME timeout // FIXME possibly limit results here m.checkPendingRegistrationUpdates(vars["h_mailbox"]) - err := m.DB.Where("hashed_signing_key = ?", vars["h_mailbox"]).Limit(int(m.MessageResponseLimit)).Find(&entries).Error + entries, err := GetMessagesFromDatabase(m.DB, vars["h_mailbox"], int(m.MessageResponseLimit)) if err != nil { log.Printf("%v", err) w.WriteHeader(http.StatusNotFound) @@ -311,7 +310,7 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) { return } // Add ETag of first message ID - etag := entries[0].ID + etag := entries[0].Serial w.Header().Add("ETag", fmt.Sprintf("%d", etag)) for _, entry := range entries { w.Write(entry.Body) @@ -337,7 +336,7 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { } if !m.MessageFee.IsZero() { var count int64 - err = m.DB.Model(&InboxEntry{}).Where("hashed_signing_key = ?", vars["h_mailbox"]).Count(&count).Error + count, err = GetMessagesCountFromDatabase(m.DB, vars["h_mailbox"]) if nil != err { m.Logf(LogError, "%v", err) http.Error(w, "Cannot look for entries", http.StatusBadRequest) @@ -350,14 +349,14 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) { } } m.checkPendingRegistrationUpdates(vars["h_mailbox"]) - err = m.DB.First(&entry, "hashed_signing_key = ? AND body = ?", vars["h_mailbox"], body, true).Error + err = GetInboxEntryFromDatabaseBySigningKeyAndBody(m.DB, &entry, vars["h_mailbox"], body) if err == nil { w.WriteHeader(http.StatusNotModified) return } entry.HashedSigningKey = vars["h_mailbox"] entry.Body = body - m.DB.Save(&entry) + err = InsertInboxEntryIntoDatabase(m.DB, &entry) w.WriteHeader(http.StatusNoContent) } @@ -365,13 +364,13 @@ func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var keyEntry MailboxMetadata m.checkPendingRegistrationUpdates(vars["h_mailbox"]) - err := m.DB.First(&keyEntry, "hashed_signing_key = ?", vars["h_mailbox"]).Error + err := GetMailboxMetadataFromDatabaseBySigningKey(m.DB, &keyEntry, vars["h_mailbox"]) if err != nil { w.WriteHeader(http.StatusNotFound) return } - m.Logf(LogDebug, "entry expires at %d, have %d", keyEntry.Expiration.Seconds, time.Now().Unix()) - if keyEntry.Expiration.Seconds < uint64(time.Now().Unix()) { + m.Logf(LogDebug, "entry expires at %d, have %d", keyEntry.Expiration, time.Now().Unix()) + if keyEntry.Expiration < int64(time.Now().Unix()) { w.WriteHeader(http.StatusNotFound) return } @@ -395,7 +394,7 @@ func (m *Mailbox) validateRegistrationSignature(msg MailboxRegistrationRequest) if nil != err { return fmt.Errorf("unable to decode signature") } - binary.BigEndian.PutUint64(expNbo[:], msg.MailboxMetadata.Expiration.Seconds) + binary.BigEndian.PutUint64(expNbo[:], uint64(msg.MailboxMetadata.Expiration)) size := signedMsg[0:4] binary.BigEndian.PutUint32(size, 64+4+4) purp := signedMsg[4:8] @@ -470,12 +469,12 @@ func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) pendingRegistration.HashedSigningKey = hMailbox // Round to the nearest multiple of a month - reqExpiration := time.Unix(int64(msg.MailboxMetadata.Expiration.Seconds), 0) + reqExpiration := time.Unix(msg.MailboxMetadata.Expiration, 0) now := time.Now() reqDuration := reqExpiration.Sub(now).Round(monthDuration) - err = m.DB.First(&registrationEntry, "hashed_signing_key = ?", hMailbox).Error + err = GetMailboxMetadataFromDatabaseBySigningKey(m.DB, &registrationEntry, hMailbox) if err == nil { - // This probably meansthe registration is modified or extended or both + // This probably means the registration is modified or extended or both entryModified := (registrationEntry.EncryptionKey != msg.MailboxMetadata.EncryptionKey) // At least one MonthlyFee if reqDuration.Microseconds() == 0 && !entryModified { @@ -486,21 +485,31 @@ func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request } else { // Entry does not yet exist, add but immediately expire it registrationEntry = msg.MailboxMetadata - registrationEntry.Expiration.Seconds = uint64(time.Now().Unix() - 1) + registrationEntry.Expiration = time.Now().Unix() - 1 hAddr := sha512.New() hAddr.Write(pk) registrationEntry.HashedSigningKey = util.Base32CrockfordEncode(hAddr.Sum(nil)) - err = m.DB.Create(&registrationEntry).Error + err = InsertMailboxRegistrationIntoDatabase(m.DB, &registrationEntry) if nil != err { w.WriteHeader(http.StatusInternalServerError) return } } - err = m.DB.First(&pendingRegistration, "hashed_signing_key = ?", hMailbox).Error + err = GetPendingRegistrationFromDatabaseBySigningKey(m.DB, &pendingRegistration, hMailbox) pendingRegistrationExists := (nil == err) if !pendingRegistrationExists { pendingRegistration.HashedSigningKey = hMailbox - pendingRegistration.Duration = reqDuration + pendingRegistration.Duration = reqDuration.Microseconds() + err = InsertPendingRegistrationIntoDatabase(m.DB, &pendingRegistration) + if nil != err { + w.WriteHeader(http.StatusInternalServerError) + return + } + err = GetPendingRegistrationFromDatabaseBySigningKey(m.DB, &pendingRegistration, hMailbox) + if nil != err { + w.WriteHeader(http.StatusInternalServerError) + return + } } // At least the update fee needs to be paid cost, err := calculateCost(m.MonthlyFee.String(), @@ -535,16 +544,21 @@ func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request return } if len(payto) != 0 { - m.DB.Save(&pendingRegistration) + err = UpdatePendingRegistrationOrderIdInDatabase(m.DB, &pendingRegistration) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } w.WriteHeader(http.StatusPaymentRequired) w.Header().Set("Taler", payto) return } } // Update expiration time of registration. - registrationEntry.Expiration.Seconds += uint64(reqDuration.Seconds()) - m.DB.Delete(pendingRegistration) - err = m.DB.Save(&registrationEntry).Error + registrationEntry.Expiration += int64(reqDuration.Seconds()) + _, err = DeletePendingRegistrationFromDatabase(m.DB, &pendingRegistration) + err = UpdateMailboxExpirationInDatabase(m.DB, &registrationEntry) if nil != err { w.WriteHeader(http.StatusInternalServerError) return @@ -555,7 +569,7 @@ func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request func (m *Mailbox) checkPendingRegistrationUpdates(hMailbox string) { var pendingEntry PendingMailboxRegistration var registrationEntry MailboxMetadata - err := m.DB.First(&pendingEntry, "hashed_signing_key = ?", hMailbox).Error + err := GetPendingRegistrationFromDatabaseBySigningKey(m.DB, &pendingEntry, hMailbox) if err != nil { return } @@ -565,7 +579,7 @@ func (m *Mailbox) checkPendingRegistrationUpdates(hMailbox string) { if rc == http.StatusNotFound { m.Logf(LogInfo, "Registration order for `%s' not found, removing\n", hMailbox) } - err := m.DB.Delete(&pendingEntry) + _, err = DeletePendingRegistrationFromDatabase(m.DB, &pendingEntry) if nil != err { m.Logf(LogInfo, "%v\n", err) } @@ -574,12 +588,15 @@ func (m *Mailbox) checkPendingRegistrationUpdates(hMailbox string) { m.Logf(LogDebug, "Order status for %s is %s", pendingEntry.HashedSigningKey, orderStatus) if merchant.OrderPaid == orderStatus { m.Logf(LogDebug, "Order for %v appears to be paid", pendingEntry) - err = m.DB.First(&registrationEntry, "hashed_signing_key = ?", hMailbox).Error + err = GetMailboxMetadataFromDatabaseBySigningKey(m.DB, &registrationEntry, hMailbox) if err == nil { - m.Logf(LogDebug, "Adding %d seconds to entry expiration", uint64(pendingEntry.Duration.Seconds())) - registrationEntry.Expiration.Seconds += uint64(pendingEntry.Duration.Seconds()) - m.DB.Save(&registrationEntry) - err := m.DB.Delete(&pendingEntry) + m.Logf(LogDebug, "Adding %d seconds to entry expiration", pendingEntry.Duration) + registrationEntry.Expiration += pendingEntry.Duration + err = UpdateMailboxExpirationInDatabase(m.DB, &registrationEntry) + if nil != err { + m.Logf(LogInfo, "%v\n", err) + } + _, err = DeletePendingRegistrationFromDatabase(m.DB, &pendingEntry) if nil != err { m.Logf(LogInfo, "%v\n", err) } @@ -632,8 +649,8 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) } h := sha512.New() h.Write(pkey) - hHailbox := util.Base32CrockfordEncode(h.Sum(nil)) - m.checkPendingRegistrationUpdates(hHailbox) + hMailbox := util.Base32CrockfordEncode(h.Sum(nil)) + m.checkPendingRegistrationUpdates(hMailbox) var signedMsg [4 * 4]byte binary.BigEndian.PutUint32(signedMsg[0:4], 4*4) binary.BigEndian.PutUint32(signedMsg[4:8], gana.TalerSignaturePurposeMailboxMessagesDelete) @@ -644,20 +661,19 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) return } // Check that expectedETag actually exists - err = m.DB.Where("hashed_signing_key = ? AND id = ?", hHailbox, expectedETag).Find(&InboxEntry{}).Error + var entry InboxEntry + err = GetInboxEntryFromDatabaseBySerial(m.DB, &entry, hMailbox, int64(expectedETag)) if err != nil { m.Logf(LogDebug, "Message to delete not found with ID %d", expectedETag) w.WriteHeader(http.StatusNotFound) return } - var entries []InboxEntry - err = m.DB.Where("hashed_signing_key = ? AND id >= ?", hHailbox, expectedETag).Limit(count).Find(&entries).Error + num, err := DeleteInboxEntryFromSerialFromDatabase(m.DB, &entry) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } - m.Logf(LogDebug, "Found matching ID, deleting %d messages", len(entries)) - m.DB.Delete(entries) + m.Logf(LogDebug, "Found matching ID, deleted %d messages", num) w.WriteHeader(http.StatusNoContent) } @@ -749,29 +765,17 @@ func (m *Mailbox) Initialize(cfg MailboxConfig) { } m.MessageFee = messageFee m.FreeMessageQuota = cfg.Ini.Section("mailbox").Key("free_message_quota").MustUint64(0) - _db, err := gorm.Open(cfg.DB, &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - }) - if err != nil { - panic(err) - } - m.DB = _db - if err := m.DB.AutoMigrate(&InboxEntry{}); err != nil { - panic(err) - } - if err := m.DB.AutoMigrate(&MailboxMetadata{}); err != nil { - panic(err) - } + m.DB = cfg.DB go func() { for { - tx := m.DB.Where("expiration < ?", time.Now()).Delete(&MailboxMetadata{}) - m.Logf(LogInfo, "Cleaned up %d stale registrations.\n", tx.RowsAffected) + num, err := DeleteStaleRegistrationsFromDatabase(m.DB, time.Now()) + if err != nil { + m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) + } + m.Logf(LogInfo, "Cleaned up %d stale registrations.\n", num) time.Sleep(time.Hour * 24) } }() - if err := m.DB.AutoMigrate(&PendingMailboxRegistration{}); err != nil { - panic(err) - } // Clean up pending pendingExpStr := cfg.Ini.Section("mailbox").Key("pending_registration_expiration").MustString("24h") pendingExp, err := time.ParseDuration(pendingExpStr) @@ -781,8 +785,11 @@ func (m *Mailbox) Initialize(cfg MailboxConfig) { } go func() { for { - tx := m.DB.Where("created_at < ?", time.Now().Add(-pendingExp)).Delete(&PendingMailboxRegistration{}) - m.Logf(LogInfo, "Cleaned up %d stale pending registrations.\n", tx.RowsAffected) + num, err := DeleteStalePendingRegistrationsFromDatabase(m.DB, time.Now().Add(-pendingExp)) + if err != nil { + m.Logf(LogDebug, "Error purging stale registrations: `%v'.\n", err) + } + m.Logf(LogInfo, "Cleaned up %d stale pending registrations.\n", num) time.Sleep(pendingExp) } }()