rucaptcha 3.2.4-aarch64-linux-musl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +191 -0
- data/Rakefile +72 -0
- data/app/controllers/ru_captcha/captcha_controller.rb +14 -0
- data/config/locales/rucaptcha.de-DE.yml +3 -0
- data/config/locales/rucaptcha.en.yml +3 -0
- data/config/locales/rucaptcha.pt-BR.yml +3 -0
- data/config/locales/rucaptcha.zh-CN.yml +3 -0
- data/config/locales/rucaptcha.zh-TW.yml +3 -0
- data/config/routes.rb +3 -0
- data/ext/rucaptcha/Cargo.lock +1226 -0
- data/ext/rucaptcha/Cargo.toml +14 -0
- data/ext/rucaptcha/extconf.rb +4 -0
- data/ext/rucaptcha/fonts/FuzzyBubbles-Regular.ttf +0 -0
- data/ext/rucaptcha/fonts/Handlee-Regular.ttf +0 -0
- data/ext/rucaptcha/src/captcha.rs +341 -0
- data/ext/rucaptcha/src/lib.rs +30 -0
- data/lib/rucaptcha/3.1/rucaptcha.so +0 -0
- data/lib/rucaptcha/3.2/rucaptcha.so +0 -0
- data/lib/rucaptcha/3.3/rucaptcha.so +0 -0
- data/lib/rucaptcha/cache.rb +12 -0
- data/lib/rucaptcha/configuration.rb +23 -0
- data/lib/rucaptcha/controller_helpers.rb +100 -0
- data/lib/rucaptcha/engine.rb +15 -0
- data/lib/rucaptcha/errors/configuration.rb +5 -0
- data/lib/rucaptcha/version.rb +3 -0
- data/lib/rucaptcha/view_helpers.rb +22 -0
- data/lib/rucaptcha.rb +87 -0
- metadata +101 -0
Binary file
|
Binary file
|
@@ -0,0 +1,341 @@
|
|
1
|
+
use image::{ImageBuffer, Rgba};
|
2
|
+
use rand::{thread_rng, Rng};
|
3
|
+
use rusttype::{Font, Scale};
|
4
|
+
use std::io::Cursor;
|
5
|
+
|
6
|
+
static BASIC_CHAR: [char; 54] = [
|
7
|
+
'2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
|
8
|
+
'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
9
|
+
'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
10
|
+
];
|
11
|
+
|
12
|
+
static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regular.ttf");
|
13
|
+
static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
|
14
|
+
|
15
|
+
// https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
|
16
|
+
static COLORS: [(u8, u8, u8, u8); 14] = [
|
17
|
+
(197, 166, 3, 255),
|
18
|
+
(187, 87, 5, 255),
|
19
|
+
(176, 7, 7, 255),
|
20
|
+
(186, 9, 56, 255),
|
21
|
+
(204, 11, 143, 255),
|
22
|
+
(124, 10, 190, 255),
|
23
|
+
(87, 0, 200, 255),
|
24
|
+
(61, 86, 168, 255),
|
25
|
+
(63, 166, 126, 255),
|
26
|
+
(69, 187, 48, 255),
|
27
|
+
(105, 208, 3, 255),
|
28
|
+
(160, 208, 3, 255),
|
29
|
+
(216, 219, 2, 255),
|
30
|
+
(50, 50, 50, 255),
|
31
|
+
];
|
32
|
+
|
33
|
+
static SCALE_SM: u32 = 32;
|
34
|
+
static SCALE_MD: u32 = 45;
|
35
|
+
static SCALE_LG: u32 = 55;
|
36
|
+
|
37
|
+
fn rand_num(len: usize) -> usize {
|
38
|
+
let mut rng = thread_rng();
|
39
|
+
rng.gen_range(0..=len)
|
40
|
+
}
|
41
|
+
|
42
|
+
fn get_captcha(len: usize) -> Vec<String> {
|
43
|
+
let mut res = vec![];
|
44
|
+
for _ in 0..len {
|
45
|
+
let rnd = rand_num(53);
|
46
|
+
res.push(BASIC_CHAR[rnd].to_string())
|
47
|
+
}
|
48
|
+
res
|
49
|
+
}
|
50
|
+
|
51
|
+
#[allow(unused)]
|
52
|
+
fn get_color() -> Rgba<u8> {
|
53
|
+
let rnd = rand_num(COLORS.len() - 1);
|
54
|
+
let c = COLORS[rnd];
|
55
|
+
Rgba([c.0, c.1, c.2, c.3])
|
56
|
+
}
|
57
|
+
|
58
|
+
fn get_colors(num: usize) -> Vec<Rgba<u8>> {
|
59
|
+
let rnd = rand_num(COLORS.len());
|
60
|
+
let mut out = vec![];
|
61
|
+
for i in 0..num {
|
62
|
+
let c = COLORS[(rnd + i) % COLORS.len()];
|
63
|
+
out.push(Rgba([c.0, c.1, c.2, c.3]))
|
64
|
+
}
|
65
|
+
|
66
|
+
out
|
67
|
+
}
|
68
|
+
|
69
|
+
fn get_next(min: f32, max: u32) -> f32 {
|
70
|
+
min + rand_num(max as usize - min as usize) as f32
|
71
|
+
}
|
72
|
+
|
73
|
+
fn get_font() -> Font<'static> {
|
74
|
+
match rand_num(2) {
|
75
|
+
0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
|
76
|
+
1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
|
77
|
+
_ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
82
|
+
ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
|
83
|
+
image::Rgba([255, 255, 255, 255])
|
84
|
+
})
|
85
|
+
}
|
86
|
+
|
87
|
+
fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
|
88
|
+
let c = (image.width() - 20) / res.len() as u32;
|
89
|
+
let y = image.height() / 3 - 15;
|
90
|
+
|
91
|
+
let h = image.height() as f32;
|
92
|
+
|
93
|
+
let scale = match res.len() {
|
94
|
+
1..=3 => SCALE_LG,
|
95
|
+
4..=5 => SCALE_MD,
|
96
|
+
_ => SCALE_SM,
|
97
|
+
} as f32;
|
98
|
+
|
99
|
+
let colors = get_colors(res.len());
|
100
|
+
let line_colors = get_colors(res.len());
|
101
|
+
|
102
|
+
let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
|
103
|
+
let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
|
104
|
+
|
105
|
+
// Draw line, ellipse first as background
|
106
|
+
for (i, _) in res.iter().enumerate() {
|
107
|
+
let line_color = line_colors[i];
|
108
|
+
|
109
|
+
if lines {
|
110
|
+
draw_interference_line(1, image, line_color);
|
111
|
+
}
|
112
|
+
draw_interference_ellipse(1, image, line_color);
|
113
|
+
}
|
114
|
+
|
115
|
+
// Draw text
|
116
|
+
for (i, _) in res.iter().enumerate() {
|
117
|
+
let text = &res[i];
|
118
|
+
|
119
|
+
let color = colors[i];
|
120
|
+
let font = get_font();
|
121
|
+
|
122
|
+
for j in 0..(rand_num(3) + 1) as i32 {
|
123
|
+
// Draw text again with offset
|
124
|
+
let offset = j * (rand_num(2) as i32);
|
125
|
+
imageproc::drawing::draw_text_mut(
|
126
|
+
image,
|
127
|
+
color,
|
128
|
+
10 + offset + (i as u32 * c) as i32,
|
129
|
+
y as i32 as i32,
|
130
|
+
Scale {
|
131
|
+
x: xscale + offset as f32,
|
132
|
+
y: yscale as f32,
|
133
|
+
},
|
134
|
+
&font,
|
135
|
+
text,
|
136
|
+
);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
|
142
|
+
for _ in 0..num {
|
143
|
+
let width = image.width();
|
144
|
+
let height = image.height();
|
145
|
+
let x1: f32 = 5.0;
|
146
|
+
let y1 = get_next(x1, height / 2);
|
147
|
+
|
148
|
+
let x2 = (width - 5) as f32;
|
149
|
+
let y2 = get_next(5.0, height - 5);
|
150
|
+
|
151
|
+
let ctrl_x = get_next((width / 6) as f32, width / 4 * 3);
|
152
|
+
let ctrl_y = get_next(x1, height - 5);
|
153
|
+
|
154
|
+
let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
|
155
|
+
let ctrl_y2 = get_next(x1, height - 5);
|
156
|
+
// Randomly draw bezier curves
|
157
|
+
imageproc::drawing::draw_cubic_bezier_curve_mut(
|
158
|
+
image,
|
159
|
+
(x1, y1),
|
160
|
+
(x2, y2),
|
161
|
+
(ctrl_x, ctrl_y),
|
162
|
+
(ctrl_x2, ctrl_y2),
|
163
|
+
color,
|
164
|
+
);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
fn draw_interference_ellipse(
|
169
|
+
num: usize,
|
170
|
+
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
|
171
|
+
color: Rgba<u8>,
|
172
|
+
) {
|
173
|
+
for _ in 0..num {
|
174
|
+
// max cycle width 20px
|
175
|
+
let w = (10 + rand_num(10)) as i32;
|
176
|
+
let x = rand_num((image.width() - 25) as usize) as i32;
|
177
|
+
let y = rand_num((image.height() - 15) as usize) as i32;
|
178
|
+
|
179
|
+
imageproc::drawing::draw_filled_ellipse_mut(image, (x, y), w, w, color);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
pub struct Captcha {
|
184
|
+
pub text: String,
|
185
|
+
pub image: Vec<u8>,
|
186
|
+
}
|
187
|
+
|
188
|
+
pub struct CaptchaBuilder {
|
189
|
+
length: usize,
|
190
|
+
width: usize,
|
191
|
+
height: usize,
|
192
|
+
complexity: usize,
|
193
|
+
line: bool,
|
194
|
+
noise: bool,
|
195
|
+
format: image::ImageFormat,
|
196
|
+
}
|
197
|
+
|
198
|
+
impl CaptchaBuilder {
|
199
|
+
pub fn new() -> Self {
|
200
|
+
CaptchaBuilder {
|
201
|
+
length: 4,
|
202
|
+
width: 220,
|
203
|
+
height: 70,
|
204
|
+
complexity: 5,
|
205
|
+
line: true,
|
206
|
+
noise: false,
|
207
|
+
format: image::ImageFormat::Png,
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
pub fn length(mut self, length: usize) -> Self {
|
212
|
+
self.length = length;
|
213
|
+
self
|
214
|
+
}
|
215
|
+
|
216
|
+
pub fn line(mut self, line: bool) -> Self {
|
217
|
+
self.line = line;
|
218
|
+
self
|
219
|
+
}
|
220
|
+
|
221
|
+
pub fn noise(mut self, noise: bool) -> Self {
|
222
|
+
self.noise = noise;
|
223
|
+
self
|
224
|
+
}
|
225
|
+
|
226
|
+
pub fn format(mut self, format: &str) -> Self {
|
227
|
+
self.format = match format {
|
228
|
+
"png" => image::ImageFormat::Png,
|
229
|
+
"jpg" | "jpeg" => image::ImageFormat::Jpeg,
|
230
|
+
"webp" => image::ImageFormat::WebP,
|
231
|
+
_ => image::ImageFormat::Png,
|
232
|
+
};
|
233
|
+
|
234
|
+
self
|
235
|
+
}
|
236
|
+
|
237
|
+
pub fn complexity(mut self, complexity: usize) -> Self {
|
238
|
+
let mut complexity = complexity;
|
239
|
+
if complexity > 10 {
|
240
|
+
complexity = 10;
|
241
|
+
}
|
242
|
+
if complexity < 1 {
|
243
|
+
complexity = 1;
|
244
|
+
}
|
245
|
+
self.complexity = complexity;
|
246
|
+
self
|
247
|
+
}
|
248
|
+
|
249
|
+
pub fn build(self) -> Captcha {
|
250
|
+
// Generate an array of captcha characters
|
251
|
+
let res = get_captcha(self.length);
|
252
|
+
|
253
|
+
let text = res.join("");
|
254
|
+
|
255
|
+
// Create a white background image
|
256
|
+
let mut image = get_image(self.width, self.height);
|
257
|
+
|
258
|
+
// Loop to write the verification code string into the background image
|
259
|
+
cyclic_write_character(&res, &mut image, self.line);
|
260
|
+
|
261
|
+
if self.noise {
|
262
|
+
imageproc::noise::gaussian_noise_mut(
|
263
|
+
&mut image,
|
264
|
+
(self.complexity - 1) as f64,
|
265
|
+
((10 * self.complexity) - 10) as f64,
|
266
|
+
((5 * self.complexity) - 5) as u64,
|
267
|
+
);
|
268
|
+
}
|
269
|
+
|
270
|
+
let mut bytes: Vec<u8> = Vec::new();
|
271
|
+
image
|
272
|
+
.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
|
273
|
+
.unwrap();
|
274
|
+
|
275
|
+
Captcha { text, image: bytes }
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
#[cfg(test)]
|
280
|
+
mod tests {
|
281
|
+
use super::*;
|
282
|
+
|
283
|
+
#[test]
|
284
|
+
fn test_format() {
|
285
|
+
let mut builder = CaptchaBuilder::new();
|
286
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
287
|
+
|
288
|
+
builder = builder.format("jpg");
|
289
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
290
|
+
builder = builder.format("jpeg");
|
291
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
292
|
+
builder = builder.format("webp");
|
293
|
+
assert_eq!(builder.format, image::ImageFormat::WebP);
|
294
|
+
builder = builder.format("png");
|
295
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
296
|
+
builder = builder.format("gif");
|
297
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
298
|
+
}
|
299
|
+
|
300
|
+
#[test]
|
301
|
+
fn test_line() {
|
302
|
+
let mut builder = CaptchaBuilder::new();
|
303
|
+
assert!(builder.line);
|
304
|
+
|
305
|
+
builder = builder.line(false);
|
306
|
+
assert!(!builder.line);
|
307
|
+
}
|
308
|
+
|
309
|
+
#[test]
|
310
|
+
fn test_noise() {
|
311
|
+
let mut builder = CaptchaBuilder::new();
|
312
|
+
assert!(!builder.noise);
|
313
|
+
|
314
|
+
builder = builder.noise(true);
|
315
|
+
assert!(builder.noise);
|
316
|
+
}
|
317
|
+
|
318
|
+
#[test]
|
319
|
+
fn test_difficulty() {
|
320
|
+
let mut builder = CaptchaBuilder::new();
|
321
|
+
assert_eq!(builder.complexity, 5);
|
322
|
+
|
323
|
+
builder = builder.complexity(10);
|
324
|
+
assert_eq!(builder.complexity, 10);
|
325
|
+
|
326
|
+
builder = builder.complexity(11);
|
327
|
+
assert_eq!(builder.complexity, 10);
|
328
|
+
|
329
|
+
builder = builder.complexity(0);
|
330
|
+
assert_eq!(builder.complexity, 1);
|
331
|
+
}
|
332
|
+
|
333
|
+
#[test]
|
334
|
+
fn test_length() {
|
335
|
+
let mut builder = CaptchaBuilder::new();
|
336
|
+
assert_eq!(builder.length, 4);
|
337
|
+
|
338
|
+
builder = builder.length(10);
|
339
|
+
assert_eq!(builder.length, 10);
|
340
|
+
}
|
341
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
use magnus::{define_class, function, Error, Object};
|
2
|
+
|
3
|
+
mod captcha;
|
4
|
+
|
5
|
+
pub fn create(
|
6
|
+
len: usize,
|
7
|
+
difficulty: usize,
|
8
|
+
line: bool,
|
9
|
+
noise: bool,
|
10
|
+
format: String,
|
11
|
+
) -> (String, Vec<u8>) {
|
12
|
+
let c = captcha::CaptchaBuilder::new();
|
13
|
+
let out = c
|
14
|
+
.complexity(difficulty)
|
15
|
+
.length(len)
|
16
|
+
.line(line)
|
17
|
+
.noise(noise)
|
18
|
+
.format(&format)
|
19
|
+
.build();
|
20
|
+
|
21
|
+
(out.text, out.image)
|
22
|
+
}
|
23
|
+
|
24
|
+
#[magnus::init]
|
25
|
+
fn init() -> Result<(), Error> {
|
26
|
+
let class = define_class("RuCaptchaCore", magnus::class::object())?;
|
27
|
+
class.define_singleton_method("create", function!(create, 5))?;
|
28
|
+
|
29
|
+
Ok(())
|
30
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RuCaptcha
|
2
|
+
class Configuration
|
3
|
+
# Store Captcha code where, this config more like Rails config.cache_store
|
4
|
+
# default: Rails application config.cache_store
|
5
|
+
attr_accessor :cache_store
|
6
|
+
# rucaptcha expire time, default 2 minutes
|
7
|
+
attr_accessor :expires_in
|
8
|
+
# Chars length: default 5, allows: [3..7]
|
9
|
+
attr_accessor :length
|
10
|
+
# Hard mode, default: 5, allows: [1..10]
|
11
|
+
attr_accessor :difficulty
|
12
|
+
# Enable or disable strikethrough lines on captcha image, default: true
|
13
|
+
attr_accessor :line
|
14
|
+
# Enable or disable noise on captcha image, default: false
|
15
|
+
attr_accessor :noise
|
16
|
+
# Image format allow: ['jpeg', 'png', 'webp'], default: 'png'
|
17
|
+
attr_accessor :format
|
18
|
+
# skip_cache_store_check, default: false
|
19
|
+
attr_accessor :skip_cache_store_check
|
20
|
+
# custom rucaptcha mount path, default: '/rucaptcha'
|
21
|
+
attr_accessor :mount_path
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module RuCaptcha
|
2
|
+
module ControllerHelpers
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :verify_rucaptcha?
|
7
|
+
end
|
8
|
+
|
9
|
+
def rucaptcha_session_id
|
10
|
+
cookies[:_rucaptcha_session_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
# session key of rucaptcha
|
14
|
+
def rucaptcha_sesion_key_key
|
15
|
+
warning_when_session_invalid if rucaptcha_session_id.blank?
|
16
|
+
|
17
|
+
# With https://github.com/rack/rack/commit/7fecaee81f59926b6e1913511c90650e76673b38
|
18
|
+
# to protected session_id into secret
|
19
|
+
session_id_digest = Digest::SHA256.hexdigest(rucaptcha_session_id.inspect)
|
20
|
+
["rucaptcha-session", session_id_digest].join(":")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate a new Captcha
|
24
|
+
def generate_rucaptcha
|
25
|
+
generate_rucaptcha_session_id
|
26
|
+
|
27
|
+
res = RuCaptcha.generate
|
28
|
+
session_val = {
|
29
|
+
code: res[0],
|
30
|
+
time: Time.now.to_i
|
31
|
+
}
|
32
|
+
RuCaptcha.cache.write(rucaptcha_sesion_key_key, session_val, expires_in: RuCaptcha.config.expires_in)
|
33
|
+
res[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Verify captcha code
|
37
|
+
#
|
38
|
+
# params:
|
39
|
+
# resource - [optional] a ActiveModel object, if given will add validation error message to object.
|
40
|
+
# :keep_session - if true, RuCaptcha will not delete the captcha code session.
|
41
|
+
# :captcha - if given, the value of it will be used to verify the captcha,
|
42
|
+
# if do not give or blank, the value of params[:_rucaptcha] will be used to verify the captcha
|
43
|
+
#
|
44
|
+
# exmaples:
|
45
|
+
#
|
46
|
+
# verify_rucaptcha?
|
47
|
+
# verify_rucaptcha?(user, keep_session: true)
|
48
|
+
# verify_rucaptcha?(nil, keep_session: true)
|
49
|
+
# verify_rucaptcha?(nil, captcha: params[:user][:captcha])
|
50
|
+
#
|
51
|
+
def verify_rucaptcha?(_resource = nil, opts = {})
|
52
|
+
opts ||= {}
|
53
|
+
|
54
|
+
store_info = RuCaptcha.cache.read(rucaptcha_sesion_key_key)
|
55
|
+
# make sure move used key
|
56
|
+
RuCaptcha.cache.delete(rucaptcha_sesion_key_key) unless opts[:keep_session]
|
57
|
+
|
58
|
+
# Make sure session exist
|
59
|
+
return add_rucaptcha_validation_error if store_info.blank?
|
60
|
+
|
61
|
+
# Make sure not expire
|
62
|
+
return add_rucaptcha_validation_error if (Time.now.to_i - store_info[:time]) > RuCaptcha.config.expires_in
|
63
|
+
|
64
|
+
# Make sure parama have captcha
|
65
|
+
captcha = (opts[:captcha] || params[:_rucaptcha] || "").downcase.strip
|
66
|
+
return add_rucaptcha_validation_error if captcha.blank?
|
67
|
+
|
68
|
+
return add_rucaptcha_validation_error if captcha != store_info[:code]
|
69
|
+
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def generate_rucaptcha_session_id
|
76
|
+
return if rucaptcha_session_id.present?
|
77
|
+
|
78
|
+
cookies[:_rucaptcha_session_id] = {
|
79
|
+
value: SecureRandom.hex(16),
|
80
|
+
expires: 1.day
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_rucaptcha_validation_error
|
85
|
+
if defined?(resource) && resource && resource.respond_to?(:errors)
|
86
|
+
resource.errors.add(:base, t("rucaptcha.invalid"))
|
87
|
+
end
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
def warning_when_session_invalid
|
92
|
+
return unless Rails.env.development?
|
93
|
+
|
94
|
+
Rails.logger.warn "
|
95
|
+
WARNING! The session.id is blank, RuCaptcha can't work properly, please keep session available.
|
96
|
+
More details about this: https://github.com/huacnlee/rucaptcha/pull/66
|
97
|
+
"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RuCaptcha
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace RuCaptcha
|
4
|
+
|
5
|
+
initializer "rucaptcha.init" do |app|
|
6
|
+
# https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/routing/route_set.rb#L268
|
7
|
+
# `app.routes.prepend` start from Rails 3.2 - 5.0
|
8
|
+
app.routes.prepend do
|
9
|
+
mount RuCaptcha::Engine => RuCaptcha.config.mount_path
|
10
|
+
end
|
11
|
+
|
12
|
+
RuCaptcha.check_cache_store! unless RuCaptcha.config.skip_cache_store_check
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RuCaptcha
|
2
|
+
module ViewHelpers
|
3
|
+
def rucaptcha_input_tag(opts = {})
|
4
|
+
opts[:name] = "_rucaptcha"
|
5
|
+
opts[:type] = "text"
|
6
|
+
opts[:autocorrect] = "off"
|
7
|
+
opts[:autocapitalize] = "off"
|
8
|
+
opts[:pattern] = "[a-zA-Z0-9]*"
|
9
|
+
opts[:autocomplete] = "off"
|
10
|
+
opts[:maxlength] = RuCaptcha.config.length
|
11
|
+
tag(:input, opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def rucaptcha_image_tag(opts = {})
|
15
|
+
@rucaptcha_image_tag__image_path_in_this_request ||= "#{ru_captcha.root_path}?t=#{Time.now.strftime('%s%L')}"
|
16
|
+
opts[:class] = opts[:class] || "rucaptcha-image"
|
17
|
+
opts[:src] = @rucaptcha_image_tag__image_path_in_this_request
|
18
|
+
opts[:onclick] = "this.src = '#{ru_captcha.root_path}?t=' + Date.now();"
|
19
|
+
tag(:img, opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/rucaptcha.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "action_controller"
|
3
|
+
require "active_support/all"
|
4
|
+
|
5
|
+
begin
|
6
|
+
# load the precompiled extension file
|
7
|
+
ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
|
8
|
+
require_relative "rucaptcha/#{ruby_version}/rucaptcha"
|
9
|
+
rescue LoadError
|
10
|
+
require "rucaptcha/rucaptcha"
|
11
|
+
end
|
12
|
+
|
13
|
+
require "rucaptcha/version"
|
14
|
+
require "rucaptcha/configuration"
|
15
|
+
require "rucaptcha/controller_helpers"
|
16
|
+
require "rucaptcha/view_helpers"
|
17
|
+
require "rucaptcha/cache"
|
18
|
+
require "rucaptcha/engine"
|
19
|
+
require "rucaptcha/errors/configuration"
|
20
|
+
|
21
|
+
module RuCaptcha
|
22
|
+
class << self
|
23
|
+
def config
|
24
|
+
return @config if defined?(@config)
|
25
|
+
|
26
|
+
@config = Configuration.new
|
27
|
+
@config.length = 5
|
28
|
+
@config.difficulty = 3
|
29
|
+
@config.expires_in = 2.minutes
|
30
|
+
@config.skip_cache_store_check = false
|
31
|
+
@config.line = true
|
32
|
+
@config.noise = true
|
33
|
+
@config.format = "png"
|
34
|
+
@config.mount_path = "/rucaptcha"
|
35
|
+
|
36
|
+
@config.cache_store = if Rails.application
|
37
|
+
Rails.application.config.cache_store
|
38
|
+
else
|
39
|
+
:mem_cache_store
|
40
|
+
end
|
41
|
+
@config.cache_store
|
42
|
+
@config
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure(&block)
|
46
|
+
config.instance_exec(&block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate
|
50
|
+
length = config.length
|
51
|
+
|
52
|
+
raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
|
53
|
+
|
54
|
+
result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format)
|
55
|
+
[result[0].downcase, result[1].pack("c*")]
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_cache_store!
|
59
|
+
cache_store = RuCaptcha.config.cache_store
|
60
|
+
store_name = cache_store.is_a?(Array) ? cache_store.first : cache_store
|
61
|
+
if %i[memory_store null_store file_store].include?(store_name)
|
62
|
+
RuCaptcha.config.cache_store = [:file_store, Rails.root.join("tmp/cache/rucaptcha/session")]
|
63
|
+
|
64
|
+
puts "
|
65
|
+
|
66
|
+
RuCaptcha's cache_store requirements are stored across processes and machines,
|
67
|
+
such as :mem_cache_store, :redis_store, or other distributed storage.
|
68
|
+
But your current set is #{cache_store}, it has changed to :file_store for working.
|
69
|
+
NOTE: :file_store is still not a good way, it only works with single server case.
|
70
|
+
|
71
|
+
Please make config file `config/initializers/rucaptcha.rb` to setup `cache_store`.
|
72
|
+
More infomation please read GitHub RuCaptcha README file.
|
73
|
+
https://github.com/huacnlee/rucaptcha
|
74
|
+
|
75
|
+
"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
ActiveSupport.on_load(:action_controller) do
|
82
|
+
ActionController::Base.include RuCaptcha::ControllerHelpers
|
83
|
+
end
|
84
|
+
|
85
|
+
ActiveSupport.on_load(:action_view) do
|
86
|
+
include RuCaptcha::ViewHelpers
|
87
|
+
end
|