middleware.go 7.2 KB

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