active_hashcash 0.2.0 → 0.3.1

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +101 -14
  4. data/app/assets/config/active_hashcash_manifest.js +1 -0
  5. data/app/assets/javascripts/hashcash.js +257 -0
  6. data/app/assets/stylesheets/active_hashcash/application.css +15 -0
  7. data/app/controllers/active_hashcash/addresses_controller.rb +7 -0
  8. data/app/controllers/active_hashcash/application_controller.rb +4 -0
  9. data/app/controllers/active_hashcash/assets_controller.rb +34 -0
  10. data/app/controllers/active_hashcash/stamps_controller.rb +11 -0
  11. data/app/helpers/active_hashcash/addresses_helper.rb +4 -0
  12. data/app/helpers/active_hashcash/application_helper.rb +4 -0
  13. data/app/helpers/active_hashcash/stamps_helper.rb +4 -0
  14. data/app/jobs/active_hashcash/application_job.rb +4 -0
  15. data/app/mailers/active_hashcash/application_mailer.rb +6 -0
  16. data/app/models/active_hashcash/application_record.rb +5 -0
  17. data/app/models/active_hashcash/stamp.rb +70 -0
  18. data/app/views/active_hashcash/addresses/index.html.erb +17 -0
  19. data/app/views/active_hashcash/assets/_logo.svg.erb +1 -0
  20. data/app/views/active_hashcash/assets/_style.css +148 -0
  21. data/app/views/active_hashcash/assets/application.css.erb +1 -0
  22. data/app/views/active_hashcash/assets/ariato.css.erb +2 -0
  23. data/app/views/active_hashcash/assets/favicon.ico +0 -0
  24. data/app/views/active_hashcash/assets/favicon.svg.erb +1 -0
  25. data/app/views/active_hashcash/assets/vendor/_ariato_base.css +1297 -0
  26. data/app/views/active_hashcash/assets/vendor/_ariato_extra.css +1206 -0
  27. data/app/views/active_hashcash/stamps/_filters.html.erb +39 -0
  28. data/app/views/active_hashcash/stamps/index.html.erb +25 -0
  29. data/app/views/active_hashcash/stamps/show.html.erb +21 -0
  30. data/app/views/layouts/active_hashcash/application.html.erb +36 -0
  31. data/config/locales/de.yml +4 -0
  32. data/config/locales/en.yml +4 -0
  33. data/config/locales/es.yml +4 -0
  34. data/config/locales/fr.yml +4 -0
  35. data/config/locales/it.yml +4 -0
  36. data/config/locales/jp.yml +4 -0
  37. data/config/locales/pt.yml +4 -0
  38. data/config/routes.rb +6 -0
  39. data/db/migrate/20240215143453_create_active_hashcash_stamps.rb +25 -0
  40. data/lib/active_hashcash/engine.rb +2 -14
  41. data/lib/active_hashcash/version.rb +1 -1
  42. data/lib/active_hashcash.rb +35 -21
  43. data/lib/tasks/active_hashcash_tasks.rake +4 -0
  44. metadata +47 -26
  45. data/lib/active_hashcash/stamp.rb +0 -52
  46. data/lib/active_hashcash/store.rb +0 -25
@@ -0,0 +1,39 @@
1
+ <%= form_tag nil, method: :get do %>
2
+ <div class="grid-auto">
3
+ <div role="group">
4
+ <%= label_tag :created_from do %>
5
+ <%= ActiveHashcash::Stamp::human_attribute_name(:created_at) + " ≥" %>
6
+ <%= date_field_tag :created_from, params[:created_from] %>
7
+ <% end %>
8
+
9
+ <%= label_tag :created_from do %>
10
+ <%= ActiveHashcash::Stamp::human_attribute_name(:created_at) + " ≤" %>
11
+ <%= date_field_tag :created_to, params[:created_to] %>
12
+ <% end %>
13
+ </div>
14
+
15
+ <div role="group">
16
+ <%= label_tag :bits_from do %>
17
+ <%= ActiveHashcash::Stamp::human_attribute_name(:bits) + " ≥" %>
18
+ <%= number_field_tag :bits_from, params[:bits_from] %>
19
+ <% end %>
20
+
21
+ <%= label_tag :bits_to do %>
22
+ <%= ActiveHashcash::Stamp::human_attribute_name(:bits) + " ≤" %>
23
+ <%= number_field_tag :bits_to, params[:bits_to] %>
24
+ <% end %>
25
+ </div>
26
+
27
+ <div>
28
+ <%= label_tag :ip_address_starts_with, ActiveHashcash::Stamp::human_attribute_name(:ip_address) %>
29
+ <%= text_field_tag :ip_address_starts_with, params[:ip_address_starts_with] %>
30
+ </div>
31
+
32
+ <div>
33
+ <%= label_tag :request_path_starts_with, ActiveHashcash::Stamp::human_attribute_name(:request_path) %>
34
+ <%= text_field_tag :request_path_starts_with, params[:request_path_starts_with] %>
35
+ </div>
36
+
37
+ <%= submit_tag t("active_hashcash.submit_filter") %>
38
+ </div>
39
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <details>
2
+ <summary>Filters</summary>
3
+ <%= render "filters" %>
4
+ </details>
5
+
6
+ <table>
7
+ <thead>
8
+ <tr>
9
+ <th><%= ActiveHashcash::Stamp.human_attribute_name(:created_at) %></th>
10
+ <th><%= ActiveHashcash::Stamp.human_attribute_name(:ip_address) %></th>
11
+ <th><%= ActiveHashcash::Stamp.human_attribute_name(:request_path) %></th>
12
+ <th><%= ActiveHashcash::Stamp.human_attribute_name(:bits) %></th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% for stamp in @stamps %>
17
+ <tr>
18
+ <td><%= link_to stamp.created_at, stamp %></td>
19
+ <td><%= stamp.ip_address %></td>
20
+ <td><%= stamp.request_path %></td>
21
+ <td><%= stamp.bits %></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
@@ -0,0 +1,21 @@
1
+
2
+ <h3><%= @stamp %></h3>
3
+ <dl>
4
+ <dt><%= @stamp.class.human_attribute_name(:created_at) %></dt>
5
+ <dd><%= l @stamp.created_at %></dd>
6
+
7
+ <dt><%= @stamp.class.human_attribute_name(:resource) %></dt>
8
+ <dd><%= @stamp.resource %></dd>
9
+
10
+ <dt><%= @stamp.class.human_attribute_name(:bits) %></dt>
11
+ <dd><%= @stamp.bits %></dd>
12
+
13
+ <dt><%= @stamp.class.human_attribute_name(:ip_address) %></dt>
14
+ <dd><%= @stamp.ip_address %></dd>
15
+
16
+ <dt><%= @stamp.class.human_attribute_name(:request_path) %></dt>
17
+ <dd><%= @stamp.request_path %></dd>
18
+
19
+ <dt><%= @stamp.class.human_attribute_name(:context) %></dt>
20
+ <dd><%= @stamp.context %></dd>
21
+ </dl>
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html class="active-analytics">
3
+ <head>
4
+ <title>Active Hashcash</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= tag.link rel: "icon", href: asset_path(:favicon, format: :ico), sizes: "32x32" %>
9
+ <%= tag.link rel: "icon", href: asset_path(:favicon, format: :svg), type: "image/svg+xml" %>
10
+
11
+ <%= tag.link rel: "stylesheet", href: asset_path(:ariato, format: :css) %>
12
+ <%= tag.link rel: "stylesheet", href: asset_path(:application, format: :css) %>
13
+ </head>
14
+ <body>
15
+ <header>
16
+ <nav aria-label="site">
17
+ <ul role="menu" aria-label="Elements" class="is-horizontal">
18
+ <li role="none" class="logo"><%= link_to(root_path) { render partial: "/active_hashcash/assets/logo", formats: [:svg] } %></li>
19
+ <li role="none"><%= link_to_unless_current ActiveHashcash::Stamp.model_name.human.pluralize, stamps_path %></li>
20
+ <li role="none"><%= link_to_unless_current "Top IP addresses", addresses_path %></li>
21
+ </ul>
22
+ </nav>
23
+ </header>
24
+
25
+ <main class="card">
26
+ <%= yield %>
27
+ </main>
28
+
29
+ <footer>
30
+ Made by <a href="https://www.rorvswild.com">RorVsWild</a> |
31
+ <a href="https://github.com/BaseSecrete/active_hashcash">Source code</a> |
32
+ <a href="https://twitter.com/rorvswild">Twitter</a> |
33
+ <a href="https://ruby.social/@rorvswild">Mastodon</a> |
34
+ </footer>
35
+ </body>
36
+ </html>
@@ -0,0 +1,4 @@
1
+ de:
2
+ active_hashcash:
3
+ waiting_label: "Warten auf die Überprüfung ..."
4
+ submit_filter: Filtern
@@ -0,0 +1,4 @@
1
+ en:
2
+ active_hashcash:
3
+ waiting_label: "Waiting for verification ..."
4
+ submit_filter: Filter
@@ -0,0 +1,4 @@
1
+ es:
2
+ active_hashcash:
3
+ waiting_label: "A la espera de la verificación ..."
4
+ submit_filter: Filtro
@@ -0,0 +1,4 @@
1
+ fr:
2
+ active_hashcash:
3
+ waiting_label: "En attente de vérification ..."
4
+ submit_filter: Filtrer
@@ -0,0 +1,4 @@
1
+ it:
2
+ active_hashcash:
3
+ waiting_label: "In attesa di verifica ..."
4
+ submit_filter: Filtro
@@ -0,0 +1,4 @@
1
+ jp:
2
+ active_hashcash:
3
+ waiting_label: "検証待ち ..."
4
+ submit_filter: フィルター
@@ -0,0 +1,4 @@
1
+ pt:
2
+ active_hashcash:
3
+ waiting_label: "À espera de verificação ..."
4
+ submit_filter: Filtro
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ ActiveHashcash::Engine.routes.draw do
2
+ resources :assets, only: [:show]
3
+ resources :stamps, only: [:index, :show]
4
+ resources :addresses, only: [:index, :show]
5
+ root "stamps#index"
6
+ end
@@ -0,0 +1,25 @@
1
+ class CreateActiveHashcashStamps < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :active_hashcash_stamps do |t|
4
+ t.string :version, null: false
5
+ t.integer :bits, null: false
6
+ t.date :date, null: false
7
+ t.string :resource, null: false
8
+ t.string :ext, null: false
9
+ t.string :rand, null: false
10
+ t.string :counter, null: false
11
+ t.string :request_path
12
+ t.string :ip_address
13
+
14
+ if t.respond_to?(:jsonb)
15
+ t.jsonb :context # SQLite JSONB support from version 3.45 (2024-01-15)
16
+ elsif t.respond_to?(:json)
17
+ t.json :context
18
+ end
19
+
20
+ t.timestamps
21
+ end
22
+ add_index :active_hashcash_stamps, [:ip_address, :created_at], where: "ip_address IS NOT NULL"
23
+ add_index :active_hashcash_stamps, [:counter, :rand, :date, :resource, :bits, :version, :ext], name: "index_active_hashcash_stamps_unique", unique: true
24
+ end
25
+ end
@@ -1,19 +1,7 @@
1
1
  module ActiveHashcash
2
2
  class Engine < ::Rails::Engine
3
- config.assets.paths << File.expand_path("../..", __FILE__)
3
+ config.assets.paths << File.expand_path("../..", __FILE__) if config.respond_to?(:assets)
4
4
 
5
- config.after_initialize { load_translations }
6
-
7
- def load_translations
8
- if !I18n.backend.exists?(I18n.locale, "active_hashcash")
9
- I18n.backend.store_translations(:de, {active_hashcash: {waiting_label: "Warten auf die Überprüfung ..."}})
10
- I18n.backend.store_translations(:en, {active_hashcash: {waiting_label: "Waiting for verification ..."}})
11
- I18n.backend.store_translations(:es, {active_hashcash: {waiting_label: "A la espera de la verificación ..."}})
12
- I18n.backend.store_translations(:fr, {active_hashcash: {waiting_label: "En attente de vérification ..."}})
13
- I18n.backend.store_translations(:it, {active_hashcash: {waiting_label: "In attesa di verifica ..."}})
14
- I18n.backend.store_translations(:jp, {active_hashcash: {waiting_label: "検証待ち ..."}})
15
- I18n.backend.store_translations(:pt, {active_hashcash: {waiting_label: "À espera de verificação ..."}})
16
- end
17
- end
5
+ isolate_namespace ActiveHashcash
18
6
  end
19
7
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveHashcash
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -1,3 +1,6 @@
1
+ require "active_hashcash/version"
2
+ require "active_hashcash/engine"
3
+
1
4
  module ActiveHashcash
2
5
  extend ActiveSupport::Concern
3
6
 
@@ -7,31 +10,41 @@ module ActiveHashcash
7
10
  helper_method :hashcash_hidden_field_tag
8
11
  end
9
12
 
10
- mattr_accessor :bits, instance_accessor: false, default: 20
11
13
  mattr_accessor :resource, instance_accessor: false
12
- mattr_accessor :redis_url, instance_accessor: false, default: ENV["ACTIVE_HASHCASH_REDIS_URL"] || ENV["REDIS_URL"]
13
-
14
- def self.store
15
- @store ||= Store.new(Redis.new(url: ActiveHashcash.redis_url))
16
- end
17
14
 
18
- def self.store=(store)
19
- @store = store
20
- end
15
+ # This is base complexity.
16
+ # Consider lowering it to not exclude people with old and slow devices.
17
+ mattr_accessor :bits, instance_accessor: false, default: 16
21
18
 
22
- # TODO: protect_from_brute_force bits: 20, exception: ActionController::InvalidAuthenticityToken, with: :handle_failed_hashcash
19
+ mattr_accessor :date_format, instance_accessor: false, default: "%y%m%d"
23
20
 
24
- # Call me via a before_action when the form is submitted : `before_action :chech_hashcash, only: :create`
21
+ # Call me via a before_action when the form is submitted : `before_action :check_hashcash, only: :create`
25
22
  def check_hashcash
26
- stamp = hashcash_param && Stamp.parse(hashcash_param)
27
- if stamp && stamp.verify(hashcash_resource, hashcash_bits, Date.yesterday) && ActiveHashcash.store.add?(stamp)
23
+ attrs = {
24
+ ip_address: hashcash_ip_address,
25
+ request_path: hashcash_request_path,
26
+ context: hashcash_stamp_context
27
+ }
28
+ if hashcash_param && Stamp.spend(hashcash_param, hashcash_resource, hashcash_bits, Date.yesterday, attrs)
28
29
  hashcash_after_success
29
30
  else
30
31
  hashcash_after_failure
31
32
  end
32
33
  end
33
34
 
34
- # Override the methods below in your controller, to change any parameter of behaviour.
35
+ # Override the methods below in your controller, to change any parameter or behaviour.
36
+
37
+ def hashcash_ip_address
38
+ request.remote_ip
39
+ end
40
+
41
+ def hashcash_request_path
42
+ request.path
43
+ end
44
+
45
+ def hashcash_stamp_context
46
+ # Override this method to store custom data for each stamp
47
+ end
35
48
 
36
49
  # By default the host name is used as the resource.
37
50
  # It' should be good for most cases and prevent from reusing the same stamp between sites.
@@ -39,10 +52,15 @@ module ActiveHashcash
39
52
  ActiveHashcash.resource || request.host
40
53
  end
41
54
 
42
- # Define the complexity, the higher the slower it is. Consider lowering this value to not exclude people with old and slow devices.
43
- # On a decent laptop, it takes around 30 seconds for the JavaScript implementation to solve a 20 bits complexity and few seconds when it's 16.
55
+ # Returns the complexity, the higher the slower it is.
56
+ # Complexity is increased logarithmicly for each IP during the last 24H to slowdown brute force attacks.
57
+ # The minimun value returned is `ActiveHashcash.bits`.
44
58
  def hashcash_bits
45
- ActiveHashcash.bits
59
+ if (previous_stamp_count = ActiveHashcash::Stamp.where(ip_address: hashcash_ip_address).where(created_at: 1.day.ago..).count) > 0
60
+ (ActiveHashcash.bits + Math.log2(previous_stamp_count)).floor
61
+ else
62
+ ActiveHashcash.bits
63
+ end
46
64
  end
47
65
 
48
66
  # Override if you want to rename the hashcash param.
@@ -72,7 +90,3 @@ module ActiveHashcash
72
90
  hidden_field_tag(name, "", "data-hashcash" => options.to_json)
73
91
  end
74
92
  end
75
-
76
- require "active_hashcash/stamp"
77
- require "active_hashcash/store"
78
- require "active_hashcash/engine"
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :active_hashcash do
3
+ # # Task goes here
4
+ # end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_hashcash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
- autorequire:
9
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-02 00:00:00.000000000 Z
11
+ date: 2024-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: redis
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 4.0.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.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rails
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,8 +24,8 @@ dependencies:
38
24
  - - ">="
39
25
  - !ruby/object:Gem::Version
40
26
  version: 5.2.0
41
- description: ActiveHashcash protects your Rails application against brute force attacks,
42
- DoS and bots.
27
+ description: Protect Rails applications against bots and brute force attacks without
28
+ annoying humans.
43
29
  email:
44
30
  - alexis@basesecrete.com
45
31
  executables: []
@@ -49,12 +35,47 @@ files:
49
35
  - CHANGELOG.md
50
36
  - LICENSE.txt
51
37
  - README.md
38
+ - app/assets/config/active_hashcash_manifest.js
39
+ - app/assets/javascripts/hashcash.js
40
+ - app/assets/stylesheets/active_hashcash/application.css
41
+ - app/controllers/active_hashcash/addresses_controller.rb
42
+ - app/controllers/active_hashcash/application_controller.rb
43
+ - app/controllers/active_hashcash/assets_controller.rb
44
+ - app/controllers/active_hashcash/stamps_controller.rb
45
+ - app/helpers/active_hashcash/addresses_helper.rb
46
+ - app/helpers/active_hashcash/application_helper.rb
47
+ - app/helpers/active_hashcash/stamps_helper.rb
48
+ - app/jobs/active_hashcash/application_job.rb
49
+ - app/mailers/active_hashcash/application_mailer.rb
50
+ - app/models/active_hashcash/application_record.rb
51
+ - app/models/active_hashcash/stamp.rb
52
+ - app/views/active_hashcash/addresses/index.html.erb
53
+ - app/views/active_hashcash/assets/_logo.svg.erb
54
+ - app/views/active_hashcash/assets/_style.css
55
+ - app/views/active_hashcash/assets/application.css.erb
56
+ - app/views/active_hashcash/assets/ariato.css.erb
57
+ - app/views/active_hashcash/assets/favicon.ico
58
+ - app/views/active_hashcash/assets/favicon.svg.erb
59
+ - app/views/active_hashcash/assets/vendor/_ariato_base.css
60
+ - app/views/active_hashcash/assets/vendor/_ariato_extra.css
61
+ - app/views/active_hashcash/stamps/_filters.html.erb
62
+ - app/views/active_hashcash/stamps/index.html.erb
63
+ - app/views/active_hashcash/stamps/show.html.erb
64
+ - app/views/layouts/active_hashcash/application.html.erb
65
+ - config/locales/de.yml
66
+ - config/locales/en.yml
67
+ - config/locales/es.yml
68
+ - config/locales/fr.yml
69
+ - config/locales/it.yml
70
+ - config/locales/jp.yml
71
+ - config/locales/pt.yml
72
+ - config/routes.rb
73
+ - db/migrate/20240215143453_create_active_hashcash_stamps.rb
52
74
  - lib/active_hashcash.rb
53
75
  - lib/active_hashcash/engine.rb
54
- - lib/active_hashcash/stamp.rb
55
- - lib/active_hashcash/store.rb
56
76
  - lib/active_hashcash/version.rb
57
77
  - lib/hashcash.js
78
+ - lib/tasks/active_hashcash_tasks.rake
58
79
  homepage: https://github.com/BaseSecrete/active_hashcash
59
80
  licenses:
60
81
  - MIT
@@ -62,7 +83,7 @@ metadata:
62
83
  homepage_uri: https://github.com/BaseSecrete/active_hashcash
63
84
  source_code_uri: https://github.com/BaseSecrete/active_hashcash
64
85
  changelog_uri: https://github.com/BaseSecrete/active_hashcash/CHANGELOG.md
65
- post_install_message:
86
+ post_install_message:
66
87
  rdoc_options: []
67
88
  require_paths:
68
89
  - lib
@@ -78,8 +99,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
99
  version: '0'
79
100
  requirements: []
80
101
  rubygems_version: 3.2.22
81
- signing_key:
102
+ signing_key:
82
103
  specification_version: 4
83
- summary: ActiveHashcash protects your Rails application against brute force attacks,
84
- DoS and bots.
104
+ summary: Protect Rails applications against bots and brute force attacks without annoying
105
+ humans.
85
106
  test_files: []
@@ -1,52 +0,0 @@
1
- module ActiveHashcash
2
- class Stamp
3
- attr_reader :version, :bits, :date, :resource, :extension, :rand, :counter
4
-
5
- def self.parse(string)
6
- args = string.split(":")
7
- new(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
8
- end
9
-
10
- def self.mint(resource, options = {})
11
- new(
12
- options[:version] || 1,
13
- options[:bits] || ActiveHashcash.bits,
14
- options[:date] || Date.today.strftime("%y%m%d"),
15
- resource,
16
- options[:ext],
17
- options[:rand] || SecureRandom.alphanumeric(16),
18
- options[:counter] || 0).work
19
- end
20
-
21
- def initialize(version, bits, date, resource, extension, rand, counter)
22
- @version = version
23
- @bits = bits.to_i
24
- @date = date.respond_to?(:strftime) ? date.strftime("%y%m%d") : date
25
- @resource = resource
26
- @extension = extension
27
- @rand = rand
28
- @counter = counter
29
- end
30
-
31
- def valid?
32
- Digest::SHA1.hexdigest(to_s).hex >> (160-bits) == 0
33
- end
34
-
35
- def verify(resource, bits, date)
36
- self.resource == resource && self.bits >= bits && parse_date >= date && valid?
37
- end
38
-
39
- def to_s
40
- [version, bits, date, resource, extension, rand, counter].join(":")
41
- end
42
-
43
- def parse_date
44
- Date.strptime(date, "%y%m%d")
45
- end
46
-
47
- def work
48
- @counter += 1 until valid?
49
- self
50
- end
51
- end
52
- end
@@ -1,25 +0,0 @@
1
- module ActiveHashcash
2
- class Store
3
- attr_reader :redis
4
-
5
- def initialize(redis = Redis.new(url: ActiveHashcash.redis_url || ENV["ACTIVE_HASHCASH_REDIS_URL"] || ENV["REDIS_URL"]))
6
- @redis = redis
7
- end
8
-
9
- def add?(stamp)
10
- redis.sadd("active_hashcash_stamps_#{stamp.date}", stamp) ? self : nil
11
- end
12
-
13
- def clear
14
- redis.del(redis.keys("active_hashcash_stamps*"))
15
- end
16
-
17
- def clean
18
- today = Date.today.strftime("%y%m%d")
19
- yesterday = (Date.today - 1).strftime("%y%m%d")
20
- keep = ["active_hashcash_stamps_#{today}", "active_hashcash_stamps_#{yesterday}"]
21
- keys = redis.keys("active_hashcash_stamps*")
22
- redis.del(keys - keep)
23
- end
24
- end
25
- end