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 }