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

reimplement v2 API controller

This commit is contained in:
Lukas Schulte Pelkum 2023-06-07 19:51:54 +02:00
parent a8077a54f9
commit 941da057ae
No known key found for this signature in database
GPG Key ID: AB3985CECFAFC962
17 changed files with 433 additions and 14 deletions

View File

@ -45,7 +45,7 @@ func main() {
var driver storage.Driver
switch strings.TrimSpace(strings.ToLower(cfg.StorageDriver)) {
case "postgres":
driver = new(postgres.Driver)
driver = postgres.New(cfg.Postgres.DSN)
break
default:
log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.")
@ -54,7 +54,7 @@ func main() {
// Initialize the configured storage driver
log.Info().Str("driver_name", cfg.StorageDriver).Msg("Initializing the storage driver...")
if err := driver.Initialize(context.Background(), cfg); err != nil {
if err := driver.Initialize(context.Background()); err != nil {
log.Fatal().Err(err).Str("driver_name", cfg.StorageDriver).Msg("The storage driver could not be initialized.")
return
}

2
go.mod
View File

@ -3,6 +3,8 @@ module github.com/lus/pasty
go 1.20
require (
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
github.com/go-chi/chi/v5 v5.0.8
github.com/golang-migrate/migrate/v4 v4.16.1
github.com/jackc/pgx/v5 v5.3.1
github.com/joho/godotenv v1.5.1

38
go.sum
View File

@ -1,5 +1,7 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY5mJ0fVKbk31NVhlgi0yrKm51Pq/I5wcz4=
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -8,6 +10,8 @@ github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m3
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0+bkSGW9zCo0=
@ -54,23 +58,57 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -0,0 +1,19 @@
package pastes
import (
"context"
"github.com/lus/pasty/internal/randx"
)
func GenerateID(ctx context.Context, repo Repository, charset string, length int) (string, error) {
for {
id := randx.String(charset, length)
existing, err := repo.FindByID(ctx, id)
if err != nil {
return "", err
}
if existing == nil {
return id, nil
}
}
}

View File

@ -1,9 +1,31 @@
package pastes
import "github.com/alexedwards/argon2id"
type Paste struct {
ID string `json:"id"`
Content string `json:"content"`
ModificationToken string `json:"modificationToken,omitempty"`
Created int64 `json:"created"`
Metadata map[string]interface{} `json:"metadata"`
Metadata map[string]any `json:"metadata"`
}
func (paste *Paste) HashModificationToken() error {
if paste.ModificationToken == "" {
return nil
}
hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams)
if err != nil {
return err
}
paste.ModificationToken = hash
return nil
}
func (paste *Paste) CheckModificationToken(modificationToken string) bool {
if paste.ModificationToken == "" {
return false
}
match, err := argon2id.ComparePasswordAndHash(modificationToken, paste.ModificationToken)
return err == nil && match
}

View File

@ -0,0 +1,10 @@
package slices
func Contains[T comparable](src []T, val T) bool {
for _, elem := range src {
if elem == val {
return true
}
}
return false
}

View File

@ -2,12 +2,11 @@ package storage
import (
"context"
"github.com/lus/pasty/internal/config"
"github.com/lus/pasty/internal/pastes"
)
type Driver interface {
Initialize(ctx context.Context, cfg *config.Config) error
Initialize(ctx context.Context) error
Close() error
Pastes() pastes.Repository
}

View File

@ -8,7 +8,6 @@ import (
_ "github.com/golang-migrate/migrate/v4/database/postgres"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/lus/pasty/internal/config"
"github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/storage"
"github.com/rs/zerolog/log"
@ -18,14 +17,21 @@ import (
var migrations embed.FS
type Driver struct {
dsn string
connPool *pgxpool.Pool
pastes *pasteRepository
}
var _ storage.Driver = (*Driver)(nil)
func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error {
pool, err := pgxpool.New(ctx, cfg.Postgres.DSN)
func New(dsn string) *Driver {
return &Driver{
dsn: dsn,
}
}
func (driver *Driver) Initialize(ctx context.Context) error {
pool, err := pgxpool.New(ctx, driver.dsn)
if err != nil {
return err
}
@ -36,7 +42,7 @@ func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error
pool.Close()
return err
}
migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN)
migrator, err := migrate.NewWithSourceInstance("iofs", source, driver.dsn)
if err != nil {
pool.Close()
return err

View File

@ -0,0 +1,33 @@
package web
import (
"encoding/json"
"net/http"
)
func writeErr(writer http.ResponseWriter, err error) {
writeString(writer, http.StatusInternalServerError, err.Error())
}
func writeString(writer http.ResponseWriter, status int, value string) {
writer.WriteHeader(status)
writer.Write([]byte(value))
}
func writeJSON(writer http.ResponseWriter, status int, value any) error {
jsonData, err := json.Marshal(value)
if err != nil {
return err
}
writer.WriteHeader(status)
writer.Write(jsonData)
return nil
}
func writeJSONOrErr(writer http.ResponseWriter, status int, value any) {
if err := writeJSON(writer, status, value); err != nil {
writeErr(writer, err)
}
}

49
internal/web/server.go Normal file
View File

@ -0,0 +1,49 @@
package web
import (
"github.com/go-chi/chi/v5"
"github.com/lus/pasty/internal/storage"
"net/http"
)
type Server struct {
// The address the web server should listen to.
Address string
// The storage driver to use.
Storage storage.Driver
// Whether the Hastebin support should be enabled.
// If this is set to 'false', the Hastebin specific endpoints will not be registered.
HastebinSupport bool
// The length of newly generated paste IDs.
PasteIDLength int
// The charset to use when generating new paste IDs.
PasteIDCharset string
// The maximum length of newly generated pastes.
PasteLengthCap int
// Whether modification tokens are enabled.
ModificationTokensEnabled bool
// The length of newly generated modification tokens.
ModificationTokenLength int
// The charset to use when generating new modification tokens.
ModificationTokenCharset string
// The administration tokens.
AdminTokens []string
}
func (server *Server) Start() error {
router := chi.NewRouter()
// Register the paste API endpoints
router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste)
router.Post("/api/v2/pastes", server.v2EndpointCreatePaste)
router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste)
router.Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste)
return http.ListenAndServe(server.Address, router)
}

View File

@ -0,0 +1,70 @@
package web
import (
"encoding/json"
"github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/randx"
"io"
"net/http"
"time"
)
type v2EndpointCreatePastePayload struct {
Content string `json:"content"`
Metadata map[string]any `json:"metadata"`
}
func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request *http.Request) {
// Read, parse and validate the request payload
body, err := io.ReadAll(request.Body)
if err != nil {
writeErr(writer, err)
return
}
payload := new(v2EndpointCreatePastePayload)
if err := json.Unmarshal(body, payload); err != nil {
writeErr(writer, err)
return
}
if payload.Content == "" {
writeString(writer, http.StatusBadRequest, "missing paste content")
return
}
if server.PasteLengthCap > 0 && len(payload.Content) > server.PasteLengthCap {
writeString(writer, http.StatusBadRequest, "too large paste content")
return
}
id, err := pastes.GenerateID(request.Context(), server.Storage.Pastes(), server.PasteIDCharset, server.PasteIDLength)
if err != nil {
writeErr(writer, err)
return
}
paste := &pastes.Paste{
ID: id,
Content: payload.Content,
Created: time.Now().Unix(),
Metadata: payload.Metadata,
}
modificationToken := ""
if server.ModificationTokensEnabled {
modificationToken = randx.String(server.ModificationTokenCharset, server.ModificationTokenLength)
paste.ModificationToken = modificationToken
if err := paste.HashModificationToken(); err != nil {
writeErr(writer, err)
return
}
}
if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil {
writeErr(writer, err)
return
}
cpy := *paste
cpy.ModificationToken = modificationToken
writeJSONOrErr(writer, http.StatusCreated, cpy)
}

View File

@ -0,0 +1,18 @@
package web
import (
"github.com/lus/pasty/internal/pastes"
"net/http"
)
func (server *Server) v2EndpointDeletePaste(writer http.ResponseWriter, request *http.Request) {
paste, ok := request.Context().Value("paste").(*pastes.Paste)
if !ok {
writeString(writer, http.StatusInternalServerError, "missing paste object")
return
}
if err := server.Storage.Pastes().DeleteByID(request.Context(), paste.ID); err != nil {
writeErr(writer, err)
}
}

View File

@ -0,0 +1,18 @@
package web
import (
"github.com/lus/pasty/internal/pastes"
"net/http"
)
func (server *Server) v2EndpointGetPaste(writer http.ResponseWriter, request *http.Request) {
paste, ok := request.Context().Value("paste").(*pastes.Paste)
if !ok {
writeString(writer, http.StatusInternalServerError, "missing paste object")
return
}
cpy := *paste
cpy.ModificationToken = ""
writeJSONOrErr(writer, http.StatusOK, cpy)
}

View File

@ -0,0 +1,60 @@
package web
import (
"encoding/json"
"github.com/lus/pasty/internal/pastes"
"io"
"net/http"
)
type v2EndpointModifyPastePayload struct {
Content *string `json:"content"`
Metadata map[string]any `json:"metadata"`
}
func (server *Server) v2EndpointModifyPaste(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(v2EndpointModifyPastePayload)
if err := json.Unmarshal(body, payload); err != nil {
writeErr(writer, err)
return
}
if payload.Content != nil && *payload.Content == "" {
writeString(writer, http.StatusBadRequest, "missing paste content")
return
}
if payload.Content != nil && server.PasteLengthCap > 0 && len(*payload.Content) > server.PasteLengthCap {
writeString(writer, http.StatusBadRequest, "too large paste content")
return
}
// Modify the paste itself
if payload.Content != nil {
paste.Content = *payload.Content
}
if payload.Metadata != nil {
for key, value := range payload.Metadata {
if value == nil {
delete(paste.Metadata, key)
continue
}
paste.Metadata[key] = value
}
}
// Save the modified paste
if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil {
writeErr(writer, err)
}
}

View File

@ -0,0 +1,37 @@
package web
import (
"github.com/lus/pasty/internal/pastes"
"github.com/lus/pasty/internal/slices"
"net/http"
"strings"
)
func (server *Server) v2MiddlewareAuthorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
paste, ok := request.Context().Value("paste").(*pastes.Paste)
if !ok {
writeString(writer, http.StatusInternalServerError, "missing paste object")
return
}
authHeader := strings.SplitN(request.Header.Get("Authorization"), " ", 2)
if len(authHeader) != 2 || authHeader[0] != "Bearer" {
writeString(writer, http.StatusUnauthorized, "unauthorized")
return
}
isAdmin := slices.Contains(server.AdminTokens, authHeader[1])
if isAdmin {
next.ServeHTTP(writer, request)
return
}
if !server.ModificationTokensEnabled || !paste.CheckModificationToken(authHeader[1]) {
writeString(writer, http.StatusUnauthorized, "unauthorized")
return
}
next.ServeHTTP(writer, request)
})
}

View File

@ -0,0 +1,38 @@
package web
import (
"context"
"github.com/go-chi/chi/v5"
"net/http"
"strings"
)
func (server *Server) v2MiddlewareInjectPaste(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
pasteID := strings.TrimSpace(chi.URLParam(request, "paste_id"))
if pasteID == "" {
writeString(writer, http.StatusNotFound, "paste not found")
return
}
paste, err := server.Storage.Pastes().FindByID(request.Context(), pasteID)
if err != nil {
if pasteID == "" {
writeErr(writer, err)
return
}
}
if paste == nil {
writeString(writer, http.StatusNotFound, "paste not found")
return
}
if paste.Metadata == nil {
paste.Metadata = make(map[string]any)
}
request = request.WithContext(context.WithValue(request.Context(), "paste", paste))
next.ServeHTTP(writer, request)
})
}

View File

@ -1,4 +1,4 @@
const API_BASE_URL = location.protocol + "//" + location.host + "/api/v2";
const API_BASE_URL = location.protocol + "//" + location.host + "/web/v2";
export async function getAPIInformation() {
return fetch(API_BASE_URL + "/info");