active_hashcash 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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