captchah 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +101 -0
- data/app/controllers/captchah/captchah_controller.rb +47 -0
- data/config/routes.rb +5 -0
- data/lib/captchah.rb +33 -0
- data/lib/captchah/base64_images.rb +185 -0
- data/lib/captchah/encryptor.rb +23 -0
- data/lib/captchah/generators/captcha.rb +106 -0
- data/lib/captchah/generators/html.rb +195 -0
- data/lib/captchah/generators/puzzle.rb +106 -0
- data/lib/captchah/generators/truth.rb +19 -0
- data/lib/captchah/verifier.rb +23 -0
- data/lib/captchah/version.rb +5 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ad66eace82032f4bd0032a0a418dc3d110c5d90ae28ae72e7433eec12bb891bb
|
4
|
+
data.tar.gz: 5916bf46a222fce1aa04f069e4cab99ee2a2276cb4a5a9c179dd2eb385e27ab4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27ff37ca4328a81c6d7477ca54223c45cd4c1cd6cb25512c9b9d8648947113dfb8586208ddcc2bb3e7d5febbf41229ba7c88054e7720e7915b71bd2f9b0bec04
|
7
|
+
data.tar.gz: 2429ba66d17e070f54b1a057e8b007c9e7ac08a6f80f3a7dc6869c1c6b757fcb11a82ae38265346ae87809d6a1842440a03ec8fec1fa4bbaa80acebe0204808e
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Captchah
|
2
|
+
|
3
|
+
A Rails captcha gem that attempts to determine whether or not a user is human.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'captchah', '~> 1.0'
|
11
|
+
```
|
12
|
+
|
13
|
+
And execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
## Requirements
|
20
|
+
|
21
|
+
ImageMagick or GraphicsMagick command-line tool has to be installed. You can check if you have it installed by running:
|
22
|
+
|
23
|
+
```
|
24
|
+
$ convert -version
|
25
|
+
```
|
26
|
+
|
27
|
+
## Dependencies
|
28
|
+
|
29
|
+
```
|
30
|
+
gem 'rails', '~> 5.0'
|
31
|
+
```
|
32
|
+
|
33
|
+
```
|
34
|
+
gem 'mini_magick', '~> 4.0'
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
Include the Captchah module into your controller. Example:
|
40
|
+
|
41
|
+
```
|
42
|
+
class YourController < ApplicationController
|
43
|
+
include Captchah
|
44
|
+
```
|
45
|
+
|
46
|
+
Add the captchah_tag form helper to your form. Note, only 1 captchah_tag per form is allowed. Example:
|
47
|
+
|
48
|
+
```
|
49
|
+
<%= form_tag('/your-path') do %>
|
50
|
+
<%= captchah_tag %>
|
51
|
+
```
|
52
|
+
|
53
|
+
Once a user submits your form, you can verify if they have typed in the correct characters by calling the verify_captchah method inside your controller. Example:
|
54
|
+
|
55
|
+
```
|
56
|
+
class YourController < ApplicationController
|
57
|
+
include Captchah
|
58
|
+
|
59
|
+
def create
|
60
|
+
redirect_to('/your-path') unless verify_captchah == :valid
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Details
|
65
|
+
|
66
|
+
The captchah_tag form helper accepts the following arguments:
|
67
|
+
```
|
68
|
+
captchah_tag(
|
69
|
+
id: 'unique-id', # String value Default: (automatically generated)
|
70
|
+
difficulty: 3, # Integer value between 1 and 5 Default: 3
|
71
|
+
expiry: 10.minutes, # ActiveSupport::Duration object Default: 10.minutes
|
72
|
+
width: 140, # Integer value Default: 140(pixels)
|
73
|
+
action_label: 'Type...', # String value Default: 'Type the letters you see:'
|
74
|
+
reload_label: 'Reload', # String value Default: 'Reload'
|
75
|
+
reload_max: 5, # Integer value Default: 5
|
76
|
+
reload: true, # Boolean value Default: true
|
77
|
+
css: true # Boolean value Default: true
|
78
|
+
)
|
79
|
+
```
|
80
|
+
|
81
|
+
The verify_captchah method returns the following statuses:
|
82
|
+
```
|
83
|
+
:valid # The user has typed in the correct characters.
|
84
|
+
:invalid # The user has not typed in the correct characters.
|
85
|
+
:expired # The captcha has expired.
|
86
|
+
:no_params # params[:captchah] is empty.
|
87
|
+
```
|
88
|
+
|
89
|
+
## Running the tests
|
90
|
+
|
91
|
+
```
|
92
|
+
$ bundle exec rspec
|
93
|
+
```
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/evgeniradev/captchah](https://github.com/evgeniradev/captchah).
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
Captchah is released under the MIT License. See LICENSE for details.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
class CaptchahController < ActionController::API
|
5
|
+
def new
|
6
|
+
return head(:forbidden) unless request.xhr?
|
7
|
+
|
8
|
+
render plain: captchah_tag
|
9
|
+
rescue StandardError => e
|
10
|
+
puts "Captchah #{e.class.name}: #{e.message}"
|
11
|
+
head :internal_server_error
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def payload
|
17
|
+
@payload ||= begin
|
18
|
+
raise Error, 'Payload missing' if captchah_params.blank?
|
19
|
+
|
20
|
+
Encryptor.decrypt(captchah_params)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def captchah_tag
|
25
|
+
Generators::Captcha.call(captchah_arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def captchah_arguments
|
29
|
+
{
|
30
|
+
id: payload[:id],
|
31
|
+
difficulty: payload[:difficulty],
|
32
|
+
expiry: payload[:expiry],
|
33
|
+
width: payload[:width],
|
34
|
+
action_label: payload[:action_label],
|
35
|
+
reload_label: payload[:reload_label],
|
36
|
+
reload_max: payload[:reload_max],
|
37
|
+
reload_count: payload[:reload_count] + 1,
|
38
|
+
reload: payload[:reload],
|
39
|
+
css: payload[:css]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def captchah_params
|
44
|
+
@captchah_params ||= params[:captchah]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/config/routes.rb
ADDED
data/lib/captchah.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'captchah/version'
|
4
|
+
require 'captchah/generators/html'
|
5
|
+
require 'captchah/generators/puzzle'
|
6
|
+
require 'captchah/generators/truth'
|
7
|
+
require 'captchah/generators/captcha'
|
8
|
+
require 'captchah/base64_images'
|
9
|
+
require 'captchah/encryptor'
|
10
|
+
require 'captchah/verifier'
|
11
|
+
|
12
|
+
module Captchah
|
13
|
+
class Error < StandardError; end
|
14
|
+
|
15
|
+
class Engine < Rails::Engine
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
return unless base.respond_to?(:helper_method)
|
20
|
+
|
21
|
+
return unless base.ancestors.include?(ActionController::Base)
|
22
|
+
|
23
|
+
base.helper_method(:captchah_tag, :verify_captchah)
|
24
|
+
end
|
25
|
+
|
26
|
+
def captchah_tag(*args)
|
27
|
+
Generators::Captcha.call(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def verify_captchah
|
31
|
+
Verifier.call(params[:captchah])
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
class Base64Images
|
5
|
+
class << self
|
6
|
+
def loader
|
7
|
+
'R0lGODlhHAAEAPcAACwsLC0tLS4uLi4uLi8vLzAwMDExMTIyMjMzMzMzMzQ0NDU1NTY' \
|
8
|
+
'2Njc3Nzg4ODg4ODk5OTo6Ojs7Ozw8PD09PT09PT4+Pj8/P0BAQEFBQUJCQkJCQkNDQ0' \
|
9
|
+
'REREVFRUZGRkdHR0dHR0hISElJSUpKSktLS0xMTExMTE1NTU5OTk9PT1BQUFFRUVFRU' \
|
10
|
+
'VJSUlNTU1RUVFVVVVZWVlZWVldXV1hYWFlZWVpaWltbW1tbW1xcXF1dXV5eXl9fX2Bg' \
|
11
|
+
'YGBgYGFhYWJiYmNjY2RkZGRkZGVlZWZmZmdnZ2hoaGlpaWlpaWpqamtra2xsbG1tbW5' \
|
12
|
+
'ubm5ubm9vb3BwcHFxcXJycnNzc3Nzc3R0dHV1dXZ2dnd3d3h4eHh4eHl5eXp6ent7e3' \
|
13
|
+
'x8fH19fX19fX5+fn9/f4CAgIGBgYKCgoKCgoODg4SEhIWFhYaGhoeHh4eHh4iIiImJi' \
|
14
|
+
'YqKiouLi4yMjIyMjI2NjY6Ojo+Pj5CQkJGRkZGRkZKSkpOTk5SUlJWVlZaWlpaWlpeX' \
|
15
|
+
'l5iYmJmZmZqampqampubm5ycnJ2dnZ6enp+fn5+fn6CgoKGhoaKioqOjo6SkpKSkpKW' \
|
16
|
+
'lpaampqenp6ioqKmpqampqaqqqqurq6ysrK2tra6urq6urq+vr7CwsLGxsbKysrOzs7' \
|
17
|
+
'Ozs7S0tLW1tba2tre3t7i4uLi4uLm5ubq6uru7u7y8vL29vb29vb6+vr+/v8DAwMHBw' \
|
18
|
+
'cLCwsLCwsPDw8TExMXFxcbGxsfHx8fHx8jIyMnJycrKysvLy8vLy8zMzM3Nzc7Ozs/P' \
|
19
|
+
'z9DQ0NDQ0NHR0dLS0tPT09TU1NXV1dXV1dbW1tfX19jY2NnZ2dra2tra2tvb29zc3N3' \
|
20
|
+
'd3d7e3t/f39/f3+Dg4OHh4eLi4uPj4+Tk5OTk5OXl5ebm5ufn5+jo6Onp6enp6erq6u' \
|
21
|
+
'vr6+zs7O3t7e7u7u7u7u/v7/Dw8PHx8fLy8vPz8/Pz8/T09PX19fb29vf39/j4+Pj4+' \
|
22
|
+
'Pn5+fr6+vv7+/z8/P39/f39/f7+/v///////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJ' \
|
23
|
+
'CgD/ACwAAAAAHAAEAAAIKAATCRwo8N+/cggTIjSoUCFBggwbLjwosdzDgRElZmx4sSB' \
|
24
|
+
'FjR8bBgQAIfkECQoA/wAsAAAAABwABAAACCwAAQgcKPDfv0QIEyI0WK6hw4YECRpUqJ' \
|
25
|
+
'DhQ4cRB06kuPDfRYwZAWzkaPFjQAAh+QQJCgD/ACwAAAAAHAAEAAAIJwATCRwo8N8/A' \
|
26
|
+
'AgTIjRIsKHDRAYVKmT4sGJEiQv/VbR4ECMAig4DAgAh+QQJCgD/ACwAAAAAHAAEAAAI' \
|
27
|
+
'LADLCRwo8N+/RAgTIjQIoKHDhgQJGlSokOFDhxEHTqS48N9FjBnLbeRo8WNAACH5BAk' \
|
28
|
+
'KAP8ALAAAAAAcAAQAAAgoAMsJHCjw3z+CBA0mWshwIcKBBh8W/NewocRyESUqrOhQ40' \
|
29
|
+
'GPHBcGBAAh+QQJGQD/ACwAAAAAHAAEAAAIHwDLCRwo8N8/ggQNIlyIUCFDhwwfHpQYM' \
|
30
|
+
'SLEhhMXBgQAOw=='
|
31
|
+
end
|
32
|
+
|
33
|
+
def puzzle_background
|
34
|
+
'/9j/4QBWRXhpZgAATU0AKgAAAAgABAESAAMAAAABAAEAAAEaAAUAAAABAAAAPgEbAAU' \
|
35
|
+
'AAAABAAAARgEoAAMAAAABAAIAAAAAAAAAAAEsAAAAAQAAASwAAAAB/+AAEEpGSUYAAQ' \
|
36
|
+
'EAAAEAAQAA/9sAQwABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' \
|
37
|
+
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB/9sAQwEBAQEBAQEBAQEBAQEBAQEBAQEB' \
|
38
|
+
'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB/8AAEQg' \
|
39
|
+
'ANwCMAwERAAIRAQMRAf/EABwAAAMBAAMBAQAAAAAAAAAAAAcICQoEBQYDAf/EACwQAA' \
|
40
|
+
'IDAQEAAQQCAgICAgMAAAQFAgMGAQcIERITFAkVABYXISIkIzElQkP/xAAUAQEAAAAAA' \
|
41
|
+
'AAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AXV94' \
|
42
|
+
'j/pVO59MDaZbCYjLDrkSsglImyC5coAvvMbGO9sursayr0Apsv7ebARLUtuZSIUgIqF' \
|
43
|
+
'cBKgJvm7vNfIGEfTlu1MeeVr3RjDMn5bLIltvq+wCqMIFWFlMVqwbmYxvQa0ydmwS1u' \
|
44
|
+
'iTqFNoYohklBt4L4t1tJnoKrFN8eZk/wDW6f26s0LQ1YZqFjPRqaY0UHiytz99cJkMD' \
|
45
|
+
'hK+MS7Y0fml0eucezgFvcSx7biEzEy9ahWBrZCPGBJhMuq7qaeQu4dwesnq6+gWyN99' \
|
46
|
+
'khzPoLbXZG23tPZdAOIlLX1HOdZ4qtXMETQlM173sdUv0cMrF6DMj+gNADXDMKmNcjz' \
|
47
|
+
'/AMhYYY4sIxFuptrjM6sGK8U8y8mzVWr3dbklwwQumiwAqpX2VdLxibcdcDUUZCB7p0' \
|
48
|
+
'MadfcXyugUPtlnIx5f2H5egY/H/E/MXe3ltc353Md5Kxh/b61k5Kn1s9Y2wtaNIqL5c' \
|
49
|
+
'U3M+8r/AATIhzp1co9Eh/4w7/gLh6zrfH/Svkxt8j4XuOZT0/xYBEt9F2PnQ2ajeFsX' \
|
50
|
+
'tls60TcogU2km4dT+uO3EYhGcGNhUKTR2nnJxCKvpvmP5PYWN+U0Wqr2en3nWrG1YVT' \
|
51
|
+
'RI0vP3zm1YH4k5SSgu/t5wjEi8OcLRI2WMKTociPCINXm/WEGz9WzR/oH9M9jgP1kt+' \
|
52
|
+
'aS4DS6XZFLpC9+kCI0mAglGLj+WF0dGJBJrnCJLOBw/ewgDBjenYjnoCduizWpqzVAz' \
|
53
|
+
'e4LSTEyJM6nB/IzvHgGM5L4AHQvpmuIlS1LLjbZ2gmuM6/pACkH7V4Zo8kdoL+aFNE3' \
|
54
|
+
'9mvRCL1wTCohkFZ+CXT1Ib5QWReXzkftjeaR26uMPpL7auR4C8emeLeD+o5s5jlVOyz' \
|
55
|
+
'LjS6Nbn69vjBx8g6GlbdK+TEo4dgcwKTrpxjcNRcfKutvWPD8lEvtvgCNIvaN15yt2n' \
|
56
|
+
'xr2WsV79IU3MTH6DZEakfagLJklG8JfZbi+7ukvKlGNRrjPkQT3LZ18uAMW38ImAw9s' \
|
57
|
+
'8C8Kdo/NTPJd+p8V1KZpCq0WrUsCc5Ew4qs0pmhq0wZwONlEoKJFRtAsgiIclSX2uic' \
|
58
|
+
'5xDl/CvK5/y30j0crfeief8ApTELZZ9baftvvomPb6EdaEUZkas0sJS54TaaC9RSc0D' \
|
59
|
+
'zFKdpdSlM0og4VlbqgKCfJn1DGtfN6cpgzFTytPSOd9omwwzwLLDi36ag9dFYnhQYH2' \
|
60
|
+
'hQtAWU0zFraJpnZ8UEEBbToyyAkHs/JfQQdGwgsW0lBFcFYVW/t/rfZM8SgooT6/8AX' \
|
61
|
+
'7X9eXbev/b/AP6/q/T/APT/AAHX+fPwe9U9Y+TNXwZCMF2mM8/+x4mQoLI5AgZ6eJNc' \
|
62
|
+
'4M29IrMsBjmgKEKcjFMR1gumV4DUifhWMdQTaJaFWfjJ/Ch6zksbpvPqvakWURMYgKM' \
|
63
|
+
'fxfkuE059ApVwXACHB0xoIZMJqViKh0f2OUbPnFB+ufv7S4XDMgRo3xkTnviv46vQk/' \
|
64
|
+
'pfsir1IHArtmAJ1IJmBlOsa8ZsG+hoelaJkngZy3T/AGos/aKCzZLFd1S1SKKGcFw33' \
|
65
|
+
'8UKbTZx1ZutVnBXK1IgtfkIbLdShdDZmdp6obTIGqvO51uIIOxsNui7TF8pXki2fs1l' \
|
66
|
+
'RotkB0o+HdUsHW7Ueg4nZoE2EnQwkvWxzyMrOxBMIJ+2OanplX4qaK5Q5BRRGjkbftH' \
|
67
|
+
'r5/1H/A+uQwfk2aT6L0j1t7PJeUeYZEbbMmrNcXks/wAQ0CWzPKpiVZc7e2V2jyE/9h' \
|
68
|
+
'WluItuo/RkfwkP8gZ8vmZ8fvlP/KNbz2Hyjear4sfG3Jzg2+OnkcH2iRnaGuqdo6L13' \
|
69
|
+
'cTSdpMzjV01osYJuM6mVyVdcIJCFLK3seAHf4Ufj5lPF03zZU/LvW7bJeqpvYc3lWfF' \
|
70
|
+
'bEF+buTr0FTIZ/eZqUZVrVe8Nu4QMxvcrLyO/fX0im3/AKkDrsPi0Ux9E0u4Nzey8/y' \
|
71
|
+
'whK+IT/UZGetf29vj2trpUg6AOhKeFaLZXOOfAJmePTyVl9rMmPKeAJfXPhJ4mu3OcU' \
|
72
|
+
'4X5Sa3i/S0/ta3RYfydljfQ62UiYX8uDamGZ9lnl9Q8oziKVZN4TKvpK0mwL80KgM2B' \
|
73
|
+
'/iI9h8t9Yt9Tw3vAftrOy9PHceTekDN80Ns/LXEbZq526Fc+0JSTXWy7a4A0nQnRFxN' \
|
74
|
+
'Vs2VRwJP/Qd783Pib8dPEclqhclv9Hb6ubPJ7NL5SueDN2uZRf2lI7xc+ForS8/RP5a' \
|
75
|
+
'RGglrMhkPRRdcNH8dHI8CfYIOuShV6cp3ahB1y9e0ng0O0PHWZcwK8sO20qutisZsro' \
|
76
|
+
'qp1dvu5FkvpacuL4p5bVXcYCZ6YMxt6NsT+7453qoF/wBGqL2Kxy9CQoXtM6LVSqs6F' \
|
77
|
+
'dFbAvpF9IpkRBLOsqom/p19r5KwO59BrUAJ+PHWrI1SrM8juny9mXJnoUa/kL+DLArp' \
|
78
|
+
'lCNgFvS6mKpNWfYi/Iussr5KXZcj0BPiEU9P6P5g+dZs+vEDMFqbm3LGrLXqtLVR0jM' \
|
79
|
+
'XlBjUlBO21InbKP1hLCKl1JFFViduRXbfwKJebeKkaZ5LPqAUuw0p9BzFWrnIsY+qvp' \
|
80
|
+
'JdaJzTAx/YvVCugZCVMUfFAlLExz+twUa+N1JgeC26b1uhivggyULBP6ij9mu6lvRIR' \
|
81
|
+
'jws6BoEaw4/rwrAtj+pCuv/AKo5T+v/APVP+BbbAe32o/5K/kULjPBtu78V1Xyi0APq' \
|
82
|
+
'fzM1Ml8cNiWeI8Ew6mXjggPEV5eXyGY2q/QLy9qzrGUwbtDg5Mz2zL9qQPbn+/yCV/I' \
|
83
|
+
'bbWX+m/FfaeMZSbDgfi2Cz2jye4XqWld9WXg+9W5p9SSs2a+wYUkrvcmUuYdrmKIAuJ' \
|
84
|
+
'IXOgQkh8ccs7R/yH6luOe20Wj2/vHsALW5s0XaMTMxthox1QiuTRLKwUAC1634VaKzD' \
|
85
|
+
'rvrolUqXii2ljxDTif/AMS/Hnz7S+i6x3kvOfKsYGbqdzp9BavpQ9FgHVNg3KO+v0rJ' \
|
86
|
+
'IvrEkR9Y96wM+vKI/sXU/UIY6n+XF9o/F/T/AElZ8NPXJ/Hz1d7o/OvKd35tqcc1sA8' \
|
87
|
+
'8kKNn6vXtZjTNAs1gqI9nM7SzuzOefcGzNo0z6ajOQsLCnOnWZ30fzrMPY+VD+w5dKm' \
|
88
|
+
'TGLVazG4DSOtTAGQJq/uWj6KTmkIc/qJFhDs9ANO+mIjGuS8/lcehwfLsX6NtIb/0L0' \
|
89
|
+
'jKNskA5ogq8z8cZUZ0c3D5UWdtY12rYZgt6EY+0R05MGcUJzRcir/GsoKa1Bd+8JI7d' \
|
90
|
+
'f8n2HyP868w9J8H8Q8iX7l/odoBrPMPQGbG3f/8AGglIyAbQVlZ0DT48yKeYX7r9fEy' \
|
91
|
+
'k2+2XK1A99fZzB8fMd/zQXE5d0kc36n0tnxexbMlGkTzikzVVi61eXnd6EA9iMt+k1n' \
|
92
|
+
'HKIEaGgrnWyl9Y2fXoFXxnxHMGaXUMbRZlSzmgVkJbXKwFlxKQjLkWF1WW0oLa3qp0f' \
|
93
|
+
'lHjRYH0eE485HkOR53oNDkEYgEnmkNq/a0Loy+LE06y0gydAll9a0Oi7pHaKVIQsvuW' \
|
94
|
+
'g0Bi8H7cTz6d53/oJSfyW4Hy9t8afQWCsFCo1TfcIv3tkYi4+aFE2s1YLQsLk628RJQ' \
|
95
|
+
'UStEAn0MeMCaPv7H/AO5dDJP8hPdbIuF/kmSD1OtLZ3nJaYZ4sU3URVU18KZmDLh7w5' \
|
96
|
+
'XKndE6AyqZDA8kNIizhEejfZIBl8c8lvdRotIt3fnrPBewK6TrsXcSO4ar/wAY5wf9U' \
|
97
|
+
'sz4QVbCuhhbYLZRoXMlMA+2QXTLMWc+y+kO098EK89LfC73PLFrDS0fpbsgzIjzrhZe' \
|
98
|
+
'X9vBBnlVBLUjrCR5Mf1xqZRj0L75SjTYt7wDh5XrKdKhFFjTLi6kdB2tc76Cz6jt4Lw' \
|
99
|
+
'qI45SsQSKkKvlcGLXtkJ2uQwwpMpcgFOPAtF/GE7I3fsOm/0ungjliA6xZmNkJNUaqe' \
|
100
|
+
'xwTF2g1a7eCq7TFOcgzEZrqtOHQWKMZaKCz/GUekLgFXGXx69H1HRW+Z88eTUSrMX11' \
|
101
|
+
'TzXnyKdBaZwzTs7Igu9A0a8CaswS3ye1izPZSRtlkGxH9vWfVUC2fxN/MrLfPrx70DY' \
|
102
|
+
'iYB6mQEagvR+rDbbKoUOZ3XtvpFU/TvS1+MVyY6BuB5/k+OFGZXGmGHNtOOQoOJESct' \
|
103
|
+
'/u3gU18gR4rH7CvxfN+bZnC2YPz1Rq4MMvJUFm3Yj3StAMs0AsV9VtFVDkzKNmrRYfm' \
|
104
|
+
'LzCDgJ0SYvKhaS3IZ/z9sL4P8AyHab3GhM5eI/PvWPRHmvVJJknOR/NDWF9GwKgogq6' \
|
105
|
+
'S2Kz51/X6we22xq2NrGEC5bwmMJB+eyZ/5Efzve6gsBJ+w+Efxy+QM0HVniu886vx+v' \
|
106
|
+
'9r1FMIGaPTbsQ605fHHTpOsCz0ZltSBYC8oDyVJdpTfoWU9wVJvAvF2hGfWqVhHlWZx' \
|
107
|
+
'VMKerJiLVWX1m8RY5uwXTAgZ+gxzeZkewsvhTcAOWNWW0okJK2voMb58WgxHieRT+XW' \
|
108
|
+
'L9PnLVaZfmLcw7YiVMVjfsJWsQDKIsRiQ5j9Jt/PWcOCdOi2iFY9ttI/QGlTG/Hel7U' \
|
109
|
+
'q3a6pU6csFTVOoIX8sC1CVUiVK2jKdAqM0eY8T6ryWd61YhaCFGROJjOuVU5ApPyzyr' \
|
110
|
+
'/T/Ij4SeyqBTSVeI3+wR6m4OsgouhFqc1+SxqB+IsYkzi29dXXyuEVxLXpFNcBybecp' \
|
111
|
+
'kHc+YeIesge+2eqv/AE9nYt0ZeisW+bPynEy0eLOsrJCZ2gtCpwhaUTzsBFwoC+ayuc' \
|
112
|
+
'oG8LI5+XgGrTa/eecevAV5Fx3TINXps2Ztcd9F556FOVGxW+br2QmkOgDcEL0c6CYlR' \
|
113
|
+
'OZk+9qlC2UuURBidTshlOsRJ9OlEHXalQXaE+Kbs1xQxYNw/K/7aHAg+rhpA2ToFmZy' \
|
114
|
+
'dUGPaa6fpyzv+BKn+WvSRTfGbTCnXZ+q5Q/Qk1ZhQ/X3sWNNl8OryWVP6H74ot3/AJc' \
|
115
|
+
'4eOXGmXYfrSnyU+8/wMq3mhKVrp9R6lr/ADtDVCnmQ7S36uMHIZJln4Lb+MaS2F9YVx' \
|
116
|
+
'hk7oFdY8DpIWjC9hbEbs6agPHtny5zIal5e3KAzgvCjS8aa6rCBPqEjAlaP0bQov8Ab' \
|
117
|
+
'i303fZ/qKv6S5VWbwge4fvJSI+gKDi/kNnt3EwHV+bb5YBO+1UcMVOOjoMXdvrpVaHv' \
|
118
|
+
'NWAAzCMlbdczmCOyOu7Efp9N49lXLIh80n9pg2q7XxZVBqKNBclYX3nW8KJrd2clZw9' \
|
119
|
+
'RXz9qnoxp8U9TI36ih2Wf9RojZ0agLcfED170nw9Ocxx3mtVahvq8+Sl9dIi3YzLzgj' \
|
120
|
+
'm3UUfYmTJF2ZZsnGdRySTSl6Ue21V1ozviVJD0oANK13zBYIFmZlrHCpS7eZhQ/KVwV' \
|
121
|
+
'1FwX2G1zpIEjYojoQ4fhMEKjKHHbO2U/uutJ723lcAQz+FXyAPzT+PL42NcyoroL9QS' \
|
122
|
+
'meg60IBXSDOnR6qRVMwrhmf1IvsyCyCbE8r79GEOZ8FcX/4Kfp0KZbG3MeWaz0z3DUA' \
|
123
|
+
'XWYxB5vhVmsMUkGTIW5zzzTeiOnbowZIvLucps8Fqz2bFbyfJVUo2ceQ+kI9mGR/3j5' \
|
124
|
+
'1Z9Z/Iq7zvhepXa4rQelGFjBKcYwUVr8RpIoXU0u9X7aKT+6MYt7c/+uKqIClWPmmdp' \
|
125
|
+
'LugOu9XcF0/4vffD/SvNfRM9sdKy1+6wZ9rlwMCpBssNFfkH3p6qet9SDA8bn6n1SSH' \
|
126
|
+
'rVrvvnOLQyBNJ3AQe5o5NhSp2ujw8slePuk2dLJaMVrcmxI6qNSryGv6fQQF0iGRgwd' \
|
127
|
+
'qao1qRO6+URLi5G1ymBsraoMgrZsnPRrOU3szZk1XCIv67OEXWRCsJlUWDQCmUQ/ZXg' \
|
128
|
+
'En3wsrtnTD7ZF2T50E3M1HPX31S/K7NYwUYsy0SuhPVZ/c5e1jRfAwt0HtRAiDzjgbY' \
|
129
|
+
'WCr2PCxxwZQKCJKpMh24ET+cHhmvuS4Fj5yW9atcn6fnNwv86A01CIHUXISJk0QuTLo' \
|
130
|
+
'ZvPF12jVHWW0zpJoAus7ZSOTbR9nQcH436byz295u93mWuuYb5DxTjH4DYc0ClDbXT+' \
|
131
|
+
'/dmh1xbYGkhSKddbyJRaNM3v/ABdu7WVTbURIOB7fgoFFmx/tXPkWiXk0K/Ot1mpGqb' \
|
132
|
+
'WWnLF/POh0XaKzRHLLyvwjQzukCOBYc+vL7IQnXzgCTG/EP3z2sz+l+S/yWM9UTYl2w' \
|
133
|
+
'TO0iHLaTzaWwTNxRmfVeqjjtQFkdHn4311QrVErqBZB1kUM0NtsqbLgV3+ULwbz/wAP' \
|
134
|
+
'+KuwpxAYoqlVs8izJx6xYGavsbM5XLhHNVv6ldtJQ9fe8LkTeGFULG3slpd3K76Azue' \
|
135
|
+
'bueq8KxEAuIJbuldwBIgxKt4BWWfz7Zkux+PanXCixquHDUirh7JREJv7Cnk+wtBMln' \
|
136
|
+
'iUWurc6htVojlqT+nozqz8Co3KLE8rbgxh4rG1DApawttt4wpstKibCy0j9LkI2Ed6B' \
|
137
|
+
'vzOf7OTPKiYPS9IV1KPwiLhFShMFWTZCy+cqo6kiZcrefYAyuo4NRRZb+ZOYFTX0moD' \
|
138
|
+
'/p/NVIsbWm7Yos2vXlVZuuos1XWSbbxfXn2KklYWpPlPjn9+cbxD6Ab6bpTHVn8cUrI' \
|
139
|
+
'UhpP/AINKPIvVPDfYhaU6kxAJ6CGGdkrwK7Orv+OGz7Hojpj2z+21YSZm4Hplg1nRqY' \
|
140
|
+
'kSrMs6RDvf8CqWn+LOqvdGsPOvbtrhUba65mQpTZzxQqgpiTfbzrAg7YYdi7PK6urXA' \
|
141
|
+
'fsXGW1VirxQ6u/+rL6h3PlXg+FwGTzmNwlcrfOcouuUrkIxPSAFVLw5fbymIvf/AFhV' \
|
142
|
+
'lMyOuKTgZ00KpV/l7dVMqEogiv8AJp8nvjD8Ifjqo+O/sfoTDygD5d5v1fx7Ea9vVpt' \
|
143
|
+
'SJif380zsVnbY1cI3PXZZc9dq872+m8wRaCwtmspLSIORBBPjvCfGND81cT6cD58p9P' \
|
144
|
+
'xHoL7LtVm+zMmDnDatXrARxbXEXoRYaR4jqEhUaPO2/o8qI2DwHL6PAO8LD+YZDFeIe' \
|
145
|
+
'nbHypblcWAuehEbbF6JKJWqaEq0ZQywjHPBaQahKRsrU/EVZ0hYTZcZRwgokUXttf3h' \
|
146
|
+
'0/yKo0ev85IG8/nm81o8m0Sa6BrTk2ipyFh21WgMDt6TJZOUTuhf1d5dpBn9aRVyzvL' \
|
147
|
+
'OL/t6AP2/tfrlQFNT/wAFXr1MJdVaP0m3QstAf+RgumZQ3wnknl4261r0wSy+JV9Llx' \
|
148
|
+
'kpAXzqYVQMhVKyIHfJn4J5nO2Ydqn0tj1PFK82i0GukqLsMSrsxzlv4BiOsg7bYyuXM' \
|
149
|
+
'IyYiSrnQ2ushHsv8DyXqnk0Wt/mzdOQV27DPBG98Vx/M0IbWJT2i0ixbQeXQRXXKUbI' \
|
150
|
+
'jcpiNURdKdX0q7dXMJ5aL5b4r4/Ee57XCeC/3bFjsB1wrDy1IgCaeg7i4qC4a7R32UG' \
|
151
|
+
'dtmQ450Oo0sCasICgxtImkHn14DLfGPzz5Bejhar3P5Pajmdea7OzXZzzDIPW9uGw64' \
|
152
|
+
'4jh5E6ilhF5zjWMa41iEaEAFP+ryHRkigJfb+QkPXJtJovAPSWgoSHR6vz3036O62Cy' \
|
153
|
+
'0xu8X6EHgonB6UbsEpy24SEPZbdUJEZlZKm221daXLlcwWL+W/Robfitqjrdwiz0Xrb' \
|
154
|
+
'J9aDbMQ9JMunpXRlAf8AU3gW2qeHEmcGiZoJJaK+dslbEK6qdcAypYHK4n0J6kci6k5' \
|
155
|
+
'PPMgmLL3OQLvWjlUJ77uSP+gWpvAsX9H5H9VksCHKjRaON986OWF1hz/Q7lGQzGL/AN' \
|
156
|
+
'T2DPZQf0Utr7dYBcpffpQjXaK4nVQjqp/X5DkhrOuftheNCq+uB5VtdkgEPbvMG/8At' \
|
157
|
+
'Oq9DKL40eHLTKiEWkq+jEoc6nlw6ocF4uvGjKsuJXFkldymhfG4LnOfl73gU+8jw/df' \
|
158
|
+
'ixNq+5lDhTQHgKpdYwgjJKf1IjKQUCZSRVGTFm0dGLyaCFK1lVWQvhMkqcIyBLB6f4O' \
|
159
|
+
'PYMf8fPaPb/A9Hv4ptN6xBLpcn5lr9KjoWI9T9rBxoaaXVJCuQBjqLMjs0IYbeoph0J' \
|
160
|
+
'2EdUS/ep1wa0x0sbao2EGGBWT5zvaQzuV1/XkYxs7P7u87ZbC3llMrfp9LI1Q7z/Ajv' \
|
161
|
+
'6VlWgv8qnxlphsfSMZmWXw/9uaaDznIbBokwe33Hmnq3mCZGRtcmpdwROlyxb6VsTE8' \
|
162
|
+
'ia7egda9DmMVVCwa4MxP8k3mdH8tX8zfqXmTjat6fil8K8KmxekrTWRzDPJRhmjXWtE' \
|
163
|
+
'yIDJC0La6C3aksbSyZqrU1qJPKK9wXfSjFJDWd/HR4R/xT8T8t4EGSvaYzzP/AGzI+N' \
|
164
|
+
'66TJ9PTF+b3umEQ6XQzSJ1gRaKyshWPSO1FqOTggVxGW8nIekIk/yTfyC/IcL07w/3b' \
|
165
|
+
'4jiPVec849p9T8sZ6I2rBSSOvHcZTkp+sni5/TMVuur0jJh563tQhF86FYvgrJnAVxd' \
|
166
|
+
'2AQXa9I9iCHXAYTzvL16Hdb7MxZaPQtqKw0Hn6LWBziUYw/dd1NGpzs46gVIsyy9gMI' \
|
167
|
+
'Vw0lySnX1DQNDzvlHmcPHWXsXq22oUkY3UczxohYlVbF6gmcnXAOHU7h1ys7vD25E+/' \
|
168
|
+
'14USLBKuxtClRDtlEALOmHVYtMkc0gCEhHNE4DkvvSaNDFLo7eh0FWF0fSk3/5rxuSD' \
|
169
|
+
'l91naSJSu/+SqXP8DiafW49RtkfnDs6y8/R0VjLlUhWn4Cwh/xkTnO8T6B8Opt/FOj9' \
|
170
|
+
'qyivsvpOVvO1/ZMFR9886z+H9Q+N/urFSoZIub1x5rq0ZUCTexp9E52jN7BWqLgSnFc' \
|
171
|
+
'pGYnOsbF9Yhxgl/aKyYwjLv8AgPaWI8zam8ZqTfYGOJYLSy/YjWwaWys7XVWQtCnNYP' \
|
172
|
+
'RDpNVFdv39JjRRzs5/d3/sBwTimDha2z9enIQ7BfbB4pZREi7LXfqFjh1WVEHWfqXVH' \
|
173
|
+
'EWXhw7PtBAlPKpSojGuJHQgX/Mn5gSV8cwMO1O2rFdqN4rC/wBz1W+L12//ALthI7/8' \
|
174
|
+
'VcvYVSyKfJRDgWZ9ybpZ1ZtAkYr7rK4E/wCBAtV4KgXKB1FJ86V2brDV8axKYi9oIJn' \
|
175
|
+
'TWMuHHS1qjo3fbXOy0r81qv7vr+QQjkuV/wCB7rM+ODvWDjQEaDRvDF6m1cBo3LmbNu' \
|
176
|
+
'DWJUxMHGXUEigBApU1VH6IKscSmM43F3d52y775B4BxifNdC+C17/NZ4XQ/vdvhzO5p' \
|
177
|
+
'dEGgdUZZfQCCA2HIBNsU1CDjyteVTqZTtld37Ox+vAKGj8w9b2nrec2zjYTF8NEz2jW' \
|
178
|
+
'YToNCG0vM63W5N4kxrHQZ2xcMQT/AHXozBSt0FmYIAsWZg1tMM2HOV84F1Pg9/Ex4p7' \
|
179
|
+
'Kvd6b1XSt9R6j5UlRZ+vQQCvTPEe3LJcaSzVqtCO8Y3F1zTaVMunaZMlu7r6uI0BPWY' \
|
180
|
+
'zi6QXc0/xj8g9FvUt/k5nUXq3pyxAszl2trSlCUEqFELID11hDOVX6ltrG5qyY122NP' \
|
181
|
+
'ytmLAsQ+laQCpVh/9k='
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
class Encryptor
|
5
|
+
class << self
|
6
|
+
def encrypt(value)
|
7
|
+
Base64.strict_encode64(encryptor.encrypt_and_sign(value))
|
8
|
+
end
|
9
|
+
|
10
|
+
def decrypt(value)
|
11
|
+
encryptor.decrypt_and_verify(Base64.strict_decode64(value))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def encryptor
|
17
|
+
secret_key_base = Rails.application.secrets.secret_key_base
|
18
|
+
|
19
|
+
ActiveSupport::MessageEncryptor.new(secret_key_base)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
module Generators
|
5
|
+
class Captcha
|
6
|
+
DEFAULT_DIFFICULTY = 3
|
7
|
+
DEFAULT_EXPIRY = 10.minutes
|
8
|
+
DEFAULT_WIDTH = 140
|
9
|
+
DEFAULT_ACTION_LABEL = 'Type the letters you see:'
|
10
|
+
DEFAULT_RELOAD_LABEL = 'Reload'
|
11
|
+
DEFAULT_RELOAD_MAX = 5
|
12
|
+
DEFAULT_RELOAD_COUNT = 1
|
13
|
+
|
14
|
+
def self.call(*args)
|
15
|
+
new(*args).send(:call)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(args = {})
|
19
|
+
@id = args[:id] || SecureRandom.uuid
|
20
|
+
@difficulty = args[:difficulty] || DEFAULT_DIFFICULTY
|
21
|
+
@expiry = args[:expiry] || DEFAULT_EXPIRY
|
22
|
+
@width = (args[:width] || DEFAULT_WIDTH).to_i
|
23
|
+
@action_label = args[:action_label] || DEFAULT_ACTION_LABEL
|
24
|
+
@reload_label = args[:reload_label] || DEFAULT_RELOAD_LABEL
|
25
|
+
@reload_max = args[:reload_max] || DEFAULT_RELOAD_MAX
|
26
|
+
@reload_count = args[:reload_count] || DEFAULT_RELOAD_COUNT
|
27
|
+
@reload = args[:reload] == false ? false : allow_reload?
|
28
|
+
@css = (args[:css] != false)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader(
|
34
|
+
:id,
|
35
|
+
:difficulty,
|
36
|
+
:expiry,
|
37
|
+
:width,
|
38
|
+
:action_label,
|
39
|
+
:reload_label,
|
40
|
+
:reload_max,
|
41
|
+
:reload_count,
|
42
|
+
:reload,
|
43
|
+
:css
|
44
|
+
)
|
45
|
+
|
46
|
+
def call
|
47
|
+
arguments_check
|
48
|
+
|
49
|
+
Html.call(
|
50
|
+
id: id,
|
51
|
+
puzzle: puzzle,
|
52
|
+
width: width,
|
53
|
+
action_label: action_label,
|
54
|
+
truth_payload: truth_payload,
|
55
|
+
reload_payload: reload_payload,
|
56
|
+
reload_label: reload_label,
|
57
|
+
reload: reload,
|
58
|
+
css: css
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def puzzle
|
63
|
+
Puzzle.call(truth, difficulty)
|
64
|
+
end
|
65
|
+
|
66
|
+
def truth
|
67
|
+
@truth ||= Truth.call(difficulty)
|
68
|
+
end
|
69
|
+
|
70
|
+
def truth_payload
|
71
|
+
Encryptor.encrypt(truth: truth, timestamp: Time.current + expiry)
|
72
|
+
end
|
73
|
+
|
74
|
+
def reload_payload
|
75
|
+
return unless reload
|
76
|
+
|
77
|
+
Encryptor.encrypt(
|
78
|
+
id: id,
|
79
|
+
difficulty: difficulty,
|
80
|
+
expiry: expiry,
|
81
|
+
width: width,
|
82
|
+
action_label: action_label,
|
83
|
+
reload_label: reload_label,
|
84
|
+
reload_max: reload_max,
|
85
|
+
reload_count: reload_count,
|
86
|
+
reload: reload,
|
87
|
+
css: css
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def allow_reload?
|
92
|
+
@reload_count <= @reload_max
|
93
|
+
end
|
94
|
+
|
95
|
+
def arguments_check
|
96
|
+
unless difficulty.is_a?(Integer) && difficulty.between?(1, 5)
|
97
|
+
raise Error, "'difficulty' must be an Integer value between 1 and 5."
|
98
|
+
end
|
99
|
+
|
100
|
+
return if expiry.is_a?(ActiveSupport::Duration)
|
101
|
+
|
102
|
+
raise Error, "'expiry' must be an ActiveSupport::Duration object."
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
module Generators
|
5
|
+
class Html
|
6
|
+
ATTR_NAMES = %i[
|
7
|
+
id
|
8
|
+
puzzle
|
9
|
+
width
|
10
|
+
action_label
|
11
|
+
truth_payload
|
12
|
+
reload_payload
|
13
|
+
reload_label
|
14
|
+
reload
|
15
|
+
css
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def self.call(*args)
|
19
|
+
new(*args).send(:call)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(args)
|
23
|
+
ATTR_NAMES.each do |attr_name|
|
24
|
+
instance_variable_set("@#{attr_name}", args[attr_name])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader(*ATTR_NAMES)
|
31
|
+
|
32
|
+
def call
|
33
|
+
tags = [
|
34
|
+
style_tag,
|
35
|
+
action_tag,
|
36
|
+
truth_tag,
|
37
|
+
guess_tag,
|
38
|
+
puzzle_tag,
|
39
|
+
reload_animation_tag,
|
40
|
+
reload_tag,
|
41
|
+
javascript_tag
|
42
|
+
].join
|
43
|
+
|
44
|
+
"<div id='#{container_id}' class='captchah'>#{tags}</div>"
|
45
|
+
.gsub(/(\s\s)*/, '')
|
46
|
+
.html_safe
|
47
|
+
end
|
48
|
+
|
49
|
+
def style_tag
|
50
|
+
return unless css
|
51
|
+
|
52
|
+
"<style type='text/css'>
|
53
|
+
##{container_id} {
|
54
|
+
background-color: #f9f9f9;
|
55
|
+
border-radius: 2px;
|
56
|
+
border: 1px solid #d3d3d3;
|
57
|
+
color: black;
|
58
|
+
font-family: 'Verdana';
|
59
|
+
font-size: 11px;
|
60
|
+
letter-spacing: 0;
|
61
|
+
max-width: #{width}px;
|
62
|
+
padding: 10px;
|
63
|
+
}
|
64
|
+
|
65
|
+
##{container_id} .captchah-guess {
|
66
|
+
background-color: white;
|
67
|
+
border-radius: 2px;
|
68
|
+
border: 1px solid #c1c1c1;
|
69
|
+
font-size: 13px;
|
70
|
+
height: 25px;
|
71
|
+
margin: 5px 0;
|
72
|
+
max-width: #{width - 12}px;
|
73
|
+
min-width: 0;
|
74
|
+
outline: none;
|
75
|
+
padding: 0 5px;
|
76
|
+
width: 100%;
|
77
|
+
}
|
78
|
+
|
79
|
+
##{container_id} .captchah-reload-animation {
|
80
|
+
margin-top: 11px;
|
81
|
+
transform: translateY(-50%);
|
82
|
+
}
|
83
|
+
|
84
|
+
##{container_id} .captchah-puzzle,
|
85
|
+
##{container_id} .captchah-guess {
|
86
|
+
display: block;
|
87
|
+
}
|
88
|
+
|
89
|
+
##{container_id} .captchah-action {
|
90
|
+
margin: 0;
|
91
|
+
padding: 0;
|
92
|
+
}
|
93
|
+
|
94
|
+
##{container_id} .captchah-puzzle {
|
95
|
+
margin: 0
|
96
|
+
padding: 0;
|
97
|
+
width: 100%;
|
98
|
+
}
|
99
|
+
|
100
|
+
##{container_id} .captchah-reload {
|
101
|
+
color: #5e5e5e;
|
102
|
+
cursor: pointer;
|
103
|
+
display: inline-block;
|
104
|
+
line-height: 1;
|
105
|
+
margin-top: 5px;
|
106
|
+
text-decoration: none;
|
107
|
+
transition: 0.3s;
|
108
|
+
}
|
109
|
+
|
110
|
+
##{container_id} .captchah-reload:hover {
|
111
|
+
color: black;
|
112
|
+
}
|
113
|
+
</style>"
|
114
|
+
end
|
115
|
+
|
116
|
+
def action_tag
|
117
|
+
"<p class='captchah-action'>#{action_label}</p>"
|
118
|
+
end
|
119
|
+
|
120
|
+
def truth_tag
|
121
|
+
'<input ' \
|
122
|
+
"type='hidden' " \
|
123
|
+
"name='captchah[truth]' " \
|
124
|
+
"value='#{truth_payload}' " \
|
125
|
+
"class='captchah-truth'>"
|
126
|
+
end
|
127
|
+
|
128
|
+
def guess_tag
|
129
|
+
'<input ' \
|
130
|
+
"type='text' " \
|
131
|
+
"autocomplete='off' " \
|
132
|
+
"name='captchah[guess]' " \
|
133
|
+
"class='captchah-guess'>"
|
134
|
+
end
|
135
|
+
|
136
|
+
def puzzle_tag
|
137
|
+
'<img ' \
|
138
|
+
"src='#{puzzle}' " \
|
139
|
+
"class='captchah-puzzle'>"
|
140
|
+
end
|
141
|
+
|
142
|
+
def reload_animation_tag
|
143
|
+
'<img ' \
|
144
|
+
"src='#{"data:image/gif;base64,#{Base64Images.loader}"}' " \
|
145
|
+
"style='display: none;' " \
|
146
|
+
"class='captchah-reload-animation'>"
|
147
|
+
end
|
148
|
+
|
149
|
+
def reload_tag
|
150
|
+
return unless reload
|
151
|
+
|
152
|
+
'<span ' \
|
153
|
+
"onclick='captchah(this)' " \
|
154
|
+
"data-payload='#{reload_payload}' " \
|
155
|
+
"class='captchah-reload'>" \
|
156
|
+
"#{reload_label}" \
|
157
|
+
'</span>'
|
158
|
+
end
|
159
|
+
|
160
|
+
def javascript_tag
|
161
|
+
return unless reload
|
162
|
+
|
163
|
+
"<script type='text/javascript'>
|
164
|
+
var captchah = function(reload) {
|
165
|
+
var captchahLoaderAnimation = function(el_show, el_hide){
|
166
|
+
el_hide.style.display='none';
|
167
|
+
el_show.style.display='inline-block';
|
168
|
+
};
|
169
|
+
|
170
|
+
captchahLoaderAnimation(reload.previousSibling, reload);
|
171
|
+
|
172
|
+
var xhr = new XMLHttpRequest();
|
173
|
+
xhr.open('POST', '/captchah');
|
174
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
175
|
+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
176
|
+
xhr.onload = function() {
|
177
|
+
if (xhr.status === 200) {
|
178
|
+
reload.parentNode.outerHTML = xhr.responseText;
|
179
|
+
}
|
180
|
+
else if (xhr.status !== 200) {
|
181
|
+
console.log('Error: Unable to change captcha.');
|
182
|
+
captchahLoaderAnimation(reload, reload.previousSibling);
|
183
|
+
}
|
184
|
+
};
|
185
|
+
xhr.send(JSON.stringify({captchah: reload.dataset.payload}));
|
186
|
+
};
|
187
|
+
</script>"
|
188
|
+
end
|
189
|
+
|
190
|
+
def container_id
|
191
|
+
@container_id ||= "captchah-#{id}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
module Generators
|
5
|
+
class Puzzle
|
6
|
+
def self.call(*args)
|
7
|
+
new(*args).send(:call)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(truth, difficulty)
|
11
|
+
@truth = truth
|
12
|
+
@difficulty = difficulty
|
13
|
+
|
14
|
+
@font = 'Verdana'
|
15
|
+
@color1 = '44,44,44'
|
16
|
+
@color2 = '235,235,235'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :truth, :difficulty, :font, :color1, :color2
|
22
|
+
|
23
|
+
def call
|
24
|
+
image.combine_options do |c|
|
25
|
+
c.font(font)
|
26
|
+
c.pointsize(23)
|
27
|
+
c.blur("0x#{difficulty < 4 ? 3 : 4}")
|
28
|
+
c.fill(rgba(color2, opacity))
|
29
|
+
c.distort(
|
30
|
+
'Shepards',
|
31
|
+
"#{rand(-108..0)},0 " \
|
32
|
+
"0,#{rand(-108..0)} " \
|
33
|
+
"#{rand(0..108)},0 " \
|
34
|
+
"#{rand(0..108)},8"
|
35
|
+
)
|
36
|
+
c.draw("text 23,#{rand(21..30)} '#{truth[0..2]}'")
|
37
|
+
c.distort(
|
38
|
+
'Shepards',
|
39
|
+
'30,-50 ' \
|
40
|
+
"0,#{rand(-7..-3)} " \
|
41
|
+
"#{rand(8..17)},#{rand(2..3)} " \
|
42
|
+
"#{difficulty > 2 ? 15 : 10},#{rand(-5..-1)}"
|
43
|
+
)
|
44
|
+
c.draw("line 0,#{rand(25..35)} 290,30") if difficulty > 1
|
45
|
+
c.fill(rgba(color1, opacity))
|
46
|
+
c.pointsize(26)
|
47
|
+
c.draw("text 82,#{rand(25..40)} '#{truth[3..-1]}'")
|
48
|
+
c.draw("line 0,#{rand(10..20)} 400,20") if difficulty > 1
|
49
|
+
|
50
|
+
if difficulty < 3
|
51
|
+
c.fill(rgba(color2, -0.1))
|
52
|
+
c.draw('circle 0,0 300,0')
|
53
|
+
end
|
54
|
+
|
55
|
+
if difficulty > 3
|
56
|
+
c.pointsize(20)
|
57
|
+
c.fill(rgba(color1, -1.3))
|
58
|
+
(difficulty == 5 ? 2 : 1).times do
|
59
|
+
c.draw("text #{rand(43..55)},#{rand(18..48)} ',./ -,_'")
|
60
|
+
end
|
61
|
+
c.fill(rgba(color1, -0.2))
|
62
|
+
c.draw("text #{rand(68..75)},#{rand(10..28)} '/,\ ^._ -'")
|
63
|
+
end
|
64
|
+
|
65
|
+
if difficulty == 5
|
66
|
+
c.pointsize(40)
|
67
|
+
c.draw("text #{rand(0..36)},#{rand(38..48)} '. \ _ . -'")
|
68
|
+
end
|
69
|
+
|
70
|
+
c.blur("1x#{difficulty - 2}") if difficulty > 2
|
71
|
+
c.blur('1x0.1')
|
72
|
+
c.quality(100)
|
73
|
+
end
|
74
|
+
|
75
|
+
base64_encode
|
76
|
+
ensure
|
77
|
+
image&.destroy!
|
78
|
+
end
|
79
|
+
|
80
|
+
def base64_encode
|
81
|
+
image_content = File.open(image.path, &:read)
|
82
|
+
|
83
|
+
"data:image/jpeg;base64,#{Base64.strict_encode64(image_content)}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def image
|
87
|
+
@image ||=
|
88
|
+
begin
|
89
|
+
base64_image = Base64Images.puzzle_background
|
90
|
+
decoded_image = Base64.strict_decode64(base64_image)
|
91
|
+
MiniMagick::Image.read(decoded_image)
|
92
|
+
rescue NameError => e
|
93
|
+
raise Error, 'Missing MiniMagick.' if e.to_s.include?('MiniMagick')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def opacity
|
98
|
+
@opacity ||= difficulty > 2 ? -1.3 : 1.2
|
99
|
+
end
|
100
|
+
|
101
|
+
def rgba(rgb, alpha)
|
102
|
+
"rgba(#{rgb},#{alpha})"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
module Generators
|
5
|
+
class Truth
|
6
|
+
def self.call(difficulty)
|
7
|
+
chrs = []
|
8
|
+
|
9
|
+
(rand(0..1).positive? ? 4 : 5).times do
|
10
|
+
up_or_down = rand(0..1).positive? && difficulty > 1 && difficulty != 3
|
11
|
+
chr = (up_or_down ? rand(97..122) : rand(65..90)).chr
|
12
|
+
chrs << (%w[i j l r].any? { |c| c == chr } ? chr.upcase : chr)
|
13
|
+
end
|
14
|
+
|
15
|
+
chrs.join
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Captchah
|
4
|
+
class Verifier
|
5
|
+
def self.call(params)
|
6
|
+
return :no_params unless params.present?
|
7
|
+
|
8
|
+
return :invalid if params[:guess].blank? || params[:truth].blank?
|
9
|
+
|
10
|
+
truth_payload = Encryptor.decrypt(params[:truth])
|
11
|
+
|
12
|
+
guess = params[:guess].downcase.delete(' ')
|
13
|
+
|
14
|
+
return :expired unless truth_payload[:timestamp] >= Time.current
|
15
|
+
|
16
|
+
return :valid if guess == truth_payload[:truth].downcase
|
17
|
+
|
18
|
+
:invalid
|
19
|
+
rescue ArgumentError, MessageEncryptor::InvalidMessage
|
20
|
+
:invalid
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: captchah
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evgeni Radev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mini_magick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.3'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- evgeniradev@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- app/controllers/captchah/captchah_controller.rb
|
78
|
+
- config/routes.rb
|
79
|
+
- lib/captchah.rb
|
80
|
+
- lib/captchah/base64_images.rb
|
81
|
+
- lib/captchah/encryptor.rb
|
82
|
+
- lib/captchah/generators/captcha.rb
|
83
|
+
- lib/captchah/generators/html.rb
|
84
|
+
- lib/captchah/generators/puzzle.rb
|
85
|
+
- lib/captchah/generators/truth.rb
|
86
|
+
- lib/captchah/verifier.rb
|
87
|
+
- lib/captchah/version.rb
|
88
|
+
homepage: https://github.com/evgeniradev/captchah
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata:
|
92
|
+
allowed_push_host: https://rubygems.org
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements:
|
108
|
+
- You must have ImageMagick or GraphicsMagick installed.
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.7.9
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: A Rails captcha gem that attempts to determine whether or not a user is human.
|
114
|
+
test_files: []
|