package captcha import ( "crypto/sha256" "encoding/base64" "image" "strconv" "sync" "time" ) var expiredScanInterval = 60 * time.Second type ICaptchaDB interface { New(data string) (ICaptcha, ID) SetExpiration(expire time.Duration) Image(id ID) (*image.Image, error) Solve(id ID, answer Answer) (bool, error) IsSolved(id ID) bool } type CaptchaDB struct { DB map[ID]ICaptcha ExpireInterval time.Duration mut sync.Mutex } // SetExpiration stores expire value and starts a goroutine // that checks for expired CAPTCHAs every minute. func (cdb *CaptchaDB) SetExpiration(expire time.Duration) { cdb.ExpireInterval = expire if expire < expiredScanInterval { expiredScanInterval = expire } go func() { for { n := time.Now().Second() sleepFor := expiredScanInterval - (time.Duration(n) * time.Second % expiredScanInterval) time.Sleep(sleepFor) for id, captcha := range cdb.DB { if time.Now().Sub(captcha.Expire()) <= 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 }