1
0
mirror of https://github.com/lus/pasty.git synced 2023-08-10 21:13:09 +03:00

implement paste reports

This commit is contained in:
Lukas Schulte Pelkum 2023-06-17 15:16:22 +02:00
parent b9a6a81821
commit bdac813e59
No known key found for this signature in database
GPG Key ID: AB3985CECFAFC962
4 changed files with 128 additions and 7 deletions

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/config"
"github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/meta"
"github.com/lus/pasty/internal/reports"
"github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/storage"
"github.com/lus/pasty/internal/storage/postgres" "github.com/lus/pasty/internal/storage/postgres"
"github.com/lus/pasty/internal/storage/sqlite" "github.com/lus/pasty/internal/storage/sqlite"
@ -74,10 +75,6 @@ func main() {
// Start the web server // Start the web server
log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...") log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...")
var adminTokens []string
if cfg.ModificationTokenMaster != "" {
adminTokens = []string{cfg.ModificationTokenMaster}
}
webServer := &web.Server{ webServer := &web.Server{
Address: cfg.WebAddress, Address: cfg.WebAddress,
Storage: driver, Storage: driver,
@ -88,7 +85,15 @@ func main() {
ModificationTokensEnabled: cfg.ModificationTokens, ModificationTokensEnabled: cfg.ModificationTokens,
ModificationTokenLength: cfg.ModificationTokenLength, ModificationTokenLength: cfg.ModificationTokenLength,
ModificationTokenCharset: cfg.ModificationTokenCharacters, ModificationTokenCharset: cfg.ModificationTokenCharacters,
AdminTokens: adminTokens, }
if cfg.Reports.Enabled {
webServer.ReportClient = &reports.Client{
WebhookURL: cfg.Reports.WebhookURL,
WebhookToken: cfg.Reports.WebhookToken,
}
}
if cfg.ModificationTokenMaster != "" {
webServer.AdminTokens = []string{cfg.ModificationTokenMaster}
} }
go func() { go func() {
if err := webServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := webServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {

View File

@ -0,0 +1,60 @@
package reports
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Report struct {
Paste string `json:"paste"`
Reason string `json:"reason"`
}
type Response struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type Client struct {
WebhookURL string
WebhookToken string
}
func (client *Client) Send(report *Report) (*Response, error) {
data, err := json.Marshal(report)
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPost, client.WebhookURL, bytes.NewReader(data))
if err != nil {
return nil, err
}
if client.WebhookToken != "" {
request.Header.Set("Authorization", "Bearer "+client.WebhookToken)
}
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
if response.StatusCode < 200 || response.StatusCode > 299 {
return nil, fmt.Errorf("the report webhook responded with an unexpected error: %d (%s)", response.StatusCode, string(body))
}
reportResponse := new(Response)
if err := json.Unmarshal(body, &reportResponse); err != nil {
return nil, err
}
return reportResponse, nil
}

View File

@ -5,6 +5,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/meta"
"github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/reports"
"github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/storage"
"net/http" "net/http"
) )
@ -16,6 +17,10 @@ type Server struct {
// The storage driver to use. // The storage driver to use.
Storage storage.Driver Storage storage.Driver
// The report client to use to send reports.
// If this is set to nil, the report system will be considered deactivated.
ReportClient *reports.Client
// Whether the Hastebin support should be enabled. // Whether the Hastebin support should be enabled.
// If this is set to 'false', the Hastebin specific endpoints will not be registered. // If this is set to 'false', the Hastebin specific endpoints will not be registered.
HastebinSupport bool HastebinSupport bool
@ -63,12 +68,15 @@ func (server *Server) Start() error {
router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) router.Post("/api/v2/pastes", server.v2EndpointCreatePaste)
router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste)
router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste)
if server.ReportClient != nil {
router.With(server.v2MiddlewareInjectPaste).Post("/api/v2/pastes/{paste_id}/report", server.v2EndpointReportPaste)
}
router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) { router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) {
writeJSONOrErr(writer, http.StatusOK, map[string]any{ writeJSONOrErr(writer, http.StatusOK, map[string]any{
"version": meta.Version, "version": meta.Version,
"modificationTokens": server.ModificationTokensEnabled, "modificationTokens": server.ModificationTokensEnabled,
"reports": false, // TODO: Return report state "reports": server.ReportClient != nil,
"pasteLifetime": -1, // TODO: Return paste lifetime "pasteLifetime": -1, // TODO: Return paste lifetime
}) })
}) })

View File

@ -0,0 +1,48 @@
package web
import (
"encoding/json"
"github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/reports"
"io"
"net/http"
)
type v2EndpointReportPastePayload struct {
Reason string `json:"reason"`
}
func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request *http.Request) {
paste, ok := request.Context().Value("paste").(*pastes.Paste)
if !ok {
writeString(writer, http.StatusInternalServerError, "missing paste object")
return
}
// Read, parse and validate the request payload
body, err := io.ReadAll(request.Body)
if err != nil {
writeErr(writer, err)
return
}
payload := new(v2EndpointReportPastePayload)
if err := json.Unmarshal(body, payload); err != nil {
writeErr(writer, err)
return
}
if payload.Reason == "" {
writeString(writer, http.StatusBadRequest, "missing report reason")
return
}
report := &reports.Report{
Paste: paste.ID,
Reason: payload.Reason,
}
response, err := server.ReportClient.Send(report)
if err != nil {
writeErr(writer, err)
return
}
writeJSONOrErr(writer, http.StatusOK, response)
}