Class: HaveAPI::Authentication::Token::Provider

Inherits:
Base
  • Object
show all
Defined in:
lib/haveapi/authentication/token/provider.rb

Overview

Provider for token authentication.

This provider has to be configured using Config.

Token auth contains API resource token. User can request a token by calling action Request. The returned token is then used for authenticating the user. Client sends the token with each request in configured Config#http_header or Config#query_parameter.

Token can be revoked by calling action Revoke and renewed with Renew.

Example usage:

Token model:

class ApiToken < ActiveRecord::Base
belongs_to :user

validates :user_id, :token, presence: true
validates :token, length: {is: 100}

enum lifetime: %i(fixed renewable_manual renewable_auto permanent)

def renew
  self.valid_to = Time.now + interval
end
end

Authentication provider configuration:

class MyTokenAuthConfig < HaveAPI::Authentication::Token::Config
request do
  handle do |req, res|
    user = ::User.find_by(login: input[:user], password: input[:password])

    if user.nil?
      res.error = 'invalid user or password'
      next res
    end

    token = SecureRandom.hex(50)
    valid_to =
      if req.input[:lifetime] == 'permanent'
        nil
      else
        Time.now + req.input[:interval]

    user.tokens << ::Token.new(
      token: token,
      lifetime: req.input[:lifetime],
      valid_to: valid_to,
      interval: req.input[:interval],
      label: req.request.user_agent,
    )

    res.token = token
    res.valid_to = valid_to
    res.complete = true
    res.ok
  end
end

renew do
  handle do |req, res|
    t = ::Token.find_by(user: req.user, token: req.token)

    if t && t.lifetime.start_with('renewable')
      t.renew
      t.save
      res.valid_to = t.valid_to
      res.ok
    else
      res.error = 'unable to renew token'
      res
    end
  end
end

revoke do
  handle do |req, res|
    req.user.tokens.delete(token: req.token)
    res.ok
  end
end

def find_user_by_token(request, token)
  t = ::Token.find_by(token: token)

  if t
    # Renew the token if needed
    if t.lifetime == 'renewable_auto'
      t.renew
      t.save
    end

    t.user # return the user
  end
end
end

Finally put the provider in the authentication chain:

api = HaveAPI.new(...)
...
api.auth_chain << HaveAPI::Authentication::Token.with_config(MyTokenAuthConfig)

Instance Attribute Summary collapse

Attributes inherited from Base

#name

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

auth_method, inherited, #register_routes

Constructor Details

#initialize(server, v, cfg) ⇒ Provider

Returns a new instance of Provider.



131
132
133
134
# File 'lib/haveapi/authentication/token/provider.rb', line 131

def initialize(server, v, cfg)
  @config = cfg.new(server, v)
  super(server, v)
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



129
130
131
# File 'lib/haveapi/authentication/token/provider.rb', line 129

def config
  @config
end

Class Method Details

.with_config(cfg) ⇒ Object

Configure the token provider

Parameters:



121
122
123
124
125
126
127
# File 'lib/haveapi/authentication/token/provider.rb', line 121

def self.with_config(cfg)
  Module.new do
    define_singleton_method(:new) do |*args|
      Provider.new(*args, cfg)
    end
  end
end

Instance Method Details

#authenticate(request) ⇒ Object

Authenticate request

Parameters:

  • request (Sinatra::Request)


152
153
154
155
156
157
158
# File 'lib/haveapi/authentication/token/provider.rb', line 152

def authenticate(request)
  t = token(request)

  t && config.find_user_by_token(request, t)
rescue HaveAPI::AuthenticationError
  nil
end

#describeObject



184
185
186
187
188
189
190
# File 'lib/haveapi/authentication/token/provider.rb', line 184

def describe
  {
    http_header: config.class.http_header,
    query_parameter: config.class.query_parameter,
    description: HaveAPI.message('haveapi.authentication.token.description')
  }
end

#resource_moduleObject



140
141
142
143
144
145
146
147
148
# File 'lib/haveapi/authentication/token/provider.rb', line 140

def resource_module
  return @module if @module

  provider = self

  @module = Module.new do
    const_set(:Token, provider.send(:token_resource))
  end
end

#setupObject



136
137
138
# File 'lib/haveapi/authentication/token/provider.rb', line 136

def setup
  @server.allow_header(config.class.http_header)
end

#token(request) ⇒ String

Extract token from HTTP request

Parameters:

  • request (Sinatra::Request)

Returns:

  • (String)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/haveapi/authentication/token/provider.rb', line 163

def token(request)
  param = config.class.query_parameter
  query_token = [
    request.params[param],
    request.params[param.to_s]
  ].find { |value| token_present?(value) }
  header_token = request.env[header_to_env]

  tokens = [query_token, header_token].select { |value| token_present?(value) }

  case tokens.length
  when 0
    nil
  when 1
    tokens.first
  else
    raise HaveAPI::Authentication::TokenConflict,
          HaveAPI.t('haveapi.authentication.multiple_tokens')
  end
end