-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcaptcha.go
162 lines (136 loc) · 3.86 KB
/
captcha.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright 2021 Flamego. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"fmt"
"html/template"
"image/color"
"net/http"
"reflect"
"strings"
"github.com/flamego/flamego"
"github.com/flamego/flamego/inject"
"github.com/flamego/session"
"github.com/flamego/captcha/internal/captchautil"
)
// Captcha represents a captcha service and is used to validate text in captcha
// images.
type Captcha interface {
// HTML returns the HTML content to display and refresh captcha images.
HTML() template.HTML
// ValidText validates the passed text against the secret text.
ValidText(t string) bool
}
var _ Captcha = (*captcha)(nil)
type captcha struct {
session session.Session
urlPrefix string
}
func (c *captcha) HTML() template.HTML {
return template.HTML(
fmt.Sprintf(`
<a class="captcha" href="javascript:" tabindex="-1">
<img onclick="this.src=('%[1]simage.jpeg?refresh=true')" src="%[1]simage.jpeg">
</a>`,
c.urlPrefix),
)
}
const textKey = "flamego::captcha::text"
func (c *captcha) ValidText(t string) bool {
defer c.session.Delete(textKey)
want, ok := c.session.Get(textKey).(string)
if !ok {
return false
}
return strings.EqualFold(want, t)
}
// Options contains options for the captcha.Captchaer middleware.
type Options struct {
// URLPrefix is the URL path prefix for serving captcha images. Default is
// "/.captcha/".
URLPrefix string
// TextLength is the length of text to be generated in captcha images. Default
// is 4.
TextLength int
// Width is the image width of captcha. Default is 240.
Width int
// Height is the image height of captcha. Default is 80.
Height int
// DPI is the image DPI of captcha. Default is 72.
DPI int
}
var _ inject.FastInvoker = (*captchaInvoker)(nil)
// captchaInvoker is an inject.FastInvoker implementation of
// `func(flamego.Context, session.Session)`.
type captchaInvoker func(flamego.Context, session.Session)
func (invoke captchaInvoker) Invoke(args []interface{}) ([]reflect.Value, error) {
invoke(args[0].(flamego.Context), args[1].(session.Session))
return nil, nil
}
// Captchaer returns a middleware handler that injects captcha.Captcha into the
// request context, which is used for generating and validating text in captcha
// images.
func Captchaer(opts ...Options) flamego.Handler {
var opt Options
if len(opts) > 0 {
opt = opts[0]
}
parseOptions := func(opts Options) Options {
if opts.URLPrefix == "" {
opts.URLPrefix = "/.captcha/"
}
if opts.TextLength <= 0 {
opts.TextLength = 4
}
if opts.Width <= 0 {
opts.Width = 240
}
if opts.Height <= 0 {
opts.Height = 80
}
if opts.DPI <= 0 {
opts.DPI = 72
}
return opts
}
opt = parseOptions(opt)
return captchaInvoker(func(c flamego.Context, s session.Session) {
cpt := &captcha{
session: s,
urlPrefix: opt.URLPrefix,
}
c.MapTo(cpt, (*Captcha)(nil))
if !strings.HasPrefix(c.Request().URL.Path, opt.URLPrefix) {
return
}
text, ok := s.Get(textKey).(string)
if !ok || c.QueryBool("refresh") {
var err error
text, err = captchautil.RandomText(opt.TextLength)
if err != nil {
panic("captcha: generate random text: " + err.Error())
}
s.Set(textKey, text)
}
img, err := captchautil.CreateImage(
opt.Width, opt.Height, opt.DPI, captchautil.RandomLightColor(),
captchautil.Noise(captchautil.ComplexityLow),
captchautil.TextNoise(captchautil.ComplexityLow),
captchautil.Text(text),
captchautil.Curve(),
captchautil.Border(
color.RGBA{R: 170, G: 170, B: 170, A: 255},
),
)
if err != nil {
panic("captcha: create image: " + err.Error())
}
c.ResponseWriter().Header().Set("Cache-Control", "no-store")
c.ResponseWriter().WriteHeader(http.StatusOK)
err = img.Encode(c.ResponseWriter())
if err != nil {
panic("captcha: encode image: " + err.Error())
}
})
}