middleware.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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/json"
  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) handleCORS(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. w.WriteHeader(http.StatusOK)
  26. return
  27. }
  28. next.ServeHTTP(w, r)
  29. })
  30. }
  31. func (m *middleware) apiKeyAuth(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. )
  40. next.ServeHTTP(w, r)
  41. return
  42. }
  43. user, err := m.store.UserByAPIKey(token)
  44. if err != nil {
  45. json.ServerError(w, r, err)
  46. return
  47. }
  48. if user == nil {
  49. slog.Warn("[API] No user found with the provided API key",
  50. slog.Bool("authentication_failed", true),
  51. slog.String("client_ip", clientIP),
  52. slog.String("user_agent", r.UserAgent()),
  53. )
  54. json.Unauthorized(w, r)
  55. return
  56. }
  57. slog.Info("[API] User authenticated successfully with the API Token Authentication",
  58. slog.Bool("authentication_successful", true),
  59. slog.String("client_ip", clientIP),
  60. slog.String("user_agent", r.UserAgent()),
  61. slog.String("username", user.Username),
  62. )
  63. m.store.SetLastLogin(user.ID)
  64. m.store.SetAPIKeyUsedTimestamp(user.ID, token)
  65. ctx := r.Context()
  66. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  67. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  68. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  69. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  70. next.ServeHTTP(w, r.WithContext(ctx))
  71. })
  72. }
  73. func (m *middleware) basicAuth(next http.Handler) http.Handler {
  74. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  75. if request.IsAuthenticated(r) {
  76. next.ServeHTTP(w, r)
  77. return
  78. }
  79. w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
  80. clientIP := request.ClientIP(r)
  81. username, password, authOK := r.BasicAuth()
  82. if !authOK {
  83. slog.Warn("[API] No Basic HTTP Authentication header sent with the request",
  84. slog.Bool("authentication_failed", true),
  85. slog.String("client_ip", clientIP),
  86. slog.String("user_agent", r.UserAgent()),
  87. )
  88. json.Unauthorized(w, r)
  89. return
  90. }
  91. if username == "" || password == "" {
  92. slog.Warn("[API] Empty username or password provided during Basic HTTP Authentication",
  93. slog.Bool("authentication_failed", true),
  94. slog.String("client_ip", clientIP),
  95. slog.String("user_agent", r.UserAgent()),
  96. )
  97. json.Unauthorized(w, r)
  98. return
  99. }
  100. if err := m.store.CheckPassword(username, password); err != nil {
  101. slog.Warn("[API] Invalid username or password provided during Basic HTTP Authentication",
  102. slog.Bool("authentication_failed", true),
  103. slog.String("client_ip", clientIP),
  104. slog.String("user_agent", r.UserAgent()),
  105. slog.String("username", username),
  106. )
  107. json.Unauthorized(w, r)
  108. return
  109. }
  110. user, err := m.store.UserByUsername(username)
  111. if err != nil {
  112. json.ServerError(w, r, err)
  113. return
  114. }
  115. if user == nil {
  116. slog.Warn("[API] User not found while using Basic HTTP Authentication",
  117. slog.Bool("authentication_failed", true),
  118. slog.String("client_ip", clientIP),
  119. slog.String("user_agent", r.UserAgent()),
  120. slog.String("username", username),
  121. )
  122. json.Unauthorized(w, r)
  123. return
  124. }
  125. slog.Info("[API] User authenticated successfully with the Basic HTTP Authentication",
  126. slog.Bool("authentication_successful", true),
  127. slog.String("client_ip", clientIP),
  128. slog.String("user_agent", r.UserAgent()),
  129. slog.String("username", username),
  130. )
  131. m.store.SetLastLogin(user.ID)
  132. ctx := r.Context()
  133. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  134. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  135. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  136. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  137. next.ServeHTTP(w, r.WithContext(ctx))
  138. })
  139. }