Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/cognito_idp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module CognitoIdp
autoload :AuthorizationUri, "cognito_idp/authorization_uri"
autoload :Client, "cognito_idp/client"
autoload :Error, "cognito_idp/error"
autoload :LogoutUri, "cognito_idp/logout_uri"
autoload :Token, "cognito_idp/token"
autoload :UserInfo, "cognito_idp/user_info"
Expand Down
23 changes: 21 additions & 2 deletions lib/cognito_idp/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def get_token(grant_type:, **options)
scope: options[:scope]
}.compact
response = connection.post("/oauth2/token", params, basic_authorization_headers)
return unless response.success?
handle_error_response(response)

token = Token.new(response.body)
yield(token) if block_given?
Expand All @@ -50,7 +50,7 @@ def get_user_info(token)
token
end
response = connection.post("/oauth2/userInfo", nil, {"Authorization" => "Bearer #{access_token}"})
return unless response.success?
handle_error_response(response)

user_info = UserInfo.new(response.body)
yield(user_info) if block_given?
Expand All @@ -76,6 +76,25 @@ def connection
end
end

def handle_error_response(response)
return if response.success?

body = response.body
if body.is_a?(Hash) && body["error"]
raise Error.new(
error: body["error"],
error_description: body["error_description"],
http_status: response.status
)
else
raise Error.new(
error: "http_error",
error_description: "the server responded with status #{response.status}",
http_status: response.status
)
end
end

def basic_authorization_headers
return if client_secret.nil?

Expand Down
19 changes: 18 additions & 1 deletion lib/cognito_idp/error.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# frozen_string_literal: true

module CognitoIdp
class Error < StandardError; end
class Error < StandardError
attr_reader :error, :error_description, :http_status

def initialize(error:, error_description: nil, http_status: nil)
@error = error
@error_description = error_description
@http_status = http_status
super(build_message)
end

private

def build_message
return error if error_description.nil?

"#{error}: #{error_description}"
end
end
end
149 changes: 144 additions & 5 deletions spec/cognito_idp/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/token") do |env|
fail "Authorization is present.#{env.request_headers}" if env.request_headers.key?("Authorization")
[200, {"Content-Type" => "application/json"}, response_payload.to_json]
end
end
end
Expand All @@ -103,6 +104,7 @@
id_and_secret = "#{client_id}:#{client_secret}"
basic_auth = "Basic #{Base64.urlsafe_encode64(id_and_secret)}"
fail "Basic Authorization is missing." unless env.request_headers["Authorization"] == basic_auth
[200, {"Content-Type" => "application/json"}, response_payload.to_json]
end
end
end
Expand All @@ -129,7 +131,66 @@
end
let(:error) { "invalid_request" }

it { is_expected.to be_nil }
it "raises a CognitoIdp::Error" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_request")
expect(e.http_status).to eq(400)
end
end
end

context "when response is an error with error_description" do
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/token") do |env|
[400, {"Content-Type" => "application/json"}, response_payload.to_json]
end
end
end
let(:response_payload) do
{error: "invalid_grant", error_description: "Authorization code has expired"}
end

it "raises a CognitoIdp::Error with error_description" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_grant")
expect(e.error_description).to eq("Authorization code has expired")
expect(e.message).to eq("invalid_grant: Authorization code has expired")
expect(e.http_status).to eq(400)
end
end
end

context "when response is a server error" do
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/token") do |env|
[500, {"Content-Type" => "text/plain"}, "Internal Server Error"]
end
end
end

it "raises a CognitoIdp::Error with http_error" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("http_error")
expect(e.error_description).to eq("the server responded with status 500")
expect(e.http_status).to eq(500)
end
end
end

context "when response is an error it does not yield" do
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/token") do |env|
[400, {"Content-Type" => "application/json"}, {error: "invalid_request"}.to_json]
end
end
end

it "does not yield the block" do
expect { |b| client.get_token(grant_type: grant_type, code: code, redirect_uri: redirect_uri, &b) }.to raise_error(CognitoIdp::Error)
end
end
end

Expand Down Expand Up @@ -192,7 +253,12 @@
end
let(:error) { "invalid_request" }

it { is_expected.to be_nil }
it "raises a CognitoIdp::Error" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_request")
expect(e.http_status).to eq(400)
end
end
end
end

Expand Down Expand Up @@ -243,6 +309,7 @@
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/token") do |env|
fail "Authorization is present.#{env.request_headers}" if env.request_headers.key?("Authorization")
[200, {"Content-Type" => "application/json"}, response_payload.to_json]
end
end
end
Expand All @@ -260,6 +327,7 @@
id_and_secret = "#{client_id}:#{client_secret}"
basic_auth = "Basic #{Base64.urlsafe_encode64(id_and_secret)}"
fail "Basic Authorization is missing." unless env.request_headers["Authorization"] == basic_auth
[200, {"Content-Type" => "application/json"}, response_payload.to_json]
end
end
end
Expand All @@ -286,7 +354,12 @@
end
let(:error) { "invalid_request" }

it { is_expected.to be_nil }
it "raises a CognitoIdp::Error" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_request")
expect(e.http_status).to eq(400)
end
end
end
end

Expand Down Expand Up @@ -349,7 +422,12 @@
end
let(:error) { "invalid_request" }

it { is_expected.to be_nil }
it "raises a CognitoIdp::Error" do
expect { token }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_request")
expect(e.http_status).to eq(400)
end
end
end
end
end
Expand Down Expand Up @@ -453,7 +531,68 @@
end
let(:error) { "invalid_request" }

it { is_expected.to be_nil }
it "raises a CognitoIdp::Error" do
expect { user_info }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_request")
expect(e.http_status).to eq(400)
end
end
end

context "when response is an unauthorized error" do
let(:token) { "ACCESS_TOKEN" }
let(:access_token) { token }
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/userInfo") do |env|
[401, {"Content-Type" => "application/json"}, {error: "invalid_token", error_description: "Access token is expired"}.to_json]
end
end
end

it "raises a CognitoIdp::Error with error_description" do
expect { user_info }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("invalid_token")
expect(e.error_description).to eq("Access token is expired")
expect(e.http_status).to eq(401)
end
end
end

context "when response is a server error" do
let(:token) { "ACCESS_TOKEN" }
let(:access_token) { token }
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/userInfo") do |env|
[500, {"Content-Type" => "text/plain"}, "Internal Server Error"]
end
end
end

it "raises a CognitoIdp::Error with http_error" do
expect { user_info }.to raise_error(CognitoIdp::Error) do |e|
expect(e.error).to eq("http_error")
expect(e.error_description).to eq("the server responded with status 500")
expect(e.http_status).to eq(500)
end
end
end

context "when response is an error it does not yield" do
let(:token) { "ACCESS_TOKEN" }
let(:access_token) { token }
let(:stubs) do
Faraday::Adapter::Test::Stubs.new do |stub|
stub.post("https://auth.example.com/oauth2/userInfo") do |env|
[400, {"Content-Type" => "application/json"}, {error: "invalid_request"}.to_json]
end
end
end

it "does not yield the block" do
expect { |b| client.get_user_info(access_token, &b) }.to raise_error(CognitoIdp::Error)
end
end
end

Expand Down
29 changes: 29 additions & 0 deletions spec/cognito_idp/error_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

RSpec.describe CognitoIdp::Error do
it "inherits from StandardError" do
expect(described_class).to be < StandardError
end

describe "#message" do
context "when only error is provided" do
subject(:error) { described_class.new(error: "invalid_request") }

it { expect(error.message).to eq("invalid_request") }
end

context "when error and error_description are provided" do
subject(:error) { described_class.new(error: "invalid_grant", error_description: "Authorization code has expired") }

it { expect(error.message).to eq("invalid_grant: Authorization code has expired") }
end
end

describe "attribute readers" do
subject(:error) { described_class.new(error: "invalid_grant", error_description: "Authorization code has expired", http_status: 400) }

it { expect(error.error).to eq("invalid_grant") }
it { expect(error.error_description).to eq("Authorization code has expired") }
it { expect(error.http_status).to eq(400) }
end
end