refactor: viewer always listens, access blocked at request level when disabled

This commit is contained in:
Claus Lohmar 2026-08-06 19:31:10 +01:00
parent f1a6243dac
commit 3b2bcd7696
4 changed files with 22 additions and 28 deletions

17
auth.go
View file

@ -224,11 +224,23 @@ func (s *Server) authRequired(next http.Handler) http.Handler {
})
}
// viewerOnly restricts access to live wall only (port 8090).
// viewerOnly restricts access when viewer port is disabled, or to live wall only.
func (s *Server) viewerOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if viewer port is enabled in config.
s.mu.RLock()
enabled := s.appConfig.Server.ViewerPort != "" && s.appConfig.Server.ViewerPort != ":0"
s.mu.RUnlock()
if !enabled {
msg := `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>NextNVR</title><style>body{font-family:-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#0d1117;color:#c9d1d9;margin:0;text-align:center}h2{font-size:24px}p{color:#8b949e;margin-top:8px}a{color:#58a6ff;text-decoration:none;font-size:14px;margin-top:16px;display:inline-block;padding:10px 24px;background:#21262d;border-radius:6px}</style></head><body><div><h2>🔒 Local View Disabled</h2><p>The owner has not enabled local network access.</p><a href="http://192.168.1.11:8080">Go to admin login →</a></div></body></html>`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(msg))
return
}
path := r.URL.Path
// Allow live wall assets and recordings.
if path == "/" || path == "/index.html" ||
strings.HasPrefix(path, "/app.js") ||
strings.HasPrefix(path, "/style.css") ||
@ -241,7 +253,6 @@ func (s *Server) viewerOnly(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
// Block everything else (settings, playback API, config).
jsonResponse(w, http.StatusForbidden, APIResponse{Error: "viewer access only"})
})
}

View file

@ -85,7 +85,7 @@ func DefaultConfig() Config {
Server: ServerConfig{
Port: ":8080",
BindHost: "0.0.0.0",
ViewerPort: "",
ViewerPort: ":8090",
},
Storage: StorageConfig{
RecordingsPath: "/mnt/recordings",

View file

@ -2,7 +2,7 @@
"server": {
"port": ":8080",
"bind_host": "0.0.0.0",
"viewer_port": ""
"viewer_port": ":8090"
},
"storage": {
"recordings_path": "/mnt/recordings",

21
main.go
View file

@ -90,18 +90,6 @@ func main() {
app.server.mu.RUnlock()
// Handle viewer server toggle via viewer_port.
viewerOn := liveCfg.Server.ViewerPort != "" && liveCfg.Server.ViewerPort != ":0"
if viewerOn && app.viewerSrv == nil {
go func() {
v, err := app.server.StartViewerServer()
if err != nil { log.Printf("Viewer server: %v", err) }
app.viewerSrv = v
}()
} else if !viewerOn && app.viewerSrv != nil {
log.Println("Stopping viewer server...")
app.viewerSrv.Close()
app.viewerSrv = nil
}
if liveCfg.Go2RTC.Enabled {
app.go2rtc = NewGo2RTCManager(liveCfg.Go2RTC)
app.go2rtc.Start(liveCfg.Cameras)
@ -120,17 +108,12 @@ func main() {
}()
// Start viewer server only if enabled.
if cfg.Server.ViewerPort != "" && cfg.Server.ViewerPort != ":0" {
// Start viewer server always — access controlled by config at request level.
go func() {
v, err := app.server.StartViewerServer()
if err != nil {
log.Printf("Viewer server: %v", err)
}
if err != nil { log.Printf("Viewer server: %v", err) }
app.viewerSrv = v
}()
} else {
log.Println("Viewer server: disabled (viewer_port empty)")
}
select {
case sig := <-sigCh: