captcher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +210 -0
  4. data/Rakefile +22 -0
  5. data/app/controllers/captcher/captchas_controller.rb +22 -0
  6. data/app/controllers/concerns/captcher/captcha_aware.rb +30 -0
  7. data/app/helpers/captcher/application_helper.rb +13 -0
  8. data/app/views/layouts/captcher/application.html.erb +16 -0
  9. data/config/routes.rb +10 -0
  10. data/lib/captcher.rb +57 -0
  11. data/lib/captcher/base_captcha.rb +46 -0
  12. data/lib/captcher/captchas/awesome_captcha.rb +7 -0
  13. data/lib/captcher/captchas/code_captcha.rb +40 -0
  14. data/lib/captcher/captchas/math_captcha.rb +7 -0
  15. data/lib/captcher/config.rb +41 -0
  16. data/lib/captcher/engine.rb +5 -0
  17. data/lib/captcher/text_image.rb +48 -0
  18. data/lib/captcher/version.rb +3 -0
  19. data/lib/fonts/Bangers-Regular.ttf +0 -0
  20. data/lib/fonts/CarterOne.ttf +0 -0
  21. data/lib/fonts/FrederickatheGreat-Regular.ttf +0 -0
  22. data/lib/fonts/IndieFlower-Regular.ttf +0 -0
  23. data/lib/fonts/LobsterTwo-BoldItalic.ttf +0 -0
  24. data/lib/fonts/SigmarOne-Regular.ttf +0 -0
  25. data/lib/tasks/captcher_tasks.rake +4 -0
  26. data/spec/dummy/Rakefile +6 -0
  27. data/spec/dummy/app/assets/config/manifest.js +4 -0
  28. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  29. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  31. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  32. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  33. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/jobs/application_job.rb +2 -0
  36. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  37. data/spec/dummy/app/models/application_record.rb +3 -0
  38. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  39. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  40. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  41. data/spec/dummy/bin/bundle +3 -0
  42. data/spec/dummy/bin/rails +4 -0
  43. data/spec/dummy/bin/rake +4 -0
  44. data/spec/dummy/bin/setup +36 -0
  45. data/spec/dummy/bin/update +31 -0
  46. data/spec/dummy/bin/yarn +11 -0
  47. data/spec/dummy/config.ru +5 -0
  48. data/spec/dummy/config/application.rb +30 -0
  49. data/spec/dummy/config/boot.rb +5 -0
  50. data/spec/dummy/config/cable.yml +10 -0
  51. data/spec/dummy/config/database.yml +25 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +61 -0
  54. data/spec/dummy/config/environments/production.rb +94 -0
  55. data/spec/dummy/config/environments/test.rb +46 -0
  56. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  57. data/spec/dummy/config/initializers/assets.rb +14 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  60. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  61. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/spec/dummy/config/initializers/inflections.rb +16 -0
  63. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +33 -0
  66. data/spec/dummy/config/puma.rb +34 -0
  67. data/spec/dummy/config/routes.rb +3 -0
  68. data/spec/dummy/config/spring.rb +6 -0
  69. data/spec/dummy/config/storage.yml +34 -0
  70. data/spec/dummy/db/development.sqlite3 +0 -0
  71. data/spec/dummy/db/test.sqlite3 +0 -0
  72. data/spec/dummy/log/development.log +6608 -0
  73. data/spec/dummy/log/test.log +6492 -0
  74. data/spec/dummy/package.json +5 -0
  75. data/spec/dummy/public/404.html +67 -0
  76. data/spec/dummy/public/422.html +67 -0
  77. data/spec/dummy/public/500.html +66 -0
  78. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  79. data/spec/dummy/public/apple-touch-icon.png +0 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/tmp/development_secret.txt +1 -0
  82. data/spec/helpers.rb +23 -0
  83. data/spec/lib/captcher/config_spec.rb +41 -0
  84. data/spec/models/captchas/code_captcha_spec.rb +45 -0
  85. data/spec/rails_helper.rb +72 -0
  86. data/spec/requests/captcha_management_spec.rb +48 -0
  87. data/spec/spec_helper.rb +96 -0
  88. data/spec/tmp/test.png +0 -0
  89. metadata +291 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 42a4bd27e904e52012256226988e854b5d21cf73a69af147f3407de4cd3e8965
4
+ data.tar.gz: 8effe1d5469292eece64a258f89dd604a2c0389473af70dd1553e80d820ae052
5
+ SHA512:
6
+ metadata.gz: 117d2b310d1ec56e1474f92c5a28ab09340df48e53e6514581f35febfe7e63ad2a6d9c5b767a9224a3209678eb90dd648d686c4c9412bbd93957cf82a0e566fd
7
+ data.tar.gz: c7a699acdf006c866d389a28caf0143db664919a1811fc039c1f79c54502ffc2c00c569e82bdbe741218af43ee338b132a2f5f8a4b393072b432c80a7ba072bc
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Ivan Zinovyev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # Captcher
2
+
3
+ Easy to use classical captcha for Rails apps
4
+
5
+ ![Example of captcha field](docs/captcha_field.png "Captcher captcha")
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+
14
+ gem 'captcher'
15
+
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```bash
21
+
22
+ $ bundle
23
+
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+
30
+ $ gem install captcher
31
+
32
+ ```
33
+
34
+ Mount the engine inside your application by adding this line to your application's routes file at `config/routes.rb`:
35
+
36
+ ```ruby
37
+
38
+ mount Captcher::Engine => "/captcher"
39
+
40
+ ```
41
+
42
+
43
+ ## Usage
44
+
45
+ ### Render on page
46
+
47
+ 1. Include the concern with helper methods to your ApplicationController:
48
+
49
+ ```ruby
50
+
51
+ class ApplicationController < ActionController::Base
52
+ include Captcher::CaptchaAware
53
+ end
54
+
55
+ ```
56
+
57
+ 2. Use helper methods in your controller:
58
+
59
+ ```ruby
60
+
61
+ class MyController < ApplicationController
62
+ def index
63
+ reload_captcha(session) # Reload the captcha
64
+ # render response with success code ...
65
+ end
66
+
67
+ def create
68
+ @comment = Comment.new(comment_params)
69
+ captcha_check = confirm_captcha?(session, params[:captcha])
70
+ if @comment.valid? && captcha_check && @comment.save
71
+ # render response with success code ...
72
+ else
73
+ @comment.errors[:captcha] << "Captcha verification failed" unless captcha_check
74
+ # render response with error code ...
75
+ end
76
+ end
77
+
78
+ # ... some other code
79
+ end
80
+
81
+ ```
82
+
83
+ 3. An example bootstrap-based html/erb code:
84
+
85
+
86
+ ```html
87
+
88
+ <%= simple_form_for(some_form) do |f| %>
89
+ <!-- Some html/erb code for all form fields -->
90
+ <!-- ... -->
91
+ <!-- /Some html/erb code for all form fields -->
92
+
93
+ <div class="input-group">
94
+ <%= text_field_tag :captcha, "",
95
+ type: :text,
96
+ label: false,
97
+ class: "form-control",
98
+ placeholder: "Enter the captcha" %>
99
+
100
+ <div class="input-group-append">
101
+ <div class="input-group-text" style="padding: 0">
102
+ <%= image_tag(captcher.captcha_path(format: :png), style: "height: 35px;",
103
+ id: "captcha-image") %>
104
+ </div>
105
+ <button class="btn btn-outline-secondary" type="button" id="captcha-reload">
106
+ <i class="fa fa-refresh"></i>
107
+ </button>
108
+ </div>
109
+ </div>
110
+
111
+ <% end %>
112
+
113
+ ```
114
+
115
+ 4. Javascript code to refresh the capture:
116
+
117
+
118
+ ```javascript
119
+
120
+ function reloadCaptcha() {
121
+ $.ajax({
122
+ type: 'GET',
123
+ url: '/captcher/captcha/reload.png',
124
+ success: function() {
125
+ var timestamp = (new Date()).getTime();
126
+ $('#captcha-image').attr("src", "/captcher/captcha.png?" + timestamp);
127
+ },
128
+ });
129
+ }
130
+
131
+ $('#captcha-reload').click(function() {
132
+ reloadCaptcha();
133
+ });
134
+
135
+ ```
136
+
137
+
138
+ ### API endpoints
139
+
140
+ These endpoints are available by default (as soon as you've mounted the `Captcher` engine to your `routes.rb` file) and can be used for some async requests:
141
+
142
+ * `http://your-application.com/captcher/captcha` - Load the captcha image
143
+
144
+ * `http://your-application.com/captcher/captcha/reload` - Reload the captcha
145
+
146
+ * `http://your-application.com/captcher/captcha/confirm?confirmation=code` - Confirm captcha code
147
+
148
+
149
+ ## Configuration
150
+
151
+ ```ruby
152
+
153
+ # config/initialiers/captcher.rb
154
+
155
+ Captcher.configure do |c|
156
+ c.mode = :code_captcha
157
+
158
+ c.code_captcha do |cc|
159
+ cc.fonts Dir[Captcher::Engine.root.join("lib/fonts/**")]
160
+ cc.font_size 50
161
+ cc.font_color "black"
162
+ cc.count 5
163
+ cc.background "#999999"
164
+ cc.format "png"
165
+ end
166
+ end
167
+
168
+ ```
169
+
170
+
171
+ ## TODO
172
+
173
+ 1. Implement some other types of captcha
174
+
175
+ 2. Integrate with Travis to test the gem against different versions of Ruby/ROR
176
+
177
+ 3. Improve code style
178
+
179
+ 4. Improve documentation
180
+
181
+
182
+ ## Contributing
183
+
184
+ Contribution directions go here.
185
+
186
+
187
+ ## Fonts
188
+
189
+ The fonts wich are shiped by default with this repo
190
+ are taken from https://github.com/google/fonts
191
+ and use the SIL Open Font License, v1.1
192
+
193
+ **There's a list of the origin paths of the fonts**:
194
+
195
+ * https://github.com/google/fonts/tree/master/ofl/bangers
196
+
197
+ * https://github.com/google/fonts/tree/master/ofl/carterone
198
+
199
+ * https://github.com/google/fonts/tree/master/ofl/frederickathegreat
200
+
201
+ * https://github.com/google/fonts/tree/master/ofl/indieflower
202
+
203
+ * https://github.com/google/fonts/tree/master/ofl/lobstertwo
204
+
205
+ * https://github.com/google/fonts/tree/master/ofl/sigmarone
206
+
207
+
208
+ ## License
209
+
210
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Captcher'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
@@ -0,0 +1,22 @@
1
+ module Captcher
2
+ class CaptchasController < ActionController::Base
3
+ include Captcher::CaptchaAware
4
+
5
+ def show
6
+ render_captcha(load_captcha(session))
7
+ end
8
+
9
+ def reload
10
+ render_captcha(reload_captcha(session))
11
+ end
12
+ alias refresh reload
13
+
14
+ def confirm
15
+ if confirm_captcha?(session, params[:confirmation])
16
+ render json: { success: true }, status: 200
17
+ else
18
+ render json: { success: false }, status: 422
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module Captcher
2
+ module CaptchaAware
3
+ extend ActiveSupport::Concern
4
+
5
+ def load_captcha(session)
6
+ Captcher.captcha_class.restore_or_create(Captcher.config, session)
7
+ end
8
+
9
+ def reload_captcha(session)
10
+ captcha = Captcher.captcha_class.new(config: Captcher.config)
11
+ captcha.store(session)
12
+ captcha
13
+ end
14
+
15
+ def confirm_captcha(session, confirmation)
16
+ captcha = load_captcha(session)
17
+ captcha.validate(confirmation)
18
+ end
19
+ alias confirm_captcha? confirm_captcha
20
+
21
+ private
22
+
23
+ def render_captcha(captcha)
24
+ format = params[:format] || captcha.own_config[:format]
25
+ filename = "captcha.#{format}"
26
+ type = "image/#{format}"
27
+ send_data captcha.represent, filename: filename, type: type, disposition: :inline
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Captcher
2
+ module ApplicationHelper
3
+ def captcha_tag(options)
4
+ options = options.symbolize_keys
5
+
6
+ src = options[:src] = captcha_path
7
+
8
+ options[:alt] = options.fetch(:alt) { image_alt(src) }
9
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
10
+ tag("img", options)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Captcher</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "captcher/application", media: "all" %>
9
+ <%= javascript_include_tag "captcher/application" %>
10
+ </head>
11
+ <body>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ Captcher::Engine.routes.draw do
2
+ resource :captcha, only: [:show] do
3
+ member do
4
+ %i[reload refresh confirm].each do |route|
5
+ send :post, route
6
+ send :get, route
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/captcher.rb ADDED
@@ -0,0 +1,57 @@
1
+ require "mini_magick"
2
+ require "captcher/engine"
3
+ require "captcher/config"
4
+ require "captcher/text_image"
5
+ require "captcher/base_captcha"
6
+ require "captcher/captchas/awesome_captcha"
7
+ require "captcher/captchas/math_captcha"
8
+ require "captcher/captchas/code_captcha"
9
+ require "pry" if Rails.env.in?(%w[development test])
10
+
11
+ module Captcher
12
+ extend self
13
+
14
+ def configure
15
+ @config = Captcher::Config.new
16
+ yield(@config)
17
+ self
18
+ end
19
+
20
+ def config
21
+ default_config.merge(@config)
22
+ end
23
+
24
+ def captcha_class
25
+ return @captcha_class if @captcha_class
26
+
27
+ klass = Captcher.config[:mode].to_s.camelize
28
+ @captcha_class = "Captcher::Captchas::#{klass}".constantize
29
+ end
30
+
31
+ private
32
+
33
+ # rubocop:disable Metrics/MethodLength
34
+ def default_config
35
+ @default_config ||= Captcher::Config.new do |c|
36
+ c.mode = :code_captcha
37
+
38
+ c.code_captcha do |cc|
39
+ cc.fonts Dir[Captcher::Engine.root.join("lib/fonts/**")]
40
+ cc.font_size 50
41
+ cc.font_color "black"
42
+ cc.count 5
43
+ cc.background "#999999"
44
+ cc.format "png"
45
+ end
46
+
47
+ c.math_captcha do |mc|
48
+
49
+ end
50
+
51
+ c.awesome_captcha do |ac|
52
+
53
+ end
54
+ end
55
+ end
56
+ # rubocop:enable Metrics/MethodLength
57
+ end
@@ -0,0 +1,46 @@
1
+ module Captcher
2
+ class BaseCaptcha
3
+ SESSION_KEY = "captcha_state".freeze
4
+
5
+ class << self
6
+ attr_accessor :name
7
+
8
+ def restore(session)
9
+ state = session[SESSION_KEY]
10
+ new(state) if state
11
+ end
12
+
13
+ def restore_or_create(config, session)
14
+ restore(session) || new(config: config).store(session)
15
+ end
16
+ end
17
+
18
+ attr_accessor :config, :payload
19
+
20
+ def initialize(options = {})
21
+ options = options.with_indifferent_access
22
+ @config = options[:config] if options[:config]
23
+ @payload = options[:payload] if options[:payload]
24
+ after_initialize
25
+ end
26
+
27
+ def after_initialize; end
28
+
29
+ def store(session)
30
+ session[SESSION_KEY] = { payload: payload, config: config }
31
+ self
32
+ end
33
+
34
+ def own_config
35
+ @own_config ||= @config[self.class.name.to_sym]
36
+ end
37
+
38
+ def represent(format = :html, options = {})
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def validate(confirmation)
43
+ raise NotImplementedError
44
+ end
45
+ end
46
+ end