Add basic authentication
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"quay/app/repository"
|
||||
"quay/internal/security"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
Repo repository.UserRepository
|
||||
}
|
||||
|
||||
func NewAuthHandler(repo repository.UserRepository) *AuthHandler {
|
||||
return &AuthHandler{Repo: repo}
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c fiber.Ctx) error {
|
||||
var req LoginRequest
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request"})
|
||||
}
|
||||
|
||||
user, err := h.Repo.GetUserByName(req.Name)
|
||||
if err != nil || user == nil {
|
||||
log.Println("login: user lookup failed", err)
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid credentials"})
|
||||
}
|
||||
|
||||
if !security.CheckPasswordHash(req.Password, user.HashedPassword) {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid credentials"})
|
||||
}
|
||||
|
||||
token, err := security.GenerateToken(user.ID, user.Role, 24*time.Hour)
|
||||
if err != nil {
|
||||
log.Println("login: token generation failed", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to generate token"})
|
||||
}
|
||||
|
||||
return c.JSON(LoginResponse{Token: token})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"quay/internal/security"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func RequireAuth() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
auth := c.Get("Authorization")
|
||||
if auth == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "missing authorization header"})
|
||||
}
|
||||
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid authorization header"})
|
||||
}
|
||||
|
||||
claims, err := security.ValidateToken(parts[1])
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
|
||||
}
|
||||
|
||||
c.Locals("user_id", claims.UserID)
|
||||
c.Locals("role", claims.Role)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
@@ -26,50 +26,69 @@ func Register(app *fiber.App, cfg *config.Config, envCfg *envconfig.EnvConfig, d
|
||||
deploymentsHandler := handlers.NewDeploymentHandler(deploymentRepository)
|
||||
|
||||
api := app.Group("/api/v1", middleware.APIHostGuard(envCfg.DashboardHost))
|
||||
api.Get("/health", handlers.HealthCheck)
|
||||
|
||||
api.Post("/deploy", deploySiteHandler.PostDeploy)
|
||||
public := api.Group("")
|
||||
public.Get("/health", handlers.HealthCheck)
|
||||
|
||||
authHandler := handlers.NewAuthHandler(userRepository)
|
||||
public.Post("/login", authHandler.Login)
|
||||
|
||||
// Protected routes - require auth for everything by default
|
||||
protected := api.Group("", middleware.RequireAuth())
|
||||
|
||||
protected.Post("/deploy", deploySiteHandler.PostDeploy)
|
||||
|
||||
// Sites
|
||||
api.Get("/sites", siteHandler.GetSites)
|
||||
api.Get("/sites/:id", siteHandler.GetSite)
|
||||
api.Post("/sites", siteHandler.PostSite)
|
||||
api.Put("/sites/:id", siteHandler.PutSite)
|
||||
api.Delete("/sites/:id", siteHandler.DeleteSite)
|
||||
api.Patch("/sites/:id/enabled", siteHandler.ToggleEnabled)
|
||||
protected.Get("/sites", siteHandler.GetSites)
|
||||
protected.Get("/sites/:id", siteHandler.GetSite)
|
||||
protected.Post("/sites", siteHandler.PostSite)
|
||||
protected.Put("/sites/:id", siteHandler.PutSite)
|
||||
protected.Delete("/sites/:id", siteHandler.DeleteSite)
|
||||
protected.Patch("/sites/:id/enabled", siteHandler.ToggleEnabled)
|
||||
|
||||
// Forward rules
|
||||
api.Get("/sites/:id/forward-rules", siteHandler.GetSiteForwardRules)
|
||||
api.Post("/sites/:id/forward-rules", siteHandler.PostForwardRule)
|
||||
api.Get("/sites/:id/forward-rules/:ruleId", siteHandler.GetForwardRule)
|
||||
api.Put("/sites/:id/forward-rules/:ruleId", siteHandler.PutForwardRule)
|
||||
api.Delete("/sites/:id/forward-rules/:ruleId", siteHandler.DeleteForwardRule)
|
||||
protected.Get("/sites/:id/forward-rules", siteHandler.GetSiteForwardRules)
|
||||
protected.Post("/sites/:id/forward-rules", siteHandler.PostForwardRule)
|
||||
protected.Get("/sites/:id/forward-rules/:ruleId", siteHandler.GetForwardRule)
|
||||
protected.Put("/sites/:id/forward-rules/:ruleId", siteHandler.PutForwardRule)
|
||||
protected.Delete("/sites/:id/forward-rules/:ruleId", siteHandler.DeleteForwardRule)
|
||||
|
||||
// Custom headers (header rules)
|
||||
api.Get("/sites/:id/custom-headers", siteHandler.GetSiteCustomHeaders)
|
||||
api.Post("/sites/:id/custom-headers", siteHandler.PostCustomHeaders)
|
||||
api.Get("/sites/:id/custom-headers/:customHeaderId", siteHandler.GetCustomHeaders)
|
||||
api.Put("/sites/:id/custom-headers/:customHeaderId", siteHandler.PutCustomHeaders)
|
||||
api.Delete("/sites/:id/custom-headers/:customHeaderId", siteHandler.DeleteCustomHeaders)
|
||||
protected.Get("/sites/:id/custom-headers", siteHandler.GetSiteCustomHeaders)
|
||||
protected.Post("/sites/:id/custom-headers", siteHandler.PostCustomHeaders)
|
||||
protected.Get("/sites/:id/custom-headers/:customHeaderId", siteHandler.GetCustomHeaders)
|
||||
protected.Put("/sites/:id/custom-headers/:customHeaderId", siteHandler.PutCustomHeaders)
|
||||
protected.Delete("/sites/:id/custom-headers/:customHeaderId", siteHandler.DeleteCustomHeaders)
|
||||
|
||||
// Headers
|
||||
api.Get("/sites/:id/custom-headers/:customHeaderId/headers", siteHandler.GetCustomHeaderHeaders)
|
||||
api.Post("/sites/:id/custom-headers/:customHeaderId/headers", siteHandler.PostHeader)
|
||||
api.Get("/sites/:id/headers/:headerId", siteHandler.GetHeader)
|
||||
api.Put("/sites/:id/headers/:headerId", siteHandler.PutHeader)
|
||||
api.Delete("/sites/:id/headers/:headerId", siteHandler.DeleteHeader)
|
||||
protected.Get("/sites/:id/custom-headers/:customHeaderId/headers", siteHandler.GetCustomHeaderHeaders)
|
||||
protected.Post("/sites/:id/custom-headers/:customHeaderId/headers", siteHandler.PostHeader)
|
||||
protected.Get("/sites/:id/headers/:headerId", siteHandler.GetHeader)
|
||||
protected.Put("/sites/:id/headers/:headerId", siteHandler.PutHeader)
|
||||
protected.Delete("/sites/:id/headers/:headerId", siteHandler.DeleteHeader)
|
||||
|
||||
// Deployments
|
||||
api.Get("/deployments/:id", deploymentsHandler.GetDeployment)
|
||||
api.Get("/sites/:id/deployments", deploymentsHandler.GetDeploymentsBySite)
|
||||
protected.Get("/deployments/:id", deploymentsHandler.GetDeployment)
|
||||
protected.Get("/sites/:id/deployments", deploymentsHandler.GetDeploymentsBySite)
|
||||
|
||||
// Users
|
||||
api.Get("/users", userHandler.GetAllUsers)
|
||||
api.Get("/users/:id", userHandler.GetUserById)
|
||||
api.Get("/users/by-name/:name", userHandler.GetUserByName)
|
||||
api.Post("/users", userHandler.CreateUser)
|
||||
api.Put("/users/:id", userHandler.UpdateUser)
|
||||
api.Delete("/users/:id", userHandler.DeleteUser)
|
||||
protected.Get("/users", userHandler.GetAllUsers)
|
||||
protected.Get("/users/:id", userHandler.GetUserById)
|
||||
protected.Get("/users/by-name/:name", userHandler.GetUserByName)
|
||||
|
||||
// Allow creating the very first admin user without auth (bootstrap).
|
||||
// If an admin already exists, require auth to create users.
|
||||
if exists, err := userRepository.AdminUserExists(); err != nil {
|
||||
// if we can't determine, be conservative and require auth
|
||||
protected.Post("/users", userHandler.CreateUser)
|
||||
} else if !exists {
|
||||
public.Post("/users", userHandler.CreateUser)
|
||||
} else {
|
||||
protected.Post("/users", userHandler.CreateUser)
|
||||
}
|
||||
|
||||
protected.Put("/users/:id", userHandler.UpdateUser)
|
||||
protected.Delete("/users/:id", userHandler.DeleteUser)
|
||||
|
||||
api.Use(func(c fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusNotFound).JSON(&models.APIError{
|
||||
|
||||
Reference in New Issue
Block a user