mirror of
https://codeberg.org/scip/ephemerup.git
synced 2025-12-18 04:51:06 +01:00
fixed error handling, added list command, switched to koanf
This commit is contained in:
@@ -20,45 +20,88 @@ package api
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
//"github.com/gofiber/keyauth/v2"
|
||||
"github.com/gofiber/keyauth/v2"
|
||||
"github.com/tlinden/up/upd/cfg"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Authurls []*regexp.Regexp
|
||||
var Apikeys []string
|
||||
// these vars can be savely global, since they don't change ever
|
||||
var (
|
||||
errMissing = &fiber.Error{
|
||||
Code: 403000,
|
||||
Message: "Missing API key",
|
||||
}
|
||||
|
||||
func AuthSetApikeys(keys []string) {
|
||||
errInvalid = &fiber.Error{
|
||||
Code: 403001,
|
||||
Message: "Invalid API key",
|
||||
}
|
||||
|
||||
Authurls []*regexp.Regexp
|
||||
Apikeys []cfg.Apicontext
|
||||
)
|
||||
|
||||
// fill from server: accepted keys
|
||||
func AuthSetApikeys(keys []cfg.Apicontext) {
|
||||
Apikeys = keys
|
||||
}
|
||||
|
||||
// fill from server: endpoints we need to authenticate
|
||||
func AuthSetEndpoints(prefix string, version string, endpoints []string) {
|
||||
for _, endpoint := range endpoints {
|
||||
Authurls = append(Authurls, regexp.MustCompile("^"+prefix+version+endpoint))
|
||||
}
|
||||
}
|
||||
|
||||
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
||||
for _, apiKey := range Apikeys {
|
||||
hashedAPIKey := sha256.Sum256([]byte(apiKey))
|
||||
// make sure we always return JSON encoded errors
|
||||
func AuthErrHandler(ctx *fiber.Ctx, err error) error {
|
||||
ctx.Status(fiber.StatusForbidden)
|
||||
|
||||
if err == errMissing {
|
||||
return ctx.JSON(errMissing)
|
||||
}
|
||||
|
||||
return ctx.JSON(errInvalid)
|
||||
}
|
||||
|
||||
// validator hook, called by fiber via server keyauth.New()
|
||||
func AuthValidateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
||||
// create a new session, it will be thrown away if something fails
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return false, errors.New("Unable to initialize session store from context!")
|
||||
}
|
||||
|
||||
// if Apikeys is empty, the server works unauthenticated
|
||||
// FIXME: maybe always reject?
|
||||
if len(Apikeys) == 0 {
|
||||
sess.Set("apicontext", "default")
|
||||
|
||||
if err := sess.Save(); err != nil {
|
||||
return false, errors.New("Unable to save session store!")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// actual key comparision
|
||||
for _, apicontext := range Apikeys {
|
||||
hashedAPIKey := sha256.Sum256([]byte(apicontext.Key))
|
||||
hashedKey := sha256.Sum256([]byte(key))
|
||||
|
||||
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
|
||||
// apikey matches, register apicontext for later use by the handlers
|
||||
sess.Set("apicontext", apicontext.Context)
|
||||
|
||||
if err := sess.Save(); err != nil {
|
||||
return false, errors.New("Unable to save session store!")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
//return false, keyauth.ErrMissingOrMalformedAPIKey
|
||||
}
|
||||
|
||||
func authFilter(c *fiber.Ctx) bool {
|
||||
originalURL := strings.ToLower(c.OriginalURL())
|
||||
|
||||
for _, pattern := range Authurls {
|
||||
if pattern.MatchString(originalURL) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return false, keyauth.ErrMissingOrMalformedAPIKey
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/alecthomas/repr"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,11 @@ type Upload struct {
|
||||
File string `json:"file"` // final filename (visible to the downloader)
|
||||
Members []string `json:"members"` // contains multiple files, so File is an archive
|
||||
Uploaded Timestamp `json:"uploaded"`
|
||||
Context string `json:"context"`
|
||||
}
|
||||
|
||||
type Uploads struct {
|
||||
Entries []*Upload `json:"uploads"`
|
||||
}
|
||||
|
||||
func NewDb(file string) (*Db, error) {
|
||||
@@ -125,22 +131,30 @@ func (db *Db) Delete(id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Db) Iterate(iterator func(id string, upload Upload)) error {
|
||||
var upload Upload
|
||||
func (db *Db) List(apicontext string) (*Uploads, error) {
|
||||
uploads := &Uploads{}
|
||||
|
||||
err := db.bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(Bucket))
|
||||
err := bucket.ForEach(func(id, j []byte) error {
|
||||
upload := &Upload{}
|
||||
if err := json.Unmarshal(j, &upload); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
iterator(string(id), upload)
|
||||
if apicontext != "" {
|
||||
if apicontext == upload.Context {
|
||||
uploads.Entries = append(uploads.Entries, upload)
|
||||
}
|
||||
} else {
|
||||
uploads.Entries = append(uploads.Entries, upload)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err // might be nil as well
|
||||
})
|
||||
|
||||
return err
|
||||
return uploads, err
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/tlinden/up/upd/cfg"
|
||||
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -89,8 +91,22 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *Db) (string, error) {
|
||||
}
|
||||
entry.File = Newfilename
|
||||
|
||||
// retrieve the API Context name from the session, assuming is has
|
||||
// been successfully authenticated. However, if there are no api
|
||||
// contexts defined, we'll use 'default' (set in
|
||||
// auth.validateAPIKey())
|
||||
sess, err := Sessionstore.Get(c)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(500, "Unable to initialize session store from context!")
|
||||
}
|
||||
apicontext := sess.Get("apicontext")
|
||||
if apicontext != nil {
|
||||
entry.Context = apicontext.(string)
|
||||
}
|
||||
|
||||
Log("Now serving %s from %s/%s", returnUrl, cfg.StorageDir, id)
|
||||
Log("Expire set to: %s", entry.Expire)
|
||||
Log("Uploaded with API-Context %s", entry.Context)
|
||||
|
||||
// we do this in the background to not thwart the server
|
||||
go db.Insert(id, entry)
|
||||
@@ -167,3 +183,24 @@ func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func List(c *fiber.Ctx, cfg *cfg.Config, db *Db) (string, error) {
|
||||
apicontext, err := Untaint(c.Params("apicontext"), `[^a-zA-Z0-9\-]`)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(403, "Invalid api context provided!")
|
||||
}
|
||||
|
||||
uploads, err := db.List(apicontext)
|
||||
repr.Print(uploads)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(500, "Unable to list uploads: "+err.Error())
|
||||
}
|
||||
|
||||
jsonlist, err := json.Marshal(uploads)
|
||||
if err != nil {
|
||||
return "", fiber.NewError(500, "json marshalling failure: "+err.Error())
|
||||
}
|
||||
|
||||
Log(string(jsonlist))
|
||||
return string(jsonlist), nil
|
||||
}
|
||||
|
||||
@@ -18,14 +18,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
"github.com/gofiber/keyauth/v2"
|
||||
"github.com/tlinden/up/upd/cfg"
|
||||
)
|
||||
|
||||
// sessions are context specific and can be global savely
|
||||
var Sessionstore *session.Store
|
||||
|
||||
func Runserver(cfg *cfg.Config, args []string) error {
|
||||
Sessionstore = session.New()
|
||||
|
||||
router := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
StrictRouting: true,
|
||||
@@ -49,10 +56,11 @@ func Runserver(cfg *cfg.Config, args []string) error {
|
||||
defer db.Close()
|
||||
|
||||
AuthSetEndpoints(cfg.ApiPrefix, ApiVersion, []string{"/file"})
|
||||
AuthSetApikeys(cfg.Apikeys)
|
||||
AuthSetApikeys(cfg.Apicontext)
|
||||
|
||||
auth := keyauth.New(keyauth.Config{
|
||||
Validator: validateAPIKey,
|
||||
Validator: AuthValidateAPIKey,
|
||||
ErrorHandler: AuthErrHandler,
|
||||
})
|
||||
|
||||
shallExpire := true
|
||||
@@ -76,6 +84,11 @@ func Runserver(cfg *cfg.Config, args []string) error {
|
||||
api.Delete("/file/:id/", auth, func(c *fiber.Ctx) error {
|
||||
return FileDelete(c, cfg, db)
|
||||
})
|
||||
|
||||
api.Get("/list/", auth, func(c *fiber.Ctx) error {
|
||||
msg, err := List(c, cfg, db)
|
||||
return SendResponse(c, msg, err)
|
||||
})
|
||||
}
|
||||
|
||||
// public routes
|
||||
@@ -96,11 +109,16 @@ func Runserver(cfg *cfg.Config, args []string) error {
|
||||
}
|
||||
|
||||
func SendResponse(c *fiber.Ctx, msg string, err error) error {
|
||||
// FIXME:
|
||||
// respect fiber.NewError(500, "Could not process uploaded file[s]!")
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(Result{
|
||||
Code: fiber.StatusBadRequest,
|
||||
code := fiber.StatusInternalServerError
|
||||
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
}
|
||||
|
||||
return c.Status(code).JSON(Result{
|
||||
Code: code,
|
||||
Message: err.Error(),
|
||||
Success: false,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user