commit 86b9901df6fdde71e29c6aef91639f7d4311c942
parent f44ce98e7221494ec966372c14e6c955cf14cc26
Author: Yannick Rehberger <yr@ityreh.de>
Date: Tue, 21 Apr 2026 19:58:30 +0200
add openapi spec
Diffstat:
6 files changed, 144 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,3 +1,4 @@
taler-mailbox
config.status
Makefile
+swagger.json
diff --git a/Makefile.in b/Makefile.in
@@ -54,4 +54,7 @@ gana:
dist:
git archive --format=tar.gz -o taler-mailbox-${GITVER}.tar.gz --prefix=taler-mailbox-${GITVER}/ HEAD
-.PHONY: all gana format check uninstall install server tools dist
+openapi:
+ swag init -g main.go --dir ./cmd/mailbox-server,./pkg/rest --outputTypes json --output . -v3.1
+
+.PHONY: all gana format check uninstall install server tools dist openapi
diff --git a/README.md b/README.md
@@ -25,6 +25,16 @@ Make sure that the database (Postgres) `taler-mailbox-test` exists and your user
$ make format
```
+# Generate OpenAPI Spec
+
+You can generate an openapi spec from code to `./swagger.json`:
+
+```
+$ go install github.com/swaggo/swag/v2/cmd/swag@latest
+$ ./configure --prefix=PREFIX
+$ make openapi
+```
+
# Funding
This project is funded through [NGI TALER Fund](https://nlnet.nl/taler), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/TALER-LookupService).
diff --git a/cmd/mailbox-server/main.go b/cmd/mailbox-server/main.go
@@ -16,6 +16,12 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
+// @title Taler Mailbox API
+// @description The Taler Mailbox service provides encrypted message delivery to wallets identified by their public key.
+// @contact.url https://taler.net
+// @license.name AGPL-3.0-or-later
+// @license.url https://www.gnu.org/licenses/agpl-3.0.html
+// @BasePath /
package main
import (
diff --git a/go.sum b/go.sum
@@ -1,12 +1,33 @@
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
-github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=
-github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -17,21 +38,44 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
+github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
+golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
+golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
-golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
-golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/getopt v0.0.0-20170811000552-20be20937449 h1:UukjJOsjQH0DIuyyrcod6CXHS6cdaMMuJmrt+SN1j4A=
diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go
@@ -203,6 +203,14 @@ type MailboxRateLimitedResponse struct {
Hint string `json:"hint"`
}
+// configResponse returns the service configuration.
+//
+// @Summary Get service configuration
+// @Description Returns service metadata including fees, message size limits, and delivery period.
+// @Tags config
+// @Produce json
+// @Success 200 {object} VersionResponse
+// @Router /config [get]
func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) {
dp, err := m.Cfg.Ini.GetDuration("mailbox", "delivery_period", 3*24*time.Hour)
if err != nil {
@@ -222,6 +230,18 @@ func (m *Mailbox) configResponse(w http.ResponseWriter, r *http.Request) {
w.Write(response)
}
+// getMessagesResponse retrieves pending messages for a mailbox.
+//
+// @Summary Retrieve messages
+// @Description Returns up to MessageResponseLimit encrypted message bodies for the given mailbox.
+// @Description The ETag response header contains the serial number of the first message.
+// @Tags mailbox
+// @Produce application/octet-stream
+// @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)"
+// @Success 200 "One or more message bodies concatenated"
+// @Success 204 "No messages available"
+// @Failure 404 "Mailbox not found"
+// @Router /{h_mailbox} [get]
func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
//to, toSet := vars["timeout_ms"]
@@ -248,6 +268,21 @@ func (m *Mailbox) getMessagesResponse(w http.ResponseWriter, r *http.Request) {
}
}
+// sendMessageResponse delivers a message to a mailbox.
+//
+// @Summary Send a message
+// @Description Stores an encrypted message body for the given mailbox. The body must be
+// @Description exactly MessageBodyBytes in size.
+// @Tags mailbox
+// @Accept application/octet-stream
+// @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)"
+// @Param body body string true "Encrypted message body (fixed size)"
+// @Success 204 "Message stored"
+// @Success 304 "Identical message already stored"
+// @Failure 400 "Missing or wrong-size body"
+// @Failure 402 "Payment required (free quota exceeded)"
+// @Failure 500
+// @Router /{h_mailbox} [post]
func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var entry InboxEntry
@@ -296,6 +331,16 @@ func (m *Mailbox) sendMessageResponse(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
+// getKeysResponse returns the public key metadata for a registered mailbox.
+//
+// @Summary Get mailbox info
+// @Description Returns the signing and encryption key metadata for the given mailbox.
+// @Tags mailbox
+// @Produce json
+// @Param h_mailbox path string true "SHA-512 hash of the mailbox signing key (Crockford base32)"
+// @Success 200 {object} MailboxMetadata
+// @Failure 404 "Mailbox not found or expired"
+// @Router /info/{h_mailbox} [get]
func (m *Mailbox) getKeysResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var keyEntry MailboxMetadata
@@ -376,6 +421,20 @@ func calculateCost(sliceCostAmount string, fixedCostAmount string, howLong time.
return sum, nil
}
+// registerMailboxResponse registers or updates a mailbox.
+//
+// @Summary Register or update mailbox
+// @Description Registers a new mailbox or updates the keys/expiration of an existing one.
+// @Description A valid EdDSA signature over the key material must be provided.
+// @Tags mailbox
+// @Accept json
+// @Param body body MailboxRegistrationRequest true "Registration request"
+// @Success 204 "Registration confirmed"
+// @Success 304 "Nothing changed"
+// @Failure 400 "Invalid request body or signature"
+// @Failure 402 "Payment required"
+// @Failure 500
+// @Router /register [post]
func (m *Mailbox) registerMailboxResponse(w http.ResponseWriter, r *http.Request) {
var msg MailboxRegistrationRequest
var pendingRegistration PendingMailboxRegistration
@@ -551,6 +610,22 @@ func (m *Mailbox) checkPendingMailboxRegistrationUpdates(hMailbox string) {
}
}
+// deleteMessagesResponse deletes messages from a mailbox.
+//
+// @Summary Delete messages
+// @Description Deletes one or more messages starting from the serial given in the If-Match header.
+// @Description Requires a valid EdDSA signature in the Taler-Mailbox-Delete-Signature header.
+// @Tags mailbox
+// @Param mailbox path string true "Crockford base32-encoded EdDSA public key of the mailbox"
+// @Param count query int false "Number of messages to delete (default: 1)"
+// @Param If-Match header string true "Serial number of the first message to delete"
+// @Param Taler-Mailbox-Delete-Signature header string true "EdDSA signature authorising the deletion"
+// @Success 204 "Messages deleted"
+// @Failure 400 "Missing or malformed headers/parameters"
+// @Failure 403 "Signature invalid"
+// @Failure 404 "Message with given serial not found"
+// @Failure 500
+// @Router /{mailbox} [delete]
func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
etagHeader := r.Header.Get("If-Match")