diff --git a/lib/rage/controller/api.rb b/lib/rage/controller/api.rb index ff7edbb4..7d67756d 100644 --- a/lib/rage/controller/api.rb +++ b/lib/rage/controller/api.rb @@ -428,6 +428,28 @@ def authenticate_with_http_token yield token end + # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header requesting the client to send a + # Bearer token. For the authentication to be considered successful, the block should return a non-nil value. + # + # @yield [token] token value extracted from the `Authorization` header + # @example + # before_action :authenticate + # + # def authenticate + # authenticate_or_request_with_http_token do |token| + # ApiToken.find_by(token: token) + # end + # end + def authenticate_or_request_with_http_token + authenticate_with_http_token { |token| yield(token) } || request_http_token_authentication + end + + # Render an HTTP header requesting the client to send a Bearer token for authentication. + def request_http_token_authentication + headers["Www-Authenticate"] = "Token" + render plain: "HTTP Token: Access denied.", status: 401 + end + if !defined?(::ActionController::Parameters) # Get the request data. The keys inside the hash are symbols, so `params.keys` returns an array of `Symbol`.
# You can also load Strong Params to have Rage automatically wrap `params` in an instance of `ActionController::Parameters`.
diff --git a/spec/controller/api/authenticate_spec.rb b/spec/controller/api/authenticate_spec.rb index fa1839dd..fa33cc00 100644 --- a/spec/controller/api/authenticate_spec.rb +++ b/spec/controller/api/authenticate_spec.rb @@ -3,22 +3,9 @@ RSpec.describe RageController::API do subject { described_class.new(env, nil) } - context "with a Bearer token" do - let(:env) { { "HTTP_AUTHORIZATION" => "Bearer my_token" } } - - it "extracts the token" do - subject.authenticate_with_http_token do |token| - expect(token).to eq("my_token") - end - end - - it "returns the value of the login procedure" do - value = subject.authenticate_with_http_token { :request_authenticated } - expect(value).to eq(:request_authenticated) - end - - context "with a token prefix" do - let(:env) { { "HTTP_AUTHORIZATION" => "Bearer token=my_token" } } + context "#authenticate_with_http_token" do + context "with a Bearer token" do + let(:env) { { "HTTP_AUTHORIZATION" => "Bearer my_token" } } it "extracts the token" do subject.authenticate_with_http_token do |token| @@ -30,25 +17,25 @@ value = subject.authenticate_with_http_token { :request_authenticated } expect(value).to eq(:request_authenticated) end - end - end - context "with a Token token" do - let(:env) { { "HTTP_AUTHORIZATION" => "Token my_token" } } + context "with a token prefix" do + let(:env) { { "HTTP_AUTHORIZATION" => "Bearer token=my_token" } } - it "extracts the token" do - subject.authenticate_with_http_token do |token| - expect(token).to eq("my_token") - end - end + it "extracts the token" do + subject.authenticate_with_http_token do |token| + expect(token).to eq("my_token") + end + end - it "returns the value of the login procedure" do - value = subject.authenticate_with_http_token { :request_authenticated } - expect(value).to eq(:request_authenticated) + it "returns the value of the login procedure" do + value = subject.authenticate_with_http_token { :request_authenticated } + expect(value).to eq(:request_authenticated) + end + end end - context "with a token prefix" do - let(:env) { { "HTTP_AUTHORIZATION" => "Token token=my_token" } } + context "with a Token token" do + let(:env) { { "HTTP_AUTHORIZATION" => "Token my_token" } } it "extracts the token" do subject.authenticate_with_http_token do |token| @@ -61,45 +48,100 @@ expect(value).to eq(:request_authenticated) end - context "with quotes" do - let(:env) { { "HTTP_AUTHORIZATION" => "Token token=\"my_token\"" } } + context "with a token prefix" do + let(:env) { { "HTTP_AUTHORIZATION" => "Token token=my_token" } } it "extracts the token" do subject.authenticate_with_http_token do |token| expect(token).to eq("my_token") end end + + it "returns the value of the login procedure" do + value = subject.authenticate_with_http_token { :request_authenticated } + expect(value).to eq(:request_authenticated) + end + + context "with quotes" do + let(:env) { { "HTTP_AUTHORIZATION" => "Token token=\"my_token\"" } } + + it "extracts the token" do + subject.authenticate_with_http_token do |token| + expect(token).to eq("my_token") + end + end + end end end - end - context "with a Digest token" do - let(:env) { { "HTTP_AUTHORIZATION" => "Digest my_token" } } + context "with a Digest token" do + let(:env) { { "HTTP_AUTHORIZATION" => "Digest my_token" } } - it "doesn't extract the token" do - value = subject.authenticate_with_http_token { :request_authenticated } - expect(value).to be_nil + it "doesn't extract the token" do + value = subject.authenticate_with_http_token { :request_authenticated } + expect(value).to be_nil + end + + it "doesn't call the login procedure" do + expect { + subject.authenticate_with_http_token { raise } + }.not_to raise_error + end end - it "doesn't call the login procedure" do - expect { - subject.authenticate_with_http_token { raise } - }.not_to raise_error + context "with no token" do + let(:env) { {} } + + it "returns nil" do + value = subject.authenticate_with_http_token { :request_authenticated } + expect(value).to be_nil + end + + it "doesn't call the login procedure" do + expect { + subject.authenticate_with_http_token { raise } + }.not_to raise_error + end end end - context "with no token" do + context "#authenticate_or_request_with_http_token" do let(:env) { {} } - it "returns nil" do - value = subject.authenticate_with_http_token { :request_authenticated } - expect(value).to be_nil + before do + expect(subject).to receive(:authenticate_with_http_token).and_yield("my_test_token") end - it "doesn't call the login procedure" do - expect { - subject.authenticate_with_http_token { raise } - }.not_to raise_error + it "extracts the token" do + subject.authenticate_or_request_with_http_token do |token| + expect(token).to eq("my_test_token") + end + end + + it "returns the value of the login procedure" do + value = subject.authenticate_or_request_with_http_token { :request_authenticated } + expect(value).to eq(:request_authenticated) + end + + it "doesn't request authentication if login procedure returns non nil" do + subject.authenticate_or_request_with_http_token { :request_authenticated } + expect(subject.response.headers).not_to have_key("Www-Authenticate") + end + + it "doesn't request authentication if login procedure returns nil" do + subject.authenticate_or_request_with_http_token {} + expect(subject.response.headers["Www-Authenticate"]).to eq("Token") + end + end + + context "#request_http_token_authentication" do + let(:env) { {} } + + it "requests token authentication" do + subject.request_http_token_authentication {} + + expect(subject.response.body).to eq("HTTP Token: Access denied.") + expect(subject.response.headers["Www-Authenticate"]).to eq("Token") end end end