require "active_record" require "globalid" require "active_support/core_ext/numeric/time" require "active_support/core_ext/securerandom" module TinyAuth module Model def self.included(base) base.extend ClassMethods end module ClassMethods # Find a resource by email, ignoring case # @param email [String] # @return [ActiveRecord::Base,nil] def find_by_email(email) find_by arel_table[:email].lower.eq(email.downcase) end # Find a resource by their email address and password # This assumes that you've added `has_secure_password` to your model. # @param email [String] # @param password [String] # @return [ActiveRecord::Base,nil] def find_by_credentials(email, password) resource = find_by_email(email) resource if resource&.authenticate(password) end # Finds a resource by a token # @param token [String] # @return [ActiveRecord::Base,nil] def find_by_token(token) resource = GlobalID::Locator.locate_signed(token, for: :access) resource if resource.kind_of?(self) rescue ActiveRecord::RecordNotFound end # Finds a resource by their reset token and nillifies `reset_password_digest` # and `reset_token_expires_at` fields # @param token [String] # @return [ActiveRecord::Base,nil] def exchange_reset_token(token) digest = TinyAuth.hexdigest(token) not_expired = arel_table[:reset_token_expires_at].gt(Time.now) resource = where(not_expired).find_by(reset_token_digest: digest) resource&.reset_token_digest = nil resource&.reset_token_expires_at = nil resource end end # Generates a stateless token for a resource # @param expires_in [ActiveSupport::Duration] defaults to 24 hours def generate_token(expires_in: 24.hours) to_signed_global_id(expires_in: expires_in, for: :access).to_s end # Generates a reset token for a resource. A hashed version of the token # is stored in the database # @param expires_in [ActiveSupport::Duration] defaults to 2 hours def generate_reset_token(expires_in: 2.hours) token = SecureRandom.base58(24) digest = TinyAuth.hexdigest(token) expiry = expires_in.from_now update_columns( reset_token_digest: digest, reset_token_expires_at: expiry ) token end end end