middleware.go 5.3 KB

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