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 +4 -4
- data/CHANGELOG.md +13 -1
- data/README.md +62 -35
- data/app/assets/javascripts/hashcash.js +22 -3
- data/app/controllers/active_hashcash/addresses_controller.rb +1 -1
- data/app/controllers/active_hashcash/application_controller.rb +2 -1
- data/app/controllers/active_hashcash/assets_controller.rb +2 -2
- data/app/controllers/active_hashcash/stamps_controller.rb +1 -1
- data/app/helpers/active_hashcash/addresses_helper.rb +1 -1
- data/app/helpers/active_hashcash/application_helper.rb +1 -1
- data/app/helpers/active_hashcash/stamps_helper.rb +1 -1
- data/app/jobs/active_hashcash/application_job.rb +1 -1
- data/app/mailers/active_hashcash/application_mailer.rb +1 -1
- data/app/models/active_hashcash/application_record.rb +1 -1
- data/app/models/active_hashcash/stamp.rb +8 -2
- data/config/locales/ca.yml +4 -0
- data/db/migrate/20240215143453_create_active_hashcash_stamps.rb +7 -0
- data/lib/active_hashcash/engine.rb +1 -1
- data/lib/active_hashcash/version.rb +1 -1
- data/lib/active_hashcash.rb +42 -10
- metadata +4 -7
- data/lib/hashcash.js +0 -257
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8e7f600a4c42efa28175ebb98d4ba2206295bb316b450f79167fae543a52f71
|
4
|
+
data.tar.gz: a62096662ee76b1b31529c71660cae12d8c0c7b01033d2440490aef839d6ed3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
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
|
-
|
3
|
+
Protect Rails applications against bots and brute force attacks without annoying humans.
|
4
4
|
|
5
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
75
|
-
|
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
|
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
|

|
@@ -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
|
91
|
-
So
|
92
|
-
|
93
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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 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.
|
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,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
|
-
|
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
|
@@ -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|
|
data/lib/active_hashcash.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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.
|
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:
|
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.
|
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()
|