# == Schema Information # # Table name: authentication_tokens # # created_at :datetime # expires_at :datetime # hashed_token :string(255) # id :integer not null, primary key # ip_address :string(255) # updated_at :datetime # user_agent :string(255) # # Indexes # # index_authentication_tokens_on_hashed_token (hashed_token) # require 'bcrypt' unless defined? BCrypt class AuthenticationToken < ActiveRecord::Base has_one :user attr_accessor :client_token after_initialize :adjust_bcrypt_cost_for_environment after_initialize :generate_token def self.verified(token) return false if token.nil? or token.blank? parts = token.to_s.split(':') return false if parts.length < 3 uuid = parts.first salt = parts.second secret = parts.last return false unless valid_salt?(salt) return false unless (uuid and salt and secret) hash = BCrypt::Engine.hash_secret(peppered(secret), salt) verified_token = self.find_by_hashed_token(token_format([uuid, salt, hash])) return false unless verified_token if verified_token.expired? # log the access attempt return false else verified_token.set_expiration_date and verified_token.save end return verified_token end def expired? not self.expires_at.future? end def generate_token unless persisted? uuid = SecureRandom.uuid.gsub('-','').to_s secret = SecureRandom.hex(64).to_s hash = BCrypt::Password.create(peppered(secret), cost: @cost) self.client_token = token_format [uuid, hash.salt, secret] # give to client self.hashed_token = token_format [uuid, hash.salt, hash] # store hashed set_expiration_date end end def set_expiration_date self.expires_at = 10.hours.from_now end ################################################################ ################################################################ private def self.valid_salt?(salt) !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/) end def persisted? self.id ? true : false end def self.token_format(parts = []) parts.join(':') end def token_format(parts) self.class.token_format(parts) end def self.peppered(secret) "#{secret}#{Devise.secret_key}" end def peppered(secret) self.class.peppered(secret) end def adjust_bcrypt_cost_for_environment # a value 10 or higher is needed for production security, but we want fast tests @cost ||= ( Rails.env.test? ? 1 : 11 ) end end