diff --git a/Gemfile.lock b/Gemfile.lock index 163044a..9c2e63b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,14 @@ PATH GEM remote: http://rubygems.org/ specs: + bacon (1.1.0) diff-lcs (1.1.3) + em-spec (0.2.6) + bacon + eventmachine + rspec (> 2.6.0) + test-unit + eventmachine (0.12.10) rspec (2.9.0) rspec-core (~> 2.9.0) rspec-expectations (~> 2.9.0) @@ -15,10 +22,12 @@ GEM rspec-expectations (2.9.1) diff-lcs (~> 1.1.3) rspec-mocks (2.9.0) + test-unit (2.4.4) PLATFORMS ruby DEPENDENCIES + em-spec rspec (~> 2.9.0) signature! diff --git a/lib/signature.rb b/lib/signature.rb index ecba0d5..6f76c77 100644 --- a/lib/signature.rb +++ b/lib/signature.rb @@ -110,6 +110,45 @@ def authenticate(timestamp_grace = 600) return token end + # Authenticate a request asynchronously + # + # This method is useful it you're running a server inside eventmachine and + # need to lookup the token asynchronously. + # + # The block is passed an auth key and a deferrable which should succeed + # with the token, or fail if the token cannot be found + # + # This method returns a deferrable which succeeds with the valid token, or + # fails with an AuthenticationError which can be used to pass the error + # back to the user + # + def authenticate_async(timestamp_grace = 600) + raise ArgumentError, "Block required" unless block_given? + df = EM::DefaultDeferrable.new + + key = @auth_hash['auth_key'] + + unless key + df.fail(AuthenticationError.new("Missing parameter: auth_key")) + return + end + + token_df = yield key + token_df.callback { |token| + begin + authenticate_by_token!(token, timestamp_grace) + df.succeed(token) + rescue AuthenticationError => e + df.fail(e) + end + } + token_df.errback { + df.fail(AuthenticationError.new("Unknown auth_key")) + } + ensure + return df + end + # Expose the authentication parameters for a signed request # def auth_hash diff --git a/signature.gemspec b/signature.gemspec index 1f757da..3217d5c 100644 --- a/signature.gemspec +++ b/signature.gemspec @@ -19,4 +19,5 @@ Gem::Specification.new do |s| s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION) s.add_development_dependency "rspec", "~> 2.9.0" + s.add_development_dependency "em-spec" end diff --git a/spec/signature_spec.rb b/spec/signature_spec.rb index 35be3a4..3aa7e5f 100644 --- a/spec/signature_spec.rb +++ b/spec/signature_spec.rb @@ -197,5 +197,67 @@ }.should raise_error(ArgumentError, "Block required") end end + + describe "authenticate_async" do + include EM::SpecHelper + default_timeout 1 + + it "returns a deferrable which succeeds if authentication passes" do + request = Signature::Request.new('POST', '/some/path', @params) + em { + df = EM::DefaultDeferrable.new + + request_df = request.authenticate_async do |key| + df + end + + df.succeed(@token) + + request_df.callback { |token| + token.should == @token + done + } + } + end + + it "returns a deferrable which fails if block df fails" do + request = Signature::Request.new('POST', '/some/path', @params) + em { + df = EM::DefaultDeferrable.new + + request_df = request.authenticate_async do |key| + df + end + + df.fail() + + request_df.errback { |e| + e.class.should == Signature::AuthenticationError + e.message.should == 'Unknown auth_key' + done + } + } + end + + it "returns a deferrable which fails if request does not validate" do + request = Signature::Request.new('POST', '/some/path', @params) + em { + df = EM::DefaultDeferrable.new + + request_df = request.authenticate_async do |key| + df + end + + token = Signature::Token.new('key', 'wrong_secret') + df.succeed(token) + + request_df.errback { |e| + e.class.should == Signature::AuthenticationError + e.message.should =~ /Invalid signature/ + done + } + } + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0c7256c..9f61973 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,8 @@ -$LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) - -require 'rubygems' require 'signature' + require 'rspec' +require 'em-spec/rspec' RSpec.configure do |config|