96256ac63e
Also adds the ability for an admin to just completely disable this service if it is not needed on the node.
147 lines
4.6 KiB
Go
147 lines
4.6 KiB
Go
package router
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"emperror.dev/errors"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/pterodactyl/wings/config"
|
|
"github.com/pterodactyl/wings/server"
|
|
)
|
|
|
|
type Middleware struct{}
|
|
|
|
// A custom handler function allowing for errors bubbled up by c.Error() to be returned in a
|
|
// standardized format with tracking UUIDs on them for easier log searching.
|
|
func (m *Middleware) ErrorHandler() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
err := c.Errors.Last()
|
|
if err == nil || err.Err == nil {
|
|
return
|
|
}
|
|
tracked := NewTrackedError(err.Err)
|
|
// If there is a server in the context for this request pull it out so that we can
|
|
// track the error specifically for that server.
|
|
if s, ok := c.Get("server"); ok {
|
|
tracked = NewServerError(err.Err, s.(*server.Server))
|
|
}
|
|
// This error occurs if you submit invalid JSON data to an endpoint.
|
|
if err.Err.Error() == io.EOF.Error() {
|
|
c.JSON(c.Writer.Status(), gin.H{"error": "A JSON formatted body is required for this endpoint."})
|
|
return
|
|
}
|
|
tracked.Abort(c)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Set the access request control headers on all of the requests.
|
|
func (m *Middleware) SetAccessControlHeaders() gin.HandlerFunc {
|
|
origins := config.Get().AllowedOrigins
|
|
location := config.Get().PanelLocation
|
|
return func(c *gin.Context) {
|
|
c.Header("Access-Control-Allow-Credentials", "true")
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Accept, Accept-Encoding, Authorization, Cache-Control, Content-Type, Content-Length, Origin, X-Real-IP, X-CSRF-Token")
|
|
|
|
o := c.GetHeader("Origin")
|
|
if o != location {
|
|
for _, origin := range origins {
|
|
if origin != "*" && o != origin {
|
|
continue
|
|
}
|
|
c.Header("Access-Control-Allow-Origin", origin)
|
|
c.Next()
|
|
return
|
|
}
|
|
}
|
|
c.Header("Access-Control-Allow-Origin", location)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// Authenticates the request token against the given permission string, ensuring that
|
|
// if it is a server permission, the token has control over that server. If it is a global
|
|
// token, this will ensure that the request is using a properly signed global token.
|
|
func (m *Middleware) RequireAuthorization() gin.HandlerFunc {
|
|
token := config.Get().AuthenticationToken
|
|
return func(c *gin.Context) {
|
|
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
|
if len(auth) != 2 || auth[0] != "Bearer" {
|
|
c.Header("WWW-Authenticate", "Bearer")
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "The required authorization heads were not present in the request.",
|
|
})
|
|
return
|
|
}
|
|
|
|
// All requests to Wings must be authorized with the authentication token present in
|
|
// the Wings configuration file. Remeber, all requests to Wings come from the Panel
|
|
// backend, or using a signed JWT for temporary authentication.
|
|
if auth[1] == token {
|
|
c.Next()
|
|
return
|
|
}
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "You are not authorized to access this endpoint.",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper function to fetch a server out of the servers collection stored in memory.
|
|
//
|
|
// This function should not be used in new controllers, prefer ExtractServer where
|
|
// possible.
|
|
func GetServer(uuid string) *server.Server {
|
|
return server.GetServers().Find(func(s *server.Server) bool {
|
|
return uuid == s.Id()
|
|
})
|
|
}
|
|
|
|
// Ensure that the requested server exists in this setup. Returns a 404 if we cannot
|
|
// locate it.
|
|
func (m *Middleware) ServerExists() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
u, err := uuid.Parse(c.Param("server"))
|
|
if err == nil {
|
|
if s := GetServer(u.String()); s != nil {
|
|
c.Set("server", s)
|
|
c.Next()
|
|
return
|
|
}
|
|
}
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
|
"error": "The resource you requested does not exist.",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Checks if remote file downloading is enabled on this instance before allowing access
|
|
// to the given endpoint.
|
|
func (m *Middleware) CheckRemoteDownloadEnabled() gin.HandlerFunc {
|
|
disabled := config.Get().Api.DisableRemoteDownload
|
|
return func(c *gin.Context) {
|
|
if disabled {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
|
"error": "This functionality is not currently enabled on this instance.",
|
|
})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// Returns the server instance from the gin context. If there is no server set in the
|
|
// context (e.g. calling from a controller not protected by ServerExists) this function
|
|
// will panic.
|
|
func ExtractServer(c *gin.Context) *server.Server {
|
|
if s, ok := c.Get("server"); ok {
|
|
return s.(*server.Server)
|
|
}
|
|
panic(errors.New("cannot extract server, missing on gin context"))
|
|
}
|