middleware.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package ui // import "miniflux.app/v2/internal/ui"
  4. import (
  5. "context"
  6. "errors"
  7. "log/slog"
  8. "net/http"
  9. "miniflux.app/v2/internal/config"
  10. "miniflux.app/v2/internal/crypto"
  11. "miniflux.app/v2/internal/http/cookie"
  12. "miniflux.app/v2/internal/http/request"
  13. "miniflux.app/v2/internal/http/response/html"
  14. "miniflux.app/v2/internal/http/route"
  15. "miniflux.app/v2/internal/model"
  16. "miniflux.app/v2/internal/storage"
  17. "miniflux.app/v2/internal/ui/session"
  18. "github.com/gorilla/mux"
  19. )
  20. type middleware struct {
  21. router *mux.Router
  22. store *storage.Storage
  23. }
  24. func newMiddleware(router *mux.Router, store *storage.Storage) *middleware {
  25. return &middleware{router, store}
  26. }
  27. func (m *middleware) handleUserSession(next http.Handler) http.Handler {
  28. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  29. session := m.getUserSessionFromCookie(r)
  30. if session == nil {
  31. if m.isPublicRoute(r) {
  32. next.ServeHTTP(w, r)
  33. } else {
  34. slog.Debug("Redirecting to login page because no user session has been found",
  35. slog.Any("url", r.RequestURI),
  36. )
  37. html.Redirect(w, r, route.Path(m.router, "login"))
  38. }
  39. } else {
  40. slog.Debug("User session found",
  41. slog.Any("url", r.RequestURI),
  42. slog.Int64("user_id", session.UserID),
  43. slog.Int64("user_session_id", session.ID),
  44. )
  45. ctx := r.Context()
  46. ctx = context.WithValue(ctx, request.UserIDContextKey, session.UserID)
  47. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  48. ctx = context.WithValue(ctx, request.UserSessionTokenContextKey, session.Token)
  49. next.ServeHTTP(w, r.WithContext(ctx))
  50. }
  51. })
  52. }
  53. func (m *middleware) handleAppSession(next http.Handler) http.Handler {
  54. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  55. var err error
  56. session := m.getAppSessionValueFromCookie(r)
  57. if session == nil {
  58. if request.IsAuthenticated(r) {
  59. userID := request.UserID(r)
  60. slog.Debug("Cookie expired but user is logged: creating a new app session",
  61. slog.Int64("user_id", userID),
  62. )
  63. session, err = m.store.CreateAppSessionWithUserPrefs(userID)
  64. if err != nil {
  65. html.ServerError(w, r, err)
  66. return
  67. }
  68. } else {
  69. slog.Debug("App session not found, creating a new one")
  70. session, err = m.store.CreateAppSession()
  71. if err != nil {
  72. html.ServerError(w, r, err)
  73. return
  74. }
  75. }
  76. http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, config.Opts.HTTPS, config.Opts.BasePath()))
  77. }
  78. if r.Method == http.MethodPost {
  79. formValue := r.FormValue("csrf")
  80. headerValue := r.Header.Get("X-Csrf-Token")
  81. if !crypto.ConstantTimeCmp(session.Data.CSRF, formValue) && !crypto.ConstantTimeCmp(session.Data.CSRF, headerValue) {
  82. slog.Warn("Invalid or missing CSRF token",
  83. slog.Any("url", r.RequestURI),
  84. slog.String("form_csrf", formValue),
  85. slog.String("header_csrf", headerValue),
  86. )
  87. if mux.CurrentRoute(r).GetName() == "checkLogin" {
  88. html.Redirect(w, r, route.Path(m.router, "login"))
  89. return
  90. }
  91. html.BadRequest(w, r, errors.New("invalid or missing CSRF"))
  92. return
  93. }
  94. }
  95. ctx := r.Context()
  96. ctx = context.WithValue(ctx, request.SessionIDContextKey, session.ID)
  97. ctx = context.WithValue(ctx, request.CSRFContextKey, session.Data.CSRF)
  98. ctx = context.WithValue(ctx, request.OAuth2StateContextKey, session.Data.OAuth2State)
  99. ctx = context.WithValue(ctx, request.OAuth2CodeVerifierContextKey, session.Data.OAuth2CodeVerifier)
  100. ctx = context.WithValue(ctx, request.FlashMessageContextKey, session.Data.FlashMessage)
  101. ctx = context.WithValue(ctx, request.FlashErrorMessageContextKey, session.Data.FlashErrorMessage)
  102. ctx = context.WithValue(ctx, request.UserLanguageContextKey, session.Data.Language)
  103. ctx = context.WithValue(ctx, request.UserThemeContextKey, session.Data.Theme)
  104. ctx = context.WithValue(ctx, request.PocketRequestTokenContextKey, session.Data.PocketRequestToken)
  105. ctx = context.WithValue(ctx, request.LastForceRefreshContextKey, session.Data.LastForceRefresh)
  106. ctx = context.WithValue(ctx, request.WebAuthnDataContextKey, session.Data.WebAuthnSessionData)
  107. next.ServeHTTP(w, r.WithContext(ctx))
  108. })
  109. }
  110. func (m *middleware) getAppSessionValueFromCookie(r *http.Request) *model.Session {
  111. cookieValue := request.CookieValue(r, cookie.CookieAppSessionID)
  112. if cookieValue == "" {
  113. return nil
  114. }
  115. session, err := m.store.AppSession(cookieValue)
  116. if err != nil {
  117. slog.Debug("Unable to fetch app session from the database; another session will be created",
  118. slog.Any("cookie_value", cookieValue),
  119. slog.Any("error", err),
  120. )
  121. return nil
  122. }
  123. return session
  124. }
  125. func (m *middleware) isPublicRoute(r *http.Request) bool {
  126. route := mux.CurrentRoute(r)
  127. switch route.GetName() {
  128. case "login",
  129. "checkLogin",
  130. "stylesheet",
  131. "javascript",
  132. "oauth2Redirect",
  133. "oauth2Callback",
  134. "appIcon",
  135. "favicon",
  136. "webManifest",
  137. "robots",
  138. "sharedEntry",
  139. "healthcheck",
  140. "offline",
  141. "proxy",
  142. "webauthnLoginBegin",
  143. "webauthnLoginFinish":
  144. return true
  145. default:
  146. return false
  147. }
  148. }
  149. func (m *middleware) getUserSessionFromCookie(r *http.Request) *model.UserSession {
  150. cookieValue := request.CookieValue(r, cookie.CookieUserSessionID)
  151. if cookieValue == "" {
  152. return nil
  153. }
  154. session, err := m.store.UserSessionByToken(cookieValue)
  155. if err != nil {
  156. slog.Error("Unable to fetch user session from the database",
  157. slog.Any("cookie_value", cookieValue),
  158. slog.Any("error", err),
  159. )
  160. return nil
  161. }
  162. return session
  163. }
  164. func (m *middleware) handleAuthProxy(next http.Handler) http.Handler {
  165. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  166. if request.IsAuthenticated(r) || config.Opts.AuthProxyHeader() == "" {
  167. next.ServeHTTP(w, r)
  168. return
  169. }
  170. username := r.Header.Get(config.Opts.AuthProxyHeader())
  171. if username == "" {
  172. next.ServeHTTP(w, r)
  173. return
  174. }
  175. clientIP := request.ClientIP(r)
  176. slog.Debug("[AuthProxy] Received authenticated requested",
  177. slog.String("client_ip", clientIP),
  178. slog.String("user_agent", r.UserAgent()),
  179. slog.String("username", username),
  180. )
  181. user, err := m.store.UserByUsername(username)
  182. if err != nil {
  183. html.ServerError(w, r, err)
  184. return
  185. }
  186. if user == nil {
  187. if !config.Opts.IsAuthProxyUserCreationAllowed() {
  188. slog.Debug("[AuthProxy] User doesn't exist and user creation is not allowed",
  189. slog.Bool("authentication_failed", true),
  190. slog.String("client_ip", clientIP),
  191. slog.String("user_agent", r.UserAgent()),
  192. slog.String("username", username),
  193. )
  194. html.Forbidden(w, r)
  195. return
  196. }
  197. if user, err = m.store.CreateUser(&model.UserCreationRequest{Username: username}); err != nil {
  198. html.ServerError(w, r, err)
  199. return
  200. }
  201. }
  202. sessionToken, _, err := m.store.CreateUserSessionFromUsername(user.Username, r.UserAgent(), clientIP)
  203. if err != nil {
  204. html.ServerError(w, r, err)
  205. return
  206. }
  207. slog.Info("[AuthProxy] User authenticated successfully",
  208. slog.Bool("authentication_successful", true),
  209. slog.String("client_ip", clientIP),
  210. slog.String("user_agent", r.UserAgent()),
  211. slog.Int64("user_id", user.ID),
  212. slog.String("username", user.Username),
  213. )
  214. m.store.SetLastLogin(user.ID)
  215. sess := session.New(m.store, request.SessionID(r))
  216. sess.SetLanguage(user.Language)
  217. sess.SetTheme(user.Theme)
  218. http.SetCookie(w, cookie.New(
  219. cookie.CookieUserSessionID,
  220. sessionToken,
  221. config.Opts.HTTPS,
  222. config.Opts.BasePath(),
  223. ))
  224. html.Redirect(w, r, route.Path(m.router, user.DefaultHomePage))
  225. })
  226. }