You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
4.1 KiB
120 lines
4.1 KiB
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"laic-backend/cache"
|
|
"laic-backend/common"
|
|
)
|
|
|
|
const captchaCookie = "laic_captcha"
|
|
|
|
var captchaChars = []byte("23456789ABCDEFGHJKLMNPQRSTUVWXYZ")
|
|
|
|
var captchaGlyphs = map[byte][7]string{
|
|
'2': {"11110", "00001", "00010", "00100", "01000", "10000", "11111"},
|
|
'3': {"11110", "00001", "00110", "00001", "00001", "00001", "11110"},
|
|
'4': {"00010", "00110", "01010", "10010", "11111", "00010", "00010"},
|
|
'5': {"11111", "10000", "11110", "00001", "00001", "10001", "01110"},
|
|
'6': {"00110", "01000", "10000", "11110", "10001", "10001", "01110"},
|
|
'7': {"11111", "00001", "00010", "00100", "01000", "01000", "01000"},
|
|
'8': {"01110", "10001", "10001", "01110", "10001", "10001", "01110"},
|
|
'9': {"01110", "10001", "10001", "01111", "00001", "00010", "11100"},
|
|
'A': {"01110", "10001", "10001", "11111", "10001", "10001", "10001"},
|
|
'B': {"11110", "10001", "10001", "11110", "10001", "10001", "11110"},
|
|
'C': {"01111", "10000", "10000", "10000", "10000", "10000", "01111"},
|
|
'D': {"11110", "10001", "10001", "10001", "10001", "10001", "11110"},
|
|
'E': {"11111", "10000", "10000", "11110", "10000", "10000", "11111"},
|
|
'F': {"11111", "10000", "10000", "11110", "10000", "10000", "10000"},
|
|
'G': {"01111", "10000", "10000", "10111", "10001", "10001", "01111"},
|
|
'H': {"10001", "10001", "10001", "11111", "10001", "10001", "10001"},
|
|
'J': {"00001", "00001", "00001", "00001", "10001", "10001", "01110"},
|
|
'K': {"10001", "10010", "10100", "11000", "10100", "10010", "10001"},
|
|
'M': {"10001", "11011", "10101", "10101", "10001", "10001", "10001"},
|
|
'N': {"10001", "11001", "10101", "10011", "10001", "10001", "10001"},
|
|
'P': {"11110", "10001", "10001", "11110", "10000", "10000", "10000"},
|
|
'Q': {"01110", "10001", "10001", "10001", "10101", "10010", "01101"},
|
|
'R': {"11110", "10001", "10001", "11110", "10100", "10010", "10001"},
|
|
'S': {"01111", "10000", "10000", "01110", "00001", "00001", "11110"},
|
|
'T': {"11111", "00100", "00100", "00100", "00100", "00100", "00100"},
|
|
'U': {"10001", "10001", "10001", "10001", "10001", "10001", "01110"},
|
|
'V': {"10001", "10001", "10001", "10001", "10001", "01010", "00100"},
|
|
'W': {"10001", "10001", "10001", "10101", "10101", "11011", "10001"},
|
|
'X': {"10001", "10001", "01010", "00100", "01010", "10001", "10001"},
|
|
'Y': {"10001", "10001", "01010", "00100", "00100", "00100", "00100"},
|
|
'Z': {"11111", "00001", "00010", "00100", "01000", "10000", "11111"},
|
|
}
|
|
|
|
func NewCaptcha() (string, []byte, error) {
|
|
id := uuid.NewString()
|
|
answer := make([]byte, 4)
|
|
for i := range answer {
|
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(captchaChars))))
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
answer[i] = captchaChars[n.Int64()]
|
|
}
|
|
if err := common.SetValueWithExpired(cache.CaptchaKeyOf(id), string(answer), 5*60); err != nil {
|
|
return "", nil, err
|
|
}
|
|
img := image.NewRGBA(image.Rect(0, 0, 160, 48))
|
|
for y := 0; y < 48; y++ {
|
|
for x := 0; x < 160; x++ {
|
|
img.Set(x, y, color.RGBA{R: 242, G: 247, B: 250, A: 255})
|
|
}
|
|
}
|
|
for i := 0; i < 7; i++ {
|
|
x := 9 + i*23
|
|
for y := 5; y < 43; y++ {
|
|
img.Set(x, y, color.RGBA{R: 130, G: 180, B: 205, A: 180})
|
|
}
|
|
}
|
|
for i, ch := range answer {
|
|
drawGlyph(img, ch, 15+i*35, 12, color.RGBA{R: 31, G: 91, B: 130, A: 255})
|
|
}
|
|
var out bytes.Buffer
|
|
if err := png.Encode(&out, img); err != nil {
|
|
return "", nil, err
|
|
}
|
|
return id, out.Bytes(), nil
|
|
}
|
|
|
|
func drawGlyph(img *image.RGBA, ch byte, left, top int, fill color.Color) {
|
|
glyph, ok := captchaGlyphs[ch]
|
|
if !ok {
|
|
return
|
|
}
|
|
for y, row := range glyph {
|
|
for x, bit := range row {
|
|
if bit == '1' {
|
|
for dy := 0; dy < 3; dy++ {
|
|
for dx := 0; dx < 3; dx++ {
|
|
img.Set(left+x*3+dx, top+y*3+dy, fill)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func CheckCaptcha(id, answer string) bool {
|
|
stored, err := common.GetString(cache.CaptchaKeyOf(id))
|
|
if err != nil || stored == "" || !strings.EqualFold(strings.TrimSpace(stored), strings.TrimSpace(answer)) {
|
|
return false
|
|
}
|
|
_ = common.Delete(cache.CaptchaKeyOf(id))
|
|
return true
|
|
}
|
|
|
|
func CaptchaCookieName() string { return captchaCookie }
|
|
func CaptchaTTL() time.Duration { return 5 * time.Minute }
|
|
|