package inmemdb import ( "image" "justcaptcha/pkg/captcha" "sync" "time" ) type InMemoryCaptchaDB struct { sync.Mutex db map[captcha.ID]captcha.Captcha expiry time.Duration expiryScanInterval time.Duration } // NewInMemoryCaptchaDB returns an initialised instance of an InMemoryCaptchaDB. // An expiry duration is a time for how long CAPTCHA will be valid. func NewInMemoryCaptchaDB(expiry time.Duration) *InMemoryCaptchaDB { db := &InMemoryCaptchaDB{ db: make(map[captcha.ID]captcha.Captcha), expiry: expiry} if expiry < captcha.DefaultExpiredScanInterval { db.expiryScanInterval = expiry } else { db.expiryScanInterval = captcha.DefaultExpiredScanInterval } go db.cleanExpired() return db } // New accepts a CAPTHA instance, generates an ID and store it in a database. // A data string is an additional random data used to generate an ID, // e.g. an IP-address. func (imcdb *InMemoryCaptchaDB) New(data string, cptcha captcha.Captcha) (captcha.Captcha, captcha.ID) { id := captcha.NewID(data, cptcha.Answer()) imcdb.Lock() imcdb.db[id] = cptcha imcdb.Unlock() return cptcha, id } // GetExpiry returns time for how long CAPTCHA will last. func (imcdb *InMemoryCaptchaDB) GetExpiry() time.Duration { return imcdb.expiry } // SetExpiry changes an expiry duration to a new one. func (imcdb *InMemoryCaptchaDB) SetExpiry(expiry time.Duration) { if expiry < captcha.DefaultExpiredScanInterval { imcdb.expiryScanInterval = expiry } else { imcdb.expiryScanInterval = captcha.DefaultExpiredScanInterval } } // Image returns a freshly generated image for a CAPTCHA. func (imcdb *InMemoryCaptchaDB) Image(id captcha.ID, style string) (*image.Image, error) { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { return c.Image(style), nil } return nil, captcha.ErrorNotFound } // Solve compares given answer with a stored one and if failed // deletes a CAPTCHA from database. func (imcdb *InMemoryCaptchaDB) Solve(id captcha.ID, answer captcha.Answer) (bool, error) { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { ok = c.Solve(answer) if !ok { delete(imcdb.db, id) } return ok, nil } return false, captcha.ErrorNotFound } // IsSolved checks if CAPTCHA was solved and removes it // from a database. func (imcdb *InMemoryCaptchaDB) IsSolved(id captcha.ID) (bool, error) { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { delete(imcdb.db, id) return c.IsSolved(), nil } return false, captcha.ErrorNotFound } // Remove a CAPTCHA from a database. func (imcdb *InMemoryCaptchaDB) Remove(id captcha.ID) error { imcdb.Lock() defer imcdb.Unlock() if _, ok := imcdb.db[id]; ok { delete(imcdb.db, id) return nil } return captcha.ErrorNotFound } // cleanExpired removes expired CAPTCHAs in a loop. func (imcdb *InMemoryCaptchaDB) cleanExpired() { for { sleepFor := imcdb.expiryScanInterval - (time.Duration(time.Now().Second()) % imcdb.expiryScanInterval) time.Sleep(sleepFor) imcdb.Lock() for id, captcha := range imcdb.db { if time.Since(captcha.Expiry()) >= imcdb.expiry { delete(imcdb.db, id) } } imcdb.Unlock() } } // An instance of InMemoryCaptchaDB var imcdb = NewInMemoryCaptchaDB(captcha.DefaultExpiredScanInterval) func GetExpiry() time.Duration { return imcdb.GetExpiry() } func SetExpiry(expiry time.Duration) { imcdb.SetExpiry(expiry) } func New(data string, captcha captcha.Captcha) (captcha.Captcha, captcha.ID) { return imcdb.New(data, captcha) } func Image(id captcha.ID, style string) (*image.Image, error) { return imcdb.Image(id, style) } func Solve(id captcha.ID, answer captcha.Answer) (bool, error) { return imcdb.Solve(id, answer) } func IsSolved(id captcha.ID) (bool, error) { return imcdb.IsSolved(id) } func Remove(id captcha.ID) error { return imcdb.Remove(id) }