gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/routes.go (about)

     1  package api
     2  
     3  import (
     4  	"net/http"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/julienschmidt/httprouter"
     9  	"gitlab.com/SiaPrime/SiaPrime/build"
    10  )
    11  
    12  // buildHttpRoutes sets up and returns an * httprouter.Router.
    13  // it connected the Router to the given api using the required
    14  // parameters: requiredUserAgent and requiredPassword
    15  func (api *API) buildHTTPRoutes() {
    16  	router := httprouter.New()
    17  	requiredPassword := api.requiredPassword
    18  	requiredUserAgent := api.requiredUserAgent
    19  
    20  	router.NotFound = http.HandlerFunc(UnrecognizedCallHandler)
    21  	router.RedirectTrailingSlash = false
    22  
    23  	// Daemon API Calls
    24  	router.GET("/daemon/constants", api.daemonConstantsHandler)
    25  	router.GET("/daemon/version", api.daemonVersionHandler)
    26  	router.GET("/daemon/update", api.daemonUpdateHandlerGET)
    27  	router.POST("/daemon/update", api.daemonUpdateHandlerPOST)
    28  	router.GET("/daemon/stop", RequirePassword(api.daemonStopHandler, requiredPassword))
    29  	router.GET("/daemon/settings", api.daemonSettingsHandlerGET)
    30  	router.POST("/daemon/settings", api.daemonSettingsHandlerPOST)
    31  
    32  	// Consensus API Calls
    33  	if api.cs != nil {
    34  		router.GET("/consensus", api.consensusHandler)
    35  		router.GET("/consensus/blocks", api.consensusBlocksHandler)
    36  		router.POST("/consensus/validate/transactionset", api.consensusValidateTransactionsetHandler)
    37  		router.GET("/consensus/blocks/:height", api.consensusBlocksHandlerSanasol)
    38  		router.GET("/consensus/future/:height", api.consensusFutureBlocksHandler)
    39  	}
    40  
    41  	// Explorer API Calls
    42  	if api.explorer != nil {
    43  		router.GET("/explorer", api.explorerHandler)
    44  		router.GET("/explorer/blocks/:height", api.explorerBlocksHandler)
    45  		router.GET("/explorer/hashes/:hash", api.explorerHashHandler)
    46  	}
    47  
    48  	// Gateway API Calls
    49  	if api.gateway != nil {
    50  		router.GET("/gateway", api.gatewayHandlerGET)
    51  		router.POST("/gateway", api.gatewayHandlerPOST)
    52  		router.POST("/gateway/connect/:netaddress", RequirePassword(api.gatewayConnectHandler, requiredPassword))
    53  		router.POST("/gateway/disconnect/:netaddress", RequirePassword(api.gatewayDisconnectHandler, requiredPassword))
    54  	}
    55  
    56  	// Host API Calls
    57  	if api.host != nil {
    58  		// Calls directly pertaining to the host.
    59  		router.GET("/host", api.hostHandlerGET)                                                   // Get the host status.
    60  		router.POST("/host", RequirePassword(api.hostHandlerPOST, requiredPassword))              // Change the settings of the host.
    61  		router.POST("/host/announce", RequirePassword(api.hostAnnounceHandler, requiredPassword)) // Announce the host to the network.
    62  		router.GET("/host/contracts", api.hostContractInfoHandler)                                // Get info about contracts.
    63  		router.GET("/host/estimatescore", api.hostEstimateScoreGET)
    64  
    65  		// Calls pertaining to the storage manager that the host uses.
    66  		router.GET("/host/storage", api.storageHandler)
    67  		router.POST("/host/storage/folders/add", RequirePassword(api.storageFoldersAddHandler, requiredPassword))
    68  		router.POST("/host/storage/folders/remove", RequirePassword(api.storageFoldersRemoveHandler, requiredPassword))
    69  		router.POST("/host/storage/folders/resize", RequirePassword(api.storageFoldersResizeHandler, requiredPassword))
    70  		router.POST("/host/storage/sectors/delete/:merkleroot", RequirePassword(api.storageSectorsDeleteHandler, requiredPassword))
    71  	}
    72  
    73  	// Miner API Calls
    74  	if api.miner != nil {
    75  		router.GET("/miner", api.minerHandler)
    76  		router.GET("/miner/header", RequirePassword(api.minerHeaderHandlerGET, requiredPassword))
    77  		router.POST("/miner/header", RequirePassword(api.minerHeaderHandlerPOST, requiredPassword))
    78  		router.GET("/miner/start", RequirePassword(api.minerStartHandler, requiredPassword))
    79  		router.GET("/miner/stop", RequirePassword(api.minerStopHandler, requiredPassword))
    80  	}
    81  
    82  	// Mining pool API Calls
    83  	if api.pool != nil {
    84  		router.GET("/pool", api.poolHandler)
    85  		// router.GET("/pool/clients", api.poolGetClientsInfo)
    86  		// router.GET("/pool/client", api.poolGetClientInfo)
    87  		router.POST("/pool/config", RequirePassword(api.poolConfigHandlerPOST, requiredPassword)) // Change the settings of the host.
    88  		router.GET("/pool/config", RequirePassword(api.poolConfigHandler, requiredPassword))
    89  		// router.GET("/pool/blocks", api.poolGetBlocksInfo)
    90  		// router.GET("/pool/block", api.poolGetBlockInfo)
    91  	}
    92  
    93  	// Renter API Calls
    94  	if api.renter != nil {
    95  		router.GET("/renter", api.renterHandlerGET)
    96  		router.POST("/renter", RequirePassword(api.renterHandlerPOST, requiredPassword))
    97  		router.GET("/renter/backups", RequirePassword(api.renterBackupsHandlerGET, requiredPassword))
    98  		router.POST("/renter/backups/create", RequirePassword(api.renterBackupsCreateHandlerPOST, requiredPassword))
    99  		router.POST("/renter/backups/restore", RequirePassword(api.renterBackupsRestoreHandlerGET, requiredPassword))
   100  		router.POST("/renter/contract/cancel", RequirePassword(api.renterContractCancelHandler, requiredPassword))
   101  		router.GET("/renter/contracts", api.renterContractsHandler)
   102  		router.GET("/renter/downloads", api.renterDownloadsHandler)
   103  		router.POST("/renter/downloads/clear", RequirePassword(api.renterClearDownloadsHandler, requiredPassword))
   104  		router.GET("/renter/files", api.renterFilesHandler)
   105  		router.GET("/renter/file/*siapath", api.renterFileHandlerGET)
   106  		router.POST("/renter/file/*siapath", RequirePassword(api.renterFileHandlerPOST, requiredPassword))
   107  		router.GET("/renter/prices", api.renterPricesHandler)
   108  		router.POST("/renter/recoveryscan", RequirePassword(api.renterRecoveryScanHandlerPOST, requiredPassword))
   109  		router.GET("/renter/recoveryscan", api.renterRecoveryScanHandlerGET)
   110  
   111  		// TODO: re-enable these routes once the new .sia format has been
   112  		// standardized and implemented.
   113  		// router.POST("/renter/load", RequirePassword(api.renterLoadHandler, requiredPassword))
   114  		// router.POST("/renter/loadascii", RequirePassword(api.renterLoadAsciiHandler, requiredPassword))
   115  		// router.GET("/renter/share", RequirePassword(api.renterShareHandler, requiredPassword))
   116  		// router.GET("/renter/shareascii", RequirePassword(api.renterShareAsciiHandler, requiredPassword))
   117  
   118  		router.POST("/renter/delete/*siapath", RequirePassword(api.renterDeleteHandler, requiredPassword))
   119  		router.GET("/renter/download/*siapath", RequirePassword(api.renterDownloadHandler, requiredPassword))
   120  		router.POST("/renter/download/cancel", RequirePassword(api.renterCancelDownloadHandler, requiredPassword))
   121  		router.GET("/renter/downloadasync/*siapath", RequirePassword(api.renterDownloadAsyncHandler, requiredPassword))
   122  		router.POST("/renter/rename/*siapath", RequirePassword(api.renterRenameHandler, requiredPassword))
   123  		router.GET("/renter/stream/*siapath", api.renterStreamHandler)
   124  		router.POST("/renter/upload/*siapath", RequirePassword(api.renterUploadHandler, requiredPassword))
   125  		router.POST("/renter/uploadstream/*siapath", RequirePassword(api.renterUploadStreamHandler, requiredPassword))
   126  		router.POST("/renter/validatesiapath/*siapath", RequirePassword(api.renterValidateSiaPathHandler, requiredPassword))
   127  
   128  		// Directory endpoints
   129  		router.POST("/renter/dir/*siapath", RequirePassword(api.renterDirHandlerPOST, requiredPassword))
   130  		router.GET("/renter/dir/*siapath", api.renterDirHandlerGET)
   131  
   132  		// HostDB endpoints.
   133  		router.GET("/hostdb", api.hostdbHandler)
   134  		router.GET("/hostdb/active", api.hostdbActiveHandler)
   135  		router.GET("/hostdb/all", api.hostdbAllHandler)
   136  		router.GET("/hostdb/hosts/:pubkey", api.hostdbHostsHandler)
   137  		router.GET("/hostdb/filtermode", api.hostdbFilterModeHandlerGET)
   138  		router.POST("/hostdb/filtermode", RequirePassword(api.hostdbFilterModeHandlerPOST, requiredPassword))
   139  
   140  		// Deprecated endpoints.
   141  		router.POST("/renter/backup", RequirePassword(api.renterBackupHandlerPOST, requiredPassword))
   142  		router.POST("/renter/recoverbackup", RequirePassword(api.renterLoadBackupHandlerPOST, requiredPassword))
   143  	}
   144  
   145  	if api.stratumminer != nil {
   146  		router.GET("/stratumminer", api.stratumminerHandler)
   147  		router.POST("/stratumminer/start", api.stratumminerStartHandler)
   148  		router.POST("/stratumminer/stop", api.stratumminerStopHandler)
   149  	}
   150  
   151  	// Transaction pool API Calls
   152  	if api.tpool != nil {
   153  		router.GET("/tpool/fee", api.tpoolFeeHandlerGET)
   154  		router.GET("/tpool/raw/:id", api.tpoolRawHandlerGET)
   155  		router.POST("/tpool/raw", api.tpoolRawHandlerPOST)
   156  		router.GET("/tpool/confirmed/:id", api.tpoolConfirmedGET)
   157  
   158  		// TODO: re-enable this route once the transaction pool API has been finalized
   159  		//router.GET("/transactionpool/transactions", api.transactionpoolTransactionsHandler)
   160  	}
   161  
   162  	// Wallet API Calls
   163  	if api.wallet != nil {
   164  		router.GET("/wallet", api.walletHandler)
   165  		router.POST("/wallet/033x", RequirePassword(api.wallet033xHandler, requiredPassword))
   166  		router.GET("/wallet/address", RequirePassword(api.walletAddressHandler, requiredPassword))
   167  		router.GET("/wallet/addresses", api.walletAddressesHandler)
   168  		router.GET("/wallet/seedaddrs", api.walletSeedAddressesHandler)
   169  		router.GET("/wallet/backup", RequirePassword(api.walletBackupHandler, requiredPassword))
   170  		router.POST("/wallet/init", RequirePassword(api.walletInitHandler, requiredPassword))
   171  		router.POST("/wallet/init/seed", RequirePassword(api.walletInitSeedHandler, requiredPassword))
   172  		router.POST("/wallet/lock", RequirePassword(api.walletLockHandler, requiredPassword))
   173  		router.POST("/wallet/seed", RequirePassword(api.walletSeedHandler, requiredPassword))
   174  		router.GET("/wallet/seeds", RequirePassword(api.walletSeedsHandler, requiredPassword))
   175  		router.POST("/wallet/siacoins", RequirePassword(api.walletSiacoinsHandler, requiredPassword))
   176  		router.POST("/wallet/siafunds", RequirePassword(api.walletSiafundsHandler, requiredPassword))
   177  		router.POST("/wallet/siagkey", RequirePassword(api.walletSiagkeyHandler, requiredPassword))
   178  		router.POST("/wallet/sweep/seed", RequirePassword(api.walletSweepSeedHandler, requiredPassword))
   179  		router.GET("/wallet/transaction/:id", api.walletTransactionHandler)
   180  		router.GET("/wallet/transactions", api.walletTransactionsHandler)
   181  		router.GET("/wallet/transactions/:addr", api.walletTransactionsAddrHandler)
   182  		router.GET("/wallet/verify/address/:addr", api.walletVerifyAddressHandler)
   183  		router.POST("/wallet/unlock", RequirePassword(api.walletUnlockHandler, requiredPassword))
   184  		router.POST("/wallet/changepassword", RequirePassword(api.walletChangePasswordHandler, requiredPassword))
   185  		router.GET("/wallet/unlockconditions/:addr", RequirePassword(api.walletUnlockConditionsHandlerGET, requiredPassword))
   186  		router.POST("/wallet/unlockconditions", RequirePassword(api.walletUnlockConditionsHandlerPOST, requiredPassword))
   187  		router.GET("/wallet/unspent", RequirePassword(api.walletUnspentHandler, requiredPassword))
   188  		router.POST("/wallet/sign", RequirePassword(api.walletSignHandler, requiredPassword))
   189  		router.GET("/wallet/watch", RequirePassword(api.walletWatchHandlerGET, requiredPassword))
   190  		router.POST("/wallet/watch", RequirePassword(api.walletWatchHandlerPOST, requiredPassword))
   191  	}
   192  
   193  	// Apply UserAgent middleware and return the Router
   194  	api.routerMu.Lock()
   195  	api.router = cleanCloseHandler(RequireUserAgent(router, requiredUserAgent))
   196  	api.routerMu.Unlock()
   197  	return
   198  }
   199  
   200  // cleanCloseHandler wraps the entire API, ensuring that underlying conns are
   201  // not leaked if the remote end closes the connection before the underlying
   202  // handler finishes.
   203  func cleanCloseHandler(next http.Handler) http.Handler {
   204  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   205  		// Close this file handle either when the function completes or when the
   206  		// connection is done.
   207  		done := make(chan struct{})
   208  		go func(w http.ResponseWriter, r *http.Request) {
   209  			defer close(done)
   210  			next.ServeHTTP(w, r)
   211  		}(w, r)
   212  		select {
   213  		case <-done:
   214  		}
   215  
   216  		// Sanity check - thread should not take more than an hour to return. This
   217  		// must be done in a goroutine, otherwise the server will not close the
   218  		// underlying socket for this API call.
   219  		timer := time.NewTimer(time.Minute * 60)
   220  		go func() {
   221  			select {
   222  			case <-done:
   223  				timer.Stop()
   224  			case <-timer.C:
   225  				build.Severe("api call is taking more than 60 minutes to return:", r.URL.Path)
   226  			}
   227  		}()
   228  	})
   229  }
   230  
   231  // RequireUserAgent is middleware that requires all requests to set a
   232  // UserAgent that contains the specified string.
   233  func RequireUserAgent(h http.Handler, ua string) http.Handler {
   234  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   235  		if !strings.Contains(req.UserAgent(), ua) && !isUnrestricted(req) {
   236  			WriteError(w, Error{"Browser access disabled due to security vulnerability. Use Sia-UI or siac."}, http.StatusBadRequest)
   237  			return
   238  		}
   239  		h.ServeHTTP(w, req)
   240  	})
   241  }
   242  
   243  // RequirePassword is middleware that requires a request to authenticate with a
   244  // password using HTTP basic auth. Usernames are ignored. Empty passwords
   245  // indicate no authentication is required.
   246  func RequirePassword(h httprouter.Handle, password string) httprouter.Handle {
   247  	// An empty password is equivalent to no password.
   248  	if password == "" {
   249  		return h
   250  	}
   251  	return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   252  		_, pass, ok := req.BasicAuth()
   253  		if !ok || pass != password {
   254  			w.Header().Set("WWW-Authenticate", "Basic realm=\"SiaAPI\"")
   255  			WriteError(w, Error{"API authentication failed."}, http.StatusUnauthorized)
   256  			return
   257  		}
   258  		h(w, req, ps)
   259  	}
   260  }
   261  
   262  // isUnrestricted checks if a request may bypass the useragent check.
   263  func isUnrestricted(req *http.Request) bool {
   264  	return strings.HasPrefix(req.URL.Path, "/renter/stream/")
   265  }