middleware.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package api // import "miniflux.app/v2/internal/api"
  4. import (
  5. "context"
  6. "log/slog"
  7. "net/http"
  8. "miniflux.app/v2/internal/config"
  9. "miniflux.app/v2/internal/http/request"
  10. "miniflux.app/v2/internal/http/response"
  11. "miniflux.app/v2/internal/storage"
  12. )
  13. type middleware struct {
  14. store *storage.Storage
  15. }
  16. func newMiddleware(s *storage.Storage) *middleware {
  17. return &middleware{s}
  18. }
  19. func (m *middleware) withCORSHeaders(next http.Handler) http.Handler {
  20. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  21. w.Header().Set("Access-Control-Allow-Origin", "*")
  22. w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
  23. w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, Authorization, Content-Type, Accept")
  24. if r.Method == http.MethodOptions {
  25. w.Header().Set("Access-Control-Max-Age", "3600")
  26. response.NoContent(w, r)
  27. return
  28. }
  29. next.ServeHTTP(w, r)
  30. })
  31. }
  32. func (m *middleware) validateAPIKeyAuth(next http.Handler) http.Handler {
  33. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  34. clientIP := request.ClientIP(r)
  35. token := r.Header.Get("X-Auth-Token")
  36. if token == "" {
  37. slog.Debug("[API] Skipped API token authentication because no API Key has been provided",
  38. slog.String("client_ip", clientIP),
  39. slog.String("user_agent", r.UserAgent()),
  40. slog.String("request_uri", r.RequestURI),
  41. )
  42. next.ServeHTTP(w, r)
  43. return
  44. }
  45. user, err := m.store.UserByAPIKey(token)
  46. if err != nil {
  47. response.JSONServerError(w, r, err)
  48. return
  49. }
  50. if user == nil {
  51. slog.Warn("[API] No user found with the provided API key",
  52. slog.Bool("authentication_failed", true),
  53. slog.String("client_ip", clientIP),
  54. slog.String("user_agent", r.UserAgent()),
  55. slog.String("request_uri", r.RequestURI),
  56. )
  57. response.JSONUnauthorized(w, r)
  58. return
  59. }
  60. slog.Info("[API] User authenticated successfully with the API Token Authentication",
  61. slog.Bool("authentication_successful", true),
  62. slog.String("client_ip", clientIP),
  63. slog.String("user_agent", r.UserAgent()),
  64. slog.String("username", user.Username),
  65. slog.String("request_uri", r.RequestURI),
  66. )
  67. m.store.SetLastLogin(user.ID)
  68. m.store.SetAPIKeyUsedTimestamp(user.ID, token)
  69. ctx := r.Context()
  70. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  71. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  72. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  73. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  74. next.ServeHTTP(w, r.WithContext(ctx))
  75. })
  76. }
  77. func (m *middleware) validateBasicAuth(next http.Handler) http.Handler {
  78. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  79. if request.IsAuthenticated(r) {
  80. next.ServeHTTP(w, r)
  81. return
  82. }
  83. w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
  84. clientIP := request.ClientIP(r)
  85. username, password, authOK := r.BasicAuth()
  86. if !authOK {
  87. slog.Warn("[API] No Basic HTTP Authentication header sent with the request",
  88. slog.Bool("authentication_failed", true),
  89. slog.String("client_ip", clientIP),
  90. slog.String("user_agent", r.UserAgent()),
  91. slog.String("request_uri", r.RequestURI),
  92. )
  93. response.JSONUnauthorized(w, r)
  94. return
  95. }
  96. if config.Opts.DisableLocalAuth() {
  97. slog.Warn("[API] Blocking Basic HTTP Authentication attempt, local auth is disabled",
  98. slog.Bool("authentication_failed", true),
  99. slog.String("client_ip", clientIP),
  100. slog.String("user_agent", r.UserAgent()),
  101. slog.String("request_uri", r.RequestURI),
  102. )
  103. response.JSONUnauthorized(w, r)
  104. return
  105. }
  106. if username == "" || password == "" {
  107. slog.Warn("[API] Empty username or password provided during Basic HTTP Authentication",
  108. slog.Bool("authentication_failed", true),
  109. slog.String("client_ip", clientIP),
  110. slog.String("user_agent", r.UserAgent()),
  111. slog.String("request_uri", r.RequestURI),
  112. )
  113. response.JSONUnauthorized(w, r)
  114. return
  115. }
  116. if err := m.store.CheckPassword(username, password); err != nil {
  117. slog.Warn("[API] Invalid username or password provided during Basic HTTP Authentication",
  118. slog.Bool("authentication_failed", true),
  119. slog.String("client_ip", clientIP),
  120. slog.String("user_agent", r.UserAgent()),
  121. slog.String("username", username),
  122. slog.String("request_uri", r.RequestURI),
  123. )
  124. response.JSONUnauthorized(w, r)
  125. return
  126. }
  127. user, err := m.store.UserByUsername(username)
  128. if err != nil {
  129. response.JSONServerError(w, r, err)
  130. return
  131. }
  132. if user == nil {
  133. slog.Warn("[API] User not found while using Basic HTTP Authentication",
  134. slog.Bool("authentication_failed", true),
  135. slog.String("client_ip", clientIP),
  136. slog.String("user_agent", r.UserAgent()),
  137. slog.String("username", username),
  138. slog.String("request_uri", r.RequestURI),
  139. )
  140. response.JSONUnauthorized(w, r)
  141. return
  142. }
  143. slog.Info("[API] User authenticated successfully with the Basic HTTP Authentication",
  144. slog.Bool("authentication_successful", true),
  145. slog.String("client_ip", clientIP),
  146. slog.String("user_agent", r.UserAgent()),
  147. slog.String("username", username),
  148. slog.String("request_uri", r.RequestURI),
  149. )
  150. m.store.SetLastLogin(user.ID)
  151. ctx := r.Context()
  152. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  153. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  154. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  155. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  156. next.ServeHTTP(w, r.WithContext(ctx))
  157. })
  158. }