Skip to content

Commit 746956a

Browse files
committed
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.
1 parent 50197c2 commit 746956a

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

internal/fever/handler.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ func Serve(router *mux.Router, store *storage.Storage) {
2525
handler := &handler{store, router}
2626

2727
sr := router.PathPrefix("/fever").Subrouter()
28-
sr.Use(newMiddleware(store).serve)
28+
middleware := newMiddleware(store)
29+
sr.Use(middleware.maybeUnauthorizedFever)
30+
sr.Use(middleware.serve)
2931
sr.HandleFunc("/", handler.serve).Name("feverEndpoint")
3032
}
3133

internal/fever/middleware.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010

1111
"miniflux.app/v2/internal/http/request"
12+
"miniflux.app/v2/internal/http/response"
1213
"miniflux.app/v2/internal/http/response/json"
1314
"miniflux.app/v2/internal/storage"
1415
)
@@ -21,6 +22,29 @@ func newMiddleware(s *storage.Storage) *middleware {
2122
return &middleware{s}
2223
}
2324

25+
func (m *middleware) maybeUnauthorizedFever(next http.Handler) http.Handler {
26+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27+
feverUsed, err := m.store.IsFeverUsed()
28+
if err != nil {
29+
builder := response.New(w, r)
30+
builder.WithStatus(http.StatusInternalServerError)
31+
builder.WithHeader("Content-Type", "text/plain")
32+
builder.Write()
33+
return
34+
}
35+
36+
if !feverUsed {
37+
builder := response.New(w, r)
38+
builder.WithStatus(http.StatusUnauthorized)
39+
builder.WithHeader("Content-Type", "text/plain")
40+
builder.Write()
41+
return
42+
}
43+
44+
next.ServeHTTP(w, r)
45+
})
46+
}
47+
2448
func (m *middleware) serve(next http.Handler) http.Handler {
2549
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2650
clientIP := request.ClientIP(r)

internal/googlereader/handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func Serve(router *mux.Router, store *storage.Storage) {
4949
sr := router.PathPrefix("/reader/api/0").Subrouter()
5050
sr.Use(middleware.handleCORS)
5151
sr.Use(middleware.apiKeyAuth)
52+
sr.Use(middleware.maybeUnauthorizedGoogleReader)
5253
sr.Methods(http.MethodOptions)
5354
sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
5455
sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")

internal/googlereader/middleware.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ func newMiddleware(s *storage.Storage) *middleware {
2525
return &middleware{s}
2626
}
2727

28+
func (m *middleware) maybeUnauthorizedGoogleReader(next http.Handler) http.Handler {
29+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
googleReaderUsed, err := m.store.IsGoogleReaderUsed()
31+
if err != nil {
32+
w.Header().Set("Content-Type", "text/plain")
33+
w.WriteHeader(http.StatusInternalServerError)
34+
return
35+
}
36+
if googleReaderUsed {
37+
next.ServeHTTP(w, r)
38+
} else {
39+
sendUnauthorizedResponse(w)
40+
}
41+
})
42+
}
43+
2844
func (m *middleware) handleCORS(next http.Handler) http.Handler {
2945
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3046
w.Header().Set("Access-Control-Allow-Origin", "*")

internal/storage/integration.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ func (s *Storage) UserByFeverToken(token string) (*model.User, error) {
5252
}
5353
}
5454

55+
func (s *Storage) IsFeverUsed() (bool, error) {
56+
query := `SELECT true FROM integrations WHERE fever_enabled=true LIMIT 1`
57+
result := false
58+
err := s.db.QueryRow(query).Scan(&result)
59+
60+
if err != nil {
61+
if err == sql.ErrNoRows {
62+
return false, nil
63+
}
64+
return false, fmt.Errorf(`store: unable to check if fever is used: %v`, err)
65+
}
66+
67+
return result, nil
68+
}
69+
5570
// GoogleReaderUserCheckPassword validates the Google Reader hashed password.
5671
func (s *Storage) GoogleReaderUserCheckPassword(username, password string) error {
5772
var hash string
@@ -105,6 +120,21 @@ func (s *Storage) GoogleReaderUserGetIntegration(username string) (*model.Integr
105120
return &integration, nil
106121
}
107122

123+
func (s *Storage) IsGoogleReaderUsed() (bool, error) {
124+
query := `SELECT true FROM integrations WHERE googlereader_enabled=true LIMIT 1`
125+
var result bool
126+
err := s.db.QueryRow(query).Scan(&result)
127+
128+
if err != nil {
129+
if err == sql.ErrNoRows {
130+
return false, nil
131+
}
132+
return false, fmt.Errorf(`store: unable to check if Google Reader is used: %v`, err)
133+
}
134+
135+
return result, nil
136+
}
137+
108138
// Integration returns user integration settings.
109139
func (s *Storage) Integration(userID int64) (*model.Integration, error) {
110140
query := `

0 commit comments

Comments
 (0)