Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invoice api guard #17

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 14 additions & 2 deletions pkg/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package giga

import "errors"
import (
"crypto/rand"
"encoding/base64"
"errors"
)

type API struct {
Store Store
Expand All @@ -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
Expand Down
11 changes: 6 additions & 5 deletions pkg/dogecoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 46 additions & 2 deletions pkg/webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"net/http"
"strings"

"github.com/julienschmidt/httprouter"
"github.com/tjstebbing/conductor"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
}