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
change rtwrvuqlooupvtzrtpqvqouyqwnsplkm
commit a5eb19cc5d5f9262cd50795312599473e9093567
author Alpha Chen <alpha@kejadlen.dev>
date
parent okxowxtu
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