From 4643989ab695a82e9f3275ee255a8d4c5563e11c Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Tue, 19 May 2026 14:00:57 +0200 Subject: [PATCH] Add proxy auth Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- internal/config/config.go | 1 + internal/config/default_config.yaml | 1 + internal/proxy/proxy.go | 35 ++++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/internal/config/config.go b/internal/config/config.go index c3db56d..749c067 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type Config struct { ProjectDir string `mapstructure:"project_dir"` PluginsDir string `mapstructure:"plugins_dir"` UpstreamProxy string `mapstructure:"upstream_proxy"` + ProxyAuth string `mapstructure:"proxy_auth"` MaxBodySizeMB int `mapstructure:"max_body_size_mb"` ExternalEditor string `mapstructure:"external_editor"` } `mapstructure:"app"` diff --git a/internal/config/default_config.yaml b/internal/config/default_config.yaml index 6c3ae93..7ce75e7 100644 --- a/internal/config/default_config.yaml +++ b/internal/config/default_config.yaml @@ -5,6 +5,7 @@ app: project_dir: ~/.local/share/spilltea plugins_dir: ~/.config/spilltea/plugins upstream_proxy: "" # e.g. http://corporate-proxy:8888 or http://user:pass@host:8888 + proxy_auth: "" # require basic auth to use the proxy, format: user:pass (empty = disabled) max_body_size_mb: 50 # max response body size read into memory for large streamed responses (MB) external_editor: "" # override $EDITOR for external editing (e.g. nvim, code --wait) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 2b83df4..c91741f 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -1,11 +1,13 @@ package proxy import ( + "encoding/base64" "fmt" "io" "log" "net/http" "os" + "strings" tea "charm.land/bubbletea/v2" "github.com/anotherhadi/spilltea/internal/config" @@ -115,7 +117,7 @@ func Start(broker *intercept.Broker, mgr *plugins.Manager) error { opts := &goproxy.Options{ Addr: addr, - StreamLargeBodies: 1024 * 1024 * 5, + StreamLargeBodies: int64(cfg.MaxBodySizeMB) * 1024 * 1024, CaRootPath: caPath, Upstream: cfg.UpstreamProxy, } @@ -125,10 +127,41 @@ func Start(broker *intercept.Broker, mgr *plugins.Manager) error { return err } + if cfg.ProxyAuth != "" { + parts := strings.SplitN(cfg.ProxyAuth, ":", 2) + if len(parts) == 2 { + wantUser, wantPass := parts[0], parts[1] + p.SetAuthProxy(func(res http.ResponseWriter, req *http.Request) (bool, error) { + user, pass, ok := parseBasicProxyAuth(req.Header.Get("Proxy-Authorization")) + if !ok || user != wantUser || pass != wantPass { + res.Header().Set("Proxy-Authenticate", `Basic realm="spilltea"`) + return false, fmt.Errorf("invalid credentials") + } + return true, nil + }) + } + } + p.AddAddon(&interceptAddon{broker: broker, plugins: mgr}) return p.Start() } +func parseBasicProxyAuth(header string) (user, pass string, ok bool) { + const prefix = "Basic " + if !strings.HasPrefix(header, prefix) { + return "", "", false + } + decoded, err := base64.StdEncoding.DecodeString(header[len(prefix):]) + if err != nil { + return "", "", false + } + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 { + return "", "", false + } + return parts[0], parts[1], true +} + func dropResponse() *goproxy.Response { return &goproxy.Response{ StatusCode: 502,