1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-09-15 18:57:04 +00:00

feat(integrations): automatically disable googlereader/fever when not used #3543

When there is no user of Fever/GoogleReader, there is no need to expose their
endpoints. This reduces quite a bit the exposition surface of miniflux, while
not breaking any existing deployments, and is pretty self-contained.
This commit is contained in:
jvoisin 2025-08-02 22:58:36 +02:00 committed by Julien Voisin
parent 3bb965913d
commit 712485b326
5 changed files with 74 additions and 1 deletions

View file

@ -25,7 +25,9 @@ func Serve(router *mux.Router, store *storage.Storage) {
handler := &handler{store, router}
sr := router.PathPrefix("/fever").Subrouter()
sr.Use(newMiddleware(store).serve)
middleware := newMiddleware(store)
sr.Use(middleware.maybeUnauthorizedFever)
sr.Use(middleware.serve)
sr.HandleFunc("/", handler.serve).Name("feverEndpoint")
}

View file

@ -9,6 +9,7 @@ import (
"net/http"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response"
"miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/storage"
)
@ -21,6 +22,29 @@ func newMiddleware(s *storage.Storage) *middleware {
return &middleware{s}
}
func (m *middleware) maybeUnauthorizedFever(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
feverUsed, err := m.store.IsFeverUsed()
if err != nil {
builder := response.New(w, r)
builder.WithStatus(http.StatusInternalServerError)
builder.WithHeader("Content-Type", "text/plain")
builder.Write()
return
}
if !feverUsed {
builder := response.New(w, r)
builder.WithStatus(http.StatusUnauthorized)
builder.WithHeader("Content-Type", "text/plain")
builder.Write()
return
}
next.ServeHTTP(w, r)
})
}
func (m *middleware) serve(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)

View file

@ -49,6 +49,7 @@ func Serve(router *mux.Router, store *storage.Storage) {
sr := router.PathPrefix("/reader/api/0").Subrouter()
sr.Use(middleware.handleCORS)
sr.Use(middleware.apiKeyAuth)
sr.Use(middleware.maybeUnauthorizedGoogleReader)
sr.Methods(http.MethodOptions)
sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")

View file

@ -25,6 +25,22 @@ func newMiddleware(s *storage.Storage) *middleware {
return &middleware{s}
}
func (m *middleware) maybeUnauthorizedGoogleReader(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
googleReaderUsed, err := m.store.IsGoogleReaderUsed()
if err != nil {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusInternalServerError)
return
}
if googleReaderUsed {
next.ServeHTTP(w, r)
} else {
sendUnauthorizedResponse(w)
}
})
}
func (m *middleware) handleCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")

View file

@ -52,6 +52,21 @@ func (s *Storage) UserByFeverToken(token string) (*model.User, error) {
}
}
func (s *Storage) IsFeverUsed() (bool, error) {
query := `SELECT true FROM integrations WHERE fever_enabled=true LIMIT 1`
result := false
err := s.db.QueryRow(query).Scan(&result)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, fmt.Errorf(`store: unable to check if fever is used: %v`, err)
}
return result, nil
}
// GoogleReaderUserCheckPassword validates the Google Reader hashed password.
func (s *Storage) GoogleReaderUserCheckPassword(username, password string) error {
var hash string
@ -105,6 +120,21 @@ func (s *Storage) GoogleReaderUserGetIntegration(username string) (*model.Integr
return &integration, nil
}
func (s *Storage) IsGoogleReaderUsed() (bool, error) {
query := `SELECT true FROM integrations WHERE googlereader_enabled=true LIMIT 1`
var result bool
err := s.db.QueryRow(query).Scan(&result)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, fmt.Errorf(`store: unable to check if Google Reader is used: %v`, err)
}
return result, nil
}
// Integration returns user integration settings.
func (s *Storage) Integration(userID int64) (*model.Integration, error) {
query := `