4
0

middleware.go 7.4 KB

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