102 lines
2.0 KiB
Go
102 lines
2.0 KiB
Go
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
|
|
}
|