diff --git a/mothership/internal/auth/handler.go b/mothership/internal/auth/handler.go
index 4ec9a14..084e79b 100644
--- a/mothership/internal/auth/handler.go
+++ b/mothership/internal/auth/handler.go
@@ -704,8 +704,32 @@ func (h *Handler) IsPINConfigured() bool {
return err == nil && pinBcrypt.Valid
}
-// Middleware returns chi-compatible middleware that enforces auth on API and
-// WebSocket routes. Static files pass through so the login page can render.
+// isStaticAsset returns true for CSS, JS, and image files needed by the login page.
+func isStaticAsset(path string) bool {
+ return strings.HasPrefix(path, "/js/") ||
+ strings.HasPrefix(path, "/css/") ||
+ strings.HasPrefix(path, "/images/") ||
+ strings.HasPrefix(path, "/favicon")
+}
+
+// loginPage is a minimal HTML page containing only the auth overlay.
+// Deleting the overlay reveals a blank page, not the dashboard.
+const loginPage = `
+
+
+
+
+Spaxel
+
+
+
+
+
+
+`
+
+// Middleware returns chi-compatible middleware that enforces auth on all routes.
+// Static assets (JS/CSS) pass through so the login page can render.
// During onboarding (no PIN configured), all requests pass through.
func (h *Handler) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -716,8 +740,8 @@ func (h *Handler) Middleware(next http.Handler) http.Handler {
return
}
- // Static files and HTML pages pass through so the login UI renders
- if !strings.HasPrefix(path, "/api/") && !strings.HasPrefix(path, "/ws/") {
+ // Static assets always pass through (needed by login page)
+ if isStaticAsset(path) {
next.ServeHTTP(w, r)
return
}
@@ -728,13 +752,21 @@ func (h *Handler) Middleware(next http.Handler) http.Handler {
return
}
- if !h.IsAuthenticated(r) {
+ if h.IsAuthenticated(r) {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // Unauthenticated: API/WS get 401, page requests get a login-only page
+ if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/ws/") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "authentication required"})
return
}
- next.ServeHTTP(w, r)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, loginPage)
})
}