frontend performance, rpc/rest jwt authentication

This commit is contained in:
2023-06-22 11:31:24 +02:00
parent 78c1559e84
commit d6c0646756
24 changed files with 691 additions and 360 deletions

View File

@@ -13,6 +13,8 @@ type serverConfig struct {
Port int `yaml:"port"`
DownloadPath string `yaml:"downloadPath"`
DownloaderPath string `yaml:"downloaderPath"`
RequireAuth bool `yaml:"require_auth"`
RPCSecret string `yaml:"rpc_secret"`
}
type config struct {
@@ -46,6 +48,13 @@ func (c *config) DownloaderPath(path string) {
c.cfg.DownloaderPath = path
}
func (c *config) RequireAuth(value bool) {
c.cfg.RequireAuth = value
}
func (c *config) RPCSecret(secret string) {
c.cfg.RPCSecret = secret
}
var instance *config
func Instance() *config {

50
server/middleware/jwt.go Normal file
View File

@@ -0,0 +1,50 @@
package middlewares
import (
"fmt"
"os"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
)
const (
TOKEN_COOKIE_NAME = "jwt"
)
var Authenticated = func(c *fiber.Ctx) error {
if !config.Instance().GetConfig().RequireAuth {
return c.Next()
}
cookie := c.Cookies(TOKEN_COOKIE_NAME)
if cookie == "" {
return c.Status(fiber.StatusUnauthorized).SendString("invalid token")
}
token, _ := jwt.Parse(cookie, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return []byte(os.Getenv("JWTSECRET")), nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
expiresAt, err := time.Parse(time.RFC3339, claims["expiresAt"].(string))
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
if time.Now().After(expiresAt) {
return c.Status(fiber.StatusBadRequest).SendString("expired token")
}
} else {
return c.Status(fiber.StatusUnauthorized).SendString("invalid token")
}
return c.Next()
}

View File

@@ -11,10 +11,15 @@ import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
)
const (
TOKEN_COOKIE_NAME = "jwt"
)
type DirectoryEntry struct {
Name string `json:"name"`
Path string `json:"path"`
@@ -139,3 +144,52 @@ func SendFile(ctx *fiber.Ctx) error {
return ctx.SendStatus(fiber.StatusUnauthorized)
}
type LoginRequest struct {
Secret string `json:"secret"`
}
func Login(ctx *fiber.Ctx) error {
req := new(LoginRequest)
err := ctx.BodyParser(req)
if err != nil {
return ctx.SendStatus(fiber.StatusInternalServerError)
}
if config.Instance().GetConfig().RPCSecret != req.Secret {
return ctx.SendStatus(fiber.StatusBadRequest)
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"expiresAt": time.Now().Add(time.Minute * 30),
})
tokenString, err := token.SignedString([]byte(os.Getenv("JWTSECRET")))
if err != nil {
return ctx.SendStatus(fiber.StatusInternalServerError)
}
ctx.Cookie(&fiber.Cookie{
Name: TOKEN_COOKIE_NAME,
HTTPOnly: true,
Secure: false,
Expires: time.Now().Add(time.Hour * 24 * 30), // 30 days
Value: tokenString,
Path: "/",
})
return ctx.SendStatus(fiber.StatusOK)
}
func Logout(ctx *fiber.Ctx) error {
ctx.Cookie(&fiber.Cookie{
Name: TOKEN_COOKIE_NAME,
HTTPOnly: true,
Secure: false,
Expires: time.Now(),
Value: "",
Path: "/",
})
return ctx.SendStatus(fiber.StatusOK)
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/websocket/v2"
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
"github.com/marcopeocchi/yt-dlp-web-ui/server/rest"
)
@@ -35,21 +36,32 @@ func RunBlocking(port int, frontend fs.FS) {
Root: http.FS(frontend),
}))
// Client side routes
app.Get("/settings", func(c *fiber.Ctx) error {
return c.Redirect("/")
})
app.Get("/archive", func(c *fiber.Ctx) error {
return c.Redirect("/")
})
app.Get("/login", func(c *fiber.Ctx) error {
return c.Redirect("/")
})
app.Post("/downloaded", rest.ListDownloaded)
// Archive routes
archive := app.Group("archive", middlewares.Authenticated)
archive.Post("/downloaded", rest.ListDownloaded)
archive.Post("/delete", rest.DeleteFile)
archive.Get("/d/:id", rest.SendFile)
app.Post("/delete", rest.DeleteFile)
app.Get("/d/:id", rest.SendFile)
// Authentication routes
app.Post("/auth/login", rest.Login)
app.Get("/auth/logout", rest.Logout)
// RPC handlers
// websocket
app.Get("/ws-rpc", websocket.New(func(c *websocket.Conn) {
rpc := app.Group("/rpc", middlewares.Authenticated)
rpc.Get("/ws", websocket.New(func(c *websocket.Conn) {
c.WriteMessage(websocket.TextMessage, []byte(`{
"status": "connected"
}`))
@@ -69,7 +81,7 @@ func RunBlocking(port int, frontend fs.FS) {
}
}))
// http-post
app.Post("/http-rpc", func(c *fiber.Ctx) error {
rpc.Post("/http", func(c *fiber.Ctx) error {
reader := c.Context().RequestBodyStream()
writer := c.Response().BodyWriter()