class PusherPlatform::Authenticator

Public Class Methods

new(instance_id, key_id, key_secret) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 11
def initialize(instance_id, key_id, key_secret)
  @instance_id = instance_id
  @key_id = key_id
  @key_secret = key_secret
  # see https://github.com/rack/rack/blob/5559676e7b5a3107d39552285ce8b714b672bde6/lib/rack/utils.rb#L27
  @query_parser = QueryParser.make_default(65536, 100)
end

Public Instance Methods

authenticate(auth_payload, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 19
def authenticate(auth_payload, options)
  grant_type = auth_payload['grant_type'] || auth_payload[:grant_type]

  unless grant_type == "client_credentials"
    return AuthenticationResponse.new({
      status: 422,
      body: {
        error: 'token_provider/invalid_grant_type',
        error_description: "The grant_type provided, #{grant_type}, is unsupported"
      }
    })
  end

  authenticate_using_client_credentials(options)
end
authenticate_with_refresh_token(auth_payload, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 40
def authenticate_with_refresh_token(auth_payload, options)
  authenticate_based_on_grant_type(auth_payload, options)
end
authenticate_with_refresh_token_and_request(request, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 44
def authenticate_with_refresh_token_and_request(request, options)
  auth_data = @query_parser.parse_nested_query request.body.read
  authenticate_based_on_grant_type(auth_data, options)
end
authenticate_with_request(request, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 35
def authenticate_with_request(request, options)
  auth_data = @query_parser.parse_nested_query request.body.read
  authenticate(auth_data, options)
end
generate_access_token(options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 49
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]
  claims.merge!(options[:service_claims]) if options[:service_claims]

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

Private Instance Methods

authenticate_based_on_grant_type(auth_data, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 71
def authenticate_based_on_grant_type(auth_data, options)
  grant_type = auth_data['grant_type'] || auth_data[:grant_type]

  if grant_type == "client_credentials"
    return authenticate_using_client_credentials(options, true)
  elsif grant_type == "refresh_token"
    refresh_token = auth_data['refresh_token'] || auth_data[:refresh_token]
    return authenticate_using_refresh_token(refresh_token, options)
  else
    return AuthenticationResponse.new({
      status: 422,
      body: ErrorBody.new({
        error: 'token_provider/invalid_grant_type',
        error_description: "The grant_type provided, #{grant_type}, is unsupported"
      })
    })
  end
end
authenticate_using_client_credentials(options, with_refresh_token = false) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 90
def authenticate_using_client_credentials(options, with_refresh_token = false)
  access_token = generate_access_token(options)[:token]
  token_payload = {
    access_token: access_token,
    token_type: "bearer",
    expires_in: TOKEN_EXPIRY
  }

  token_payload[:refresh_token] = generate_refresh_token(options)[:token] if with_refresh_token

  return AuthenticationResponse.new({
    status: 200,
    body: token_payload
  })
end
authenticate_using_refresh_token(refresh_token, options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 106
def authenticate_using_refresh_token(refresh_token, options)
  old_refresh_token = begin
    JWT.decode(refresh_token, @key_secret, true, {
      algorithm: 'HS256',
      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 token has expired"
    else
      "Refresh token is invalid"
    end

    return AuthenticationResponse.new({
      status: 401,
      body: ErrorBody.new({
        error: "token_provider/invalid_refresh_token",
        error_description: error_description
      })
    })
  end

  if old_refresh_token["refresh"] != true
    return AuthenticationResponse.new({
      status: 401,
      body: ErrorBody.new({
        error: "token_provider/invalid_refresh_token",
        error_description: "Refresh token does not have a refresh claim"
      })
    })
  end

  if options[:user_id] != old_refresh_token["sub"]
    return AuthenticationResponse.new({
      status: 401,
      body: ErrorBody.new({
        error: "token_provider/invalid_user_id_in_refresh_token",
        error_description: "Refresh token has an invalid user id"
      })
    })
  end

  return AuthenticationResponse.new({
    status: 200,
    body: new_token_pair(options)
  })
end
generate_refresh_token(options) click to toggle source
# File lib/pusher-platform/authenticator.rb, line 174
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
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 163
def new_token_pair(options)
  access_token = generate_access_token(options)[:token]
  refresh_token = generate_refresh_token(options)[:token]
  {
    access_token: access_token,
    token_type: "bearer",
    expires_in: TOKEN_EXPIRY,
    refresh_token: refresh_token,
  }
end