Module: GrapeDeviseAuth::Concerns::User

Extended by:
ActiveSupport::Concern
Defined in:
lib/grape_devise_auth/concerns/user.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.tokens_match?(token_hash, token) ⇒ Boolean

Returns:

  • (Boolean)


8
9
10
11
12
13
14
15
16
17
# 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

Instance Method Details

#build_auth_header(token, client_id = 'default') ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 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) ⇒ Object

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



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 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

#extend_batch_buffer(token, client_id) ⇒ Object



92
93
94
95
96
# 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) ⇒ Boolean

allow batch requests to use the previous token

Returns:

  • (Boolean)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 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') ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
106
107
108
# 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