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:
parent
3bb965913d
commit
712485b326
5 changed files with 74 additions and 1 deletions
|
@ -25,7 +25,9 @@ func Serve(router *mux.Router, store *storage.Storage) {
|
||||||
handler := &handler{store, router}
|
handler := &handler{store, router}
|
||||||
|
|
||||||
sr := router.PathPrefix("/fever").Subrouter()
|
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")
|
sr.HandleFunc("/", handler.serve).Name("feverEndpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/http/request"
|
"miniflux.app/v2/internal/http/request"
|
||||||
|
"miniflux.app/v2/internal/http/response"
|
||||||
"miniflux.app/v2/internal/http/response/json"
|
"miniflux.app/v2/internal/http/response/json"
|
||||||
"miniflux.app/v2/internal/storage"
|
"miniflux.app/v2/internal/storage"
|
||||||
)
|
)
|
||||||
|
@ -21,6 +22,29 @@ func newMiddleware(s *storage.Storage) *middleware {
|
||||||
return &middleware{s}
|
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 {
|
func (m *middleware) serve(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
clientIP := request.ClientIP(r)
|
clientIP := request.ClientIP(r)
|
||||||
|
|
|
@ -49,6 +49,7 @@ func Serve(router *mux.Router, store *storage.Storage) {
|
||||||
sr := router.PathPrefix("/reader/api/0").Subrouter()
|
sr := router.PathPrefix("/reader/api/0").Subrouter()
|
||||||
sr.Use(middleware.handleCORS)
|
sr.Use(middleware.handleCORS)
|
||||||
sr.Use(middleware.apiKeyAuth)
|
sr.Use(middleware.apiKeyAuth)
|
||||||
|
sr.Use(middleware.maybeUnauthorizedGoogleReader)
|
||||||
sr.Methods(http.MethodOptions)
|
sr.Methods(http.MethodOptions)
|
||||||
sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
|
sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
|
||||||
sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")
|
sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")
|
||||||
|
|
|
@ -25,6 +25,22 @@ func newMiddleware(s *storage.Storage) *middleware {
|
||||||
return &middleware{s}
|
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 {
|
func (m *middleware) handleCORS(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
|
@ -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.
|
// GoogleReaderUserCheckPassword validates the Google Reader hashed password.
|
||||||
func (s *Storage) GoogleReaderUserCheckPassword(username, password string) error {
|
func (s *Storage) GoogleReaderUserCheckPassword(username, password string) error {
|
||||||
var hash string
|
var hash string
|
||||||
|
@ -105,6 +120,21 @@ func (s *Storage) GoogleReaderUserGetIntegration(username string) (*model.Integr
|
||||||
return &integration, nil
|
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.
|
// Integration returns user integration settings.
|
||||||
func (s *Storage) Integration(userID int64) (*model.Integration, error) {
|
func (s *Storage) Integration(userID int64) (*model.Integration, error) {
|
||||||
query := `
|
query := `
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue