Files
owenandClaude Sonnet 5 ba1f7c4c03 Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go,
with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS
framework), and a REST API under /api/v1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
2026-08-17 10:28:59 -05:00

57 lines
1.5 KiB
Go

package certificates
import (
"net/http"
"strings"
"sync"
)
// httpChallengeProvider implements lego's challenge.Provider for HTTP-01,
// backed by an in-memory token map served by our own HTTP listener at
// /.well-known/acme-challenge/. This avoids spinning up a second server on
// port 80 — the main proxy HTTP listener delegates to ServeHTTP below.
type httpChallengeProvider struct {
mu sync.RWMutex
tokens map[string]string // token -> key authorization
}
func newHTTPChallengeProvider() *httpChallengeProvider {
return &httpChallengeProvider{tokens: map[string]string{}}
}
func (p *httpChallengeProvider) Present(domain, token, keyAuth string) error {
p.mu.Lock()
defer p.mu.Unlock()
p.tokens[token] = keyAuth
return nil
}
func (p *httpChallengeProvider) CleanUp(domain, token, keyAuth string) error {
p.mu.Lock()
defer p.mu.Unlock()
delete(p.tokens, token)
return nil
}
const acmeChallengePrefix = "/.well-known/acme-challenge/"
// ServeHTTP handles ACME HTTP-01 challenge requests. Returns true if the
// request was an ACME challenge (handled or not found), false if the
// caller should continue normal routing.
func (p *httpChallengeProvider) ServeHTTP(w http.ResponseWriter, r *http.Request) bool {
if !strings.HasPrefix(r.URL.Path, acmeChallengePrefix) {
return false
}
token := strings.TrimPrefix(r.URL.Path, acmeChallengePrefix)
p.mu.RLock()
keyAuth, ok := p.tokens[token]
p.mu.RUnlock()
if !ok {
http.NotFound(w, r)
return true
}
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(keyAuth))
return true
}