authentication-logic 0.1.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/console +11 -0
  3. data/bin/setup +8 -0
  4. data/lib/auth/logic/acts_as_authentic/base.rb +118 -0
  5. data/lib/auth/logic/acts_as_authentic/email.rb +32 -0
  6. data/lib/auth/logic/acts_as_authentic/logged_in_status.rb +87 -0
  7. data/lib/auth/logic/acts_as_authentic/login.rb +65 -0
  8. data/lib/auth/logic/acts_as_authentic/magic_columns.rb +40 -0
  9. data/lib/auth/logic/acts_as_authentic/password.rb +362 -0
  10. data/lib/auth/logic/acts_as_authentic/perishable_token.rb +125 -0
  11. data/lib/auth/logic/acts_as_authentic/persistence_token.rb +72 -0
  12. data/lib/auth/logic/acts_as_authentic/queries/case_sensitivity.rb +55 -0
  13. data/lib/auth/logic/acts_as_authentic/queries/find_with_case.rb +85 -0
  14. data/lib/auth/logic/acts_as_authentic/session_maintenance.rb +189 -0
  15. data/lib/auth/logic/acts_as_authentic/single_access_token.rb +85 -0
  16. data/lib/auth/logic/config.rb +41 -0
  17. data/lib/auth/logic/controller_adapters/abstract_adapter.rb +121 -0
  18. data/lib/auth/logic/controller_adapters/rack_adapter.rb +74 -0
  19. data/lib/auth/logic/controller_adapters/rails_adapter.rb +49 -0
  20. data/lib/auth/logic/controller_adapters/sinatra_adapter.rb +69 -0
  21. data/lib/auth/logic/cookie_credentials.rb +65 -0
  22. data/lib/auth/logic/crypto_providers/bcrypt.rb +116 -0
  23. data/lib/auth/logic/crypto_providers/md5/v2.rb +37 -0
  24. data/lib/auth/logic/crypto_providers/md5.rb +38 -0
  25. data/lib/auth/logic/crypto_providers/scrypt.rb +96 -0
  26. data/lib/auth/logic/crypto_providers/sha1/v2.rb +42 -0
  27. data/lib/auth/logic/crypto_providers/sha1.rb +43 -0
  28. data/lib/auth/logic/crypto_providers/sha256/v2.rb +60 -0
  29. data/lib/auth/logic/crypto_providers/sha256.rb +61 -0
  30. data/lib/auth/logic/crypto_providers/sha512/v2.rb +41 -0
  31. data/lib/auth/logic/crypto_providers/sha512.rb +40 -0
  32. data/lib/auth/logic/crypto_providers.rb +89 -0
  33. data/lib/auth/logic/errors.rb +52 -0
  34. data/lib/auth/logic/i18n/translator.rb +20 -0
  35. data/lib/auth/logic/i18n.rb +100 -0
  36. data/lib/auth/logic/random.rb +18 -0
  37. data/lib/auth/logic/session/base.rb +2205 -0
  38. data/lib/auth/logic/session/magic_column/assigns_last_request_at.rb +49 -0
  39. data/lib/auth/logic/test_case/mock_api_controller.rb +53 -0
  40. data/lib/auth/logic/test_case/mock_controller.rb +59 -0
  41. data/lib/auth/logic/test_case/mock_cookie_jar.rb +112 -0
  42. data/lib/auth/logic/test_case/mock_logger.rb +14 -0
  43. data/lib/auth/logic/test_case/mock_request.rb +36 -0
  44. data/lib/auth/logic/test_case/rails_request_adapter.rb +40 -0
  45. data/lib/auth/logic/test_case.rb +216 -0
  46. data/lib/auth/logic/version.rb +7 -0
  47. data/lib/auth/logic.rb +46 -0
  48. metadata +426 -0
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bcrypt"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ # The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2)
9
+ # is the best choice for password storage today. They have the
10
+ # three properties of password hashing that are desirable. They
11
+ # are one-way, unique, and slow. While a salted SHA or MD5 hash is
12
+ # one-way and unique, preventing rainbow table attacks, they are
13
+ # still lightning fast and attacks on the stored passwords are
14
+ # much more effective. This benchmark demonstrates the effective
15
+ # slowdown that BCrypt provides:
16
+ #
17
+ # require "bcrypt"
18
+ # require "digest"
19
+ # require "benchmark"
20
+ #
21
+ # Benchmark.bm(18) do |x|
22
+ # x.report("BCrypt (cost = 10:") {
23
+ # 100.times { BCrypt::Password.create("mypass", :cost => 10) }
24
+ # }
25
+ # x.report("BCrypt (cost = 4:") {
26
+ # 100.times { BCrypt::Password.create("mypass", :cost => 4) }
27
+ # }
28
+ # x.report("Sha512:") {
29
+ # 100.times { Digest::SHA512.hexdigest("mypass") }
30
+ # }
31
+ # x.report("Sha1:") {
32
+ # 100.times { Digest::SHA1.hexdigest("mypass") }
33
+ # }
34
+ # end
35
+ #
36
+ # user system total real
37
+ # BCrypt (cost = 10): 37.360000 0.020000 37.380000 ( 37.558943)
38
+ # BCrypt (cost = 4): 0.680000 0.000000 0.680000 ( 0.677460)
39
+ # Sha512: 0.000000 0.000000 0.000000 ( 0.000672)
40
+ # Sha1: 0.000000 0.000000 0.000000 ( 0.000454)
41
+ #
42
+ # You can play around with the cost to get that perfect balance
43
+ # between performance and security. A default cost of 10 is the
44
+ # best place to start.
45
+ #
46
+ # Decided BCrypt is for you? Just install the bcrypt gem:
47
+ #
48
+ # gem install bcrypt
49
+ #
50
+ # Tell acts_as_authentic to use it:
51
+ #
52
+ # acts_as_authentic do |c|
53
+ # c.crypto_provider = Authentication::Logic::CryptoProviders::BCrypt
54
+ # end
55
+ #
56
+ # You are good to go!
57
+ class BCrypt
58
+ class << self
59
+ # This is the :cost option for the BCrpyt library. The higher the cost
60
+ # the more secure it is and the longer is take the generate a hash. By
61
+ # default this is 10. Set this to any value >= the engine's minimum
62
+ # (currently 4), play around with it to get that perfect balance between
63
+ # security and performance.
64
+ def cost
65
+ @cost ||= 10
66
+ end
67
+
68
+ def cost=(val)
69
+ if val < ::BCrypt::Engine::MIN_COST
70
+ raise ArgumentError, "Authentication::Logic's bcrypt cost cannot be set below the engine's " \
71
+ "min cost (#{::BCrypt::Engine::MIN_COST})"
72
+ end
73
+ @cost = val
74
+ end
75
+
76
+ # Creates a BCrypt hash for the password passed.
77
+ def encrypt(*tokens)
78
+ ::BCrypt::Password.create(join_tokens(tokens), cost: cost)
79
+ end
80
+
81
+ # Does the hash match the tokens? Uses the same tokens that were used to
82
+ # encrypt.
83
+ def matches?(hash, *tokens)
84
+ hash = new_from_hash(hash)
85
+ return false if hash.blank?
86
+
87
+ hash == join_tokens(tokens)
88
+ end
89
+
90
+ # This method is used as a flag to tell Authentication::Logic to "resave" the
91
+ # password upon a successful login, using the new cost
92
+ def cost_matches?(hash)
93
+ hash = new_from_hash(hash)
94
+ if hash.blank?
95
+ false
96
+ else
97
+ hash.cost == cost
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def join_tokens(tokens)
104
+ tokens.flatten.join
105
+ end
106
+
107
+ def new_from_hash(hash)
108
+ ::BCrypt::Password.new(hash)
109
+ rescue ::BCrypt::Errors::InvalidHash
110
+ nil
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ class MD5
9
+ # A poor choice. There are known attacks against this algorithm.
10
+ class V2
11
+ class << self
12
+ attr_accessor :join_token
13
+
14
+ # The number of times to loop through the encryption.
15
+ def stretches
16
+ @stretches ||= 1
17
+ end
18
+ attr_writer :stretches
19
+
20
+ # Turns your raw password into a MD5 hash.
21
+ def encrypt(*tokens)
22
+ digest = tokens.flatten.join(join_token)
23
+ stretches.times { digest = Digest::MD5.digest(digest) }
24
+ digest.unpack1("H*")
25
+ end
26
+
27
+ # Does the crypted password match the tokens? Uses the same tokens that
28
+ # were used to encrypt.
29
+ def matches?(crypted, *tokens)
30
+ encrypt(*tokens) == crypted
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ # A poor choice. There are known attacks against this algorithm.
9
+ class MD5
10
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
11
+ autoload :V2, File.join(__dir__, "md5", "v2")
12
+
13
+ class << self
14
+ attr_accessor :join_token
15
+
16
+ # The number of times to loop through the encryption.
17
+ def stretches
18
+ @stretches ||= 1
19
+ end
20
+ attr_writer :stretches
21
+
22
+ # Turns your raw password into a MD5 hash.
23
+ def encrypt(*tokens)
24
+ digest = tokens.flatten.join(join_token)
25
+ stretches.times { digest = Digest::MD5.hexdigest(digest) }
26
+ digest
27
+ end
28
+
29
+ # Does the crypted password match the tokens? Uses the same tokens that
30
+ # were used to encrypt.
31
+ def matches?(crypted, *tokens)
32
+ encrypt(*tokens) == crypted
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "scrypt"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ # SCrypt is the default provider for Authentication::Logic. It is the only
9
+ # choice in the adaptive hash family that accounts for hardware
10
+ # based attacks by compensating with memory bound as well as cpu
11
+ # bound computational constraints. It offers the same guarantees
12
+ # as BCrypt in the way of one-way, unique and slow.
13
+ #
14
+ # Decided SCrypt is for you? Just install the scrypt gem:
15
+ #
16
+ # gem install scrypt
17
+ #
18
+ # Tell acts_as_authentic to use it:
19
+ #
20
+ # acts_as_authentic do |c|
21
+ # c.crypto_provider = Authentication::Logic::CryptoProviders::SCrypt
22
+ # end
23
+ class SCrypt
24
+ class << self
25
+ DEFAULTS = {
26
+ key_len: 32,
27
+ salt_size: 8,
28
+ max_time: 0.2,
29
+ max_mem: 1024 * 1024,
30
+ max_memfrac: 0.5
31
+ }.freeze
32
+
33
+ attr_writer :key_len, :salt_size, :max_time, :max_mem, :max_memfrac
34
+
35
+ # Key length - length in bytes of generated key, from 16 to 512.
36
+ def key_len
37
+ @key_len ||= DEFAULTS[:key_len]
38
+ end
39
+
40
+ # Salt size - size in bytes of random salt, from 8 to 32
41
+ def salt_size
42
+ @salt_size ||= DEFAULTS[:salt_size]
43
+ end
44
+
45
+ # Max time - maximum time spent in computation
46
+ def max_time
47
+ @max_time ||= DEFAULTS[:max_time]
48
+ end
49
+
50
+ # Max memory - maximum memory usage. The minimum is always 1MB
51
+ def max_mem
52
+ @max_mem ||= DEFAULTS[:max_mem]
53
+ end
54
+
55
+ # Max memory fraction - maximum memory out of all available. Always
56
+ # greater than zero and <= 0.5.
57
+ def max_memfrac
58
+ @max_memfrac ||= DEFAULTS[:max_memfrac]
59
+ end
60
+
61
+ # Creates an SCrypt hash for the password passed.
62
+ def encrypt(*tokens)
63
+ ::SCrypt::Password.create(
64
+ join_tokens(tokens),
65
+ key_len: key_len,
66
+ salt_size: salt_size,
67
+ max_mem: max_mem,
68
+ max_memfrac: max_memfrac,
69
+ max_time: max_time
70
+ )
71
+ end
72
+
73
+ # Does the hash match the tokens? Uses the same tokens that were used to encrypt.
74
+ def matches?(hash, *tokens)
75
+ hash = new_from_hash(hash)
76
+ return false if hash.blank?
77
+
78
+ hash == join_tokens(tokens)
79
+ end
80
+
81
+ private
82
+
83
+ def join_tokens(tokens)
84
+ tokens.flatten.join
85
+ end
86
+
87
+ def new_from_hash(hash)
88
+ ::SCrypt::Password.new(hash)
89
+ rescue ::SCrypt::Errors::InvalidHash
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ class Sha1
9
+ # A poor choice. There are known attacks against this algorithm.
10
+ class V2
11
+ class << self
12
+ def join_token
13
+ @join_token ||= "--"
14
+ end
15
+ attr_writer :join_token, :stretches
16
+
17
+ # The number of times to loop through the encryption.
18
+ def stretches
19
+ @stretches ||= 10
20
+ end
21
+
22
+ # Turns your raw password into a Sha1 hash.
23
+ def encrypt(*tokens)
24
+ tokens = tokens.flatten
25
+ digest = tokens.shift
26
+ stretches.times do
27
+ digest = Digest::SHA1.digest([digest, *tokens].join(join_token))
28
+ end
29
+ digest.unpack1("H*")
30
+ end
31
+
32
+ # Does the crypted password match the tokens? Uses the same tokens that
33
+ # were used to encrypt.
34
+ def matches?(crypted, *tokens)
35
+ encrypt(*tokens) == crypted
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ # A poor choice. There are known attacks against this algorithm.
9
+ class Sha1
10
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
11
+ autoload :V2, File.join(__dir__, "sha1", "v2")
12
+
13
+ class << self
14
+ def join_token
15
+ @join_token ||= "--"
16
+ end
17
+ attr_writer :join_token, :stretches
18
+
19
+ # The number of times to loop through the encryption.
20
+ def stretches
21
+ @stretches ||= 10
22
+ end
23
+
24
+ # Turns your raw password into a Sha1 hash.
25
+ def encrypt(*tokens)
26
+ tokens = tokens.flatten
27
+ digest = tokens.shift
28
+ stretches.times do
29
+ digest = Digest::SHA1.hexdigest([digest, *tokens].join(join_token))
30
+ end
31
+ digest
32
+ end
33
+
34
+ # Does the crypted password match the tokens? Uses the same tokens that
35
+ # were used to encrypt.
36
+ def matches?(crypted, *tokens)
37
+ encrypt(*tokens) == crypted
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authentication
6
+ module Logic
7
+ # The acts_as_authentic method has a crypto_provider option. This allows you
8
+ # to use any type of encryption you like. Just create a class with a class
9
+ # level encrypt and matches? method. See example below.
10
+ #
11
+ # === Example
12
+ #
13
+ # class MyAwesomeEncryptionMethod
14
+ # def self.encrypt(*tokens)
15
+ # # the tokens passed will be an array of objects, what type of object
16
+ # # is irrelevant, just do what you need to do with them and return a
17
+ # # single encrypted string. for example, you will most likely join all
18
+ # # of the objects into a single string and then encrypt that string
19
+ # end
20
+ #
21
+ # def self.matches?(crypted, *tokens)
22
+ # # return true if the crypted string matches the tokens. Depending on
23
+ # # your algorithm you might decrypt the string then compare it to the
24
+ # # token, or you might encrypt the tokens and make sure it matches the
25
+ # # crypted string, its up to you.
26
+ # end
27
+ # end
28
+ module CryptoProviders
29
+ class Sha256
30
+ # = Sha256
31
+ #
32
+ # Uses the Sha256 hash algorithm to encrypt passwords.
33
+ class V2
34
+ class << self
35
+ attr_accessor :join_token
36
+
37
+ # The number of times to loop through the encryption.
38
+ def stretches
39
+ @stretches ||= 20
40
+ end
41
+ attr_writer :stretches
42
+
43
+ # Turns your raw password into a Sha256 hash.
44
+ def encrypt(*tokens)
45
+ digest = tokens.flatten.join(join_token)
46
+ stretches.times { digest = Digest::SHA256.digest(digest) }
47
+ digest.unpack1("H*")
48
+ end
49
+
50
+ # Does the crypted password match the tokens? Uses the same tokens that
51
+ # were used to encrypt.
52
+ def matches?(crypted, *tokens)
53
+ encrypt(*tokens) == crypted
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authentication
6
+ module Logic
7
+ # The acts_as_authentic method has a crypto_provider option. This allows you
8
+ # to use any type of encryption you like. Just create a class with a class
9
+ # level encrypt and matches? method. See example below.
10
+ #
11
+ # === Example
12
+ #
13
+ # class MyAwesomeEncryptionMethod
14
+ # def self.encrypt(*tokens)
15
+ # # the tokens passed will be an array of objects, what type of object
16
+ # # is irrelevant, just do what you need to do with them and return a
17
+ # # single encrypted string. for example, you will most likely join all
18
+ # # of the objects into a single string and then encrypt that string
19
+ # end
20
+ #
21
+ # def self.matches?(crypted, *tokens)
22
+ # # return true if the crypted string matches the tokens. Depending on
23
+ # # your algorithm you might decrypt the string then compare it to the
24
+ # # token, or you might encrypt the tokens and make sure it matches the
25
+ # # crypted string, its up to you.
26
+ # end
27
+ # end
28
+ module CryptoProviders
29
+ # = Sha256
30
+ #
31
+ # Uses the Sha256 hash algorithm to encrypt passwords.
32
+ class Sha256
33
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
34
+ autoload :V2, File.join(__dir__, "sha256", "v2")
35
+
36
+ class << self
37
+ attr_accessor :join_token
38
+
39
+ # The number of times to loop through the encryption.
40
+ def stretches
41
+ @stretches ||= 20
42
+ end
43
+ attr_writer :stretches
44
+
45
+ # Turns your raw password into a Sha256 hash.
46
+ def encrypt(*tokens)
47
+ digest = tokens.flatten.join(join_token)
48
+ stretches.times { digest = Digest::SHA256.hexdigest(digest) }
49
+ digest
50
+ end
51
+
52
+ # Does the crypted password match the tokens? Uses the same tokens that
53
+ # were used to encrypt.
54
+ def matches?(crypted, *tokens)
55
+ encrypt(*tokens) == crypted
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ class Sha512
9
+ # SHA-512 does not have any practical known attacks against it. However,
10
+ # there are better choices. We recommend transitioning to a more secure,
11
+ # adaptive hashing algorithm, like scrypt.
12
+ class V2
13
+ class << self
14
+ attr_accessor :join_token
15
+
16
+ # The number of times to loop through the encryption.
17
+ def stretches
18
+ @stretches ||= 20
19
+ end
20
+ attr_writer :stretches
21
+
22
+ # Turns your raw password into a Sha512 hash.
23
+ def encrypt(*tokens)
24
+ digest = tokens.flatten.join(join_token)
25
+ stretches.times do
26
+ digest = Digest::SHA512.digest(digest)
27
+ end
28
+ digest.unpack1("H*")
29
+ end
30
+
31
+ # Does the crypted password match the tokens? Uses the same tokens that
32
+ # were used to encrypt.
33
+ def matches?(crypted, *tokens)
34
+ encrypt(*tokens) == crypted
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authentication
6
+ module Logic
7
+ module CryptoProviders
8
+ # SHA-512 does not have any practical known attacks against it. However,
9
+ # there are better choices. We recommend transitioning to a more secure,
10
+ # adaptive hashing algorithm, like scrypt.
11
+ class Sha512
12
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
13
+ autoload :V2, File.join(__dir__, "sha512", "v2")
14
+
15
+ class << self
16
+ attr_accessor :join_token
17
+
18
+ # The number of times to loop through the encryption.
19
+ def stretches
20
+ @stretches ||= 20
21
+ end
22
+ attr_writer :stretches
23
+
24
+ # Turns your raw password into a Sha512 hash.
25
+ def encrypt(*tokens)
26
+ digest = tokens.flatten.join(join_token)
27
+ stretches.times { digest = Digest::SHA512.hexdigest(digest) }
28
+ digest
29
+ end
30
+
31
+ # Does the crypted password match the tokens? Uses the same tokens that
32
+ # were used to encrypt.
33
+ def matches?(crypted, *tokens)
34
+ encrypt(*tokens) == crypted
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authentication
4
+ module Logic
5
+ # The acts_as_authentic method has a crypto_provider option. This allows you
6
+ # to use any type of encryption you like. Just create a class with a class
7
+ # level encrypt and matches? method. See example below.
8
+ #
9
+ # === Example
10
+ #
11
+ # class MyAwesomeEncryptionMethod
12
+ # def self.encrypt(*tokens)
13
+ # # The tokens passed will be an array of objects, what type of object
14
+ # # is irrelevant, just do what you need to do with them and return a
15
+ # # single encrypted string. For example, you will most likely join all
16
+ # # of the objects into a single string and then encrypt that string.
17
+ # end
18
+ #
19
+ # def self.matches?(crypted, *tokens)
20
+ # # Return true if the crypted string matches the tokens. Depending on
21
+ # # your algorithm you might decrypt the string then compare it to the
22
+ # # token, or you might encrypt the tokens and make sure it matches the
23
+ # # crypted string, its up to you.
24
+ # end
25
+ # end
26
+ module CryptoProviders
27
+ autoload :MD5, "auth/logic/crypto_providers/md5"
28
+ autoload :Sha1, "auth/logic/crypto_providers/sha1"
29
+ autoload :Sha256, "auth/logic/crypto_providers/sha256"
30
+ autoload :Sha512, "auth/logic/crypto_providers/sha512"
31
+ autoload :BCrypt, "auth/logic/crypto_providers/bcrypt"
32
+ autoload :SCrypt, "auth/logic/crypto_providers/scrypt"
33
+
34
+ # Guide users to choose a better crypto provider.
35
+ class Guidance
36
+ BUILTIN_PROVIDER_PREFIX = "Authentication::Logic::CryptoProviders::"
37
+ NONADAPTIVE_ALGORITHM = <<~EOS
38
+ You have selected %s as your auth-logic crypto provider. This algorithm
39
+ does not have any practical known attacks against it. However, there are
40
+ better choices.
41
+
42
+ Authentication::Logic has no plans yet to deprecate this crypto provider. However,
43
+ we recommend transitioning to a more secure, adaptive hashing algorithm,
44
+ like scrypt. Adaptive algorithms are designed to slow down brute force
45
+ attacks, and over time the iteration count can be increased to make it
46
+ slower, so it remains resistant to brute-force search attacks even in
47
+ the face of increasing computation power.
48
+
49
+ Use the transition_from_crypto_providers option to make the transition
50
+ painless for your users.
51
+ EOS
52
+ VULNERABLE_ALGORITHM = <<~EOS
53
+ You have selected %s as your auth-logic crypto provider. It is a poor
54
+ choice because there are known attacks against this algorithm.
55
+
56
+ Authentication::Logic has no plans yet to deprecate this crypto provider. However,
57
+ we recommend transitioning to a secure hashing algorithm. We recommend
58
+ an adaptive algorithm, like scrypt.
59
+
60
+ Use the transition_from_crypto_providers option to make the transition
61
+ painless for your users.
62
+ EOS
63
+
64
+ def initialize(provider)
65
+ @provider = provider
66
+ end
67
+
68
+ def impart_wisdom
69
+ return unless @provider.is_a?(Class)
70
+
71
+ # We can only impart wisdom about our own built-in providers.
72
+ absolute_name = @provider.name
73
+ return unless absolute_name.start_with?(BUILTIN_PROVIDER_PREFIX)
74
+
75
+ # Inspect the string name of the provider, rather than using the
76
+ # constants in our `when` clauses. If we used the constants, we'd
77
+ # negate the benefits of the `autoload` above.
78
+ name = absolute_name.demodulize
79
+ case name
80
+ when "MD5", "Sha1"
81
+ warn(format(VULNERABLE_ALGORITHM, name))
82
+ when "Sha256", "Sha512"
83
+ warn(format(NONADAPTIVE_ALGORITHM, name))
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end