middleware.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. next.ServeHTTP(w, r.WithContext(ctx))
  105. })
  106. }
  107. func (m *middleware) getAppSessionValueFromCookie(r *http.Request) *model.Session {
  108. cookieValue := request.CookieValue(r, cookie.CookieAppSessionID)
  109. if cookieValue == "" {
  110. return nil
  111. }
  112. session, err := m.store.AppSession(cookieValue)
  113. if err != nil {
  114. slog.Debug("Unable to fetch app session from the database; another session will be created",
  115. slog.Any("cookie_value", cookieValue),
  116. slog.Any("error", err),
  117. )
  118. return nil
  119. }
  120. return session
  121. }
  122. func (m *middleware) isPublicRoute(r *http.Request) bool {
  123. route := mux.CurrentRoute(r)
  124. switch route.GetName() {
  125. case "login",
  126. "checkLogin",
  127. "stylesheet",
  128. "javascript",
  129. "oauth2Redirect",
  130. "oauth2Callback",
  131. "appIcon",
  132. "favicon",
  133. "webManifest",
  134. "robots",
  135. "sharedEntry",
  136. "healthcheck",
  137. "offline",
  138. "proxy":
  139. return true
  140. default:
  141. return false
  142. }
  143. }
  144. func (m *middleware) getUserSessionFromCookie(r *http.Request) *model.UserSession {
  145. cookieValue := request.CookieValue(r, cookie.CookieUserSessionID)
  146. if cookieValue == "" {
  147. return nil
  148. }
  149. session, err := m.store.UserSessionByToken(cookieValue)
  150. if err != nil {
  151. slog.Error("Unable to fetch user session from the database",
  152. slog.Any("cookie_value", cookieValue),
  153. slog.Any("error", err),
  154. )
  155. return nil
  156. }
  157. return session
  158. }
  159. func (m *middleware) handleAuthProxy(next http.Handler) http.Handler {
  160. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  161. if request.IsAuthenticated(r) || config.Opts.AuthProxyHeader() == "" {
  162. next.ServeHTTP(w, r)
  163. return
  164. }
  165. username := r.Header.Get(config.Opts.AuthProxyHeader())
  166. if username == "" {
  167. next.ServeHTTP(w, r)
  168. return
  169. }
  170. clientIP := request.ClientIP(r)
  171. slog.Debug("[AuthProxy] Received authenticated requested",
  172. slog.String("client_ip", clientIP),
  173. slog.String("user_agent", r.UserAgent()),
  174. slog.String("username", username),
  175. )
  176. user, err := m.store.UserByUsername(username)
  177. if err != nil {
  178. html.ServerError(w, r, err)
  179. return
  180. }
  181. if user == nil {
  182. if !config.Opts.IsAuthProxyUserCreationAllowed() {
  183. slog.Debug("[AuthProxy] User doesn't exist and user creation is not allowed",
  184. slog.Bool("authentication_failed", true),
  185. slog.String("client_ip", clientIP),
  186. slog.String("user_agent", r.UserAgent()),
  187. slog.String("username", username),
  188. )
  189. html.Forbidden(w, r)
  190. return
  191. }
  192. if user, err = m.store.CreateUser(&model.UserCreationRequest{Username: username}); err != nil {
  193. html.ServerError(w, r, err)
  194. return
  195. }
  196. }
  197. sessionToken, _, err := m.store.CreateUserSessionFromUsername(user.Username, r.UserAgent(), clientIP)
  198. if err != nil {
  199. html.ServerError(w, r, err)
  200. return
  201. }
  202. slog.Info("[AuthProxy] User authenticated successfully",
  203. slog.Bool("authentication_successful", true),
  204. slog.String("client_ip", clientIP),
  205. slog.String("user_agent", r.UserAgent()),
  206. slog.Int64("user_id", user.ID),
  207. slog.String("username", user.Username),
  208. )
  209. m.store.SetLastLogin(user.ID)
  210. sess := session.New(m.store, request.SessionID(r))
  211. sess.SetLanguage(user.Language)
  212. sess.SetTheme(user.Theme)
  213. http.SetCookie(w, cookie.New(
  214. cookie.CookieUserSessionID,
  215. sessionToken,
  216. config.Opts.HTTPS,
  217. config.Opts.BasePath(),
  218. ))
  219. html.Redirect(w, r, route.Path(m.router, "unread"))
  220. })
  221. }