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 }