refactor: replace Username/Password with AuthHeader/AuthValue in httpcache

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.
This commit is contained in:
AJ ONeal 2026-04-20 09:58:08 -06:00
parent d0a5e0a9d2
commit 3feb248ce1
No known key found for this signature in database
2 changed files with 18 additions and 27 deletions

View File

@ -3,6 +3,7 @@ package geoip
import ( import (
"archive/tar" "archive/tar"
"compress/gzip" "compress/gzip"
"encoding/base64"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -50,14 +51,15 @@ func (d *Downloader) NewCacher(edition, path string) *httpcache.Cacher {
if timeout == 0 { if timeout == 0 {
timeout = defaultTimeout timeout = defaultTimeout
} }
creds := base64.StdEncoding.EncodeToString([]byte(d.AccountID + ":" + d.LicenseKey))
return &httpcache.Cacher{ return &httpcache.Cacher{
URL: fmt.Sprintf("%s/%s/download?suffix=tar.gz", downloadBase, edition), URL: fmt.Sprintf("%s/%s/download?suffix=tar.gz", downloadBase, edition),
Path: path, Path: path,
MaxAge: time.Duration(freshDays) * 24 * time.Hour, MaxAge: time.Duration(freshDays) * 24 * time.Hour,
Timeout: timeout, Timeout: timeout,
Username: d.AccountID, AuthHeader: "Authorization",
Password: d.LicenseKey, AuthValue: "Basic " + creds,
Transform: ExtractMMDB, Transform: ExtractMMDB,
} }
} }

View File

@ -37,9 +37,9 @@ func (NopSyncer) Fetch() (bool, error) { return false, nil }
// - MinInterval: skips if Fetch was called within this duration (in-memory). // - MinInterval: skips if Fetch was called within this duration (in-memory).
// Guards against tight poll loops hammering a rate-limited API. // Guards against tight poll loops hammering a rate-limited API.
// //
// Auth — Username/Password sets HTTP Basic Auth on the initial request only. // Auth — AuthHeader/AuthValue set a request header on every attempt, including
// The Authorization header is stripped before following any redirect, so // redirects. Use any scheme: "Authorization"/"Bearer token",
// presigned redirect targets (e.g. Cloudflare R2) never receive credentials. // "X-API-Key"/"secret", "Authorization"/"Basic base64(user:pass)", etc.
// //
// Transform — if set, called with the response body instead of the default // Transform — if set, called with the response body instead of the default
// atomic file copy. The func is responsible for writing to path atomically. // 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 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 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 MinInterval time.Duration // 0 disables; skip HTTP if last Fetch attempt was within this
Username string // Basic Auth — not forwarded on redirects AuthHeader string // e.g. "Authorization" or "X-API-Key"
Password string AuthValue string // e.g. "Bearer token" or "Basic base64(user:pass)"
Transform func(r io.Reader, path string) error // nil = direct atomic copy Transform func(r io.Reader, path string) error // nil = direct atomic copy
mu sync.Mutex mu sync.Mutex
@ -116,23 +116,12 @@ func (c *Cacher) Fetch() (updated bool, err error) {
TLSHandshakeTimeout: connTimeout, TLSHandshakeTimeout: connTimeout,
} }
var client *http.Client if c.AuthHeader != "" {
if c.Username != "" { req.Header.Set(c.AuthHeader, c.AuthValue)
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}
} }
client := &http.Client{Timeout: timeout, Transport: transport}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return false, err return false, err