| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package googlereader // import "miniflux.app/v2/internal/googlereader"
- import (
- "context"
- "crypto/hmac"
- "crypto/sha1"
- "encoding/hex"
- "log/slog"
- "net/http"
- "strings"
- "miniflux.app/v2/internal/http/request"
- "miniflux.app/v2/internal/model"
- "miniflux.app/v2/internal/storage"
- )
- type middleware struct {
- store *storage.Storage
- }
- func newMiddleware(s *storage.Storage) *middleware {
- return &middleware{s}
- }
- func (m *middleware) apiKeyAuth(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- clientIP := request.ClientIP(r)
- var token string
- if r.Method == http.MethodPost {
- if err := r.ParseForm(); err != nil {
- slog.Warn("[GoogleReader] Could not parse request form data",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- slog.Any("error", err),
- )
- sendUnauthorizedResponse(w)
- return
- }
- token = r.Form.Get("T")
- if token == "" {
- slog.Warn("[GoogleReader] Post-Form T field is empty",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- } else {
- authorization := r.Header.Get("Authorization")
- if authorization == "" {
- slog.Warn("[GoogleReader] No token provided",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- fields := strings.Fields(authorization)
- if len(fields) != 2 {
- slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- if fields[0] != "GoogleLogin" {
- slog.Warn("[GoogleReader] Authorization header does not begin with GoogleLogin",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- auths := strings.Split(fields[1], "=")
- if len(auths) != 2 {
- slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- if auths[0] != "auth" {
- slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- token = auths[1]
- }
- parts := strings.Split(token, "/")
- if len(parts) != 2 {
- slog.Warn("[GoogleReader] Auth token does not have the expected structure username/hash",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- slog.String("token", token),
- )
- sendUnauthorizedResponse(w)
- return
- }
- var integration *model.Integration
- var user *model.User
- var err error
- if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
- slog.Warn("[GoogleReader] No user found with the given Google Reader username",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- slog.Any("error", err),
- )
- sendUnauthorizedResponse(w)
- return
- }
- expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
- if expectedToken != token {
- slog.Warn("[GoogleReader] Token does not match",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- if user, err = m.store.UserByID(integration.UserID); err != nil {
- slog.Error("[GoogleReader] Unable to fetch user from database",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- slog.Any("error", err),
- )
- sendUnauthorizedResponse(w)
- return
- }
- if user == nil {
- slog.Warn("[GoogleReader] No user found with the given Google Reader credentials",
- slog.Bool("authentication_failed", true),
- slog.String("client_ip", clientIP),
- slog.String("user_agent", r.UserAgent()),
- )
- sendUnauthorizedResponse(w)
- return
- }
- m.store.SetLastLogin(integration.UserID)
- ctx := r.Context()
- ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
- ctx = context.WithValue(ctx, request.UserNameContextKey, user.Username)
- ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
- ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
- ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
- ctx = context.WithValue(ctx, request.GoogleReaderTokenKey, token)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- }
- func getAuthToken(username, password string) string {
- token := hex.EncodeToString(hmac.New(sha1.New, []byte(username+password)).Sum(nil))
- token = username + "/" + token
- return token
- }
|