From 7e143291368b8b762241d5bc180f8b13a4c1ac61 Mon Sep 17 00:00:00 2001 From: Carl Mercier Date: Fri, 4 Sep 2026 12:58:04 -0500 Subject: [PATCH] fix(config): read HTTP Basic credentials from Rails credentials Rails returns a nested credentials key as an ActiveSupport::OrderedOptions, whose method_missing answers respond_to?(:call) with true and returns nil for `call`. normalize invoked it and resolved the whole source to nil, so auth configured via credentials left the dashboard reporting itself unconfigured. Only invoke genuine callables (Proc/Method), which keeps the documented lambda support for rotating secrets working. Fixes #7 Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 6 ++++++ lib/flightdeck/configuration.rb | 6 +++++- test/configuration_test.rb | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 973fd22..d0b9646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed + +- HTTP Basic credentials set in Rails credentials are now read correctly. Rails + returns them as an `ActiveSupport::OrderedOptions`, which Flightdeck mistook + for a callable and resolved to nothing, leaving the dashboard unconfigured. + ## [1.1.0] - 2026-08-12 ### Added diff --git a/lib/flightdeck/configuration.rb b/lib/flightdeck/configuration.rb index 152237b..030eb3b 100644 --- a/lib/flightdeck/configuration.rb +++ b/lib/flightdeck/configuration.rb @@ -73,7 +73,11 @@ def from_credentials def normalize(source) return nil if source.nil? - source = source.call if source.respond_to?(:call) + # Only genuine callables are invoked. `respond_to?(:call)` is not a safe + # test here: Rails credentials hand back an ActiveSupport::OrderedOptions, + # whose method_missing answers true for every name and returns nil for + # `call` — which used to swallow credential-configured auth entirely. + source = source.call if source.is_a?(Proc) || source.is_a?(Method) return nil if source.nil? username = fetch(source, :username) diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 9552b61..0f9fea6 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -92,6 +92,27 @@ class Flightdeck::ConfigurationTest < ActiveSupport::TestCase end end + test "resolve_http_basic reads credentials returned as OrderedOptions" do + # This is what Rails actually hands back for a nested credentials key, and + # it claims to respond_to?(:call) for every name. + credentials = ActiveSupport::OrderedOptions.new + credentials.username = "cred" + credentials.password = "secret" + + Rails.application.credentials.stub(:flightdeck, credentials) do + assert_equal({ username: "cred", password: "secret" }, @config.resolve_http_basic) + end + end + + test "resolve_http_basic reads explicit http_basic given as OrderedOptions" do + @config.http_basic = ActiveSupport::OrderedOptions.new.tap do |options| + options.username = "a" + options.password = "b" + end + + assert_equal({ username: "a", password: "b" }, @config.resolve_http_basic) + end + test "base_controller_class defaults to ActionController::Base and constantizes when set" do # Load the controller while nothing is configured, so this test cannot # leave a half-defined constant behind for the rest of the suite.