From 3feb248ce1420d6b6307235b6a3a1b4fea39b63f Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Apr 2026 09:58:08 -0600 Subject: [PATCH] refactor: replace Username/Password with AuthHeader/AuthValue in httpcache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic header pair works for any auth scheme — Bearer, X-API-Key, Basic, etc. Auth is forwarded on redirects; the MaxMind-specific stripping is removed. geoip.go encodes Basic auth credentials directly into AuthValue. --- net/geoip/geoip.go | 16 +++++++++------- net/httpcache/httpcache.go | 29 +++++++++-------------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/net/geoip/geoip.go b/net/geoip/geoip.go index 0694d5e..40ab5c5 100644 --- a/net/geoip/geoip.go +++ b/net/geoip/geoip.go @@ -3,6 +3,7 @@ package geoip import ( "archive/tar" "compress/gzip" + "encoding/base64" "fmt" "io" "os" @@ -50,14 +51,15 @@ func (d *Downloader) NewCacher(edition, path string) *httpcache.Cacher { if timeout == 0 { timeout = defaultTimeout } + creds := base64.StdEncoding.EncodeToString([]byte(d.AccountID + ":" + d.LicenseKey)) return &httpcache.Cacher{ - URL: fmt.Sprintf("%s/%s/download?suffix=tar.gz", downloadBase, edition), - Path: path, - MaxAge: time.Duration(freshDays) * 24 * time.Hour, - Timeout: timeout, - Username: d.AccountID, - Password: d.LicenseKey, - Transform: ExtractMMDB, + URL: fmt.Sprintf("%s/%s/download?suffix=tar.gz", downloadBase, edition), + Path: path, + MaxAge: time.Duration(freshDays) * 24 * time.Hour, + Timeout: timeout, + AuthHeader: "Authorization", + AuthValue: "Basic " + creds, + Transform: ExtractMMDB, } } diff --git a/net/httpcache/httpcache.go b/net/httpcache/httpcache.go index 4af559b..467bf0a 100644 --- a/net/httpcache/httpcache.go +++ b/net/httpcache/httpcache.go @@ -37,9 +37,9 @@ func (NopSyncer) Fetch() (bool, error) { return false, nil } // - MinInterval: skips if Fetch was called within this duration (in-memory). // Guards against tight poll loops hammering a rate-limited API. // -// Auth — Username/Password sets HTTP Basic Auth on the initial request only. -// The Authorization header is stripped before following any redirect, so -// presigned redirect targets (e.g. Cloudflare R2) never receive credentials. +// Auth — AuthHeader/AuthValue set a request header on every attempt, including +// redirects. Use any scheme: "Authorization"/"Bearer token", +// "X-API-Key"/"secret", "Authorization"/"Basic base64(user:pass)", etc. // // Transform — if set, called with the response body instead of the default // atomic file copy. The func is responsible for writing to path atomically. @@ -51,8 +51,8 @@ type Cacher struct { Timeout time.Duration // 0 uses 5m; caps overall request including body read MaxAge time.Duration // 0 disables; skip HTTP if file mtime is within this MinInterval time.Duration // 0 disables; skip HTTP if last Fetch attempt was within this - Username string // Basic Auth — not forwarded on redirects - Password string + AuthHeader string // e.g. "Authorization" or "X-API-Key" + AuthValue string // e.g. "Bearer token" or "Basic base64(user:pass)" Transform func(r io.Reader, path string) error // nil = direct atomic copy mu sync.Mutex @@ -116,23 +116,12 @@ func (c *Cacher) Fetch() (updated bool, err error) { TLSHandshakeTimeout: connTimeout, } - var client *http.Client - if c.Username != "" { - req.SetBasicAuth(c.Username, c.Password) - // Strip auth before following any redirect — presigned URLs (e.g. R2) - // must not receive our credentials. - client = &http.Client{ - Timeout: timeout, - Transport: transport, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - req.Header.Del("Authorization") - return nil - }, - } - } else { - client = &http.Client{Timeout: timeout, Transport: transport} + if c.AuthHeader != "" { + req.Header.Set(c.AuthHeader, c.AuthValue) } + client := &http.Client{Timeout: timeout, Transport: transport} + resp, err := client.Do(req) if err != nil { return false, err