diff --git a/Makefile b/Makefile index 526b07b..0f193b0 100755 --- a/Makefile +++ b/Makefile @@ -7,3 +7,6 @@ clean: build/gigawallet: clean mkdir -p build/ go build -o build/gigawallet ./cmd/gigawallet/main.go + +dev: + go run ./cmd/gigawallet/main.go devconf.toml diff --git a/pkg/api.go b/pkg/api.go index 9236b15..870fd0e 100644 --- a/pkg/api.go +++ b/pkg/api.go @@ -1,6 +1,10 @@ package giga -import "errors" +import ( + "crypto/rand" + "encoding/base64" + "errors" +) type API struct { Store Store @@ -26,7 +30,15 @@ func (a API) CreateInvoice(request InvoiceCreateRequest, foreignID string) (Invo if err != nil { return Invoice{}, err } - i := Invoice{ID: invoiceID, Account: acc.Address, Vendor: request.Vendor, Items: request.Items, KeyIndex: keyIndex} + // generate an access token + b := make([]byte, 16) + _, err = rand.Read(b) + if err != nil { + return Invoice{}, err + } + token := base64.URLEncoding.EncodeToString(b) + + i := Invoice{ID: invoiceID, Account: acc.Address, Vendor: request.Vendor, Items: request.Items, AccessToken: token, KeyIndex: keyIndex} err = a.Store.StoreInvoice(i) if err != nil { return Invoice{}, err diff --git a/pkg/dogecoin.go b/pkg/dogecoin.go index c11e412..f6d9d6a 100755 --- a/pkg/dogecoin.go +++ b/pkg/dogecoin.go @@ -39,11 +39,12 @@ type Txn struct{} type Invoice struct { // ID is the single-use address that the invoice needs to be paid to. - ID Address `json:"id"` - Account Address `json:"account"` // from Account.Address - TXID string `json:"txid"` - Vendor string `json:"vendor"` - Items []Item `json:"items"` + ID Address `json:"id"` + Account Address `json:"account"` // from Account.Address + TXID string `json:"txid"` + Vendor string `json:"vendor"` + Items []Item `json:"items"` + AccessToken string `json:"access_token"` // used to authenticate public API requests // These are used internally to track invoice status. KeyIndex uint32 // which HD Wallet child-key was generated BlockID string // transaction seen in this mined block diff --git a/pkg/webapi.go b/pkg/webapi.go index 32a7e3e..39d1594 100644 --- a/pkg/webapi.go +++ b/pkg/webapi.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + "strings" "github.com/julienschmidt/httprouter" "github.com/tjstebbing/conductor" @@ -29,7 +30,7 @@ func (t WebAPI) Run(started, stopped chan bool, stop chan context.Context) error go func() { mux := httprouter.New() mux.POST("/invoice/:foreignID", t.createInvoice) - mux.GET("/invoice/:invoiceID", t.getInvoice) + mux.GET("/invoice/:invoiceID", t.protectInvoiceRoute(t.getInvoice)) mux.POST("/account/:foreignID", t.createAccount) mux.GET("/account/:foreignID", t.getAccount) mux.GET("/accountbyaddr/:address", t.getAccountByAddress) // TODO: figure out some way to to merge this and the above @@ -86,7 +87,7 @@ func (t WebAPI) getInvoice(w http.ResponseWriter, r *http.Request, p httprouter. fmt.Fprintf(w, "error: missing invoice ID") return } - invoice, err := t.api.GetInvoice(Address(id)) + invoice, r, err := t.getRequestCachedInvoice(Address(id), r) if err != nil { fmt.Fprintf(w, "error: %v", err) return @@ -158,3 +159,46 @@ func (t WebAPI) getAccountByAddress(w http.ResponseWriter, r *http.Request, p ht w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, "%s", string(b)) } + +/* Readthrough cache for invoice on the Request, specifically for webapi only */ +type InvoiceCtxKey string + +func (t WebAPI) getRequestCachedInvoice(id Address, r *http.Request) (Invoice, *http.Request, error) { + // found the invoice on the Request.Context, move on + if v := r.Context().Value(InvoiceCtxKey("inv")); v != nil { + fmt.Println("Fetched Inv from cache") + return v.(Invoice), r, nil + } + // Need to fetch and cache an invoice + invoice, err := t.api.GetInvoice(id) + if err != nil { + return Invoice{}, r, err + } + fmt.Println("Fetched Inv fresh") + return invoice, r.Clone(context.WithValue(r.Context(), InvoiceCtxKey("inv"), invoice)), nil +} + +/* Wraps a route handler and ensures Authorization header matches invoice token */ +func (t WebAPI) protectInvoiceRoute(h httprouter.Handle) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + id := p.ByName("invoiceID") + if id == "" { + fmt.Fprintf(w, "error: missing invoice ID") + return + } + invoice, r, err := t.getRequestCachedInvoice(Address(id), r) + if err != nil { + fmt.Fprintf(w, "error: invoice not found") + return + } + + //Check authorization header matches + bits := strings.Split(strings.ToUpper(r.Header.Get("Authorization")), "TOKEN ") + if len(bits) == 2 && invoice.AccessToken == bits[1] { + h(w, r, p) + return + } + + fmt.Fprintf(w, "error: invalid access token for invoice") + } +}