mirror of
https://github.com/lukaszraczylo/gohoarder.git
synced 2026-10-12 00:45:16 +00:00
chore(deps): migrate fiber from v2 to v3
The automated update changed only go.mod, so the build failed on the old v2 imports. This commit moves the code to the v3 API. The commit replaces BodyParser with Bind().Body, Static with the static middleware, and ListenTLS with ListenConfig. It also updates the tests for TestConfig. Fixes #179
This commit is contained in:
@@ -11,7 +11,6 @@ require (
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.7
|
||||
github.com/go-sql-driver/mysql v1.10.1
|
||||
github.com/goccy/go-json v0.11.2
|
||||
github.com/gofiber/fiber/v2 v2.52.15
|
||||
github.com/gofiber/fiber/v3 v3.5.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/lib/pq v1.12.3
|
||||
@@ -52,10 +51,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 // indirect
|
||||
github.com/aws/smithy-go v1.28.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v7 v7.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v7 v7.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
@@ -73,6 +70,8 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/gofiber/schema v1.8.3 // indirect
|
||||
github.com/gofiber/utils/v2 v2.4.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
@@ -93,7 +92,6 @@ require (
|
||||
github.com/magiconair/properties v1.18.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.30 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.52 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.3.3 // indirect
|
||||
@@ -109,6 +107,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0 // indirect
|
||||
github.com/prometheus/client_model v0.6.3 // indirect
|
||||
github.com/prometheus/common v0.72.0 // indirect
|
||||
@@ -121,6 +120,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
||||
github.com/tklauser/numcpus v0.12.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
|
||||
@@ -48,11 +48,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v7 v7.0.1/go.mod h1:qcKBGwsu4hpxHtQ8tWYsQ+ifzx2+sS+Xx/3jfe30lI8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudsoda/go-smb2 v0.0.0-20260918041005-0c5d69b69701 h1:xiTow1n7oNJPzn9QH+U2sT65mO2F12zNNlY4ZzOew70=
|
||||
github.com/cloudsoda/go-smb2 v0.0.0-20260918041005-0c5d69b69701/go.mod h1:1pQXB0vAlzRlqcY7LYKOOZMw0wKfJPFxTLsJRF2Gswo=
|
||||
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc h1:0xCWmFKBmarCqqqLeM7jFBSw/Or81UEElFqO8MY+GDs=
|
||||
@@ -86,6 +83,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64=
|
||||
github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.7 h1:PdT4jVPbRb4R+0Ey2R0yJOdctVf4Whiq1Qi4necaZdg=
|
||||
@@ -104,9 +103,12 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.11.2 h1:jdZv93Tt4ioR8yW1CoNsvSxrcZlCXAUU1aZXN7gpXUA=
|
||||
github.com/goccy/go-json v0.11.2/go.mod h1:3NdmfEkZlB7YI5UFw/qdFKq8XN1aiWR0YyRPWZNQltY=
|
||||
github.com/gofiber/fiber/v2 v2.52.15 h1:Cov1uKeVPyu9q0jSrN60W+A8XNX+/WK8J7cy5osHLIk=
|
||||
github.com/gofiber/fiber/v2 v2.52.15/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/fiber/v3 v3.5.0 h1:dk7TOUH6DXJGtOLsN2XEG+0ZML7cznzHILTVozbNEK8=
|
||||
github.com/gofiber/fiber/v3 v3.5.0/go.mod h1:GOVDTW+gjJvfe0iJyVujbQ1Lnx+JUjFySJRI/9/xX/w=
|
||||
github.com/gofiber/schema v1.8.3 h1:06ZedxIYjngzc0095PYy7uWnFnbRflWFpikvZH61fDc=
|
||||
github.com/gofiber/schema v1.8.3/go.mod h1:jWnnZdhcW1mHyV+VnfRxKJDPNcepJsTZ9RIWxrr32Ng=
|
||||
github.com/gofiber/utils/v2 v2.4.1 h1:E2X9G8O5Mn7b2GDb0JU3IUk42Rw2npuhhepIbuJQ2po=
|
||||
github.com/gofiber/utils/v2 v2.4.1/go.mod h1:I+RTsgMUdzFuifVc3LOEkfh32wQW9BfRl7l5RYjamW4=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -165,8 +167,6 @@ github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk=
|
||||
github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||
github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U=
|
||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
@@ -203,6 +203,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0 h1:XA01Vk/wv9YikCi1V51yRzIHPMT5of9+cMpZoZvDn/M=
|
||||
github.com/power-devops/perfstat v0.0.0-20260916203055-22a1a467d9f0/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
@@ -221,6 +223,8 @@ github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/shamaton/msgpack/v3 v3.2.0 h1:1q2Ms+MWmuRju+PuDMSFDB7p7621npeX4zprJN5Zck8=
|
||||
github.com/shamaton/msgpack/v3 v3.2.0/go.mod h1:sgBYvEiyz8JR1NC3yGRoPVME9xXovpnh3l/plW1nfRo=
|
||||
github.com/shirou/gopsutil/v4 v4.26.9 h1:CaBo/hBFqvlJoLvAQEKdHdf5GD97/MN2ACfyv63+2Ig=
|
||||
github.com/shirou/gopsutil/v4 v4.26.9/go.mod h1:nKH+8wX2zxr/mDbsR+AAy6Qd2z4ZOQ7wfQaEp+yrYmg=
|
||||
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||
@@ -257,6 +261,8 @@ github.com/testcontainers/testcontainers-go/modules/mysql v0.44.0 h1:oJPJPxNE6YQ
|
||||
github.com/testcontainers/testcontainers-go/modules/mysql v0.44.0/go.mod h1:MSOAU6ukCpehJVHQDN1k9JgOZXZuqHD+2pT20M3JkIg=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0 h1:8fdv/9y3JMxjQ+ULAcOG8RtgeNu5t9XF9LolSXDuTwM=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0/go.mod h1:CFr2LncGYokw+OKjXcr8ARCKG1SaC2UEnGxFBovE86g=
|
||||
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
||||
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
||||
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
||||
@@ -265,6 +271,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.74.0 h1:wMS9fnO2QTALozYx5pId2Vi7ZwU/epUkY8i/KPWCHoU=
|
||||
github.com/valyala/fasthttp v1.74.0/go.mod h1:3ARmLamUcw7ElxVtC8PXaGzQ6VEuvnetlkrwIklQBSE=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
|
||||
+7
-5
@@ -15,8 +15,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/adaptor"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/adaptor"
|
||||
"github.com/gofiber/fiber/v3/middleware/static"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/analytics"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/cache"
|
||||
@@ -554,10 +555,10 @@ func (a *App) setupServer() error {
|
||||
frontendDir := "frontend/dist"
|
||||
if _, err := os.Stat(frontendDir); err == nil {
|
||||
log.Info().Str("dir", frontendDir).Msg("Serving frontend static files")
|
||||
a.app.Static("/", frontendDir)
|
||||
a.app.Use("/", static.New(frontendDir))
|
||||
} else {
|
||||
log.Warn().Msg("Frontend dist directory not found, frontend won't be served")
|
||||
a.app.Get("/", func(c *fiber.Ctx) error {
|
||||
a.app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.Type("html").SendString(`
|
||||
<html>
|
||||
<head><title>GoHoarder</title></head>
|
||||
@@ -607,7 +608,7 @@ func (a *App) Run() error {
|
||||
Str("addr", addr).
|
||||
Str("cert_file", a.config.Server.TLS.CertFile).
|
||||
Msg("Starting Fiber server with TLS")
|
||||
if err := a.app.ListenTLS(addr, a.config.Server.TLS.CertFile, a.config.Server.TLS.KeyFile); err != nil {
|
||||
if err := a.app.Listen(addr, fiber.ListenConfig{CertFile: a.config.Server.TLS.CertFile, CertKeyFile: a.config.Server.TLS.KeyFile}); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
return
|
||||
@@ -643,6 +644,7 @@ func (a *App) Run() error {
|
||||
// Graceful shutdown
|
||||
return a.Shutdown()
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the application
|
||||
func (a *App) Shutdown() error {
|
||||
log.Info().Msg("Starting graceful shutdown")
|
||||
|
||||
+8
-8
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/internal/version"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/metadata"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/websocket"
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// handlePackages handles /api/packages endpoint
|
||||
func (a *App) handlePackages(c *fiber.Ctx) error {
|
||||
func (a *App) handlePackages(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, DELETE, OPTIONS")
|
||||
@@ -38,7 +38,7 @@ func (a *App) handlePackages(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleListPackages returns list of cached packages
|
||||
func (a *App) handleListPackages(c *fiber.Ctx) error {
|
||||
func (a *App) handleListPackages(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Get packages from metadata store
|
||||
@@ -201,7 +201,7 @@ func (a *App) handleListPackages(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleDeletePackage deletes a cached package
|
||||
func (a *App) handleDeletePackage(c *fiber.Ctx) error {
|
||||
func (a *App) handleDeletePackage(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Parse path: /api/packages/{registry}/{name}/{version}
|
||||
@@ -331,7 +331,7 @@ func (a *App) handleDeletePackage(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleStats handles /api/stats endpoint
|
||||
func (a *App) handleStats(c *fiber.Ctx) error {
|
||||
func (a *App) handleStats(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
@@ -421,7 +421,7 @@ func (a *App) handleStats(c *fiber.Ctx) error {
|
||||
|
||||
// handleTimeSeriesStats handles /api/stats/timeseries endpoint
|
||||
// Returns time-series download statistics for charts
|
||||
func (a *App) handleTimeSeriesStats(c *fiber.Ctx) error {
|
||||
func (a *App) handleTimeSeriesStats(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
@@ -463,7 +463,7 @@ func (a *App) handleTimeSeriesStats(c *fiber.Ctx) error {
|
||||
|
||||
// handleConfig handles /api/config endpoint
|
||||
// Returns runtime configuration for the frontend
|
||||
func (a *App) handleConfig(c *fiber.Ctx) error {
|
||||
func (a *App) handleConfig(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
@@ -497,7 +497,7 @@ func (a *App) handleConfig(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleInfo handles /api/info endpoint
|
||||
func (a *App) handleInfo(c *fiber.Ctx) error {
|
||||
func (a *App) handleInfo(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
|
||||
+10
-10
@@ -3,7 +3,7 @@ package app
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/errors"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/metadata"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// requireAdmin middleware checks for admin authentication. It accepts the API
|
||||
// key via either the Authorization Bearer header or the X-API-Key header
|
||||
// (shared extractAPIKey), so admin clients are not limited to the bearer form.
|
||||
func (a *App) requireAdmin(c *fiber.Ctx) error {
|
||||
func (a *App) requireAdmin(c fiber.Ctx) error {
|
||||
// Get API key from Authorization or X-API-Key header
|
||||
apiKey, ok := extractAPIKey(c)
|
||||
if !ok {
|
||||
@@ -43,7 +43,7 @@ func (a *App) requireAdmin(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAdminBypasses handles /api/admin/bypasses endpoint
|
||||
func (a *App) handleAdminBypasses(c *fiber.Ctx) error {
|
||||
func (a *App) handleAdminBypasses(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
|
||||
@@ -74,7 +74,7 @@ func (a *App) handleAdminBypasses(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleListBypasses lists all CVE bypasses
|
||||
func (a *App) handleListBypasses(c *fiber.Ctx) error {
|
||||
func (a *App) handleListBypasses(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Parse query parameters
|
||||
@@ -112,11 +112,11 @@ type CreateBypassRequest struct {
|
||||
}
|
||||
|
||||
// handleCreateBypass creates a new CVE bypass
|
||||
func (a *App) handleCreateBypass(c *fiber.Ctx) error {
|
||||
func (a *App) handleCreateBypass(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
var req CreateBypassRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON in request body"})
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ func (a *App) handleCreateBypass(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleGetBypass gets a specific bypass by ID
|
||||
func (a *App) handleGetBypass(c *fiber.Ctx) error {
|
||||
func (a *App) handleGetBypass(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Extract ID from parameter
|
||||
@@ -217,7 +217,7 @@ type UpdateBypassRequest struct {
|
||||
}
|
||||
|
||||
// handleUpdateBypass updates a bypass (activate/deactivate or extend expiration)
|
||||
func (a *App) handleUpdateBypass(c *fiber.Ctx) error {
|
||||
func (a *App) handleUpdateBypass(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Extract ID from parameter
|
||||
@@ -228,7 +228,7 @@ func (a *App) handleUpdateBypass(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
var req UpdateBypassRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON in request body"})
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ func (a *App) handleUpdateBypass(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleDeleteBypass deletes a bypass
|
||||
func (a *App) handleDeleteBypass(c *fiber.Ctx) error {
|
||||
func (a *App) handleDeleteBypass(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
|
||||
// Extract ID from parameter
|
||||
|
||||
@@ -3,12 +3,12 @@ package app
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// handleAnalyticsTopPackages returns the most downloaded packages
|
||||
func (a *App) handleAnalyticsTopPackages(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsTopPackages(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -29,7 +29,7 @@ func (a *App) handleAnalyticsTopPackages(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsTrendingPackages returns trending packages
|
||||
func (a *App) handleAnalyticsTrendingPackages(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsTrendingPackages(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -50,7 +50,7 @@ func (a *App) handleAnalyticsTrendingPackages(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsTrends returns download trends over time
|
||||
func (a *App) handleAnalyticsTrends(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsTrends(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -62,7 +62,7 @@ func (a *App) handleAnalyticsTrends(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsTotalStats returns overall statistics
|
||||
func (a *App) handleAnalyticsTotalStats(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsTotalStats(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -72,7 +72,7 @@ func (a *App) handleAnalyticsTotalStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsRegistryStats returns per-registry statistics
|
||||
func (a *App) handleAnalyticsRegistryStats(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsRegistryStats(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -89,7 +89,7 @@ func (a *App) handleAnalyticsRegistryStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsPackageStats returns statistics for a specific package
|
||||
func (a *App) handleAnalyticsPackageStats(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsPackageStats(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
@@ -115,7 +115,7 @@ func (a *App) handleAnalyticsPackageStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleAnalyticsSearch searches for packages matching a query
|
||||
func (a *App) handleAnalyticsSearch(c *fiber.Ctx) error {
|
||||
func (a *App) handleAnalyticsSearch(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/analytics"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package app
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -16,11 +16,11 @@ type GenerateAPIKeyRequest struct {
|
||||
}
|
||||
|
||||
// handleGenerateAPIKey generates a new API key
|
||||
func (a *App) handleGenerateAPIKey(c *fiber.Ctx) error {
|
||||
func (a *App) handleGenerateAPIKey(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
var req GenerateAPIKeyRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid JSON in request body",
|
||||
})
|
||||
@@ -82,7 +82,7 @@ func (a *App) handleGenerateAPIKey(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleListAPIKeys lists all API keys
|
||||
func (a *App) handleListAPIKeys(c *fiber.Ctx) error {
|
||||
func (a *App) handleListAPIKeys(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
keys := a.authManager.ListAPIKeys()
|
||||
@@ -108,7 +108,7 @@ func (a *App) handleListAPIKeys(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handleRevokeAPIKey revokes an API key
|
||||
func (a *App) handleRevokeAPIKey(c *fiber.Ctx) error {
|
||||
func (a *App) handleRevokeAPIKey(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
keyID := c.Params("key_id")
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@@ -114,7 +115,7 @@ func (s *AuthHandlersTestSuite) TestHandleGenerateAPIKey() {
|
||||
req := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
|
||||
resp, err := s.app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(tt.expectedStatus, resp.StatusCode)
|
||||
|
||||
@@ -144,7 +145,7 @@ func (s *AuthHandlersTestSuite) TestHandleListAPIKeys() {
|
||||
_, _, _ = s.authManager.GenerateAPIKey("test-key-3", auth.RoleAdmin, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/admin/keys", nil)
|
||||
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
|
||||
resp, err := s.app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(200, resp.StatusCode)
|
||||
|
||||
@@ -189,7 +190,7 @@ func (s *AuthHandlersTestSuite) TestHandleRevokeAPIKey() {
|
||||
for _, tt := range tests {
|
||||
s.Run(tt.name, func() {
|
||||
req := httptest.NewRequest("DELETE", "/api/admin/keys/"+tt.keyID, nil)
|
||||
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
|
||||
resp, err := s.app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(tt.expectedStatus, resp.StatusCode)
|
||||
|
||||
@@ -207,7 +208,7 @@ func (s *AuthHandlersTestSuite) TestHandleGenerateAPIKeyInvalidJSON() {
|
||||
req := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader([]byte("invalid json")))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
|
||||
resp, err := s.app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(400, resp.StatusCode)
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func (s *AuthHandlersTestSuite) TestGenerateAndRevokeKeyFlow() {
|
||||
|
||||
req1 := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader(bodyBytes))
|
||||
req1.Header.Set("Content-Type", "application/json")
|
||||
resp1, err := s.app.Test(req1, 5000) // 5 second timeout for CI environments
|
||||
resp1, err := s.app.Test(req1, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(201, resp1.StatusCode)
|
||||
|
||||
@@ -235,7 +236,7 @@ func (s *AuthHandlersTestSuite) TestGenerateAndRevokeKeyFlow() {
|
||||
|
||||
// List keys - should include our new key
|
||||
req2 := httptest.NewRequest("GET", "/api/admin/keys", nil)
|
||||
resp2, err := s.app.Test(req2, 5000) // 5 second timeout for CI environments
|
||||
resp2, err := s.app.Test(req2, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(200, resp2.StatusCode)
|
||||
|
||||
@@ -257,13 +258,13 @@ func (s *AuthHandlersTestSuite) TestGenerateAndRevokeKeyFlow() {
|
||||
|
||||
// Revoke the key
|
||||
req3 := httptest.NewRequest("DELETE", "/api/admin/keys/"+keyID, nil)
|
||||
resp3, err := s.app.Test(req3, 5000) // 5 second timeout for CI environments
|
||||
resp3, err := s.app.Test(req3, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(200, resp3.StatusCode)
|
||||
|
||||
// List keys again - should not include the revoked key
|
||||
req4 := httptest.NewRequest("GET", "/api/admin/keys", nil)
|
||||
resp4, err := s.app.Test(req4, 5000) // 5 second timeout for CI environments
|
||||
resp4, err := s.app.Test(req4, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true}) // 5 second timeout for CI environments
|
||||
s.Require().NoError(err)
|
||||
s.Equal(200, resp4.StatusCode)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// handlePrewarmingStatus returns the status of the pre-warming worker
|
||||
func (a *App) handlePrewarmingStatus(c *fiber.Ctx) error {
|
||||
func (a *App) handlePrewarmingStatus(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
status := a.prewarmWorker.GetStatus()
|
||||
@@ -15,7 +15,7 @@ func (a *App) handlePrewarmingStatus(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// handlePrewarmingTrigger manually triggers a pre-warming cycle
|
||||
func (a *App) handlePrewarmingTrigger(c *fiber.Ctx) error {
|
||||
func (a *App) handlePrewarmingTrigger(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
ctx := c.Context()
|
||||
@@ -36,11 +36,11 @@ type PrewarmPackageRequest struct {
|
||||
}
|
||||
|
||||
// handlePrewarmingPackage pre-warms a specific package
|
||||
func (a *App) handlePrewarmingPackage(c *fiber.Ctx) error {
|
||||
func (a *App) handlePrewarmingPackage(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
|
||||
var req PrewarmPackageRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid JSON in request body",
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/prewarming"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@@ -3,13 +3,13 @@ package app
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/metadata"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// handleVulnerabilities handles /api/packages/{registry}/{name}/{version}/vulnerabilities endpoint
|
||||
func (a *App) handleVulnerabilities(c *fiber.Ctx) error {
|
||||
func (a *App) handleVulnerabilities(c fiber.Ctx) error {
|
||||
c.Set("Content-Type", "application/json")
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
|
||||
@@ -3,7 +3,7 @@ package app
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -20,7 +20,7 @@ const (
|
||||
// extractAPIKey pulls the raw API key from either the Authorization bearer
|
||||
// header or the X-API-Key header. Returns ("", false) when neither is set or
|
||||
// the Authorization header has the wrong shape.
|
||||
func extractAPIKey(c *fiber.Ctx) (string, bool) {
|
||||
func extractAPIKey(c fiber.Ctx) (string, bool) {
|
||||
if h := strings.TrimSpace(c.Get("Authorization")); h != "" {
|
||||
// Expect: "Bearer <token>". Be tolerant of casing on the scheme.
|
||||
parts := strings.SplitN(h, " ", 2)
|
||||
@@ -40,7 +40,7 @@ func extractAPIKey(c *fiber.Ctx) (string, bool) {
|
||||
// writeAuthError sends a structured error response matching the project's
|
||||
// errors envelope (errors.Error JSON shape). Uses the HTTPStatusCode map so
|
||||
// callers don't need to remember which status maps to which code.
|
||||
func writeAuthError(c *fiber.Ctx, code, message string) error {
|
||||
func writeAuthError(c fiber.Ctx, code, message string) error {
|
||||
status, ok := errors.HTTPStatusCode[code]
|
||||
if !ok {
|
||||
status = fiber.StatusInternalServerError
|
||||
@@ -51,7 +51,7 @@ func writeAuthError(c *fiber.Ctx, code, message string) error {
|
||||
// requestIDFromCtx returns the request ID set by an upstream middleware or
|
||||
// the X-Request-ID header. Empty string when neither is available — callers
|
||||
// should treat empty as "no correlation id".
|
||||
func requestIDFromCtx(c *fiber.Ctx) string {
|
||||
func requestIDFromCtx(c fiber.Ctx) string {
|
||||
if v := c.Locals("request_id"); v != nil {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
return s
|
||||
@@ -65,7 +65,7 @@ func requestIDFromCtx(c *fiber.Ctx) string {
|
||||
// resolved key plus a boolean indicating whether a key was present at all
|
||||
// (false when no header). The error is non-nil only when validation failed
|
||||
// (i.e. a key was present but invalid/expired).
|
||||
func validateAndAttach(c *fiber.Ctx, authMgr *auth.Manager) (key *auth.APIKey, present bool, err error) {
|
||||
func validateAndAttach(c fiber.Ctx, authMgr *auth.Manager) (key *auth.APIKey, present bool, err error) {
|
||||
rawKey, ok := extractAPIKey(c)
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
@@ -83,7 +83,7 @@ func validateAndAttach(c *fiber.Ctx, authMgr *auth.Manager) (key *auth.APIKey, p
|
||||
// API key. On success the resolved *auth.APIKey is stored in c.Locals under
|
||||
// LocalAuthKey.
|
||||
func RequireAuth(authMgr *auth.Manager) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return func(c fiber.Ctx) error {
|
||||
key, present, err := validateAndAttach(c, authMgr)
|
||||
if !present {
|
||||
log.Debug().
|
||||
@@ -115,7 +115,7 @@ func RequireAuth(authMgr *auth.Manager) fiber.Handler {
|
||||
// requires the resolved key's role to be present in the supplied list (any-of
|
||||
// semantics). An empty roles list behaves like RequireAuth.
|
||||
func RequireRole(authMgr *auth.Manager, roles ...string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return func(c fiber.Ctx) error {
|
||||
key, present, err := validateAndAttach(c, authMgr)
|
||||
if !present {
|
||||
return writeAuthError(c, errors.ErrCodeUnauthorized, "missing API key; provide Authorization: Bearer <key> or X-API-Key header")
|
||||
@@ -146,7 +146,7 @@ func RequireRole(authMgr *auth.Manager, roles ...string) fiber.Handler {
|
||||
// then requires the resolved key to hold at least one of the supplied
|
||||
// permissions. An empty perms list behaves like RequireAuth.
|
||||
func RequirePermission(authMgr *auth.Manager, perms ...string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return func(c fiber.Ctx) error {
|
||||
key, present, err := validateAndAttach(c, authMgr)
|
||||
if !present {
|
||||
return writeAuthError(c, errors.ErrCodeUnauthorized, "missing API key; provide Authorization: Bearer <key> or X-API-Key header")
|
||||
@@ -177,7 +177,7 @@ func RequirePermission(authMgr *auth.Manager, perms ...string) fiber.Handler {
|
||||
// changes when the caller is authenticated (e.g. per-key rate limits) but
|
||||
// which still serve anonymous traffic. Locals are populated on success.
|
||||
func OptionalAuth(authMgr *auth.Manager) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
return func(c fiber.Ctx) error {
|
||||
_, present, err := validateAndAttach(c, authMgr)
|
||||
if present && err != nil {
|
||||
// Key was provided but invalid — log for observability, continue
|
||||
|
||||
+41
-41
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/auth"
|
||||
"github.com/lukaszraczylo/gohoarder/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -23,9 +23,9 @@ func mwTestSetup(t *testing.T, role auth.Role, mw fiber.Handler) (*fiber.App, *a
|
||||
apiKey, raw, err := mgr.GenerateAPIKey("test-"+string(role), role, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", mw)
|
||||
app.Get("/protected", func(c *fiber.Ctx) error {
|
||||
app.Get("/protected", func(c fiber.Ctx) error {
|
||||
got, _ := c.Locals(LocalAuthKey).(*auth.APIKey)
|
||||
gotRole, _ := c.Locals(LocalAuthRole).(string)
|
||||
body := fiber.Map{"ok": true, "have_key": got != nil}
|
||||
@@ -48,7 +48,7 @@ func decodeErrorBody(t *testing.T, body io.Reader) errors.Error {
|
||||
func TestRequireAuth_NoHeader_Returns401(t *testing.T) {
|
||||
app, _, _, _ := mwTestSetup(t, auth.RoleReadOnly, RequireAuth(auth.New()))
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusUnauthorized, resp.StatusCode)
|
||||
body := decodeErrorBody(t, resp.Body)
|
||||
@@ -63,7 +63,7 @@ func TestRequireAuth_BadKey_Returns401(t *testing.T) {
|
||||
app, _, _, _ := mwTestSetup(t, auth.RoleReadOnly, RequireAuth(mgr))
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer not-a-real-key")
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusUnauthorized, resp.StatusCode)
|
||||
body := decodeErrorBody(t, resp.Body)
|
||||
@@ -84,14 +84,14 @@ func TestRequireAuth_BadHeaderShape_Returns401(t *testing.T) {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mgr := auth.New()
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
if tc.value != "" {
|
||||
req.Header.Set(tc.header, tc.value)
|
||||
}
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusUnauthorized, resp.StatusCode)
|
||||
})
|
||||
@@ -103,9 +103,9 @@ func TestRequireAuth_ValidBearer_Returns200_AndPopulatesLocals(t *testing.T) {
|
||||
apiKey, raw, err := mgr.GenerateAPIKey("good", auth.RoleReadWrite, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error {
|
||||
app.Get("/protected", func(c fiber.Ctx) error {
|
||||
k, _ := c.Locals(LocalAuthKey).(*auth.APIKey)
|
||||
require.NotNil(t, k)
|
||||
require.Equal(t, apiKey.ID, k.ID)
|
||||
@@ -115,7 +115,7 @@ func TestRequireAuth_ValidBearer_Returns200_AndPopulatesLocals(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
@@ -129,13 +129,13 @@ func TestRequireAuth_ValidXAPIKey_Returns200(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("good", auth.RoleReadOnly, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("X-API-Key", raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
@@ -146,13 +146,13 @@ func TestRequireAuth_ExpiredKey_Returns401(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("expired", auth.RoleReadOnly, &d)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusUnauthorized, resp.StatusCode)
|
||||
}
|
||||
@@ -162,13 +162,13 @@ func TestRequireRole_Match_Returns200(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("admin", auth.RoleAdmin, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireRole(mgr, string(auth.RoleAdmin), string(auth.RoleReadWrite)))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
@@ -178,13 +178,13 @@ func TestRequireRole_Mismatch_Returns403(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("ro", auth.RoleReadOnly, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireRole(mgr, string(auth.RoleAdmin)))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusForbidden, resp.StatusCode)
|
||||
body := decodeErrorBody(t, resp.Body)
|
||||
@@ -193,12 +193,12 @@ func TestRequireRole_Mismatch_Returns403(t *testing.T) {
|
||||
|
||||
func TestRequireRole_NoHeader_Returns401(t *testing.T) {
|
||||
mgr := auth.New()
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequireRole(mgr, string(auth.RoleAdmin)))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusUnauthorized, resp.StatusCode)
|
||||
}
|
||||
@@ -208,13 +208,13 @@ func TestRequirePermission_Match_Returns200(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("rw", auth.RoleReadWrite, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequirePermission(mgr, string(auth.PermissionWritePackage)))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
@@ -224,43 +224,43 @@ func TestRequirePermission_Mismatch_Returns403(t *testing.T) {
|
||||
_, raw, err := mgr.GenerateAPIKey("ro", auth.RoleReadOnly, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", RequirePermission(mgr, string(auth.PermissionDeletePackage)))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error { return c.SendStatus(200) })
|
||||
app.Get("/protected", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusForbidden, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestOptionalAuth_NoHeader_Continues(t *testing.T) {
|
||||
mgr := auth.New()
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", OptionalAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error {
|
||||
app.Get("/protected", func(c fiber.Ctx) error {
|
||||
require.Nil(t, c.Locals(LocalAuthKey))
|
||||
return c.SendStatus(200)
|
||||
})
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestOptionalAuth_BadKey_ContinuesAnonymously(t *testing.T) {
|
||||
mgr := auth.New()
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", OptionalAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error {
|
||||
app.Get("/protected", func(c fiber.Ctx) error {
|
||||
// Bad key should NOT populate locals.
|
||||
require.Nil(t, c.Locals(LocalAuthKey))
|
||||
return c.SendStatus(200)
|
||||
})
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer not-real")
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
@@ -270,9 +270,9 @@ func TestOptionalAuth_GoodKey_PopulatesLocals(t *testing.T) {
|
||||
apiKey, raw, err := mgr.GenerateAPIKey("good", auth.RoleAdmin, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New(fiber.Config{})
|
||||
app.Use("/protected", OptionalAuth(mgr))
|
||||
app.Get("/protected", func(c *fiber.Ctx) error {
|
||||
app.Get("/protected", func(c fiber.Ctx) error {
|
||||
k, _ := c.Locals(LocalAuthKey).(*auth.APIKey)
|
||||
require.NotNil(t, k)
|
||||
require.Equal(t, apiKey.ID, k.ID)
|
||||
@@ -281,7 +281,7 @@ func TestOptionalAuth_GoodKey_PopulatesLocals(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second, FailOnTimeout: true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user