go-mockid/mockid/hashcash.go

102 lines
2.0 KiB
Go
Raw Normal View History

package mockid
import (
"errors"
"net/http"
"sync"
"time"
"git.rootprojects.org/root/hashcash"
)
var hashcashes = &hashcashDB{
db: sync.Map{},
}
func NewHashcash(sub string, exp time.Time) *hashcash.Hashcash {
h := hashcash.New(hashcash.Hashcash{
Subject: sub,
ExpiresAt: exp,
})
// ignoring the error because this implementation is backed by an in-memory map
_ = hashcashes.Store(h.Nonce, h)
return h
}
var ErrNotFound = errors.New("not found")
func UseHashcash(hc, sub string) error {
phony, err := hashcash.Parse(hc)
if nil != err {
return err
}
hi, ok, _ := hashcashes.Load(phony.Nonce)
if !ok {
return ErrNotFound
}
mccoy := hi.(*hashcash.Hashcash)
mccopy := *mccoy
mccopy.Solution = phony.Solution
if err := mccopy.Verify(sub); nil != err {
return err
}
_ = hashcashes.Delete(mccoy.Nonce)
return nil
}
func issueHashcash(w http.ResponseWriter, r *http.Request) *hashcash.Hashcash {
h := NewHashcash(r.Host, time.Now().Add(5*time.Minute))
w.Header().Set("Hashcash-Challenge", h.String())
return h
}
func requireHashcash(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
hc := r.Header.Get("Hashcash")
_ = issueHashcash(w, r)
if err := UseHashcash(hc, r.Host); nil != err {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
next(w, r)
}
}
type hashcashDB struct {
db sync.Map
}
func (h *hashcashDB) Load(key interface{}) (value interface{}, ok bool, err error) {
v, ok := h.db.Load(key)
return v, ok, nil
}
func (h *hashcashDB) Store(key interface{}, value interface{}) (err error) {
h.db.Store(key, value)
return nil
}
func (h *hashcashDB) Delete(key interface{}) (err error) {
h.db.Delete(key)
return nil
}
func (h *hashcashDB) vacuum() (err error) {
now := time.Now().UTC()
h.db.Range(func(key interface{}, val interface{}) bool {
v := val.(*hashcash.Hashcash)
if v.ExpiresAt.Sub(now) < 0 {
h.db.Delete(key)
}
return true
})
return nil
}