db.go. InMemoryCaptchaDB was moved out. ErrorNotFound is now public. And DefaultExpiredScanInterval is now a public const. For more security a string of random data added to the end of a hash.
This commit is contained in:
parent
4078bb03bc
commit
d9aba868db
@ -1,147 +1,45 @@
|
|||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorNotFound = errors.New("captcha not found")
|
var ErrorNotFound = errors.New("captcha not found")
|
||||||
|
|
||||||
var defaultExpiredScanInterval = 60 * time.Second
|
const DefaultExpiredScanInterval = 60 * time.Second
|
||||||
|
|
||||||
|
// ID is a CAPTCHA identifier.
|
||||||
type ID string
|
type ID string
|
||||||
|
|
||||||
// NewID generates an ID as a sha256 hash of additionalData, current time
|
// NewID generates an ID as a sha256 hash of additionalData (usually IP-address),
|
||||||
// and answer encoded with base64 in raw URL variant.
|
// current time, answer and more, it adds a set of random bytes and encodes all
|
||||||
|
// of it with base64 in raw URL variant.
|
||||||
func NewID(additionalData string, answer Answer) ID {
|
func NewID(additionalData string, answer Answer) ID {
|
||||||
idHash := sha256.New()
|
idHash := sha256.New()
|
||||||
|
|
||||||
idHash.Write([]byte(additionalData))
|
idHash.Write([]byte(additionalData))
|
||||||
idHash.Write([]byte(strconv.FormatInt(time.Now().UnixMicro(), 16)))
|
idHash.Write([]byte(strconv.FormatInt(time.Now().UnixMicro(), 16)))
|
||||||
idHash.Write([]byte(answer))
|
idHash.Write([]byte(answer))
|
||||||
|
randData := make([]byte, 32)
|
||||||
|
rand.Read(randData)
|
||||||
|
idHash.Write(randData)
|
||||||
|
|
||||||
return ID(base64.RawURLEncoding.EncodeToString(idHash.Sum(nil)))
|
return ID(base64.RawURLEncoding.EncodeToString(idHash.Sum(nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaptchaDB interface with all necessary methods.
|
||||||
type CaptchaDB interface {
|
type CaptchaDB interface {
|
||||||
New(data string, captcha Captcha) (Captcha, ID)
|
New(data string, captcha Captcha) (Captcha, ID)
|
||||||
GetExpiry() time.Duration
|
GetExpiry() time.Duration
|
||||||
|
SetExpiry(expiry time.Duration)
|
||||||
Image(id ID, style string) (*image.Image, error)
|
Image(id ID, style string) (*image.Image, error)
|
||||||
Solve(id ID, answer Answer) (bool, error)
|
Solve(id ID, answer Answer) (bool, error)
|
||||||
IsSolved(id ID) (bool, error)
|
IsSolved(id ID) (bool, error)
|
||||||
Remove(id ID) error
|
Remove(id ID) error
|
||||||
cleanExpired()
|
|
||||||
}
|
|
||||||
|
|
||||||
type InMemoryCaptchaDB struct {
|
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
db map[ID]Captcha
|
|
||||||
expireIn time.Duration
|
|
||||||
expireScanInterval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInMemoryCaptchaDB(expire time.Duration) *InMemoryCaptchaDB {
|
|
||||||
db := &InMemoryCaptchaDB{
|
|
||||||
db: make(map[ID]Captcha),
|
|
||||||
expireIn: expire}
|
|
||||||
|
|
||||||
if expire < defaultExpiredScanInterval {
|
|
||||||
db.expireScanInterval = expire
|
|
||||||
} else {
|
|
||||||
db.expireScanInterval = defaultExpiredScanInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
db.cleanExpired()
|
|
||||||
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
// New accepts an Captcha 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 *InMemoryCaptchaDB) New(data string, captcha Captcha) (Captcha, ID) {
|
|
||||||
id := NewID(data, captcha.Answer())
|
|
||||||
|
|
||||||
cdb.Lock()
|
|
||||||
cdb.db[id] = captcha
|
|
||||||
cdb.Unlock()
|
|
||||||
|
|
||||||
return captcha, id
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanExpired starts a goroutine that deletes expired CAPTCHAs.
|
|
||||||
func (cdb *InMemoryCaptchaDB) cleanExpired() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
sleepFor := cdb.expireScanInterval - (time.Duration(time.Now().Second()) % cdb.expireScanInterval)
|
|
||||||
time.Sleep(sleepFor)
|
|
||||||
|
|
||||||
cdb.Lock()
|
|
||||||
for id, captcha := range cdb.db {
|
|
||||||
if time.Since(captcha.Expiry()) >= cdb.expireIn {
|
|
||||||
delete(cdb.db, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cdb.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExpiry returns time for how long CAPTCHA will last.
|
|
||||||
func (cdb *InMemoryCaptchaDB) GetExpiry() time.Duration {
|
|
||||||
return cdb.expireIn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image returns image for a CAPTCHA.
|
|
||||||
func (cdb *InMemoryCaptchaDB) Image(id ID, style string) (*image.Image, error) {
|
|
||||||
cdb.Lock()
|
|
||||||
defer cdb.Unlock()
|
|
||||||
if c, ok := cdb.db[id]; ok {
|
|
||||||
return c.Image(style), nil
|
|
||||||
}
|
|
||||||
return nil, errorNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solve compares given answer with a stored one and if failed
|
|
||||||
// deletes a CAPTCHA from database.
|
|
||||||
func (cdb *InMemoryCaptchaDB) Solve(id ID, answer Answer) (bool, error) {
|
|
||||||
cdb.Lock()
|
|
||||||
defer cdb.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 *InMemoryCaptchaDB) IsSolved(id ID) (bool, error) {
|
|
||||||
cdb.Lock()
|
|
||||||
defer cdb.Unlock()
|
|
||||||
if c, ok := cdb.db[id]; ok {
|
|
||||||
delete(cdb.db, id)
|
|
||||||
return c.IsSolved(), nil
|
|
||||||
}
|
|
||||||
return false, errorNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a CAPTCHA from a database.
|
|
||||||
func (cdb *InMemoryCaptchaDB) Remove(id ID) error {
|
|
||||||
cdb.Lock()
|
|
||||||
defer cdb.Unlock()
|
|
||||||
if _, ok := cdb.db[id]; ok {
|
|
||||||
delete(cdb.db, id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errorNotFound
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user