middleware.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 {
  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. slog.String("token", token),
  110. )
  111. sendUnauthorizedResponse(w, r)
  112. return
  113. }
  114. var integration *model.Integration
  115. var user *model.User
  116. var err error
  117. if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
  118. slog.Warn("[GoogleReader] No user found with the given Google Reader username",
  119. slog.Bool("authentication_failed", true),
  120. slog.String("client_ip", clientIP),
  121. slog.String("user_agent", r.UserAgent()),
  122. slog.Any("error", err),
  123. )
  124. sendUnauthorizedResponse(w, r)
  125. return
  126. }
  127. expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
  128. if !crypto.ConstantTimeCmp(expectedToken, token) {
  129. slog.Warn("[GoogleReader] Token does not match",
  130. slog.Bool("authentication_failed", true),
  131. slog.String("client_ip", clientIP),
  132. slog.String("user_agent", r.UserAgent()),
  133. )
  134. sendUnauthorizedResponse(w, r)
  135. return
  136. }
  137. if user, err = m.store.UserByID(integration.UserID); err != nil {
  138. slog.Error("[GoogleReader] Unable to fetch user from database",
  139. slog.Bool("authentication_failed", true),
  140. slog.String("client_ip", clientIP),
  141. slog.String("user_agent", r.UserAgent()),
  142. slog.Any("error", err),
  143. )
  144. sendUnauthorizedResponse(w, r)
  145. return
  146. }
  147. if user == nil {
  148. slog.Warn("[GoogleReader] No user found with the given Google Reader credentials",
  149. slog.Bool("authentication_failed", true),
  150. slog.String("client_ip", clientIP),
  151. slog.String("user_agent", r.UserAgent()),
  152. )
  153. sendUnauthorizedResponse(w, r)
  154. return
  155. }
  156. m.store.SetLastLogin(integration.UserID)
  157. ctx := r.Context()
  158. ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
  159. ctx = context.WithValue(ctx, request.UserNameContextKey, user.Username)
  160. ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
  161. ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
  162. ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
  163. ctx = context.WithValue(ctx, request.GoogleReaderTokenKey, token)
  164. next.ServeHTTP(w, r.WithContext(ctx))
  165. }
  166. func getAuthToken(username, password string) string {
  167. token := hex.EncodeToString(hmac.New(sha256.New, []byte(username+password)).Sum(nil))
  168. token = username + "/" + token
  169. return token
  170. }