1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-26 18:21:01 +00:00

Use Gorilla middleware (refactoring)

This commit is contained in:
Frédéric Guillot 2018-04-27 20:38:46 -07:00
parent 322b265d7a
commit 6b360d08c1
25 changed files with 1254 additions and 331 deletions

72
middleware/app_session.go Normal file
View file

@ -0,0 +1,72 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"context"
"net/http"
"github.com/miniflux/miniflux/http/cookie"
"github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model"
)
// AppSession handles application session middleware.
func (m *Middleware) AppSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
session := m.getSessionValueFromCookie(r)
if session == nil {
logger.Debug("[Middleware:Session] Session not found")
session, err = m.store.CreateSession()
if err != nil {
logger.Error("[Middleware:Session] %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, m.cfg.IsHTTPS, m.cfg.BasePath()))
} else {
logger.Debug("[Middleware:Session] %s", session)
}
if r.Method == "POST" {
formValue := r.FormValue("csrf")
headerValue := r.Header.Get("X-Csrf-Token")
if session.Data.CSRF != formValue && session.Data.CSRF != headerValue {
logger.Error(`[Middleware:Session] Invalid or missing CSRF token: Form="%s", Header="%s"`, formValue, headerValue)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid or missing CSRF session!"))
return
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, SessionIDContextKey, session.ID)
ctx = context.WithValue(ctx, CSRFContextKey, session.Data.CSRF)
ctx = context.WithValue(ctx, OAuth2StateContextKey, session.Data.OAuth2State)
ctx = context.WithValue(ctx, FlashMessageContextKey, session.Data.FlashMessage)
ctx = context.WithValue(ctx, FlashErrorMessageContextKey, session.Data.FlashErrorMessage)
ctx = context.WithValue(ctx, UserLanguageContextKey, session.Data.Language)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (m *Middleware) getSessionValueFromCookie(r *http.Request) *model.Session {
sessionCookie, err := r.Cookie(cookie.CookieSessionID)
if err == http.ErrNoCookie {
return nil
}
session, err := m.store.Session(sessionCookie.Value)
if err != nil {
logger.Error("[Middleware:Session] %v", err)
return nil
}
return session
}

61
middleware/basic_auth.go Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"context"
"net/http"
"github.com/miniflux/miniflux/logger"
)
// BasicAuth handles HTTP basic authentication.
func (m *Middleware) BasicAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
errorResponse := `{"error_message": "Not Authorized"}`
username, password, authOK := r.BasicAuth()
if !authOK {
logger.Debug("[Middleware:BasicAuth] No authentication headers sent")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(errorResponse))
return
}
if err := m.store.CheckPassword(username, password); err != nil {
logger.Info("[Middleware:BasicAuth] Invalid username or password: %s", username)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(errorResponse))
return
}
user, err := m.store.UserByUsername(username)
if err != nil {
logger.Error("[Middleware:BasicAuth] %v", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(errorResponse))
return
}
if user == nil {
logger.Info("[Middleware:BasicAuth] User not found: %s", username)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(errorResponse))
return
}
logger.Info("[Middleware:BasicAuth] User authenticated: %s", username)
m.store.SetLastLogin(user.ID)
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

View file

@ -0,0 +1,49 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
// ContextKey represents a context key.
type ContextKey struct {
name string
}
func (c ContextKey) String() string {
return c.name
}
var (
// UserIDContextKey is the context key used to store the user ID.
UserIDContextKey = &ContextKey{"UserID"}
// UserTimezoneContextKey is the context key used to store the user timezone.
UserTimezoneContextKey = &ContextKey{"UserTimezone"}
// IsAdminUserContextKey is the context key used to store the user role.
IsAdminUserContextKey = &ContextKey{"IsAdminUser"}
// IsAuthenticatedContextKey is the context key used to store the authentication flag.
IsAuthenticatedContextKey = &ContextKey{"IsAuthenticated"}
// UserSessionTokenContextKey is the context key used to store the user session ID.
UserSessionTokenContextKey = &ContextKey{"UserSessionToken"}
// UserLanguageContextKey is the context key to store user language.
UserLanguageContextKey = &ContextKey{"UserLanguageContextKey"}
// SessionIDContextKey is the context key used to store the session ID.
SessionIDContextKey = &ContextKey{"SessionID"}
// CSRFContextKey is the context key used to store CSRF token.
CSRFContextKey = &ContextKey{"CSRF"}
// OAuth2StateContextKey is the context key used to store OAuth2 state.
OAuth2StateContextKey = &ContextKey{"OAuth2State"}
// FlashMessageContextKey is the context key used to store a flash message.
FlashMessageContextKey = &ContextKey{"FlashMessage"}
// FlashErrorMessageContextKey is the context key used to store a flash error message.
FlashErrorMessageContextKey = &ContextKey{"FlashErrorMessage"}
)

46
middleware/fever.go Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"context"
"net/http"
"github.com/miniflux/miniflux/logger"
)
// FeverAuth handles Fever API authentication.
func (m *Middleware) FeverAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Debug("[Middleware:Fever]")
apiKey := r.FormValue("api_key")
user, err := m.store.UserByFeverToken(apiKey)
if err != nil {
logger.Error("[Fever] %v", err)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"api_version": 3, "auth": 0}`))
return
}
if user == nil {
logger.Info("[Middleware:Fever] Fever authentication failure")
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"api_version": 3, "auth": 0}`))
return
}
logger.Info("[Middleware:Fever] User #%d is authenticated", user.ID)
m.store.SetLastLogin(user.ID)
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

20
middleware/logging.go Normal file
View file

@ -0,0 +1,20 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"net/http"
"github.com/miniflux/miniflux/logger"
"github.com/tomasen/realip"
)
// Logging logs the HTTP request.
func (m *Middleware) Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Debug("[HTTP] %s %s %s", realip.RealIP(r), r.Method, r.RequestURI)
next.ServeHTTP(w, r)
})
}

23
middleware/middleware.go Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"github.com/gorilla/mux"
"github.com/miniflux/miniflux/config"
"github.com/miniflux/miniflux/storage"
)
// Middleware handles different middleware handlers.
type Middleware struct {
cfg *config.Config
store *storage.Storage
router *mux.Router
}
// New returns a new middleware.
func New(cfg *config.Config, store *storage.Storage, router *mux.Router) *Middleware {
return &Middleware{cfg, store, router}
}

View file

@ -0,0 +1,74 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package middleware
import (
"context"
"net/http"
"github.com/miniflux/miniflux/http/cookie"
"github.com/miniflux/miniflux/http/route"
"github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model"
"github.com/gorilla/mux"
)
// UserSession handles the user session middleware.
func (m *Middleware) UserSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := m.getSessionFromCookie(r)
if session == nil {
logger.Debug("[Middleware:UserSession] Session not found")
if m.isPublicRoute(r) {
next.ServeHTTP(w, r)
} else {
http.Redirect(w, r, route.Path(m.router, "login"), http.StatusFound)
}
} else {
logger.Debug("[Middleware:UserSession] %s", session)
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, session.UserID)
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
ctx = context.WithValue(ctx, UserSessionTokenContextKey, session.Token)
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func (m *Middleware) isPublicRoute(r *http.Request) bool {
route := mux.CurrentRoute(r)
switch route.GetName() {
case "login",
"checkLogin",
"stylesheet",
"javascript",
"oauth2Redirect",
"oauth2Callback",
"appIcon",
"favicon",
"webManifest":
return true
default:
return false
}
}
func (m *Middleware) getSessionFromCookie(r *http.Request) *model.UserSession {
sessionCookie, err := r.Cookie(cookie.CookieUserSessionID)
if err == http.ErrNoCookie {
return nil
}
session, err := m.store.UserSessionByToken(sessionCookie.Value)
if err != nil {
logger.Error("[Middleware:UserSession] %v", err)
return nil
}
return session
}