middleware.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package googlereader // import "miniflux.app/v2/internal/googlereader"
  4. import (
  5. "context"
  6. "crypto/hmac"
  7. "crypto/sha256"
  8. "encoding/hex"
  9. "log/slog"
  10. "net/http"
  11. "strings"
  12. "miniflux.app/v2/internal/crypto"
  13. "miniflux.app/v2/internal/http/request"
  14. "miniflux.app/v2/internal/model"
  15. "miniflux.app/v2/internal/storage"
  16. )
  17. type authMiddleware struct {
  18. store *storage.Storage
  19. }
  20. func newAuthMiddleware(s *storage.Storage) *authMiddleware {
  21. return &authMiddleware{s}
  22. }
  23. func (m *authMiddleware) validateApiKey(next http.Handler) http.Handler {
  24. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  25. clientIP := request.ClientIP(r)
  26. var token string
  27. if r.Method == http.MethodPost {
  28. if err := r.ParseForm(); err != nil {
  29. slog.Warn("[GoogleReader] Could not parse request form data",
  30. slog.Bool("authentication_failed", true),
  31. slog.String("client_ip", clientIP),
  32. slog.String("user_agent", r.UserAgent()),
  33. slog.Any("error", err),
  34. )
  35. sendUnauthorizedResponse(w, r)
  36. return
  37. }
  38. token = r.Form.Get("T")
  39. if token == "" {
  40. slog.Warn("[GoogleReader] Post-Form T field is empty",
  41. slog.Bool("authentication_failed", true),
  42. slog.String("client_ip", clientIP),
  43. slog.String("user_agent", r.UserAgent()),
  44. )
  45. sendUnauthorizedResponse(w, r)
  46. return
  47. }
  48. } else {
  49. authorization := r.Header.Get("Authorization")
  50. if authorization == "" {
  51. slog.Warn("[GoogleReader] No token provided",
  52. slog.Bool("authentication_failed", true),
  53. slog.String("client_ip", clientIP),
  54. slog.String("user_agent", r.UserAgent()),
  55. )
  56. sendUnauthorizedResponse(w, r)
  57. return
  58. }
  59. fields := strings.Fields(authorization)
  60. if len(fields) != 2 {
  61. slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
  62. slog.Bool("authentication_failed", true),
  63. slog.String("client_ip", clientIP),
  64. slog.String("user_agent", r.UserAgent()),
  65. )
  66. sendUnauthorizedResponse(w, r)
  67. return
  68. }
  69. if fields[0] != "GoogleLogin" {
  70. slog.Warn("[GoogleReader] Authorization header does not begin with GoogleLogin",
  71. slog.Bool("authentication_failed", true),
  72. slog.String("client_ip", clientIP),
  73. slog.String("user_agent", r.UserAgent()),
  74. )
  75. sendUnauthorizedResponse(w, r)
  76. return
  77. }
  78. auths := strings.Split(fields[1], "=")
  79. if len(auths) != 2 {
  80. slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
  81. slog.Bool("authentication_failed", true),
  82. slog.String("client_ip", clientIP),
  83. slog.String("user_agent", r.UserAgent()),
  84. )
  85. sendUnauthorizedResponse(w, r)
  86. return
  87. }
  88. if auths[0] != "auth" {
  89. slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
  90. slog.Bool("authentication_failed", true),
  91. slog.String("client_ip", clientIP),
  92. slog.String("user_agent", r.UserAgent()),
  93. )
  94. sendUnauthorizedResponse(w, r)
  95. return
  96. }
  97. token = auths[1]
  98. }
  99. parts := strings.Split(token, "/")
  100. if len(parts) != 2 {
  101. slog.Warn("[GoogleReader] Auth token does not have the expected structure username/hash",
  102. slog.Bool("authentication_failed", true),
  103. slog.String("client_ip", clientIP),
  104. slog.String("user_agent", r.UserAgent()),
  105. slog.String("token", token),
  106. )
  107. sendUnauthorizedResponse(w, r)
  108. return
  109. }
  110. var integration *model.Integration
  111. var user *model.User
  112. var err error
  113. if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
  114. slog.Warn("[GoogleReader] No user found with the given Google Reader username",
  115. slog.Bool("authentication_failed", true),
  116. slog.String("client_ip", clientIP),
  117. slog.String("user_agent", r.UserAgent()),
  118. slog.Any("error", err),
  119. )
  120. sendUnauthorizedResponse(w, r)
  121. return
  122. }
  123. expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
  124. if !crypto.ConstantTimeCmp(expectedToken, token) {
  125. slog.Warn("[GoogleReader] Token does not match",
  126. slog.Bool("authentication_failed", true),
  127. slog.String("client_ip", clientIP),
  128. slog.String("user_agent", r.UserAgent()),
  129. )
  130. sendUnauthorizedResponse(w, r)
  131. return
  132. }
  133. if user, err = m.store.UserByID(integration.UserID); err != nil {
  134. slog.Error("[GoogleReader] Unable to fetch user from database",
  135. slog.Bool("authentication_failed", true),
  136. slog.String("client_ip", clientIP),
  137. slog.String("user_agent", r.UserAgent()),
  138. slog.Any("error", err),
  139. )
  140. sendUnauthorizedResponse(w, r)
  141. return
  142. }
  143. if user == nil {
  144. slog.Warn("[GoogleReader] No user found with the given Google Reader credentials",
  145. slog.Bool("authentication_failed", true),
  146. slog.String("client_ip", clientIP),
  147. slog.String("user_agent", r.UserAgent()),
  148. )
  149. sendUnauthorizedResponse(w, r)
  150. return
  151. }
  152. m.store.SetLastLogin(integration.UserID)
  153. ctx := r.Context()
  154. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  155. ctx = context.WithValue(ctx, request.UserNameContextKey, user.Username)
  156. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  157. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  158. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  159. ctx = context.WithValue(ctx, request.GoogleReaderTokenKey, token)
  160. next.ServeHTTP(w, r.WithContext(ctx))
  161. })
  162. }
  163. func getAuthToken(username, password string) string {
  164. token := hex.EncodeToString(hmac.New(sha256.New, []byte(username+password)).Sum(nil))
  165. token = username + "/" + token
  166. return token
  167. }