aikotoba 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0d639e691f230c3bcb02686f18ecef8dab6a4fecf9310121c8e581a4079ab62
4
- data.tar.gz: 169419bd1301489504a2d1049ee9f1c939432b95b0b6eea33df195b43bee0c6d
3
+ metadata.gz: 173aca2780a3086a9276f56c4bd59a1c4aedb6aa2a8f2d3e29068f00e8a5f417
4
+ data.tar.gz: d96bea3b841912e7586c07a984e58c6b0daea49109a3b73bc3cc90090cf7434f
5
5
  SHA512:
6
- metadata.gz: 3bae2cfbf6a1ccacc972abf66057b83b85261ac642800a563ea81b416d83f19f154049d5c9ccc8af0fa6d49c31eec38a0a19cdf3e9ffcd1920e556f02941d0f4
7
- data.tar.gz: 347e3fbde3a2af2a02828faabebd94ca67adfe1c71e97f93c7c42e1ab2d83ee6bdee3df30ad5af7d760509f0d41b1f82fedbe9981423c2bec52fbc8ffa9122aa
6
+ metadata.gz: 050d2f984527212f1a0426fd830c0b0ae1dfff51ea3e75fda1a5596cc702175996f70c3bbf1ca9acf6b21ea2099863ee5dfa705654db8ad338053ce384aeb635
7
+ data.tar.gz: 7efcedd94cb09d06f9d846d6d43931ded9eb243dd7625f5a6ab74a41682082ca2d2b4c00feb27a1c282006ab04e44b70086d7024554ac4fe7e73cae273369f93
data/README.md CHANGED
@@ -226,7 +226,7 @@ Rails.application.config.to_prepare do
226
226
  end
227
227
 
228
228
  class Profile < ApplicationRecord
229
- has_one :account, class_name: 'Aikotoba::Account'
229
+ has_one :account, class_name: 'Aikotoba::Account', as: :authenticate_target
230
230
  end
231
231
 
232
232
  current_account.profile #=> Profile instance
@@ -10,7 +10,7 @@ module Aikotoba
10
10
  scope :active, ->(now: Time.current) { where("expired_at >= ?", now) }
11
11
 
12
12
  after_initialize do |record|
13
- token = Account::Value::Token.new(extipry: Aikotoba.confirmation_token_expiry)
13
+ token = Account::Token.new(extipry: Aikotoba.confirmation_token_expiry)
14
14
  record.token ||= token.value
15
15
  record.expired_at ||= token.expired_at
16
16
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "argon2"
4
+
5
+ module Aikotoba
6
+ class Account::Password::Argon2
7
+ def initialize(password:)
8
+ @password = password
9
+ end
10
+
11
+ def verify_password?(digest)
12
+ Argon2::Password.verify_password(@password, digest)
13
+ rescue Argon2::ArgonHashFail # NOTE: If an invalid digest is passed, consider it a mismatch.
14
+ false
15
+ end
16
+
17
+ def generate_hash
18
+ # NOTE: Adjusted to be OWASAP's recommended value by default.
19
+ # > Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism.
20
+ # > https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction
21
+ argon = Argon2::Password.new(t_cost: 2, m_cost: 14, p_cost: 1)
22
+ argon.create(@password)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikotoba
4
+ class Account::Password
5
+ LENGTH_RENGE = Aikotoba.password_length_range
6
+
7
+ def initialize(
8
+ value:,
9
+ pepper: Aikotoba.password_pepper,
10
+ algorithm_class: Argon2
11
+ )
12
+ @value = value
13
+ @pepper = pepper
14
+ @algorithm = algorithm_class.new(password: password_with_pepper(@value))
15
+ end
16
+
17
+ attr_reader :value
18
+
19
+ def match?(digest:)
20
+ @algorithm.verify_password?(digest)
21
+ end
22
+
23
+ def digest
24
+ return "" if value.blank?
25
+ @algorithm.generate_hash
26
+ end
27
+
28
+ private
29
+
30
+ def password_with_pepper(password)
31
+ "#{password}-#{@pepper}"
32
+ end
33
+ end
34
+ end
@@ -10,7 +10,7 @@ module Aikotoba
10
10
  scope :active, ->(now: Time.current) { where("expired_at >= ?", now) }
11
11
 
12
12
  after_initialize do |record|
13
- token = Account::Value::Token.new(extipry: Aikotoba.recovery_token_expiry)
13
+ token = Account::Token.new(extipry: Aikotoba.recovery_token_expiry)
14
14
  record.token ||= token.value
15
15
  record.expired_at ||= token.expired_at
16
16
  end
@@ -6,12 +6,17 @@ module Aikotoba
6
6
  new(email: email, password: password).call!
7
7
  end
8
8
 
9
- def initialize(email:, password:)
10
- @account_class = Account
11
- @lock_service = Account::Service::Lock
12
- @lockable = @account_class.lockable?
9
+ def initialize(email:, password:,
10
+ account_class: Account,
11
+ lock_service: Account::Service::Lock,
12
+ lockable: Account.lockable?,
13
+ prevent_timing_atack: Aikotoba.prevent_timing_atack)
14
+ @account_class = account_class
15
+ @lock_service = lock_service
16
+ @lockable = lockable
13
17
  @email = email
14
18
  @password = password
19
+ @aikotoba_prevent_timing_atack = prevent_timing_atack
15
20
  end
16
21
 
17
22
  def call!
@@ -29,16 +34,12 @@ module Aikotoba
29
34
 
30
35
  # NOTE: Verify passwords even when accounts are not found to prevent timing attacks.
31
36
  def prevent_timing_atack
32
- return true unless aikotoba_prevent_timing_atack
37
+ return true unless @aikotoba_prevent_timing_atack
33
38
  account = @account_class.build_by(attributes: {email: @email, password: @password})
34
39
  account.password_match?(@password)
35
40
  true
36
41
  end
37
42
 
38
- def aikotoba_prevent_timing_atack
39
- Aikotoba.prevent_timing_atack
40
- end
41
-
42
43
  def success_callback(account)
43
44
  account.authentication_success!
44
45
  end
@@ -6,10 +6,10 @@ module Aikotoba
6
6
  new.call!(account: account)
7
7
  end
8
8
 
9
- def initialize
10
- @account_class = Account
11
- @confirm_service = Account::Service::Confirmation
12
- @confirmable = @account_class.confirmable?
9
+ def initialize(account_class: Account, confirm_service: Account::Service::Confirmation, confirmable: Account.confirmable?)
10
+ @account_class = account_class
11
+ @confirm_service = confirm_service
12
+ @confirmable = confirmable
13
13
  end
14
14
 
15
15
  def call!(account:)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aikotoba
4
- class Account::Value::Token
4
+ class Account::Token
5
5
  def initialize(extipry:)
6
6
  @value = build_token
7
7
  @expired_at = extipry.since
@@ -10,7 +10,7 @@ module Aikotoba
10
10
  scope :active, ->(now: Time.current) { where("expired_at >= ?", now) }
11
11
 
12
12
  after_initialize do |record|
13
- token = Account::Value::Token.new(extipry: Aikotoba.unlock_token_expiry)
13
+ token = Account::Token.new(extipry: Aikotoba.unlock_token_expiry)
14
14
  record.token ||= token.value
15
15
  record.expired_at ||= token.expired_at
16
16
  end
@@ -13,7 +13,7 @@ module Aikotoba
13
13
  attribute :max_failed_attempts, :integer, default: -> { Aikotoba.max_failed_attempts }
14
14
 
15
15
  validates :email, presence: true, uniqueness: true, format: EMAIL_REGEXP, length: {maximum: EMAIL_MAXIMUM_LENGTH}
16
- validates :password, presence: true, length: {in: Value::Password::LENGTH_RENGE}, on: [:create, :recover]
16
+ validates :password, presence: true, length: {in: Password::LENGTH_RENGE}, on: [:create, :recover]
17
17
  validates :password_digest, presence: true
18
18
  validates :confirmed, inclusion: [true, false]
19
19
  validates :failed_attempts, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
@@ -30,7 +30,7 @@ module Aikotoba
30
30
  attr_reader :password
31
31
 
32
32
  def password=(value)
33
- new_password = Value::Password.new(value: value)
33
+ new_password = Password.new(value: value)
34
34
  @password = new_password.value
35
35
  assign_attributes(password_digest: new_password.digest)
36
36
  end
@@ -53,7 +53,7 @@ module Aikotoba
53
53
  end
54
54
 
55
55
  def password_match?(password)
56
- Value::Password.new(value: password).match?(digest: password_digest)
56
+ Password.new(value: password).match?(digest: password_digest)
57
57
  end
58
58
 
59
59
  def authentication_failed!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aikotoba
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aikotoba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Madogiwa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-11 00:00:00.000000000 Z
11
+ date: 2022-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -61,15 +61,16 @@ files:
61
61
  - app/mailers/aikotoba/application_mailer.rb
62
62
  - app/models/aikotoba/account.rb
63
63
  - app/models/aikotoba/account/confirmation_token.rb
64
+ - app/models/aikotoba/account/password.rb
65
+ - app/models/aikotoba/account/password/argon2.rb
64
66
  - app/models/aikotoba/account/recovery_token.rb
65
67
  - app/models/aikotoba/account/service/authentication.rb
66
68
  - app/models/aikotoba/account/service/confirmation.rb
67
69
  - app/models/aikotoba/account/service/lock.rb
68
70
  - app/models/aikotoba/account/service/recovery.rb
69
71
  - app/models/aikotoba/account/service/registration.rb
72
+ - app/models/aikotoba/account/token.rb
70
73
  - app/models/aikotoba/account/unlock_token.rb
71
- - app/models/aikotoba/account/value/password.rb
72
- - app/models/aikotoba/account/value/token.rb
73
74
  - app/models/concerns/aikotoba/enabled_feature_checkable.rb
74
75
  - app/models/concerns/aikotoba/token_encryptable.rb
75
76
  - app/views/aikotoba/account_mailer/confirm.html.erb
@@ -120,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
121
  - !ruby/object:Gem::Version
121
122
  version: '0'
122
123
  requirements: []
123
- rubygems_version: 3.3.3
124
+ rubygems_version: 3.3.7
124
125
  signing_key:
125
126
  specification_version: 4
126
127
  summary: Aikotoba is a Rails engine that makes it easy to implement simple email and
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "argon2"
4
-
5
- module Aikotoba
6
- class Account::Value::Password
7
- LENGTH_RENGE = Aikotoba.password_length_range
8
-
9
- def initialize(
10
- value:,
11
- pepper: Aikotoba.password_pepper
12
- )
13
- @value = value
14
- @pepper = pepper
15
- end
16
-
17
- attr_reader :value
18
-
19
- def match?(digest:)
20
- verify_password?(password_with_pepper(value), digest)
21
- end
22
-
23
- def digest
24
- return "" if value.blank?
25
- generate_hash(password_with_pepper(value))
26
- end
27
-
28
- private
29
-
30
- def verify_password?(password, digest)
31
- Argon2::Password.verify_password(password, digest)
32
- rescue Argon2::ArgonHashFail # NOTE: If an invalid digest is passed, consider it a mismatch.
33
- false
34
- end
35
-
36
- def password_with_pepper(password)
37
- "#{password}-#{@pepper}"
38
- end
39
-
40
- def generate_hash(password)
41
- # NOTE: Adjusted to be OWASAP's recommended value by default.
42
- # > Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism.
43
- # > https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction
44
- argon = Argon2::Password.new(t_cost: 2, m_cost: 14, p_cost: 1)
45
- argon.create(password)
46
- end
47
- end
48
- end