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:
parent
322b265d7a
commit
6b360d08c1
25 changed files with 1254 additions and 331 deletions
72
middleware/app_session.go
Normal file
72
middleware/app_session.go
Normal 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
61
middleware/basic_auth.go
Normal 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))
|
||||
})
|
||||
}
|
49
middleware/context_keys.go
Normal file
49
middleware/context_keys.go
Normal 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
46
middleware/fever.go
Normal 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
20
middleware/logging.go
Normal 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
23
middleware/middleware.go
Normal 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}
|
||||
}
|
74
middleware/user_session.go
Normal file
74
middleware/user_session.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue