AX: clarify CLI and service naming
This commit is contained in:
parent
0893b0ef9e
commit
53b2156216
5 changed files with 103 additions and 103 deletions
|
|
@ -11,10 +11,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
remoteController *node.Controller
|
||||
peerTransport *node.Transport
|
||||
remoteControllerOnce sync.Once
|
||||
remoteControllerErr error
|
||||
cachedRemoteController *node.Controller
|
||||
cachedPeerTransport *node.Transport
|
||||
loadRemoteControllerOnce sync.Once
|
||||
cachedRemoteControllerErr error
|
||||
)
|
||||
|
||||
// remote status peer-19f3, remote start peer-19f3 --type xmrig, and remote ping peer-19f3 --count 4 live under this command group.
|
||||
|
|
@ -325,29 +325,29 @@ func init() {
|
|||
|
||||
// getController() returns the cached controller after `node init` succeeds.
|
||||
func getController() (*node.Controller, error) {
|
||||
remoteControllerOnce.Do(func() {
|
||||
loadRemoteControllerOnce.Do(func() {
|
||||
nodeManager, err := getNodeManager()
|
||||
if err != nil {
|
||||
remoteControllerErr = fmt.Errorf("failed to get node manager: %w", err)
|
||||
cachedRemoteControllerErr = fmt.Errorf("failed to get node manager: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !nodeManager.HasIdentity() {
|
||||
remoteControllerErr = fmt.Errorf("no node identity found. Run `node init` first")
|
||||
cachedRemoteControllerErr = fmt.Errorf("no node identity found. Run `node init` first")
|
||||
return
|
||||
}
|
||||
|
||||
peerRegistry, err := getPeerRegistry()
|
||||
if err != nil {
|
||||
remoteControllerErr = fmt.Errorf("failed to get peer registry: %w", err)
|
||||
cachedRemoteControllerErr = fmt.Errorf("failed to get peer registry: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
transportConfig := node.DefaultTransportConfig()
|
||||
peerTransport = node.NewTransport(nodeManager, peerRegistry, transportConfig)
|
||||
remoteController = node.NewController(nodeManager, peerRegistry, peerTransport)
|
||||
cachedPeerTransport = node.NewTransport(nodeManager, peerRegistry, transportConfig)
|
||||
cachedRemoteController = node.NewController(nodeManager, peerRegistry, cachedPeerTransport)
|
||||
})
|
||||
return remoteController, remoteControllerErr
|
||||
return cachedRemoteController, cachedRemoteControllerErr
|
||||
}
|
||||
|
||||
// findPeerByPartialID("peer-19f3") returns the peer whose ID starts with `peer-19f3`.
|
||||
|
|
@ -364,13 +364,13 @@ func findPeerByPartialID(partialID string) *node.Peer {
|
|||
}
|
||||
|
||||
// peerRegistry.ListPeers() falls back to partial IDs such as `peer-19`.
|
||||
for _, p := range peerRegistry.ListPeers() {
|
||||
if strings.HasPrefix(p.ID, partialID) {
|
||||
return p
|
||||
for _, peer := range peerRegistry.ListPeers() {
|
||||
if strings.HasPrefix(peer.ID, partialID) {
|
||||
return peer
|
||||
}
|
||||
// Also try matching by name
|
||||
if strings.EqualFold(p.Name, partialID) {
|
||||
return p
|
||||
if strings.EqualFold(peer.Name, partialID) {
|
||||
return peer
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
host string
|
||||
port int
|
||||
namespace string
|
||||
serveHost string
|
||||
servePort int
|
||||
apiNamespace string
|
||||
)
|
||||
|
||||
// mining serve starts the HTTP API and interactive shell.
|
||||
|
|
@ -31,7 +31,7 @@ var serveCmd = &cobra.Command{
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
displayHostName := host
|
||||
displayHostName := serveHost
|
||||
if displayHostName == "0.0.0.0" {
|
||||
var err error
|
||||
displayHostName, err = getLocalIP()
|
||||
|
|
@ -39,13 +39,13 @@ var serveCmd = &cobra.Command{
|
|||
displayHostName = "localhost"
|
||||
}
|
||||
}
|
||||
displayAddress := fmt.Sprintf("%s:%d", displayHostName, port)
|
||||
listenAddress := fmt.Sprintf("%s:%d", host, port)
|
||||
displayAddress := fmt.Sprintf("%s:%d", displayHostName, servePort)
|
||||
listenAddress := fmt.Sprintf("%s:%d", serveHost, servePort)
|
||||
|
||||
// manager := getManager() shares the same miner lifecycle state across `mining start`, `mining stop`, and `mining serve`.
|
||||
manager := getManager()
|
||||
|
||||
service, err := mining.NewService(manager, listenAddress, displayAddress, namespace)
|
||||
service, err := mining.NewService(manager, listenAddress, displayAddress, apiNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new service: %w", err)
|
||||
}
|
||||
|
|
@ -64,8 +64,8 @@ var serveCmd = &cobra.Command{
|
|||
|
||||
// go func() { fmt.Print(">> ") } // keeps the interactive shell responsive while the API serves requests.
|
||||
go func() {
|
||||
fmt.Printf("Mining service started on http://%s:%d\n", displayHostName, port)
|
||||
fmt.Printf("Swagger documentation is available at http://%s:%d%s/index.html\n", displayHostName, port, service.SwaggerUIPath)
|
||||
fmt.Printf("Mining service started on http://%s:%d\n", displayHostName, servePort)
|
||||
fmt.Printf("Swagger documentation is available at http://%s:%d%s/index.html\n", displayHostName, servePort, service.SwaggerUIPath)
|
||||
fmt.Println("Entering interactive shell. Type 'exit' or 'quit' to stop.")
|
||||
fmt.Print(">> ")
|
||||
|
||||
|
|
@ -215,9 +215,9 @@ var serveCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func init() {
|
||||
serveCmd.Flags().StringVar(&host, "host", "127.0.0.1", "Host to listen on")
|
||||
serveCmd.Flags().IntVarP(&port, "port", "p", 9090, "Port to listen on")
|
||||
serveCmd.Flags().StringVarP(&namespace, "namespace", "n", "/api/v1/mining", "API namespace for the swagger UI")
|
||||
serveCmd.Flags().StringVar(&serveHost, "host", "127.0.0.1", "Host to listen on")
|
||||
serveCmd.Flags().IntVarP(&servePort, "port", "p", 9090, "Port to listen on")
|
||||
serveCmd.Flags().StringVarP(&apiNamespace, "namespace", "n", "/api/v1/mining", "API namespace for the swagger UI")
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Available presets:
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
displayHost := host
|
||||
displayHost := serveHost
|
||||
if displayHost == "0.0.0.0" {
|
||||
var err error
|
||||
displayHost, err = getLocalIP()
|
||||
|
|
@ -57,8 +57,8 @@ Available presets:
|
|||
displayHost = "localhost"
|
||||
}
|
||||
}
|
||||
displayAddress := fmt.Sprintf("%s:%d", displayHost, port)
|
||||
listenAddress := fmt.Sprintf("%s:%d", host, port)
|
||||
displayAddress := fmt.Sprintf("%s:%d", displayHost, servePort)
|
||||
listenAddress := fmt.Sprintf("%s:%d", serveHost, servePort)
|
||||
|
||||
// manager := mining.NewManagerForSimulation() // keeps simulated miners isolated from the real autostart state.
|
||||
manager := mining.NewManagerForSimulation()
|
||||
|
|
@ -82,8 +82,8 @@ Available presets:
|
|||
simulatedConfig.Name, simulatedConfig.Algorithm, simulatedConfig.BaseHashrate)
|
||||
}
|
||||
|
||||
// service, err := mining.NewService(manager, listenAddr, displayAddr, namespace) // serves the simulator on http://127.0.0.1:9090.
|
||||
service, err := mining.NewService(manager, listenAddress, displayAddress, namespace)
|
||||
// service, err := mining.NewService(manager, listenAddress, displayAddress, apiNamespace) // serves the simulator on http://127.0.0.1:9090.
|
||||
service, err := mining.NewService(manager, listenAddress, displayAddress, apiNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new service: %w", err)
|
||||
}
|
||||
|
|
@ -97,8 +97,8 @@ Available presets:
|
|||
}()
|
||||
|
||||
fmt.Printf("\n=== SIMULATION MODE ===\n")
|
||||
fmt.Printf("Mining service started on http://%s:%d\n", displayHost, port)
|
||||
fmt.Printf("Swagger documentation is available at http://%s:%d%s/swagger/index.html\n", displayHost, port, namespace)
|
||||
fmt.Printf("Mining service started on http://%s:%d\n", displayHost, servePort)
|
||||
fmt.Printf("Swagger documentation is available at http://%s:%d%s/swagger/index.html\n", displayHost, servePort, apiNamespace)
|
||||
fmt.Printf("\nSimulating %d miner(s). Press Ctrl+C to stop.\n", simulatedMinerCount)
|
||||
fmt.Printf("Note: All data is simulated - no actual mining is occurring.\n\n")
|
||||
|
||||
|
|
@ -161,9 +161,9 @@ func init() {
|
|||
simulateCmd.Flags().IntVar(&simulationHashrate, "hashrate", 0, "Custom base hashrate (overrides preset)")
|
||||
simulateCmd.Flags().StringVar(&simulationAlgorithm, "algorithm", "", "Custom algorithm (overrides preset)")
|
||||
|
||||
simulateCmd.Flags().StringVar(&host, "host", "127.0.0.1", "Host to listen on")
|
||||
simulateCmd.Flags().IntVarP(&port, "port", "p", 9090, "Port to listen on")
|
||||
simulateCmd.Flags().StringVarP(&namespace, "namespace", "n", "/api/v1/mining", "API namespace")
|
||||
simulateCmd.Flags().StringVar(&serveHost, "host", "127.0.0.1", "Host to listen on")
|
||||
simulateCmd.Flags().IntVarP(&servePort, "port", "p", 9090, "Port to listen on")
|
||||
simulateCmd.Flags().StringVarP(&apiNamespace, "namespace", "n", "/api/v1/mining", "API namespace")
|
||||
|
||||
rootCmd.AddCommand(simulateCmd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ type SystemInfo struct {
|
|||
InstalledMinersInfo []*InstallationDetails `json:"installed_miners_info"`
|
||||
}
|
||||
|
||||
// Config{Miner: "xmrig", Pool: "stratum+tcp://pool.example.com:3333", Wallet: "4ABC...", Threads: 4}
|
||||
// Config{Miner: "xmrig", Pool: "stratum+tcp://pool.example.com:3333", Wallet: "44Affq5kSiGBoZ...", Threads: 4}
|
||||
type Config struct {
|
||||
Miner string `json:"miner"`
|
||||
Pool string `json:"pool"`
|
||||
|
|
@ -131,7 +131,7 @@ type Config struct {
|
|||
Seed string `json:"seed,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
NoDMI bool `json:"noDMI,omitempty"`
|
||||
// GPU-specific options (for XMRig dual CPU+GPU mining)
|
||||
// Config{GPUEnabled: true, GPUPool: "stratum+ssl://gpu.pool.example.com:4444", GPUWallet: "44Affq5kSiGBoZ..."} enables the GPU mining fields.
|
||||
GPUEnabled bool `json:"gpuEnabled,omitempty"` // Enable GPU mining
|
||||
GPUPool string `json:"gpuPool,omitempty"` // Separate pool for GPU (can differ from CPU)
|
||||
GPUWallet string `json:"gpuWallet,omitempty"` // Wallet for GPU pool (defaults to main Wallet)
|
||||
|
|
@ -147,61 +147,61 @@ type Config struct {
|
|||
}
|
||||
|
||||
// if err := config.Validate(); err != nil { return err }
|
||||
func (c *Config) Validate() error {
|
||||
func (config *Config) Validate() error {
|
||||
// Pool URL validation
|
||||
if c.Pool != "" {
|
||||
if config.Pool != "" {
|
||||
// Block shell metacharacters in pool URL
|
||||
if containsShellChars(c.Pool) {
|
||||
if containsShellChars(config.Pool) {
|
||||
return ErrInvalidConfig("pool URL contains invalid characters")
|
||||
}
|
||||
}
|
||||
|
||||
// Wallet validation (basic alphanumeric + special chars allowed in addresses)
|
||||
if c.Wallet != "" {
|
||||
if containsShellChars(c.Wallet) {
|
||||
if config.Wallet != "" {
|
||||
if containsShellChars(config.Wallet) {
|
||||
return ErrInvalidConfig("wallet address contains invalid characters")
|
||||
}
|
||||
// Most wallet addresses are 40-128 chars
|
||||
if len(c.Wallet) > 256 {
|
||||
if len(config.Wallet) > 256 {
|
||||
return ErrInvalidConfig("wallet address too long (max 256 chars)")
|
||||
}
|
||||
}
|
||||
|
||||
// Thread count validation
|
||||
if c.Threads < 0 {
|
||||
if config.Threads < 0 {
|
||||
return ErrInvalidConfig("threads cannot be negative")
|
||||
}
|
||||
if c.Threads > 1024 {
|
||||
if config.Threads > 1024 {
|
||||
return ErrInvalidConfig("threads value too high (max 1024)")
|
||||
}
|
||||
|
||||
// Algorithm validation (alphanumeric, dash, slash)
|
||||
if c.Algo != "" {
|
||||
if !isValidAlgo(c.Algo) {
|
||||
if config.Algo != "" {
|
||||
if !isValidAlgo(config.Algo) {
|
||||
return ErrInvalidConfig("algorithm name contains invalid characters")
|
||||
}
|
||||
}
|
||||
|
||||
// Intensity validation
|
||||
if c.Intensity < 0 || c.Intensity > 100 {
|
||||
if config.Intensity < 0 || config.Intensity > 100 {
|
||||
return ErrInvalidConfig("intensity must be between 0 and 100")
|
||||
}
|
||||
if c.GPUIntensity < 0 || c.GPUIntensity > 100 {
|
||||
if config.GPUIntensity < 0 || config.GPUIntensity > 100 {
|
||||
return ErrInvalidConfig("GPU intensity must be between 0 and 100")
|
||||
}
|
||||
|
||||
// Donate level validation
|
||||
if c.DonateLevel < 0 || c.DonateLevel > 100 {
|
||||
if config.DonateLevel < 0 || config.DonateLevel > 100 {
|
||||
return ErrInvalidConfig("donate level must be between 0 and 100")
|
||||
}
|
||||
|
||||
// CLIArgs validation - check for shell metacharacters
|
||||
if c.CLIArgs != "" {
|
||||
if containsShellChars(c.CLIArgs) {
|
||||
if config.CLIArgs != "" {
|
||||
if containsShellChars(config.CLIArgs) {
|
||||
return ErrInvalidConfig("CLI arguments contain invalid characters")
|
||||
}
|
||||
// Limit length to prevent abuse
|
||||
if len(c.CLIArgs) > 1024 {
|
||||
if len(config.CLIArgs) > 1024 {
|
||||
return ErrInvalidConfig("CLI arguments too long (max 1024 chars)")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ func sanitizeErrorDetails(details string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// respondWithError(c, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", "process exited with code 1")
|
||||
func respondWithError(c *gin.Context, status int, code string, message string, details string) {
|
||||
// respondWithError(requestContext, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", "process exited with code 1")
|
||||
func respondWithError(requestContext *gin.Context, status int, code string, message string, details string) {
|
||||
apiError := APIError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
|
|
@ -80,7 +80,7 @@ func respondWithError(c *gin.Context, status int, code string, message string, d
|
|||
Retryable: isRetryableError(status),
|
||||
}
|
||||
|
||||
// respondWithError(c, http.StatusServiceUnavailable, ErrCodeServiceUnavailable, "service unavailable", "database offline") adds a retry suggestion.
|
||||
// respondWithError(requestContext, http.StatusServiceUnavailable, ErrCodeServiceUnavailable, "service unavailable", "database offline") adds a retry suggestion.
|
||||
switch code {
|
||||
case ErrCodeMinerNotFound:
|
||||
apiError.Suggestion = "Check the miner name or install the miner first"
|
||||
|
|
@ -97,12 +97,12 @@ func respondWithError(c *gin.Context, status int, code string, message string, d
|
|||
apiError.Retryable = true
|
||||
}
|
||||
|
||||
c.JSON(status, apiError)
|
||||
requestContext.JSON(status, apiError)
|
||||
}
|
||||
|
||||
// respondWithMiningError(c, ErrMinerNotFound("xmrig"))
|
||||
// respondWithMiningError(c, ErrInternal("failed to read config").WithCause(err))
|
||||
func respondWithMiningError(c *gin.Context, err *MiningError) {
|
||||
// respondWithMiningError(requestContext, ErrMinerNotFound("xmrig"))
|
||||
// respondWithMiningError(requestContext, ErrInternal("failed to read config").WithCause(err))
|
||||
func respondWithMiningError(requestContext *gin.Context, err *MiningError) {
|
||||
details := ""
|
||||
if err.Cause != nil {
|
||||
details = err.Cause.Error()
|
||||
|
|
@ -122,7 +122,7 @@ func respondWithMiningError(c *gin.Context, err *MiningError) {
|
|||
Retryable: err.Retryable,
|
||||
}
|
||||
|
||||
c.JSON(err.StatusCode(), apiError)
|
||||
requestContext.JSON(err.StatusCode(), apiError)
|
||||
}
|
||||
|
||||
// isRetryableError(http.StatusServiceUnavailable) returns true.
|
||||
|
|
@ -135,57 +135,57 @@ func isRetryableError(status int) bool {
|
|||
|
||||
// router.Use(securityHeadersMiddleware()) adds X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none' to GET /api/v1/mining/status.
|
||||
func securityHeadersMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
c.Header("X-Frame-Options", "DENY")
|
||||
c.Header("X-XSS-Protection", "1; mode=block")
|
||||
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
c.Header("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
|
||||
c.Next()
|
||||
return func(requestContext *gin.Context) {
|
||||
requestContext.Header("X-Content-Type-Options", "nosniff")
|
||||
requestContext.Header("X-Frame-Options", "DENY")
|
||||
requestContext.Header("X-XSS-Protection", "1; mode=block")
|
||||
requestContext.Header("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
requestContext.Header("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
|
||||
requestContext.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// router.Use(contentTypeValidationMiddleware()) returns 415 for POST /api/v1/mining/profiles when Content-Type is text/plain.
|
||||
func contentTypeValidationMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
return func(requestContext *gin.Context) {
|
||||
method := requestContext.Request.Method
|
||||
if method != http.MethodPost && method != http.MethodPut && method != http.MethodPatch {
|
||||
c.Next()
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if c.Request.ContentLength == 0 {
|
||||
c.Next()
|
||||
if requestContext.Request.ContentLength == 0 {
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
contentType := requestContext.GetHeader("Content-Type")
|
||||
if strings.HasPrefix(contentType, "application/json") ||
|
||||
strings.HasPrefix(contentType, "application/x-www-form-urlencoded") ||
|
||||
strings.HasPrefix(contentType, "multipart/form-data") {
|
||||
c.Next()
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
respondWithError(c, http.StatusUnsupportedMediaType, ErrCodeInvalidInput,
|
||||
respondWithError(requestContext, http.StatusUnsupportedMediaType, ErrCodeInvalidInput,
|
||||
"Unsupported Content-Type",
|
||||
"Use application/json for API requests")
|
||||
c.Abort()
|
||||
requestContext.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
// router.Use(requestIDMiddleware()) keeps X-Request-ID: trace-123 stable across the request and response.
|
||||
func requestIDMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
return func(requestContext *gin.Context) {
|
||||
requestID := requestContext.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
}
|
||||
|
||||
c.Set("requestID", requestID)
|
||||
c.Header("X-Request-ID", requestID)
|
||||
requestContext.Set("requestID", requestID)
|
||||
requestContext.Header("X-Request-ID", requestID)
|
||||
|
||||
c.Next()
|
||||
requestContext.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,9 +198,9 @@ func generateRequestID() string {
|
|||
return strconv.FormatInt(time.Now().UnixMilli(), 10) + "-" + hex.EncodeToString(randomBytes)
|
||||
}
|
||||
|
||||
// requestID := requestIDFromContext(c) returns "trace-123" after requestIDMiddleware stores the header.
|
||||
func requestIDFromContext(c *gin.Context) string {
|
||||
if requestIDValue, exists := c.Get("requestID"); exists {
|
||||
// requestID := requestIDFromContext(requestContext) returns "trace-123" after requestIDMiddleware stores the header.
|
||||
func requestIDFromContext(requestContext *gin.Context) string {
|
||||
if requestIDValue, exists := requestContext.Get("requestID"); exists {
|
||||
if stringValue, ok := requestIDValue.(string); ok {
|
||||
return stringValue
|
||||
}
|
||||
|
|
@ -208,13 +208,13 @@ func requestIDFromContext(c *gin.Context) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// logWithRequestContext(c, "error", "miner failed to start", logging.Fields{"type": "xmrig", "name": "xmrig-main"})
|
||||
// logWithRequestContext(c, "info", "miner started", logging.Fields{"name": "xmrig-1", "request_id": "trace-123"})
|
||||
func logWithRequestContext(c *gin.Context, level string, message string, fields logging.Fields) {
|
||||
// logWithRequestContext(requestContext, "error", "miner failed to start", logging.Fields{"type": "xmrig", "name": "xmrig-main"})
|
||||
// logWithRequestContext(requestContext, "info", "miner started", logging.Fields{"name": "xmrig-1", "request_id": "trace-123"})
|
||||
func logWithRequestContext(requestContext *gin.Context, level string, message string, fields logging.Fields) {
|
||||
if fields == nil {
|
||||
fields = logging.Fields{}
|
||||
}
|
||||
if requestID := requestIDFromContext(c); requestID != "" {
|
||||
if requestID := requestIDFromContext(requestContext); requestID != "" {
|
||||
fields["request_id"] = requestID
|
||||
}
|
||||
switch level {
|
||||
|
|
@ -231,38 +231,38 @@ func logWithRequestContext(c *gin.Context, level string, message string, fields
|
|||
|
||||
// csrfMiddleware() allows POST /api/v1/mining/profiles when the request includes Authorization or X-Requested-With.
|
||||
func csrfMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
return func(requestContext *gin.Context) {
|
||||
// Only check state-changing methods such as POST /api/v1/mining/profiles.
|
||||
method := c.Request.Method
|
||||
method := requestContext.Request.Method
|
||||
if method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions {
|
||||
c.Next()
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests like `Authorization: Digest username="miner-admin"` from API clients.
|
||||
if c.GetHeader("Authorization") != "" {
|
||||
c.Next()
|
||||
if requestContext.GetHeader("Authorization") != "" {
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests like `X-Requested-With: XMLHttpRequest` from browser clients.
|
||||
if c.GetHeader("X-Requested-With") != "" {
|
||||
c.Next()
|
||||
if requestContext.GetHeader("X-Requested-With") != "" {
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests like `Content-Type: application/json` from API clients.
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
contentType := requestContext.GetHeader("Content-Type")
|
||||
if strings.HasPrefix(contentType, "application/json") {
|
||||
c.Next()
|
||||
requestContext.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Reject requests like `POST /api/v1/mining/profiles` from a plain HTML form.
|
||||
respondWithError(c, http.StatusForbidden, "CSRF_PROTECTION",
|
||||
respondWithError(requestContext, http.StatusForbidden, "CSRF_PROTECTION",
|
||||
"Request blocked by CSRF protection",
|
||||
"Include X-Requested-With header or use application/json content type")
|
||||
c.Abort()
|
||||
requestContext.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue