diff options
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG.md | 28 | ||||
-rw-r--r-- | activesupport/lib/active_support/message_encryptor.rb | 32 | ||||
-rw-r--r-- | activesupport/lib/active_support/message_verifier.rb | 40 |
3 files changed, 100 insertions, 0 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index f5542c6d25..ef46bc06e0 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,31 @@ +* Add purpose and expiry support to `MessageVerifier` & `MessageEncryptor`. + + Messages generated using `MessageVerifier` and `MessageEncryptor` now + support expiry and purpose. You can set the purpose of the message using + the key :purpose, and likewise, the expiration using :expires_at or :expires_in. + + This ensures that the message is used only for its intended purpose and also + while it hasn't expired. + + For instance, to ensure a message is only usable for one intended purpose: + + token = @verifier.generate("x", purpose: :shipping) + + @verifier.verified(token, purpose: :shipping) # => "x" + @verifier.verified(token) # => nil + + Or make it expire after a set time: + + @verifier.generate("x", expires_in: 1.month) + @verifier.generate("y", expires_at: Time.now.end_of_year) + + Showcased with `ActiveSupport::MessageVerifier`, but works the same for + `ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`. + + Pull requests: #29599, #29854 + + *Assain Jaleel* + * Make the order of `Hash#reverse_merge!` consistent with `HashWithIndifferentAccess`. *Erol Fornoles* diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 9ceb3a3a7f..090d51933a 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -22,6 +22,38 @@ module ActiveSupport # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = crypt.encrypt_and_sign("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair" + # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil + # crypt.decrypt_and_verify(token) # => nil + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = crypt.encrypt_and_sign("the conversation is lively") + # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil + # crypt.decrypt_and_verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # crypt.encrypt_and_sign(parcel, expires_in: 1.month) + # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, verifying returns +nil+. class MessageEncryptor class << self attr_accessor :use_authenticated_message_encryption #:nodoc: diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 76b3865bf2..fdd2185f7f 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -33,6 +33,46 @@ module ActiveSupport # `:digest` key as an option while initializing the verifier: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # @verifier.generate(parcel, expires_in: 1.month) + # @verifier.generate(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, the +verified+ method returns +nil+ while +verify+ raises + # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>. class MessageVerifier class InvalidSignature < StandardError; end |