jgorset / facebook-messenger

Definitely the best way to make Bots on Facebook Messenger with Ruby
MIT License
961 stars 211 forks source link

Facebook::Messenger::FacebookError: An active access token must be used to query information about the current user. #167

Closed angelacode closed 6 years ago

angelacode commented 7 years ago

I have a valid access token.

I have an output 'true' when I subscribe in my app.

However, I still get this error:

Facebook::Messenger::FacebookError: An active access token must be used to query information about the current user.

I successfully exit at binding.pry but get the error when I message.reply.

When I output the ENV variable for message_access_token, it matches what is in the Facebook app under developer.facebook.com.

So this is confusing why message fails.

Bot.on :message do |message|
    binding.pry
    puts "inside bot message"
    message.reply(text: 'Hello!')

end

How can I better debug this?

angelacode commented 7 years ago

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

Facebook::Messenger::FacebookError at /webhook

Facebook::Messenger::FacebookError at /webhook

An active access token must be used to query information about the current user.

Ruby /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot/error_parser.rb: in raise_errors_from, line 53
Web POST dev.hellokaya.com/webhook

Jump to:

Traceback (innermost first)

  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot/error_parser.rb: in raise_errors_from
    1. error_subcode = error['error_subcode']
    2. raise_code_only_error(error_code, error) if error_subcode.nil?
    3. raise_code_subcode_error(error_code, error_subcode, error)
    4. # Default to unidentified error
    1. raise FacebookError, error...
    1. end
    2. private
    3. def raise_code_only_error(error_code, args)
    4. raise InternalError, args if internal_error?(error_code)
    5. raise AccessTokenError, args if access_token_error?(error_code)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot.rb: in deliver
    1. response = post '/messages',
    2. body: JSON.dump(message),
    3. format: :json,
    4. query: {
    5. access_token: access_token
    6. }
    1. Facebook::Messenger::Bot::ErrorParser.raise_errors_from(response)...
    1. response.body
    2. end
    3. # Register a hook for the given event.
    4. #
    5. # event - A String describing a Messenger event.
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/incoming/common.rb: in reply
    1. def reply(message)
    2. payload = {
    3. recipient: sender,
    4. message: message
    5. }
    1. Facebook::Messenger::Bot.deliver(payload, access_token: access_token)...
    1. end
    2. def access_token
    3. Facebook::Messenger.config.provider.access_token_for(recipient)
    4. end
    5. end
    6. end
  • /root/kaya-nitrous/bot.rb: in block in <top (required)>
    1. subscribe = Facebook::Messenger::Subscriptions.subscribe(access_token: ENV["MESSENGER_ACCESS_TOKEN"])
    2. puts 'running bot.rb'
    3. puts subscribe
    4. Bot.on :message do |message|
    1. message.reply(text: 'Hello!')...
    1. binding.pry
    2. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot.rb: in call
    1. end
    2. # Trigger the hook for the given event.
    3. #
    4. # event - A String describing a Messenger event.
    5. # args - Arguments to pass to the hook.
    6. def trigger(event, *args)
    1. hooks.fetch(event).call(*args)...
    1. rescue KeyError
    2. $stderr.puts "Ignoring #{event} (no hook registered)"
    3. end
    4. # Return a Hash of hooks.
    5. def hooks
    6. @hooks ||= {}
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot.rb: in trigger
    1. end
    2. # Trigger the hook for the given event.
    3. #
    4. # event - A String describing a Messenger event.
    5. # args - Arguments to pass to the hook.
    6. def trigger(event, *args)
    1. hooks.fetch(event).call(*args)...
    1. rescue KeyError
    2. $stderr.puts "Ignoring #{event} (no hook registered)"
    3. end
    4. # Return a Hash of hooks.
    5. def hooks
    6. @hooks ||= {}
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/bot.rb: in receive
    1. #
    2. # payload - A Hash describing the message.
    3. #
    4. # * https://developers.facebook.com/docs/messenger-platform/webhook-reference
    5. def receive(payload)
    6. callback = Facebook::Messenger::Incoming.parse(payload)
    7. event = Facebook::Messenger::Incoming::EVENTS.invert[callback.class]
    1. trigger(event.to_sym, callback)...
    1. end
    2. # Trigger the hook for the given event.
    3. #
    4. # event - A String describing a Messenger event.
    5. # args - Arguments to pass to the hook.
    6. def trigger(event, *args)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in block (2 levels) in trigger
    1. events['entry'.freeze].each do |entry|
    2. # If the application has subscribed to webhooks other than Messenger,
    3. # 'messaging' won't be available and it is not relevant to us.
    4. next unless entry['messaging'.freeze]
    5. # Facebook may batch several items in the 'messaging' array during
    6. # periods of high load.
    7. entry['messaging'.freeze].each do |messaging|
    1. Facebook::Messenger::Bot.receive(messaging)...
    1. end
    2. end
    3. end
    4. def respond_with_error(error)
    5. @response.status = 400
    6. @response.write(error.message)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in each
    1. # periods of high load.
    2. events['entry'.freeze].each do |entry|
    3. # If the application has subscribed to webhooks other than Messenger,
    4. # 'messaging' won't be available and it is not relevant to us.
    5. next unless entry['messaging'.freeze]
    6. # Facebook may batch several items in the 'messaging' array during
    7. # periods of high load.
    1. entry['messaging'.freeze].each do |messaging|...
    1. Facebook::Messenger::Bot.receive(messaging)
    2. end
    3. end
    4. end
    5. def respond_with_error(error)
    6. @response.status = 400
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in block in trigger
    1. # periods of high load.
    2. events['entry'.freeze].each do |entry|
    3. # If the application has subscribed to webhooks other than Messenger,
    4. # 'messaging' won't be available and it is not relevant to us.
    5. next unless entry['messaging'.freeze]
    6. # Facebook may batch several items in the 'messaging' array during
    7. # periods of high load.
    1. entry['messaging'.freeze].each do |messaging|...
    1. Facebook::Messenger::Bot.receive(messaging)
    2. end
    3. end
    4. end
    5. def respond_with_error(error)
    6. @response.status = 400
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in each
    1. rescue JSON::ParserError
    2. raise BadRequestError, 'Error parsing request body format'
    3. end
    4. def trigger(events)
    5. # Facebook may batch several items in the 'entry' array during
    6. # periods of high load.
    1. events['entry'.freeze].each do |entry|...
    1. # If the application has subscribed to webhooks other than Messenger,
    2. # 'messaging' won't be available and it is not relevant to us.
    3. next unless entry['messaging'.freeze]
    4. # Facebook may batch several items in the 'messaging' array during
    5. # periods of high load.
    6. entry['messaging'.freeze].each do |messaging|
    7. Facebook::Messenger::Bot.receive(messaging)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in trigger
    1. rescue JSON::ParserError
    2. raise BadRequestError, 'Error parsing request body format'
    3. end
    4. def trigger(events)
    5. # Facebook may batch several items in the 'entry' array during
    6. # periods of high load.
    1. events['entry'.freeze].each do |entry|...
    1. # If the application has subscribed to webhooks other than Messenger,
    2. # 'messaging' won't be available and it is not relevant to us.
    3. next unless entry['messaging'.freeze]
    4. # Facebook may batch several items in the 'messaging' array during
    5. # periods of high load.
    6. entry['messaging'.freeze].each do |messaging|
    7. Facebook::Messenger::Bot.receive(messaging)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in receive
    1. @response.write 'Error; wrong verify token'
    2. end
    3. end
    4. def receive
    5. check_integrity
    1. trigger(parsed_body)...
    1. rescue BadRequestError => error
    2. respond_with_error(error)
    3. end
    4. # Check the integrity of the request.
    5. #
    6. # Raises BadRequestError if the request has been tampered with.
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in call
    1. def call(env)
    2. @request = Rack::Request.new(env)
    3. @response = Rack::Response.new
    4. if @request.get?
    5. verify
    6. elsif @request.post?
    1. receive...
    1. else
    2. @response.status = 405
    3. end
    4. @response.finish
    5. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/facebook-messenger-1.0.0/lib/facebook/messenger/server.rb: in call
    1. some time, check your app's secret token.
    2. HEREDOC
    3. # This module holds the server that processes incoming messages from the
    4. # Facebook Messenger Platform.
    5. class Server
    6. def self.call(env)
    1. new.call(env)...
    1. end
    2. def call(env)
    3. @request = Rack::Request.new(env)
    4. @response = Rack::Response.new
    5. if @request.get?
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/urlmap.rb: in block in call
    1. rest = m[1]
    2. next unless !rest || rest.empty? || rest[0] == ?/
    3. env['SCRIPT_NAME'] = (script_name + location)
    4. env['PATH_INFO'] = rest
    1. return app.call(env)...
    1. end
    2. [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
    3. ensure
    4. env['PATH_INFO'] = path
    5. env['SCRIPT_NAME'] = script_name
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/urlmap.rb: in each
    1. def call(env)
    2. path = env[PATH_INFO]
    3. script_name = env['SCRIPT_NAME']
    4. hHost = env['HTTP_HOST']
    5. sName = env['SERVER_NAME']
    6. sPort = env['SERVER_PORT']
    1. @mapping.each do |host, location, match, app|...
    1. unless casecmp?(hHost, host) \
    2. || casecmp?(sName, host) \
    3. || (!host && (casecmp?(hHost, sName) ||
    4. casecmp?(hHost, sName+':'+sPort)))
    5. next
    6. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/urlmap.rb: in call
    1. def call(env)
    2. path = env[PATH_INFO]
    3. script_name = env['SCRIPT_NAME']
    4. hHost = env['HTTP_HOST']
    5. sName = env['SERVER_NAME']
    6. sPort = env['SERVER_PORT']
    1. @mapping.each do |host, location, match, app|...
    1. unless casecmp?(hHost, host) \
    2. || casecmp?(sName, host) \
    3. || (!host && (casecmp?(hHost, sName) ||
    4. casecmp?(hHost, sName+':'+sPort)))
    5. next
    6. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/tempfile_reaper.rb: in call
    1. class TempfileReaper
    2. def initialize(app)
    3. @app = app
    4. end
    5. def call(env)
    6. env['rack.tempfiles'] ||= []
    1. status, headers, body = @app.call(env)...
    1. body_proxy = BodyProxy.new(body) do
    2. env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
    3. end
    4. [status, headers, body_proxy]
    5. end
    6. end
    7. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/lint.rb: in _call
    1. assert("No env given") { env }
    2. check_env env
    3. env['rack.input'] = InputWrapper.new(env['rack.input'])
    4. env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
    5. ## and returns an Array of exactly three values:
    1. status, headers, @body = @app.call(env)...
    1. ## The *status*,
    2. check_status status
    3. ## the *headers*,
    4. check_headers headers
    5. check_hijack_response headers, env
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/lint.rb: in call
    1. ## after to catch all mistakes.
    2. ## = Rack applications
    3. ## A Rack application is a Ruby object (not a class) that
    4. ## responds to +call+.
    5. def call(env=nil)
    1. dup._call(env)...
    1. end
    2. def _call(env)
    3. ## It takes exactly one argument, the *environment*
    4. assert("No env given") { env }
    5. check_env env
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/showexceptions.rb: in call
    1. def initialize(app)
    2. @app = app
    3. @template = ERB.new(TEMPLATE)
    4. end
    5. def call(env)
    1. @app.call(env)...
    1. rescue StandardError, LoadError, SyntaxError => e
    2. exception_string = dump_exception(e)
    3. env["rack.errors"].puts(exception_string)
    4. env["rack.errors"].flush
    5. if accepts_html?(env)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/commonlogger.rb: in call
    1. def initialize(app, logger=nil)
    2. @app = app
    3. @logger = logger
    4. end
    5. def call(env)
    6. began_at = Time.now
    1. status, header, body = @app.call(env)...
    1. header = Utils::HeaderHash.new(header)
    2. body = BodyProxy.new(body) { log(env, status, header, began_at) }
    3. [status, header, body]
    4. end
    5. private
  • /usr/local/rvm/gems/ruby-2.2.1/gems/sinatra-1.4.8/lib/sinatra/base.rb: in call
    1. env['sinatra.commonlogger'] ? @app.call(env) : super
    2. end
    3. superclass.class_eval do
    4. alias call_without_check call unless method_defined? :call_without_check
    5. def call(env)
    6. env['sinatra.commonlogger'] = true
    1. call_without_check(env)...
    1. end
    2. end
    3. end
    4. class NotFound < NameError #:nodoc:
    5. def http_status; 404 end
    6. end
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/chunked.rb: in call
    1. false
    2. else
    3. true
    4. end
    5. end
    6. def call(env)
    1. status, headers, body = @app.call(env)...
    1. headers = HeaderHash.new(headers)
    2. if ! chunkable_version?(env['HTTP_VERSION']) ||
    3. STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
    4. headers[CONTENT_LENGTH] ||
    5. headers['Transfer-Encoding']
    6. [status, headers, body]
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/content_length.rb: in call
    1. include Rack::Utils
    2. def initialize(app)
    3. @app = app
    4. end
    5. def call(env)
    1. status, headers, body = @app.call(env)...
    1. headers = HeaderHash.new(headers)
    2. if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
    3. !headers[CONTENT_LENGTH] &&
    4. !headers['Transfer-Encoding'] &&
    5. body.respond_to?(:to_ary)
  • /usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/handler/webrick.rb: in service
    1. env[QUERY_STRING] ||= ""
    2. unless env[PATH_INFO] == ""
    3. path, n = req.request_uri.path, env["SCRIPT_NAME"].length
    4. env[PATH_INFO] = path[n, path.length-n]
    5. end
    6. env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env[PATH_INFO]].join
    1. status, headers, body = @app.call(env)...
    1. begin
    2. res.status = status.to_i
    3. headers.each { |k, vs|
    4. next if k.downcase == "rack.hijack"
    5. if k.downcase == "set-cookie"
    6. res.cookies.concat vs.split("\n")
  • /usr/local/rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/webrick/httpserver.rb: in service
    1. servlet, options, script_name, path_info = search_servlet(req.path)
    2. raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
    3. req.script_name = script_name
    4. req.path_info = path_info
    5. si = servlet.get_instance(self, *options)
    6. @logger.debug(format("%s is invoked.", si.class.name))
    1. si.service(req, res)...
    1. end
    2. ##
    3. # The default OPTIONS request handler says GET, HEAD, POST and OPTIONS
    4. # requests are allowed.
    5. def do_OPTIONS(req, res)
  • /usr/local/rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/webrick/httpserver.rb: in run
    1. if callback = server[:RequestCallback]
    2. callback.call(req, res)
    3. elsif callback = server[:RequestHandler]
    4. msg = ":RequestHandler is deprecated, please use :RequestCallback"
    5. @logger.warn(msg)
    6. callback.call(req, res)
    7. end
    1. server.service(req, res)...
    1. rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
    2. res.set_error(ex)
    3. rescue HTTPStatus::Error => ex
    4. @logger.error(ex.message)
    5. res.set_error(ex)
    6. rescue HTTPStatus::Status => ex
    7. res.status = ex.code
  • /usr/local/rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/webrick/server.rb: in block in start_thread
    1. addr = sock.peeraddr
    2. @logger.debug "accept: #{addr[3]}:#{addr[1]}"
    3. rescue SocketError
    4. @logger.debug "accept: <address unknown>"
    5. raise
    6. end
    7. call_callback(:AcceptCallback, sock)
    1. block ? block.call(sock) : run(sock)...
    1. rescue Errno::ENOTCONN
    2. @logger.debug "Errno::ENOTCONN raised"
    3. rescue ServerError => ex
    4. msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
    5. @logger.error msg
    6. rescue Exception => ex
    7. @logger.error ex

Request information

GET

No GET data.

POST

No POST data.

No cookie data.

Rack ENV

Variable Value
CONTENT_LENGTH
348
CONTENT_TYPE
application/json
GATEWAY_INTERFACE
CGI/1.1
HTTP_ACCEPT
*/*
HTTP_ACCEPT_ENCODING
gzip
HTTP_CF_CONNECTING_IP
54.226.227.229
HTTP_CF_IPCOUNTRY
US
HTTP_CF_RAY
38d9b44f8f082456-IAD
HTTP_CF_VISITOR
{"scheme":"https"}
HTTP_CONNECTION
Keep-Alive
HTTP_HOST
dev.hellokaya.com
HTTP_VERSION
HTTP/1.1
HTTP_X_FORWARDED_FOR
54.226.227.229
HTTP_X_FORWARDED_PROTO
https
HTTP_X_HUB_SIGNATURE
sha1=9950b65cbac721687c836b0bd3e14e2384096075
PATH_INFO
/webhook
QUERY_STRING
REMOTE_ADDR
162.158.78.252
REMOTE_HOST
162.158.78.252
REQUEST_METHOD
POST
REQUEST_PATH
/webhook
REQUEST_URI
https://dev.hellokaya.com/webhook
SCRIPT_NAME
SERVER_NAME
dev.hellokaya.com
SERVER_PORT
443
SERVER_PROTOCOL
HTTP/1.1
SERVER_SOFTWARE
WEBrick/1.3.1 (Ruby/2.2.1/2015-02-26)
rack.errors
#<Rack::Lint::ErrorWrapper:0x000000022ba960 @error=#<IO:<STDERR>>>
rack.hijack
#<Proc:0x000000022bb400@/usr/local/rvm/gems/ruby-2.2.1/gems/rack-1.6.8/lib/rack/lint.rb:525>
rack.hijack?
true
rack.hijack_io
nil
rack.input
#<Rack::Lint::InputWrapper:0x000000022ba988 @input=#<StringIO:0x000000022e4558>>
rack.multiprocess
false
rack.multithread
true
rack.request.cookie_hash
{}
rack.request.query_hash
{}
rack.request.query_string
rack.run_once
false
rack.tempfiles
[]
rack.url_scheme
http
rack.version
[1, 3]
sinatra.commonlogger
true

You're seeing this error because you use Rack::ShowExceptions.

angelacode commented 7 years ago

I have discovered the problem. The code implies that by subscribing and providing the access token, that all methods would work when requiring it.

It turns out the ENV['ACCESS_TOKEN'] is hard coded elsewhere....I subscribed with the following:

subscribe = Facebook::Messenger::Subscriptions.subscribe(access_token: ENV["MESSENGER_ACCESS_TOKEN"])

I have tons of other access tokens in my app so each one needs to be identified.

The error is fixed when I explicitly do the following:

ENV['ACCESS_TOKEN'] = ENV['MESSENGER_ACCESS_TOKEN']

This is confusing: the code implies that the application understand the access token identified when subscribing. But it looks like a hard-coded value for it is used elsewhere.

Is there a way to make token assignments more explicit and consistent?

sorich87 commented 7 years ago

You can make a configuration provider https://github.com/jgorset/facebook-messenger#make-a-configuration-provider

oscarsiniscalchi commented 6 years ago

I ended up with something like this

class FacebookVerificationProvider < Facebook::Messenger::Configuration::Providers::Base
  def valid_verify_token?(token)
    ENV['FACEBOOK_VERIFICATION_TOKEN'] == token
  end

  def app_secret_for(page_id)
    ENV['FACEBOOK_APP_SECRET']
  end

  def access_token_for(page_id)
    ENV['FACEBOOK_ACCESS_TOKEN']
  end
end

and on my bot

require 'facebook/messenger'
require_relative './verification_provider'
include Facebook::Messenger

Facebook::Messenger.configure do |config|
  config.provider = FacebookVerificationProvider.new
end

...

You could potentially include some sort of validation that the page_id you get is the one you want your bot to reply from but not even sure if necessary.

However, I agree with @angelacode, the name for the environment variable should not be hardcoded in the gem and rather get that from a configuration users can set on an initializer or similar, especially the name of such variable being 'ACCESS_TOKEN'

jgorset commented 6 years ago

I agree, @oscarsiniscalchi -- it's hard to change it now without breaking compatibility, though, and considering that it is possible to change by writing your own configuration provider I guess that'll have to do.