middleware.go 5.6 KB

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