mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-13 06:56:06 +00:00
Harden public URL detection and setup token handling
This commit is contained in:
parent
17d55b8cf4
commit
d6cbfc23ec
2 changed files with 82 additions and 13 deletions
|
|
@ -185,8 +185,7 @@ func (h *ConfigHandlers) ValidateSetupToken(token string) bool {
|
|||
defer h.codeMutex.RUnlock()
|
||||
|
||||
if code, exists := h.setupCodes[tokenHash]; exists {
|
||||
// Allow tokens while they are valid or within a short grace period after use.
|
||||
if now.Before(code.ExpiresAt.Add(2 * time.Minute)) {
|
||||
if !code.Used && now.Before(code.ExpiresAt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -5555,8 +5554,8 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
|||
}
|
||||
|
||||
log.Debug().
|
||||
Str("authToken", req.AuthToken).
|
||||
Str("authCode", authCode).
|
||||
Bool("hasAuthToken", strings.TrimSpace(req.AuthToken) != "").
|
||||
Bool("hasSetupCode", strings.TrimSpace(authCode) != "").
|
||||
Bool("hasConfigToken", h.config.HasAPITokens()).
|
||||
Msg("Checking authentication for auto-register")
|
||||
|
||||
|
|
@ -5578,7 +5577,7 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
|||
// Not the API token, check if it's a temporary setup code
|
||||
codeHash := internalauth.HashAPIToken(authCode)
|
||||
log.Debug().
|
||||
Str("authCode", authCode).
|
||||
Bool("hasAuthCode", true).
|
||||
Str("codeHash", codeHash[:8]+"...").
|
||||
Msg("Checking auth token as setup code")
|
||||
h.codeMutex.Lock()
|
||||
|
|
@ -5593,10 +5592,9 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
|||
// what's entered in the UI and what's provided in the setup script URL
|
||||
if setupCode.NodeType == req.Type {
|
||||
setupCode.Used = true // Mark as used immediately
|
||||
// Allow the token to be reused for a brief grace period so the setup
|
||||
// script can complete follow-up actions (temperature verification, etc).
|
||||
graceExpiry := time.Now().Add(5 * time.Minute)
|
||||
if setupCode.ExpiresAt.After(graceExpiry) {
|
||||
// Allow a short grace period for follow-up actions without keeping tokens alive too long
|
||||
graceExpiry := time.Now().Add(1 * time.Minute)
|
||||
if setupCode.ExpiresAt.Before(graceExpiry) {
|
||||
graceExpiry = setupCode.ExpiresAt
|
||||
}
|
||||
h.recentSetupTokens[codeHash] = graceExpiry
|
||||
|
|
|
|||
|
|
@ -1524,11 +1524,21 @@ func (r *Router) capturePublicURLFromRequest(req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !canCapturePublicURL(r.config, req) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.config.EnvOverrides != nil && r.config.EnvOverrides["publicURL"] {
|
||||
return
|
||||
}
|
||||
|
||||
rawHost := firstForwardedValue(req.Header.Get("X-Forwarded-Host"))
|
||||
peerIP := extractRemoteIP(req.RemoteAddr)
|
||||
trustedProxy := isTrustedProxyIP(peerIP)
|
||||
|
||||
rawHost := ""
|
||||
if trustedProxy {
|
||||
rawHost = firstForwardedValue(req.Header.Get("X-Forwarded-Host"))
|
||||
}
|
||||
if rawHost == "" {
|
||||
rawHost = req.Host
|
||||
}
|
||||
|
|
@ -1540,9 +1550,12 @@ func (r *Router) capturePublicURLFromRequest(req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
rawProto := firstForwardedValue(req.Header.Get("X-Forwarded-Proto"))
|
||||
if rawProto == "" {
|
||||
rawProto = firstForwardedValue(req.Header.Get("X-Forwarded-Scheme"))
|
||||
rawProto := ""
|
||||
if trustedProxy {
|
||||
rawProto = firstForwardedValue(req.Header.Get("X-Forwarded-Proto"))
|
||||
if rawProto == "" {
|
||||
rawProto = firstForwardedValue(req.Header.Get("X-Forwarded-Scheme"))
|
||||
}
|
||||
}
|
||||
scheme := strings.ToLower(strings.TrimSpace(rawProto))
|
||||
switch scheme {
|
||||
|
|
@ -1668,6 +1681,64 @@ func shouldAppendForwardedPort(port, scheme string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func canCapturePublicURL(cfg *config.Config, req *http.Request) bool {
|
||||
if cfg == nil || req == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if isDirectLoopbackRequest(req) {
|
||||
return true
|
||||
}
|
||||
|
||||
return isRequestAuthenticated(cfg, req)
|
||||
}
|
||||
|
||||
func isRequestAuthenticated(cfg *config.Config, req *http.Request) bool {
|
||||
if cfg == nil || req == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if cfg.ProxyAuthSecret != "" {
|
||||
if ok, _, _ := CheckProxyAuth(cfg, req); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.HasAPITokens() {
|
||||
if token := strings.TrimSpace(req.Header.Get("X-API-Token")); token != "" {
|
||||
if _, ok := cfg.ValidateAPIToken(token); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if authHeader := strings.TrimSpace(req.Header.Get("Authorization")); strings.HasPrefix(strings.ToLower(authHeader), "bearer ") {
|
||||
if _, ok := cfg.ValidateAPIToken(strings.TrimSpace(authHeader[7:])); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cookie, err := req.Cookie("pulse_session"); err == nil && cookie.Value != "" {
|
||||
if ValidateSession(cookie.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.AuthUser != "" && cfg.AuthPass != "" {
|
||||
const prefix = "Basic "
|
||||
if authHeader := req.Header.Get("Authorization"); strings.HasPrefix(authHeader, prefix) {
|
||||
if decoded, err := base64.StdEncoding.DecodeString(authHeader[len(prefix):]); err == nil {
|
||||
if parts := strings.SplitN(string(decoded), ":", 2); len(parts) == 2 {
|
||||
if parts[0] == cfg.AuthUser && auth.CheckPasswordHash(parts[1], cfg.AuthPass) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// handleHealth handles health check requests
|
||||
func (r *Router) handleHealth(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet && req.Method != http.MethodHead {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue