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

Constructor Details

#initialize(server, v, cfg) ⇒ Provider

Returns a new instance of Provider.



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

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.



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

def config
  @config
end

Class Method Details

.with_config(cfg) ⇒ Object

Configure the token provider

Parameters:



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

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
# File 'lib/haveapi/authentication/token/provider.rb', line 152

def authenticate(request)
  t = token(request)

  t && config.find_user_by_token(request, t)
end

#describeObject



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/haveapi/authentication/token/provider.rb', line 165

def describe
  {
    http_header: config.class.http_header,
    query_parameter: config.class.query_parameter,
    description: "The client authenticates with credentials, usually "+
                 "username and password, and gets a token. "+
                 "From this point, the credentials can be forgotten and "+
                 "the token is used instead. Tokens can have different lifetimes, "+
                 "can be renewed and revoked. The token is passed either via HTTP "+
                 "header or query parameter."
  }
end

#resource_moduleObject



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

def resource_module
  return @module if @module
  provider = self

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

#setupObject



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

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

#token(request) ⇒ String

Extract token from HTTP request

Parameters:

  • request (Sinatra::Request)

Returns:

  • (String)


161
162
163
# File 'lib/haveapi/authentication/token/provider.rb', line 161

def token(request)
  request[config.class.query_parameter] || request.env[header_to_env]
end