module GrapeDeviseAuth::Concerns::User

Public Class Methods

tokens_match?(token_hash, token) click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 8
def self.tokens_match?(token_hash, token)
  @token_equality_cache ||= {}

  key = "#{token_hash}/#{token}"
  result = @token_equality_cache[key] ||= (::BCrypt::Password.new(token_hash) == token)
  if @token_equality_cache.size > 10000
    @token_equality_cache = {}
  end
  result
end

Public Instance Methods

allow_password_change() click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 44
def allow_password_change
  @allow_password_change || false
end
build_auth_header(token, client_id='default') click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 68
def build_auth_header(token, client_id='default')
  client_id ||= 'default'

  # client may use expiry to prevent validation request if expired
  # must be cast as string or headers will break
  expiry = self.tokens[client_id]['expiry'] || self.tokens[client_id][:expiry]

  max_clients = GrapeDeviseAuth.max_number_of_devices
  while self.tokens.keys.length > 0 and max_clients < self.tokens.keys.length
    oldest_token = self.tokens.min_by { |cid, v| v[:expiry] || v["expiry"] }
    self.tokens.delete(oldest_token.first)
  end

  self.save!

  return {
    GrapeDeviseAuth.headers_names[:"access-token"] => token,
    GrapeDeviseAuth.headers_names[:"token-type"]   => "Bearer",
    GrapeDeviseAuth.headers_names[:"client"]       => client_id,
    GrapeDeviseAuth.headers_names[:"expiry"]       => expiry.to_s,
    GrapeDeviseAuth.headers_names[:"uid"]          => self.uid
  }
end
create_new_auth_token(client_id=nil) click to toggle source

update user's auth token (should happen on each request)

# File lib/grape_devise_auth/concerns/user.rb, line 147
def create_new_auth_token(client_id=nil)
  client_id  ||= SecureRandom.urlsafe_base64(nil, false)
  last_token ||= nil
  token        = SecureRandom.urlsafe_base64(nil, false)
  token_hash   = ::BCrypt::Password.create(token)
  expiry       = (Time.now + GrapeDeviseAuth.token_lifespan).to_i

  if self.tokens[client_id] and self.tokens[client_id]['token']
    last_token = self.tokens[client_id]['token']
  end

  self.tokens[client_id] = {
    token:      token_hash,
    expiry:     expiry,
    last_token: last_token,
    updated_at: Time.now
  }
  
  return build_auth_header(token, client_id)
end
email_changed?() click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 53
def email_changed?
  false
end
email_required?() click to toggle source

don't use default devise email validation

# File lib/grape_devise_auth/concerns/user.rb, line 49
def email_required?
  false
end
extend_batch_buffer(token, client_id) click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 92
def extend_batch_buffer(token, client_id)
  self.tokens[client_id]['updated_at'] = Time.now

  return build_auth_header(token, client_id)
end
token_can_be_reused?(token, client_id) click to toggle source

allow batch requests to use the previous token

# File lib/grape_devise_auth/concerns/user.rb, line 128
def token_can_be_reused?(token, client_id)
  # ghetto HashWithIndifferentAccess
  updated_at = self.tokens[client_id]['updated_at'] || self.tokens[client_id][:updated_at]
  last_token = self.tokens[client_id]['last_token'] || self.tokens[client_id][:last_token]


  return true if (
    # ensure that the last token and its creation time exist
    updated_at and last_token and

    # ensure that previous token falls within the batch buffer throttle time of the last request
    Time.parse(updated_at) > Time.now - GrapeDeviseAuth.batch_request_buffer_throttle and

    # ensure that the token is valid
    ::BCrypt::Password.new(last_token) == token
  )
end
token_is_current?(token, client_id) click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 110
def token_is_current?(token, client_id)
  # ghetto HashWithIndifferentAccess
  expiry     = self.tokens[client_id]['expiry'] || self.tokens[client_id][:expiry]
  token_hash = self.tokens[client_id]['token'] || self.tokens[client_id][:token]

  return true if (
    # ensure that expiry and token are set
    expiry and token and

    # ensure that the token has not yet expired
    DateTime.strptime(expiry.to_s, '%s') > Time.now and

    # ensure that the token is valid
    GrapeDeviseAuth::Concerns::User.tokens_match?(token_hash, token)
  )
end
valid_token?(token, client_id='default') click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 98
def valid_token?(token, client_id='default')
  client_id ||= 'default'

  return false unless self.tokens[client_id]

  return true if token_is_current?(token, client_id)
  return true if token_can_be_reused?(token, client_id)

  # return false if none of the above conditions are met
  return false
end

Protected Instance Methods

destroy_expired_tokens() click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 174
def destroy_expired_tokens
  if self.tokens
    self.tokens.delete_if do |cid, v|
      expiry = v[:expiry] || v["expiry"]
      DateTime.strptime(expiry.to_s, '%s') < Time.now
    end
  end
end
remove_tokens_after_password_reset() click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 183
def remove_tokens_after_password_reset
  there_is_more_than_one_token = self.tokens && self.tokens.keys.length > 1
  should_remove_old_tokens = GrapeDeviseAuth.remove_tokens_after_password_reset &&
                             encrypted_password_changed? && there_is_more_than_one_token

  if should_remove_old_tokens
    latest_token = self.tokens.max_by { |cid, v| v[:expiry] || v["expiry"] }
    self.tokens = { latest_token.first => latest_token.last }
  end
end
set_empty_token_hash() click to toggle source
# File lib/grape_devise_auth/concerns/user.rb, line 170
def set_empty_token_hash
  self.tokens ||= {} if has_attribute?(:tokens)
end