81 lines
2.2 KiB
Go
81 lines
2.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"html"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"0451meishiditu/backend/internal/resp"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type captchaNewResp struct {
|
|
CaptchaID string `json:"captcha_id"`
|
|
SVG string `json:"svg"`
|
|
}
|
|
|
|
// CaptchaNew returns a simple SVG captcha and stores the answer in Redis.
|
|
// TTL: 5 minutes. One-time: successful validate will delete it.
|
|
func (h *Handlers) CaptchaNew(c *gin.Context) {
|
|
code := randomCaptchaCode(5)
|
|
id := randomHexStr(16)
|
|
key := "captcha:" + id
|
|
if err := h.rdb.Set(c.Request.Context(), key, strings.ToLower(code), 5*time.Minute).Err(); err != nil {
|
|
resp.Fail(c, http.StatusInternalServerError, "captcha store failed")
|
|
return
|
|
}
|
|
resp.OK(c, captchaNewResp{
|
|
CaptchaID: id,
|
|
SVG: captchaSVG(code),
|
|
})
|
|
}
|
|
|
|
func (h *Handlers) verifyCaptcha(c *gin.Context, captchaID, captchaCode string) bool {
|
|
captchaID = strings.TrimSpace(captchaID)
|
|
captchaCode = strings.ToLower(strings.TrimSpace(captchaCode))
|
|
if captchaID == "" || captchaCode == "" {
|
|
return false
|
|
}
|
|
key := "captcha:" + captchaID
|
|
val, err := h.rdb.Get(c.Request.Context(), key).Result()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
ok := strings.ToLower(strings.TrimSpace(val)) == captchaCode
|
|
if ok {
|
|
_ = h.rdb.Del(c.Request.Context(), key).Err()
|
|
}
|
|
return ok
|
|
}
|
|
|
|
func randomHexStr(n int) string {
|
|
b := make([]byte, n)
|
|
_, _ = rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
func randomCaptchaCode(n int) string {
|
|
const alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
b := make([]byte, n)
|
|
_, _ = rand.Read(b)
|
|
out := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
out[i] = alphabet[int(b[i])%len(alphabet)]
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
func captchaSVG(code string) string {
|
|
// Minimal SVG; frontends can render via v-html or <img src="data:image/svg+xml;utf8,...">
|
|
esc := html.EscapeString(code)
|
|
return `<?xml version="1.0" encoding="UTF-8"?>` +
|
|
`<svg xmlns="http://www.w3.org/2000/svg" width="140" height="44" viewBox="0 0 140 44">` +
|
|
`<rect x="0" y="0" width="140" height="44" rx="10" fill="#f8fafc" stroke="#e2e8f0"/>` +
|
|
`<text x="70" y="29" text-anchor="middle" font-family="ui-sans-serif,system-ui" font-size="22" font-weight="700" fill="#0f172a" letter-spacing="3">` + esc + `</text>` +
|
|
`</svg>`
|
|
}
|