From 7c4cad369ce1448e71e7b4c951a2af5b06acbe49 Mon Sep 17 00:00:00 2001 From: Laura Mosher Date: Wed, 4 Jan 2023 12:29:32 -0500 Subject: [PATCH] Add config to allow specifying public, non-authenticated routes --- CHANGELOG.md | 4 +++ lib/rack/firebase/configuration.rb | 3 +- lib/rack/firebase/middleware.rb | 51 +++++++++++++++------------ spec/rack/firebase/middleware_spec.rb | 24 +++++++++++++ spec/rack/firebase_spec.rb | 3 ++ 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 010b84a..147460b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added ability to specify public routes that do not require authentication. + ## [0.2.0] - 2023-01-04 ### Added diff --git a/lib/rack/firebase/configuration.rb b/lib/rack/firebase/configuration.rb index 77bdc00..0f6ea12 100644 --- a/lib/rack/firebase/configuration.rb +++ b/lib/rack/firebase/configuration.rb @@ -1,7 +1,7 @@ module Rack module Firebase class Configuration - attr_accessor :project_ids + attr_accessor :project_ids, :public_routes def initialize reset! @@ -9,6 +9,7 @@ def initialize def reset! @project_ids = [] + @public_routes = [] end end end diff --git a/lib/rack/firebase/middleware.rb b/lib/rack/firebase/middleware.rb index cf30852..0e6860a 100644 --- a/lib/rack/firebase/middleware.rb +++ b/lib/rack/firebase/middleware.rb @@ -16,30 +16,37 @@ def initialize(app) end def call(env) - token = AuthorizationHeader.read_token(env) - decoded_token = TokenDecoder.new.call(token) + path = env.fetch("PATH_INFO", "no-match") + if config.public_routes.none? { |r| r.match(path) } + begin + token = AuthorizationHeader.read_token(env) + decoded_token = TokenDecoder.new.call(token) - raise Rack::Firebase::InvalidSubError.new("Invalid subject") if decoded_token["sub"].nil? || decoded_token["sub"] == "" - raise Rack::Firebase::InvalidAuthTimeError.new("Invalid auth time") unless decoded_token["auth_time"] <= Time.now.to_i + raise Rack::Firebase::InvalidSubError.new("Invalid subject") if decoded_token["sub"].nil? || decoded_token["sub"] == "" + raise Rack::Firebase::InvalidAuthTimeError.new("Invalid auth time") unless decoded_token["auth_time"] <= Time.now.to_i - env[USER_UID] = decoded_token["sub"] - @app.call(env) - rescue JWT::JWKError => error # Issues with fetched JWKs - error_responder.call(error, "unauthorized") - rescue JWT::ExpiredSignature => error # Token has expired - error_responder.call(error, "expired") - rescue JWT::InvalidIatError => error # invalid issued at claim (iat) - error_responder.call(error, "unauthorized") - rescue JWT::InvalidIssuerError => error # invalid issuer - error_responder.call(error, "unauthorized") - rescue JWT::InvalidAudError => error # invalid audience - error_responder.call(error, "unauthorized") - rescue JWT::DecodeError => error # General JWT error - error_responder.call(error, "unauthorized") - rescue Rack::Firebase::InvalidSubError => error # subject is empty or missing - error_responder.call(error, "unauthorized") - rescue Rack::Firebase::InvalidAuthTimeError => error # auth time is in the future - error_responder.call(error, "unauthorized") + env[USER_UID] = decoded_token["sub"] + @app.call(env) + rescue JWT::JWKError => error # Issues with fetched JWKs + error_responder.call(error, "unauthorized") + rescue JWT::ExpiredSignature => error # Token has expired + error_responder.call(error, "expired") + rescue JWT::InvalidIatError => error # invalid issued at claim (iat) + error_responder.call(error, "unauthorized") + rescue JWT::InvalidIssuerError => error # invalid issuer + error_responder.call(error, "unauthorized") + rescue JWT::InvalidAudError => error # invalid audience + error_responder.call(error, "unauthorized") + rescue JWT::DecodeError => error # General JWT error + error_responder.call(error, "unauthorized") + rescue Rack::Firebase::InvalidSubError => error # subject is empty or missing + error_responder.call(error, "unauthorized") + rescue Rack::Firebase::InvalidAuthTimeError => error # auth time is in the future + error_responder.call(error, "unauthorized") + end + else + @app.call(env) + end end private diff --git a/spec/rack/firebase/middleware_spec.rb b/spec/rack/firebase/middleware_spec.rb index 0038d75..8a73dbf 100644 --- a/spec/rack/firebase/middleware_spec.rb +++ b/spec/rack/firebase/middleware_spec.rb @@ -287,6 +287,24 @@ expect(last_response).to be_ok expect(last_request.env[described_class::USER_UID]).to eq("123") end + + context "and route is considered public" do + before do + Rack::Firebase.configure do |config| + config.public_routes = ["/"] + end + end + + after do + Rack::Firebase.configuration.reset! + end + + it "returns a 200 success" do + get "/" + + expect(last_response).to be_ok + end + end end end @@ -296,6 +314,12 @@ let(:net_response) { Net::HTTPResponse.new(1.0, "200", "OK") } + before do + Rack::Firebase.configure do |config| + config.project_ids = ["test-project-id"] + end + end + context "when there are no cached public keys" do let(:certs) { File.read("#{CERT_PATH}/certificates.json") } let(:cache_control) { "public, max-age=19302, must-revalidate, no-transform" } diff --git a/spec/rack/firebase_spec.rb b/spec/rack/firebase_spec.rb index 1e7376d..5b2599a 100644 --- a/spec/rack/firebase_spec.rb +++ b/spec/rack/firebase_spec.rb @@ -21,13 +21,16 @@ before do Rack::Firebase.configure do |config| config.project_ids = 1 + config.public_routes = ["/healthcheck"] end end it "resets the configuration" do expect(Rack::Firebase.configuration.project_ids).to eq(1) + expect(Rack::Firebase.configuration.public_routes).to eq(["/healthcheck"]) Rack::Firebase.configuration.reset! expect(Rack::Firebase.configuration.project_ids).to eq([]) + expect(Rack::Firebase.configuration.public_routes).to eq([]) end end end