109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
package captcha
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"image"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var expiredScanInterval = 60 * time.Second
|
|
|
|
type ICaptchaDB interface {
|
|
New(data string) (ICaptcha, ID)
|
|
SetExpiry(expiry time.Duration)
|
|
Image(id ID) (*image.Image, error)
|
|
Solve(id ID, answer Answer) (bool, error)
|
|
IsSolved(id ID) bool
|
|
GetExpiry() time.Duration
|
|
}
|
|
|
|
type CaptchaDB struct {
|
|
DB map[ID]ICaptcha
|
|
ExpireIn time.Duration
|
|
mut sync.Mutex
|
|
}
|
|
|
|
// SetExpiry stores expire value and starts a goroutine
|
|
// that checks for expired CAPTCHAs every minute.
|
|
func (cdb *CaptchaDB) SetExpiry(expire time.Duration) {
|
|
cdb.ExpireIn = expire
|
|
if expire < expiredScanInterval {
|
|
expiredScanInterval = expire
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
sleepFor := expiredScanInterval - (time.Duration(time.Now().Second()) % expiredScanInterval)
|
|
time.Sleep(sleepFor)
|
|
|
|
for id, captcha := range cdb.DB {
|
|
if time.Now().Sub(captcha.Expiry()) <= 0 {
|
|
delete(cdb.DB, id)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// New accepts an ICaptcha instance, generates an ID and store it in a database.
|
|
// `data` string is an additional random data used to generate an ID,
|
|
// e.g. IP-address.
|
|
func (cdb *CaptchaDB) New(data string, captcha ICaptcha) (ICaptcha, ID) {
|
|
idHash := sha256.New()
|
|
|
|
idHash.Write([]byte(data))
|
|
idHash.Write([]byte(strconv.FormatInt(time.Now().UnixMicro(), 16)))
|
|
idHash.Write([]byte(captcha.GetAnswer()))
|
|
|
|
id := ID(base64.RawURLEncoding.EncodeToString(idHash.Sum(nil)))
|
|
|
|
cdb.mut.Lock()
|
|
defer cdb.mut.Unlock()
|
|
cdb.DB[id] = captcha
|
|
|
|
return captcha, id
|
|
}
|
|
|
|
// Image returns image for a captcha.
|
|
func (cdb *CaptchaDB) Image(id ID) (*image.Image, error) {
|
|
if c, ok := cdb.DB[id]; ok {
|
|
return c.GetImage(), nil
|
|
}
|
|
return nil, errorNotFound
|
|
}
|
|
|
|
// Solve compares given answer with a stored one and if failed
|
|
// deletes a captcha from database.
|
|
func (cdb *CaptchaDB) Solve(id ID, answer Answer) (bool, error) {
|
|
cdb.mut.Lock()
|
|
defer cdb.mut.Unlock()
|
|
if c, ok := cdb.DB[id]; ok {
|
|
ok = c.Solve(answer)
|
|
if !ok {
|
|
delete(cdb.DB, id)
|
|
}
|
|
return ok, nil
|
|
}
|
|
return false, errorNotFound
|
|
}
|
|
|
|
// IsSolved checks if captcha was solved and removes it
|
|
// from a database.
|
|
func (cdb *CaptchaDB) IsSolved(id ID) (bool, error) {
|
|
cdb.mut.Lock()
|
|
defer cdb.mut.Unlock()
|
|
if c, ok := cdb.DB[id]; ok {
|
|
ok = c.IsSolved()
|
|
delete(cdb.DB, id)
|
|
return ok, nil
|
|
}
|
|
return false, errorNotFound
|
|
}
|
|
|
|
func (cdb *CaptchaDB) GetExpireInterval() time.Duration {
|
|
return cdb.ExpireIn
|
|
}
|