aikotoba 0.1.0 → 0.1.1

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