Add DevAuth middleware for local development without Tailscale
Auth requires Tailscale-User-Login headers set by the reverse proxy,
so the app returns 403 locally. DevAuth injects default headers from
DEFAULT_USER when none are present. Snapshot capture now uses this
middleware instead of setting browser-level headers.
Assisted-by: Claude Opus 4.6 via Claude Code
diff --git a/config.ru b/config.ru
index 2da94df..2e45a76 100644
--- a/config.ru
+++ b/config.ru
@@ -16,4 +16,9 @@ if CONFIG.sentry
use Sentry::Rack::CaptureExceptions
end
+if CONFIG.default_user
+ require_relative "lib/ketchup/dev_auth"
+ use Ketchup::DevAuth, CONFIG.default_user
+end
+
run Web.freeze.app
diff --git a/lib/ketchup/config.rb b/lib/ketchup/config.rb
index fa8f901..7431c69 100644
--- a/lib/ketchup/config.rb
+++ b/lib/ketchup/config.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
-Config = Data.define(:database_url, :sentry) do
+Config = Data.define(:database_url, :sentry, :default_user) do
SentryConfig = Data.define(:dsn, :env)
def self.from_env(env = ENV)
sentry_dsn = env["SENTRY_DSN"]
new(
database_url: env.fetch("DATABASE_URL") { "db/ketchup.db" },
- sentry: sentry_dsn ? SentryConfig.new(dsn: sentry_dsn, env: env["SENTRY_ENV"]) : nil
+ sentry: sentry_dsn ? SentryConfig.new(dsn: sentry_dsn, env: env["SENTRY_ENV"]) : nil,
+ default_user: env["DEFAULT_USER"]
)
end
end
diff --git a/lib/ketchup/dev_auth.rb b/lib/ketchup/dev_auth.rb
new file mode 100644
index 0000000..684c52a
--- /dev/null
+++ b/lib/ketchup/dev_auth.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Ketchup
+ class DevAuth
+ def initialize(app, default_user)
+ login, name = default_user.split(":", 2)
+ @app = app
+ @login = login
+ @name = name || login
+ end
+
+ def call(env)
+ unless env["HTTP_TAILSCALE_USER_LOGIN"]
+ env["HTTP_TAILSCALE_USER_LOGIN"] = @login
+ env["HTTP_TAILSCALE_USER_NAME"] = @name
+ end
+ @app.call(env)
+ end
+ end
+end
diff --git a/lib/ketchup/snapshots.rb b/lib/ketchup/snapshots.rb
index 5e1cbf6..76d6e66 100644
--- a/lib/ketchup/snapshots.rb
+++ b/lib/ketchup/snapshots.rb
@@ -211,8 +211,15 @@ module Ketchup
end
def default_server(browser)
+ require_relative "dev_auth"
+
+ app = Rack::Builder.app do
+ use Ketchup::DevAuth, "snapshot@example.com:Snapshot User"
+ run Web.freeze.app
+ end
+
config = Puma::Configuration.new do |c|
- c.app Web.freeze.app
+ c.app app
c.bind "tcp://127.0.0.1:0"
c.log_requests false
c.quiet
@@ -231,11 +238,6 @@ module Ketchup
user = User.find_or_create(login: "snapshot@example.com") { |u| u.name = "Snapshot User" }
Ketchup::Seed.call(user: user, series: Ketchup::Seed::DATA)
- browser.headers.set(
- "Tailscale-User-Login" => user.login,
- "Tailscale-User-Name" => user.name
- )
-
yield url
ensure
if launcher