This restructures dbcore (now the db package) and jwtcore (now the jwt package) to use a single struct. There is now a state package, which contains a struct with the full application state. After this, instead of initializing the API routes directly in the main function, the state object gets passed, and the API routes get initialized with their accompanying code. One fix done to reduce memory usage and increase speed is that the validator object is now persistent across requests, instead of recreating it each time. This should speed things up slightly, and improve memory usage. One additional chore done is that the database models have been moved to be a seperate file from the DB initialization itself.
177 lines
4.5 KiB
Go
177 lines
4.5 KiB
Go
package proxies
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
|
"github.com/charmbracelet/log"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type ProxyCreationRequest struct {
|
|
Token string `validate:"required" json:"token"`
|
|
Name string `validate:"required" json:"name"`
|
|
Description *string `json:"description"`
|
|
Protocol string `validate:"required" json:"protocol"`
|
|
SourceIP string `validate:"required" json:"sourceIP"`
|
|
SourcePort uint16 `validate:"required" json:"sourcePort"`
|
|
DestinationPort uint16 `validate:"required" json:"destinationPort"`
|
|
ProviderID uint `validate:"required" json:"providerID"`
|
|
AutoStart *bool `json:"autoStart"`
|
|
}
|
|
|
|
func SetupCreateProxy(state *state.State) {
|
|
state.Engine.POST("/api/v1/forward/create", func(c *gin.Context) {
|
|
var req ProxyCreationRequest
|
|
|
|
if err := c.BindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": fmt.Sprintf("Failed to parse body: %s", err.Error()),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if err := state.Validator.Struct(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
|
|
|
if err != nil {
|
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": err.Error(),
|
|
})
|
|
|
|
return
|
|
} else {
|
|
log.Warnf("Failed to get user from the provided JWT token: %s", err.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to parse token",
|
|
})
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
if !permissions.UserHasPermission(user, "routes.add") {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "Missing permissions",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if req.Protocol != "tcp" && req.Protocol != "udp" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Protocol must be either 'tcp' or 'udp'",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
var backend db.Backend
|
|
backendRequest := state.DB.DB.Where("id = ?", req.ProviderID).First(&backend)
|
|
|
|
if backendRequest.Error != nil {
|
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to find if backend exists",
|
|
})
|
|
}
|
|
|
|
backendExists := backendRequest.RowsAffected > 0
|
|
|
|
if !backendExists {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Could not find backend",
|
|
})
|
|
}
|
|
|
|
autoStart := false
|
|
|
|
if req.AutoStart != nil {
|
|
autoStart = *req.AutoStart
|
|
}
|
|
|
|
proxy := &db.Proxy{
|
|
UserID: user.ID,
|
|
BackendID: req.ProviderID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Protocol: req.Protocol,
|
|
SourceIP: req.SourceIP,
|
|
SourcePort: req.SourcePort,
|
|
DestinationPort: req.DestinationPort,
|
|
AutoStart: autoStart,
|
|
}
|
|
|
|
if result := state.DB.DB.Create(proxy); result.Error != nil {
|
|
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to add forward rule to database",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if autoStart {
|
|
backend, ok := backendruntime.RunningBackends[proxy.BackendID]
|
|
|
|
if !ok {
|
|
log.Warnf("Couldn't fetch backend runtime from backend ID #%d", proxy.BackendID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"id": proxy.ID,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
backendResponse, err := backend.ProcessCommand(&commonbackend.AddProxy{
|
|
SourceIP: proxy.SourceIP,
|
|
SourcePort: proxy.SourcePort,
|
|
DestPort: proxy.DestinationPort,
|
|
Protocol: proxy.Protocol,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, err.Error())
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "failed to get response from backend",
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
switch responseMessage := backendResponse.(type) {
|
|
case *commonbackend.ProxyStatusResponse:
|
|
if !responseMessage.IsActive {
|
|
log.Warnf("Failed to start proxy for backend #%d", proxy.BackendID)
|
|
}
|
|
default:
|
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"id": proxy.ID,
|
|
})
|
|
})
|
|
}
|