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 }