// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package ui // import "miniflux.app/v2/internal/ui"

import (
	"errors"
	"log/slog"
	"net/http"

	"miniflux.app/v2/internal/crypto"
	"miniflux.app/v2/internal/http/request"
	"miniflux.app/v2/internal/http/response"
)

type csrfMiddleware struct {
	basePath string
}

func newCSRFMiddleware(basePath string) *csrfMiddleware {
	return &csrfMiddleware{basePath: basePath}
}

// handle validates the CSRF token on state-changing requests. It must be
// chained inside handleWebSession so that the session is already present
// in the request context.
func (m *csrfMiddleware) handle(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet, http.MethodHead, http.MethodOptions:
			// Safe methods don't require CSRF validation.
		default:
			if !m.validate(w, r) {
				return
			}
		}
		next.ServeHTTP(w, r)
	})
}

func (m *csrfMiddleware) validate(w http.ResponseWriter, r *http.Request) bool {
	csrfToken := request.WebSession(r).CSRF()
	formValue := r.FormValue("csrf")
	headerValue := r.Header.Get("X-Csrf-Token")

	if crypto.ConstantTimeCmp(csrfToken, formValue) || crypto.ConstantTimeCmp(csrfToken, headerValue) {
		return true
	}

	slog.Warn("Invalid or missing CSRF token",
		slog.String("url", r.RequestURI),
	)

	if r.URL.Path == "/login" {
		response.HTMLRedirect(w, r, m.basePath+"/")
	} else {
		response.HTMLBadRequest(w, r, errors.New("invalid or missing CSRF"))
	}

	return false
}
