web_session_middleware.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. "log/slog"
  7. "net/http"
  8. "strings"
  9. "miniflux.app/v2/internal/http/request"
  10. "miniflux.app/v2/internal/http/response"
  11. "miniflux.app/v2/internal/model"
  12. "miniflux.app/v2/internal/storage"
  13. )
  14. type webSessionMiddleware struct {
  15. basePath string
  16. store *storage.Storage
  17. }
  18. func newWebSessionMiddleware(basePath string, store *storage.Storage) *webSessionMiddleware {
  19. return &webSessionMiddleware{basePath: basePath, store: store}
  20. }
  21. func (m *webSessionMiddleware) handle(next http.Handler) http.Handler {
  22. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  23. if isStaticAssetRoute(r) {
  24. next.ServeHTTP(w, r)
  25. return
  26. }
  27. session, err := m.loadWebSessionFromCookie(r)
  28. if err != nil {
  29. response.HTMLServerError(w, r, err)
  30. return
  31. }
  32. if session == nil {
  33. // Visitors without a session are sent to the login page and have
  34. // nothing to remember yet, so don't persist a session for them.
  35. if !isPublicRoute(r) {
  36. response.HTMLRedirect(w, r, loginRedirectURL(m.basePath, r.RequestURI))
  37. return
  38. }
  39. var secret string
  40. session, secret = model.NewWebSession(r.UserAgent(), request.ClientIP(r))
  41. if language, theme, ok := preferencesFromCookie(r); ok {
  42. session.SeedPreferences(language, theme)
  43. }
  44. if err := m.store.CreateWebSession(session); err != nil {
  45. response.HTMLServerError(w, r, err)
  46. return
  47. }
  48. setSessionCookie(w, session, secret)
  49. }
  50. ctx := context.WithValue(r.Context(), request.WebSessionContextKey, session)
  51. r = r.WithContext(ctx)
  52. if !request.IsAuthenticated(r) && !isPublicRoute(r) {
  53. response.HTMLRedirect(w, r, loginRedirectURL(m.basePath, r.RequestURI))
  54. return
  55. }
  56. next.ServeHTTP(w, r)
  57. if session.IsDirty() {
  58. if err := m.store.UpdateWebSession(session); err != nil {
  59. slog.Error("Unable to persist web session changes",
  60. slog.String("session_id", session.ID),
  61. slog.Any("error", err),
  62. )
  63. }
  64. }
  65. })
  66. }
  67. func (m *webSessionMiddleware) loadWebSessionFromCookie(r *http.Request) (*model.WebSession, error) {
  68. cookieValue := request.CookieValue(r, sessionCookieName)
  69. if cookieValue == "" {
  70. return nil, nil
  71. }
  72. sessionID, secret, ok := strings.Cut(cookieValue, ".")
  73. if !ok || sessionID == "" || secret == "" {
  74. return nil, nil
  75. }
  76. session, err := m.store.WebSessionByID(sessionID)
  77. if err != nil {
  78. return nil, err
  79. }
  80. if session == nil || !session.VerifySecret(secret) {
  81. return nil, nil
  82. }
  83. return session, nil
  84. }