middleware.go 5.5 KB

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