class PusherPlatform::Authenticator

Public Class Methods

new(instance_id, key_id, key_secret) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 8
def initialize(instance_id, key_id, key_secret)
  @instance_id = instance_id
  @key_id = key_id
  @key_secret = key_secret
end

Public Instance Methods

authenticate(request, options) click to toggle source

Takes a Rack request to the authorization endpoint and and handles it either returning a new access/refresh token pair, or an error.

@param request [Rack::Request] the request to authenticate @return the response object

# File lib/pusher-platform/authenticator.rb, line 19
def authenticate(request, options)
  form_data = Rack::Utils.parse_nested_query request.body.read
  grant_type = form_data['grant_type']

  if grant_type == "client_credentials"
    return authenticate_with_client_credentials(options)
  elsif grant_type == "refresh_token"
    old_refresh_jwt = form_data['refresh_token']
    return authenticate_with_refresh_token(old_refresh_jwt, options)
  else
    return response(401, {
      error: "unsupported_grant_type"
    })
  end
end
generate_access_token(options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 35
def generate_access_token(options)
  now = Time.now.utc.to_i

  claims = {
    instance: @instance_id,
    iss: "api_keys/#{@key_id}",
    iat: now,
    exp: now + TOKEN_EXPIRY
  }

  claims.merge!({ sub: options[:user_id] }) unless options[:user_id].nil?
  claims.merge!({ su: true }) if options[:su]

  {
    token: JWT.encode(claims, @key_secret, 'HS256'),
    expires_in: TOKEN_EXPIRY
  }
end

Private Instance Methods

authenticate_with_client_credentials(options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 56
def authenticate_with_client_credentials(options)
  return respond_with_new_token_pair(options)
end
authenticate_with_refresh_token(old_refresh_jwt, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 60
def authenticate_with_refresh_token(old_refresh_jwt, options)
  old_refresh_token = begin
    JWT.decode(old_refresh_jwt, @key_secret, true, {
      iss: "api_keys/#{@key_id}",
      verify_iss: true,
    }).first
  rescue => e
    error_description = if e.is_a?(JWT::InvalidIssuerError)
      "refresh token issuer is invalid"
    elsif e.is_a?(JWT::ImmatureSignature)
      "refresh token is not valid yet"
    elsif e.is_a?(JWT::ExpiredSignature)
      "refresh tokan has expired"
    else
      "refresh token is invalid"
    end

    return response(401, {
      error: "invalid_grant",
      error_description: error_description,
      # TODO error_uri
    })
  end

  if old_refresh_token["refresh"] != true
    return response(401, {
      error: "invalid_grant",
      error_description: "refresh token does not have a refresh claim",
      # TODO error_uri
    })
  end

  if options[:user_id] != old_refresh_token["sub"]
    return response(401, {
      error: "invalid_grant",
      error_description: "refresh token has an invalid user id",
      # TODO error_uri
    })
  end

  return respond_with_new_token_pair(options)
end
generate_refresh_token(options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 118
def generate_refresh_token(options)
  now = Time.now.utc.to_i

  claims = {
    instance: @instance_id,
    iss: "api_keys/#{@key_id}",
    iat: now,
    refresh: true,
    sub: options[:user_id],
  }

  { token: JWT.encode(claims, @key_secret, 'HS256') }
end
respond_with_new_token_pair(options) click to toggle source

Creates a payload dictionary made out of access and refresh token pair and TTL for the access token.

@param user_id [String] optional id of the user, ignore for anonymous users @return [Hash] Payload as a hash

# File lib/pusher-platform/authenticator.rb, line 107
def respond_with_new_token_pair(options)
  access_token = generate_access_token(options)[:token]
  refresh_token = generate_refresh_token(options)[:token]
  return response(200, {
    access_token: access_token,
    token_type: "bearer",
    expires_in: TOKEN_EXPIRY,
    refresh_token: refresh_token,
  })
end
response(status, body) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 132
def response(status, body)
  return {
    status: status,
    json: body,
  }
end