active_hashcash 0.3.1 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbc710b7b2cc6fef0667915ac1a95ccfdd6d1cb3eaa1fa9b97d29ef81e79985d
4
- data.tar.gz: 323c22e60aa27d2d87e39ee8f5c356d2fc6e8190426e49831a29d762d2e74f28
3
+ metadata.gz: a8e7f600a4c42efa28175ebb98d4ba2206295bb316b450f79167fae543a52f71
4
+ data.tar.gz: a62096662ee76b1b31529c71660cae12d8c0c7b01033d2440490aef839d6ed3f
5
5
  SHA512:
6
- metadata.gz: ed74328b7b1f91eb2dece7a29efb5b9d68079872694e589caca3e79de1a06e6d0bd5d2556dcab12f92fcede4c08b075a4dc4b9077e79c8511cb4045ce509c82c
7
- data.tar.gz: e8f580f0668529b3bd7e220f2bdbd4b2eae6bc63272228b9cb4d1fe75ef0e30202983e3e06d91664558d36239137b69d7ba96996c80b370f4b1638300f77781a
6
+ metadata.gz: 4fa81355c730a651547d30e0e115bbfc3bcc35623482efb85d324e6ecf826acec46dc7f3b47a8dce48264f8da227b487465d98ba964571b3670f244188653590
7
+ data.tar.gz: 9aeb5f2fa5c053795402d30c286491f43034a173a26913e3ec81210bb6da95dbc11a547cba98fab1bff81fc22eb832e2984744df1bdc580482c8d882ba213926
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Changelog of ActiveHashcash
2
2
 
3
- ## Unrelease
3
+ ## 0.4.0 (2025-05-15)
4
+
5
+ - Prevent from password managers to submit the form before the stamp has been computed
6
+ - Added support for the "button" submit form tag
7
+ - Added Catalan language
8
+ - Added `base_controller_class` configuration option to allow specifying a custom base controller for the ActiveHashcash dashboard, enhancing flexibility in diverse application architectures.
9
+
10
+ ## 0.3.2 (2024-08-29)
11
+
12
+ - Fix methods conflict by not including ActionView::Helpers::FormTagHelper
13
+ - Sanitize params by forcing as a String
14
+
15
+ ## 0.3.1 - 2024-04-04
4
16
 
5
17
  - Fix gem spec list files
6
18
 
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # ActiveHashcash
2
2
 
3
- <img align="right" width="200px" src="logo.png" alt="Active Hashcash logo"/>
3
+ Protect Rails applications against bots and brute force attacks without annoying humans.
4
4
 
5
- ActiveHashcash protects Rails applications against bots and brute force attacks without annoying humans.
5
+ <div><img align="right" width="200px" src="logo.png" alt="Active Hashcash logo"/></div>
6
6
 
7
7
  Hashcash is proof-of-work algorithm, invented by Adam Back in 1997, to protect systems against denial of service attacks.
8
8
  ActiveHashcash is an easy way to protect any Rails application against brute force attacks and bots.
9
9
 
10
10
  The idea is to force clients to spend some time to solve a hard problem that is very easy to verify for the server.
11
- We have developped ActiveHashcash after seeing brute force attacks against our Rails application monitoring service [RorVsWild](https://rorvswild.com).
11
+ We have developed ActiveHashcash after seeing brute force attacks against our Rails application monitoring service [RorVsWild](https://rorvswild.com).
12
12
 
13
13
  ActiveHashcash is ideal to set up on sensitive forms such as login and registration.
14
14
  While the user is filling the form, the problem is solved in JavaScript and set the result into a hidden input text.
@@ -27,33 +27,27 @@ Here is a [demo on a registration form](https://www.rorvswild.com/session) :
27
27
 
28
28
  ---
29
29
 
30
- <img align="left" height="24px" src="rorvswild_logo.jpg" alt="RorVsWild logo"/>Made by <a href="https://www.rorvswild.com">RorVsWild</a>, performances & exceptions monitoring for Ruby on Rails applications.
30
+ <div><img align="left" height="24px" src="rorvswild_logo.jpg" alt="RorVsWild logo"/>Made by <a href="https://www.rorvswild.com">RorVsWild</a>, performances & exceptions monitoring for Ruby on Rails applications.</div>
31
31
 
32
32
  ---
33
33
 
34
34
  ## Installation
35
35
 
36
- Add this line to your application's Gemfile:
36
+ Add this line to your application's Gemfile and run `bundle install`:
37
37
 
38
38
  ```ruby
39
39
  gem "active_hashcash"
40
40
  ```
41
41
 
42
- Require hashcash from your JavaScript manifest.
42
+ Stamps are stored into the database to prevents from spending them more than once.
43
+ You must install and run a migration:
43
44
 
44
- ```js
45
- //= require hashcash
46
45
  ```
47
-
48
- Add a Hashcash hidden field into the form you want to protect.
49
-
50
- ```erb
51
- <form>
52
- <%= hashcash_hidden_field_tag %>
53
- </form>
46
+ rails active_hashcash:install:migrations
47
+ rails db:migrate
54
48
  ```
55
49
 
56
- Then you have to define a `before_action :check_hashcash` in you controller.
50
+ Then you have to include ActiveHashcash and add a `before_action :check_hashcash` in you controller:
57
51
 
58
52
  ```ruby
59
53
  class SessionController < ApplicationController
@@ -64,20 +58,41 @@ class SessionController < ApplicationController
64
58
  end
65
59
  ```
66
60
 
67
- To customize some behaviour, you can override most of the methods which begins with `hashcash_`.
68
- Simply have a look to `active_hashcash.rb`.
61
+ The action `SessionController#create` is now protected.
62
+ The final step is compute the hashcash from the client side.
63
+ Start by adding a Hashcash hidden field into the form you want to protect.
64
+
65
+ ```erb
66
+ <form>
67
+ <%= hashcash_hidden_field_tag %>
68
+ </form>
69
+ ```
70
+
71
+ Require hashcash from your JavaScript manifest.
72
+
73
+ ```js
74
+ //= require hashcash
75
+ ```
69
76
 
70
- Stamps are stored into into the database to prevents from spending them more than once.
71
- You must run a migration:
77
+ Or, link hashcash to your JavaScript manifest and load it to your head.
72
78
 
79
+ ```js
80
+ //= link hashcash.js
73
81
  ```
74
- rails active_hashcash:install:migrations
75
- rails db:migrate
82
+
83
+ ```erb
84
+ <%= javascript_include_tag "hashcash", "data-turbo-track": "reload", defer: true %>
76
85
  ```
77
86
 
87
+ The hashcash stamp will be set in the hidden input once computed and the submit button enabled.
88
+
89
+ To customize behaviours, you can override methods of ActiveHashcash module.
90
+
91
+
92
+
78
93
  ### Dashboard
79
94
 
80
- There is a mountable dahsboard which allows to see all spent stamps.
95
+ There is a mountable dashboard which allows to see all spent stamps.
81
96
  It's not mandatory, but useful for monitoring purpose.
82
97
 
83
98
  ![ActiveHashcash dashboard](active_hashcash_dashboard.png "ActiveHashcash dashboard")
@@ -87,10 +102,22 @@ It's not mandatory, but useful for monitoring purpose.
87
102
  mount ActiveHashcash::Engine, at: "hashcash"
88
103
  ```
89
104
 
90
- ActiveHashcash cannot guess how you handle user authentication, because it is different for all Rails applications.
91
- So you have to monkey patch `ActiveHashcash::ApplicationController` in order to inject your own mechanism.
92
- The patch can be saved wherever you want.
93
- For example, I like to have all the patches in one place, so I put them in `lib/patches`.
105
+ ActiveHashcash cannot guess how user authentication is handled, because it is different for all Rails applications.
106
+ So here is 3 options.
107
+
108
+ #### Inheritance
109
+
110
+ By default ActiveHashcash extends `ActionController::Base`, but you can change it to any controller, such as `AdminController`.
111
+
112
+ ```ruby
113
+ # config/initializers/active_hashcash.rb
114
+ Rails.application.configure do
115
+ ActiveHashcash.base_controller_class = "AdminController"
116
+ end
117
+ ```
118
+ #### Monkey patching
119
+
120
+ Monkey patching `ActiveHashcash::ApplicationController` let you inject your own mechanism.
94
121
 
95
122
  ```ruby
96
123
  # lib/patches/active_hashcash.rb
@@ -106,9 +133,7 @@ ActiveHashcash::ApplicationController.class_eval do
106
133
  end
107
134
  ```
108
135
 
109
- Then you have to require the monkey patch.
110
- Because it's loaded via require, it won't be reloaded in development.
111
- Since you are not supposed to change this file often, it should not be an issue.
136
+ Then the patch has to be loaded from after initialization:
112
137
 
113
138
  ```ruby
114
139
  # config/application.rb
@@ -117,7 +142,9 @@ config.after_initialize do
117
142
  end
118
143
  ```
119
144
 
120
- If you use Devise, you can check the permission directly from routes.rb:
145
+ #### With Devise
146
+
147
+ Permission check can be achieved directly from routes.rb:
121
148
 
122
149
  ```ruby
123
150
  # config/routes.rb
@@ -129,7 +156,7 @@ end
129
156
  ### Before version 0.3.0
130
157
 
131
158
  You must have Redis in order to prevent double spent stamps. Otherwise it will be useless.
132
- It automatically tries to connect with the environement variables `ACTIVE_HASHCASH_REDIS_URL` or `REDIS_URL`.
159
+ It automatically tries to connect with the environment variables `ACTIVE_HASHCASH_REDIS_URL` or `REDIS_URL`.
133
160
  You can also manually set the URL with `ActiveHashcash.redis_url = redis://user:password@localhost:6379`.
134
161
 
135
162
  You should call `ActiveHashcash::Store#clean` once a day, to remove expired stamps.
@@ -145,7 +172,7 @@ rails db:migrate
145
172
 
146
173
  Complexity is the most important parameter. By default its value is 20 and requires most of the time 5 to 20 seconds to be solved on a decent laptop.
147
174
  The user won't wait that long, since he needs to fill the form while the problem is solving.
148
- Howevever, if your application includes people with slow and old devices, then consider lowering this value, to 16 or 18.
175
+ However, if your application includes people with slow and old devices, then consider lowering this value, to 16 or 18.
149
176
 
150
177
  You can change the minimum complexity with `ActiveHashcash.bits = 20`.
151
178
 
@@ -156,7 +183,7 @@ Thus it becomes very efficient to slow down brute force attacks.
156
183
 
157
184
  The JavaScript implementation is 10 to 20 times slower than the official C version.
158
185
  I first used the SubtleCrypto API but it is surprisingly slower than a custom SHA1 implementation.
159
- Maybe I did in an unefficient way 2df3ba5?
186
+ Maybe I did in an inefficient way 2df3ba5?
160
187
  Another idea would be to compile the work algorithm in wasm.
161
188
 
162
189
  Unfortunately, I'm not a JavaScript expert.
@@ -171,4 +198,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/BaseSe
171
198
 
172
199
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
173
200
 
174
- Made by Alexis Bernard at [RorVsWild](https://www.rorvswild.com).
201
+ Made by [Alexis Bernard](https://alexis.bernard.io/).
@@ -10,6 +10,9 @@ Hashcash = function(input) {
10
10
  Hashcash.enableParentForm(input, options)
11
11
  input.dispatchEvent(new CustomEvent("hashcash:minted", {bubbles: true, detail: {stamp: stamp}}))
12
12
  })
13
+
14
+ this.input = input
15
+ input.form.addEventListener("submit", this.preventFromAutoSubmitFromPasswordManagers.bind(this))
13
16
  }
14
17
 
15
18
  Hashcash.setup = function() {
@@ -20,21 +23,37 @@ Hashcash.setup = function() {
20
23
  document.addEventListener("DOMContentLoaded", Hashcash.setup )
21
24
  }
22
25
 
26
+ Hashcash.setSubmitText = function(submit, text) {
27
+ if (!text) {
28
+ return
29
+ }
30
+ if (submit.tagName == "BUTTON") {
31
+ !submit.originalValue && (submit.originalValue = submit.innerHTML)
32
+ submit.innerHTML = text
33
+ } else {
34
+ !submit.originalValue && (submit.originalValue = submit.value)
35
+ submit.value = text
36
+ }
37
+ }
38
+
23
39
  Hashcash.disableParentForm = function(input, options) {
24
40
  input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
25
- submit.originalValue = submit.value
26
- options["waiting_message"] && (submit.value = options["waiting_message"])
41
+ Hashcash.setSubmitText(submit, options["waiting_message"])
27
42
  submit.disabled = true
28
43
  })
29
44
  }
30
45
 
31
46
  Hashcash.enableParentForm = function(input, options) {
32
47
  input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
33
- submit.originalValue && (submit.value = submit.originalValue)
48
+ Hashcash.setSubmitText(submit, submit.originalValue)
34
49
  submit.disabled = null
35
50
  })
36
51
  }
37
52
 
53
+ Hashcash.prototype.preventFromAutoSubmitFromPasswordManagers = function(event) {
54
+ this.input.value == "" && event.preventDefault()
55
+ }
56
+
38
57
  Hashcash.default = {
39
58
  version: 1,
40
59
  bits: 20,
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class AddressesController < ApplicationController
2
+ class AddressesController < ApplicationController # :nodoc:
3
3
  def index
4
4
  @addresses = Stamp.filter_by(params).group(:ip_address).order(count_all: :desc).limit(1000).count
5
5
  end
@@ -1,4 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationController < ActionController::Base
2
+ class ApplicationController < ActiveHashcash.base_controller_class.constantize # :nodoc:
3
+ layout "active_hashcash/application"
3
4
  end
4
5
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class AssetsController < ApplicationController
2
+ class AssetsController < ApplicationController # :nodoc:
3
3
  protect_from_forgery except: :show
4
4
 
5
5
  Mime::Type.register "image/x-icon", :ico
@@ -7,7 +7,7 @@ module ActiveHashcash
7
7
  def show
8
8
  if endpoints.include?(file_name = File.basename(request.path))
9
9
  file_path = ActiveHashcash::Engine.root.join / "app/views/active_hashcash/assets" / file_name
10
- if File.exists?("#{file_path}.erb")
10
+ if File.exist?("#{file_path}.erb")
11
11
  render(params[:id], mime_type: mime_type)
12
12
  else
13
13
  render(file: file_path)
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class StampsController < ApplicationController
2
+ class StampsController < ApplicationController # :nodoc:
3
3
  def index
4
4
  @stamps = Stamp.filter_by(params).order(created_at: :desc).limit(1000)
5
5
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module AddressesHelper
2
+ module AddressesHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module ApplicationHelper
2
+ module ApplicationHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- module StampsHelper
2
+ module StampsHelper # :nodoc:
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module ActiveHashcash
2
- class ApplicationJob < ActiveJob::Base
2
+ class ApplicationJob < ActiveJob::Base # :nodoc:
3
3
  end
4
4
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationMailer < ActionMailer::Base
2
+ class ApplicationMailer < ActionMailer::Base # :nodoc:
3
3
  default from: "from@example.com"
4
4
  layout "mailer"
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class ApplicationRecord < ActiveRecord::Base
2
+ class ApplicationRecord < ActiveRecord::Base # :nodoc:
3
3
  self.abstract_class = true
4
4
  end
5
5
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveHashcash
4
+ # This is the model to store hashcash stamps.
5
+ # Unless you need something really specific, you should not need interact directly with that class.
4
6
  class Stamp < ApplicationRecord
5
7
  validates_presence_of :version, :bits, :date, :resource, :rand, :counter
6
8
 
@@ -24,6 +26,8 @@ module ActiveHashcash
24
26
  scope
25
27
  end
26
28
 
29
+ # Verify and save the hashcash stamp.
30
+ # Saving in the database prevent from double spending the same stamp.
27
31
  def self.spend(string, resource, bits, date, options = {})
28
32
  return false unless stamp = parse(string)
29
33
  stamp.attributes = options
@@ -32,9 +36,11 @@ module ActiveHashcash
32
36
  false
33
37
  end
34
38
 
39
+ # Pare and instanciate a stamp from a sting which respects the hashcash format:
40
+ #
41
+ # ver:bits:date:resource:[ext]:rand:counter
35
42
  def self.parse(string)
36
- return unless string
37
- args = string.split(":")
43
+ args = string.to_s.split(":")
38
44
  return if args.size != 7
39
45
  new(version: args[0], bits: args[1], date: Date.strptime(args[2], ActiveHashcash.date_format), resource: args[3], ext: args[4], rand: args[5], counter: args[6])
40
46
  end
@@ -0,0 +1,4 @@
1
+ ca:
2
+ active_hashcash:
3
+ waiting_label: "Esperant a validar el formulari..."
4
+ submit_filter: Filtra
@@ -1,3 +1,10 @@
1
+ # Successful hashcash stamp are stored in the database.
2
+ # This migration creates the table for the model ActiveHashcash::Stamp.
3
+ # Run the following commands to add it to your Rails application:
4
+ #
5
+ # rails active_hashcash:install:migrations
6
+ # rails db:migrate
7
+ #
1
8
  class CreateActiveHashcashStamps < ActiveRecord::Migration[5.2]
2
9
  def change
3
10
  create_table :active_hashcash_stamps do |t|
@@ -1,5 +1,5 @@
1
1
  module ActiveHashcash
2
- class Engine < ::Rails::Engine
2
+ class Engine < ::Rails::Engine # :nodoc:
3
3
  config.assets.paths << File.expand_path("../..", __FILE__) if config.respond_to?(:assets)
4
4
 
5
5
  isolate_namespace ActiveHashcash
@@ -1,3 +1,3 @@
1
1
  module ActiveHashcash
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,11 +1,23 @@
1
1
  require "active_hashcash/version"
2
2
  require "active_hashcash/engine"
3
3
 
4
+ # ActiveHashcash protects Rails applications against bots and brute force attacks without annoying humans.
5
+ # See the rdoc-ref:README.md for more explanations about Hashcash.
6
+ #
7
+ # Include this module into your Rails controller.
8
+ #
9
+ # class SessionController < ApplicationController
10
+ # include ActiveHashcash
11
+ #
12
+ # before_action :check_hashcash, only: :create
13
+ # end
14
+ #
15
+ # Your are welcome to override most of the methods to customize to your needs.
16
+ # For example, if your app runs behind a loab balancer you should probably override #hashcash_ip_address.
17
+ #
4
18
  module ActiveHashcash
5
19
  extend ActiveSupport::Concern
6
20
 
7
- include ActionView::Helpers::FormTagHelper
8
-
9
21
  included do
10
22
  helper_method :hashcash_hidden_field_tag
11
23
  end
@@ -18,7 +30,14 @@ module ActiveHashcash
18
30
 
19
31
  mattr_accessor :date_format, instance_accessor: false, default: "%y%m%d"
20
32
 
21
- # Call me via a before_action when the form is submitted : `before_action :check_hashcash, only: :create`
33
+ mattr_accessor :base_controller_class, default: "ActionController::Base"
34
+
35
+ # Call that method via a before_action when the form is submitted:
36
+ #
37
+ # before_action :check_hashcash, only: :create
38
+ #
39
+ # In case of invalid hashcash it calls hashcash_after_failure that you can override.
40
+ # Otherwise, hashcash stamp is stored in database to prevent from double spending.
22
41
  def check_hashcash
23
42
  attrs = {
24
43
  ip_address: hashcash_ip_address,
@@ -32,21 +51,26 @@ module ActiveHashcash
32
51
  end
33
52
  end
34
53
 
35
- # Override the methods below in your controller, to change any parameter or behaviour.
36
-
54
+ # Returns remote IP address.
55
+ # They are used to automatically increase complexity when the same IP sends many valid hashcash.
56
+ # If you're app is behind a load balancer, you should probably override it to read the right HTTP header.
37
57
  def hashcash_ip_address
38
58
  request.remote_ip
39
59
  end
40
60
 
61
+ # Return current request path to be saved to the sucessful ActiveHash::Stamp.
62
+ # If multiple forms are protected via hashcash this is an interesting info.
41
63
  def hashcash_request_path
42
64
  request.path
43
65
  end
44
66
 
67
+ # Override this method to store custom data for each stamp.
68
+ # It must returns a hash or nil.
45
69
  def hashcash_stamp_context
46
- # Override this method to store custom data for each stamp
47
70
  end
48
71
 
49
- # By default the host name is used as the resource.
72
+ # This is the resource used to build the hashcash stamp.
73
+ # By default the host name is returned.
50
74
  # It' should be good for most cases and prevent from reusing the same stamp between sites.
51
75
  def hashcash_resource
52
76
  ActiveHashcash.resource || request.host
@@ -54,7 +78,7 @@ module ActiveHashcash
54
78
 
55
79
  # Returns the complexity, the higher the slower it is.
56
80
  # Complexity is increased logarithmicly for each IP during the last 24H to slowdown brute force attacks.
57
- # The minimun value returned is `ActiveHashcash.bits`.
81
+ # The minimun value returned is ActiveHashcash.bits.
58
82
  def hashcash_bits
59
83
  if (previous_stamp_count = ActiveHashcash::Stamp.where(ip_address: hashcash_ip_address).where(created_at: 1.day.ago..).count) > 0
60
84
  (ActiveHashcash.bits + Math.log2(previous_stamp_count)).floor
@@ -73,7 +97,9 @@ module ActiveHashcash
73
97
  t("active_hashcash.waiting_label")
74
98
  end
75
99
 
76
- # Override to provide a different behaviour when hashcash failed
100
+ # That method is called when #check_hashcash fails.
101
+ # It raises ActionController::InvalidAuthenticityToken so HTTP response will be 422 by default.
102
+ # Override this method to provide a different behaviour.
77
103
  def hashcash_after_failure
78
104
  raise ActionController::InvalidAuthenticityToken.new("Invalid hashcash #{hashcash_param}")
79
105
  end
@@ -85,8 +111,14 @@ module ActiveHashcash
85
111
 
86
112
  # Call it inside the form that have to be protected and don't forget to initialize the JavaScript Hascash.setup().
87
113
  # Unless you need something really special, you should not need to override this method.
114
+ #
115
+ # <% form_for model do |form| %>
116
+ # <%= hashcash_hidden_field_tag %>
117
+ #
118
+ # <% end %>
119
+ #
88
120
  def hashcash_hidden_field_tag(name = :hashcash)
89
121
  options = {resource: hashcash_resource, bits: hashcash_bits, waiting_message: hashcash_waiting_message}
90
- hidden_field_tag(name, "", "data-hashcash" => options.to_json)
122
+ view_context.hidden_field_tag(name, "", "data-hashcash" => options.to_json)
91
123
  end
92
124
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_hashcash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-04-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -62,6 +61,7 @@ files:
62
61
  - app/views/active_hashcash/stamps/index.html.erb
63
62
  - app/views/active_hashcash/stamps/show.html.erb
64
63
  - app/views/layouts/active_hashcash/application.html.erb
64
+ - config/locales/ca.yml
65
65
  - config/locales/de.yml
66
66
  - config/locales/en.yml
67
67
  - config/locales/es.yml
@@ -74,7 +74,6 @@ files:
74
74
  - lib/active_hashcash.rb
75
75
  - lib/active_hashcash/engine.rb
76
76
  - lib/active_hashcash/version.rb
77
- - lib/hashcash.js
78
77
  - lib/tasks/active_hashcash_tasks.rake
79
78
  homepage: https://github.com/BaseSecrete/active_hashcash
80
79
  licenses:
@@ -83,7 +82,6 @@ metadata:
83
82
  homepage_uri: https://github.com/BaseSecrete/active_hashcash
84
83
  source_code_uri: https://github.com/BaseSecrete/active_hashcash
85
84
  changelog_uri: https://github.com/BaseSecrete/active_hashcash/CHANGELOG.md
86
- post_install_message:
87
85
  rdoc_options: []
88
86
  require_paths:
89
87
  - lib
@@ -98,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
96
  - !ruby/object:Gem::Version
99
97
  version: '0'
100
98
  requirements: []
101
- rubygems_version: 3.2.22
102
- signing_key:
99
+ rubygems_version: 3.6.7
103
100
  specification_version: 4
104
101
  summary: Protect Rails applications against bots and brute force attacks without annoying
105
102
  humans.
data/lib/hashcash.js DELETED
@@ -1,257 +0,0 @@
1
- // http://www.hashcash.org/docs/hashcash.html
2
- // <input type="hiden" name="hashcash" data-hashcash="{resource: 'site.example', bits: 16}"/>
3
- Hashcash = function(input) {
4
- options = JSON.parse(input.getAttribute("data-hashcash"))
5
- Hashcash.disableParentForm(input, options)
6
- input.dispatchEvent(new CustomEvent("hashcash:mint", {bubbles: true}))
7
-
8
- Hashcash.mint(options.resource, options, function(stamp) {
9
- input.value = stamp.toString()
10
- Hashcash.enableParentForm(input, options)
11
- input.dispatchEvent(new CustomEvent("hashcash:minted", {bubbles: true, detail: {stamp: stamp}}))
12
- })
13
- }
14
-
15
- Hashcash.setup = function() {
16
- if (document.readyState != "loading") {
17
- var input = document.querySelector("input#hashcash")
18
- input && new Hashcash(input)
19
- } else
20
- document.addEventListener("DOMContentLoaded", Hashcash.setup )
21
- }
22
-
23
- Hashcash.disableParentForm = function(input, options) {
24
- input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
25
- submit.originalValue = submit.value
26
- options["waiting_message"] && (submit.value = options["waiting_message"])
27
- submit.disabled = true
28
- })
29
- }
30
-
31
- Hashcash.enableParentForm = function(input, options) {
32
- input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
33
- submit.originalValue && (submit.value = submit.originalValue)
34
- submit.disabled = null
35
- })
36
- }
37
-
38
- Hashcash.default = {
39
- version: 1,
40
- bits: 20,
41
- extension: null,
42
- }
43
-
44
- Hashcash.mint = function(resource, options, callback) {
45
- // Format date to YYMMDD
46
- var date = new Date
47
- var year = date.getFullYear().toString()
48
- year = year.slice(year.length - 2, year.length)
49
- var month = (date.getMonth() + 1).toString().padStart(2, "0")
50
- var day = date.getDate().toString().padStart(2, "0")
51
-
52
- var stamp = new Hashcash.Stamp(
53
- options.version || Hashcash.default.version,
54
- options.bits || Hashcash.default.bits,
55
- options.date || year + month + day,
56
- resource,
57
- options.extension || Hashcash.default.extension,
58
- options.rand || Math.random().toString(36).substr(2, 10),
59
- )
60
- return stamp.work(callback)
61
- }
62
-
63
- Hashcash.Stamp = function(version, bits, date, resource, extension, rand, counter = 0) {
64
- this.version = version
65
- this.bits = bits
66
- this.date = date
67
- this.resource = resource
68
- this.extension = extension
69
- this.rand = rand
70
- this.counter = counter
71
- }
72
-
73
- Hashcash.Stamp.parse = function(string) {
74
- var args = string.split(":")
75
- return new Hashcash.Stamp(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
76
- }
77
-
78
- Hashcash.Stamp.prototype.toString = function() {
79
- return [this.version, this.bits, this.date, this.resource, this.extension, this.rand, this.counter].join(":")
80
- }
81
-
82
- // Trigger the given callback when the problem is solved.
83
- // In order to not freeze the page, setTimeout is called every 100ms to let some CPU to other tasks.
84
- Hashcash.Stamp.prototype.work = function(callback) {
85
- this.startClock()
86
- var timer = performance.now()
87
- while (!this.check())
88
- if (this.counter++ && performance.now() - timer > 100)
89
- return setTimeout(this.work.bind(this), 0, callback)
90
- this.stopClock()
91
- callback(this)
92
- }
93
-
94
- Hashcash.Stamp.prototype.check = function() {
95
- var array = Hashcash.sha1(this.toString())
96
- return array[0] >> (160-this.bits) == 0
97
- }
98
-
99
- Hashcash.Stamp.prototype.startClock = function() {
100
- this.startedAt || (this.startedAt = performance.now())
101
- }
102
-
103
- Hashcash.Stamp.prototype.stopClock = function() {
104
- this.endedAt || (this.endedAt = performance.now())
105
- var duration = this.endedAt - this.startedAt
106
- var speed = Math.round(this.counter * 1000 / duration)
107
- console.debug("Hashcash " + this.toString() + " minted in " + duration + "ms (" + speed + " per seconds)")
108
- }
109
-
110
- /**
111
- * Secure Hash Algorithm (SHA1)
112
- * http://www.webtoolkit.info/
113
- **/
114
- Hashcash.sha1 = function(msg) {
115
- var rotate_left = Hashcash.sha1.rotate_left
116
- var Utf8Encode = Hashcash.sha1.Utf8Encode
117
-
118
- var blockstart;
119
- var i, j;
120
- var W = new Array(80);
121
- var H0 = 0x67452301;
122
- var H1 = 0xEFCDAB89;
123
- var H2 = 0x98BADCFE;
124
- var H3 = 0x10325476;
125
- var H4 = 0xC3D2E1F0;
126
- var A, B, C, D, E;
127
- var temp;
128
- msg = Utf8Encode(msg);
129
- var msg_len = msg.length;
130
- var word_array = new Array();
131
- for (i = 0; i < msg_len - 3; i += 4) {
132
- j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |
133
- msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);
134
- word_array.push(j);
135
- }
136
- switch (msg_len % 4) {
137
- case 0:
138
- i = 0x080000000;
139
- break;
140
- case 1:
141
- i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;
142
- break;
143
- case 2:
144
- i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;
145
- break;
146
- case 3:
147
- i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;
148
- break;
149
- }
150
- word_array.push(i);
151
- while ((word_array.length % 16) != 14) word_array.push(0);
152
- word_array.push(msg_len >>> 29);
153
- word_array.push((msg_len << 3) & 0x0ffffffff);
154
- for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
155
- for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
156
- for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
157
- A = H0;
158
- B = H1;
159
- C = H2;
160
- D = H3;
161
- E = H4;
162
- for (i = 0; i <= 19; i++) {
163
- temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
164
- E = D;
165
- D = C;
166
- C = rotate_left(B, 30);
167
- B = A;
168
- A = temp;
169
- }
170
- for (i = 20; i <= 39; i++) {
171
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
172
- E = D;
173
- D = C;
174
- C = rotate_left(B, 30);
175
- B = A;
176
- A = temp;
177
- }
178
- for (i = 40; i <= 59; i++) {
179
- temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
180
- E = D;
181
- D = C;
182
- C = rotate_left(B, 30);
183
- B = A;
184
- A = temp;
185
- }
186
- for (i = 60; i <= 79; i++) {
187
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
188
- E = D;
189
- D = C;
190
- C = rotate_left(B, 30);
191
- B = A;
192
- A = temp;
193
- }
194
- H0 = (H0 + A) & 0x0ffffffff;
195
- H1 = (H1 + B) & 0x0ffffffff;
196
- H2 = (H2 + C) & 0x0ffffffff;
197
- H3 = (H3 + D) & 0x0ffffffff;
198
- H4 = (H4 + E) & 0x0ffffffff;
199
- }
200
- return [H0, H1, H2, H3, H4]
201
- }
202
-
203
- Hashcash.hexSha1 = function(msg) {
204
- var array = Hashcash.sha1(msg)
205
- var cvt_hex = Hashcash.sha1.cvt_hex
206
- return cvt_hex(array[0]) + cvt_hex(array[1]) + cvt_hex(array[2]) + cvt_hex(array3) + cvt_hex(array[4])
207
- }
208
-
209
- Hashcash.sha1.rotate_left = function(n, s) {
210
- var t4 = (n << s) | (n >>> (32 - s));
211
- return t4;
212
- };
213
-
214
- Hashcash.sha1.lsb_hex = function(val) {
215
- var str = '';
216
- var i;
217
- var vh;
218
- var vl;
219
- for (i = 0; i <= 6; i += 2) {
220
- vh = (val >>> (i * 4 + 4)) & 0x0f;
221
- vl = (val >>> (i * 4)) & 0x0f;
222
- str += vh.toString(16) + vl.toString(16);
223
- }
224
- return str;
225
- };
226
-
227
- Hashcash.sha1.cvt_hex = function(val) {
228
- var str = '';
229
- var i;
230
- var v;
231
- for (i = 7; i >= 0; i--) {
232
- v = (val >>> (i * 4)) & 0x0f;
233
- str += v.toString(16);
234
- }
235
- return str;
236
- };
237
-
238
- Hashcash.sha1.Utf8Encode = function(string) {
239
- string = string.replace(/\r\n/g, '\n');
240
- var utftext = '';
241
- for (var n = 0; n < string.length; n++) {
242
- var c = string.charCodeAt(n);
243
- if (c < 128) {
244
- utftext += String.fromCharCode(c);
245
- } else if ((c > 127) && (c < 2048)) {
246
- utftext += String.fromCharCode((c >> 6) | 192);
247
- utftext += String.fromCharCode((c & 63) | 128);
248
- } else {
249
- utftext += String.fromCharCode((c >> 12) | 224);
250
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
251
- utftext += String.fromCharCode((c & 63) | 128);
252
- }
253
- }
254
- return utftext;
255
- };
256
-
257
- Hashcash.setup()