Skip to content
Snippets Groups Projects
Commit 2a39284b authored by Luca Moser's avatar Luca Moser
Browse files

refactors webauth plugin and client

parent b19a815d
No related branches found
No related tags found
No related merge requests found
// Implements a very simple wrapper for GoShimmer's HTTP API . // Implements a very simple wrapper for GoShimmer's web API .
package goshimmer package goshimmer
import ( import (
...@@ -6,16 +6,18 @@ import ( ...@@ -6,16 +6,18 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
webapi_broadcastData "github.com/iotaledger/goshimmer/plugins/webapi/broadcastData" webapi_broadcastData "github.com/iotaledger/goshimmer/plugins/webapi/broadcastData"
webapi_findTransactions "github.com/iotaledger/goshimmer/plugins/webapi/findTransactionHashes" webapi_findTransactionHashes "github.com/iotaledger/goshimmer/plugins/webapi/findTransactionHashes"
webapi_getNeighbors "github.com/iotaledger/goshimmer/plugins/webapi/getNeighbors" webapi_getNeighbors "github.com/iotaledger/goshimmer/plugins/webapi/getNeighbors"
webapi_getTransactions "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionObjectsByHash" webapi_getTransactionObjectsByHash "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionObjectsByHash"
webapi_getTrytes "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionTrytesByHash" webapi_getTransactionTrytesByHash "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionTrytesByHash"
webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi/gtta" webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi/gtta"
webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi/spammer" webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi/spammer"
webapi_auth "github.com/iotaledger/goshimmer/plugins/webauth"
"github.com/iotaledger/iota.go/consts" "github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/guards" "github.com/iotaledger/iota.go/guards"
"github.com/iotaledger/iota.go/trinary" "github.com/iotaledger/iota.go/trinary"
...@@ -25,17 +27,19 @@ var ( ...@@ -25,17 +27,19 @@ var (
ErrBadRequest = errors.New("bad request") ErrBadRequest = errors.New("bad request")
ErrInternalServerError = errors.New("internal server error") ErrInternalServerError = errors.New("internal server error")
ErrNotFound = errors.New("not found") ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrUnknownError = errors.New("unknown error") ErrUnknownError = errors.New("unknown error")
) )
const ( const (
routeBroadcastData = "broadcastData" routeBroadcastData = "broadcastData"
routeGetTrytes = "getTransactionTrytesByHash" routeGetTransactionTrytesByHash = "getTransactionTrytesByHash"
routeGetTransactions = "getTransactionObjectsByHash" routeGetTransactionObjectsByHash = "getTransactionObjectsByHash"
routeFindTransactions = "findTransactionHashes" routeFindTransactionsHashes = "findTransactionHashes"
routeGetNeighbors = "getNeighbors" routeGetNeighbors = "getNeighbors"
routeGetTransactionsToApprove = "getTransactionsToApprove" routeGetTransactionsToApprove = "getTransactionsToApprove"
routeSpammer = "spammer" routeSpammer = "spammer"
routeLogin = "login"
contentTypeJSON = "application/json" contentTypeJSON = "application/json"
) )
...@@ -47,9 +51,11 @@ func NewGoShimmerAPI(node string, httpClient ...http.Client) *GoShimmerAPI { ...@@ -47,9 +51,11 @@ func NewGoShimmerAPI(node string, httpClient ...http.Client) *GoShimmerAPI {
return &GoShimmerAPI{node: node} return &GoShimmerAPI{node: node}
} }
// GoShimmerAPI is an API wrapper over the web API of GoShimmer.
type GoShimmerAPI struct { type GoShimmerAPI struct {
httpClient http.Client httpClient http.Client
node string node string
jwt string
} }
type errorresponse struct { type errorresponse struct {
...@@ -79,34 +85,85 @@ func interpretBody(res *http.Response, decodeTo interface{}) error { ...@@ -79,34 +85,85 @@ func interpretBody(res *http.Response, decodeTo interface{}) error {
return fmt.Errorf("%w: %s", ErrNotFound, errRes.Error) return fmt.Errorf("%w: %s", ErrNotFound, errRes.Error)
case http.StatusBadRequest: case http.StatusBadRequest:
return fmt.Errorf("%w: %s", ErrBadRequest, errRes.Error) return fmt.Errorf("%w: %s", ErrBadRequest, errRes.Error)
case http.StatusUnauthorized:
return fmt.Errorf("%w: %s", ErrUnauthorized, errRes.Error)
} }
return fmt.Errorf("%w: %s", ErrUnknownError, errRes.Error) return fmt.Errorf("%w: %s", ErrUnknownError, errRes.Error)
} }
func (api *GoShimmerAPI) BroadcastData(targetAddress trinary.Trytes, data string) (trinary.Hash, error) { func (api *GoShimmerAPI) do(method string, route string, reqObj interface{}, resObj interface{}) error {
if !guards.IsHash(targetAddress) { // marshal request object
return "", fmt.Errorf("%w: invalid address: %s", consts.ErrInvalidHash, targetAddress) var data []byte
if reqObj != nil {
var err error
data, err = json.Marshal(reqObj)
if err != nil {
return err
}
} }
reqBytes, err := json.Marshal(&webapi_broadcastData.Request{Address: targetAddress, Data: data}) // construct request
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", api.node, route), func() io.Reader {
if data == nil {
return nil
}
return bytes.NewReader(data)
}())
if err != nil { if err != nil {
return "", err return err
}
// add authorization header with JWT
if len(api.jwt) > 0 {
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", api.jwt))
} }
res, err := api.httpClient.Post(fmt.Sprintf("%s/%s", api.node, routeBroadcastData), contentTypeJSON, bytes.NewReader(reqBytes)) // make the request
res, err := api.httpClient.Do(req)
if err != nil { if err != nil {
return "", err return err
} }
resObj := &webapi_broadcastData.Response{} if resObj == nil {
return nil
}
// write response into response object
if err := interpretBody(res, resObj); err != nil { if err := interpretBody(res, resObj); err != nil {
return err
}
return nil
}
// Login authorizes this API instance against the web API.
// You must call this function before any before any other call, if the web-auth plugin is enabled.
func (api *GoShimmerAPI) Login(username string, password string) error {
res := &webapi_auth.Response{}
if err := api.do(http.MethodPost, routeLogin,
&webapi_auth.Request{Username: username, Password: password}, res); err != nil {
return err
}
api.jwt = res.Token
return nil
}
// BroadcastData sends the given data by creating a zero value transaction in the backend targeting the given address.
func (api *GoShimmerAPI) BroadcastData(targetAddress trinary.Trytes, data string) (trinary.Hash, error) {
if !guards.IsHash(targetAddress) {
return "", fmt.Errorf("%w: invalid address: %s", consts.ErrInvalidHash, targetAddress)
}
res := &webapi_broadcastData.Response{}
if err := api.do(http.MethodPost, routeBroadcastData,
&webapi_broadcastData.Request{Address: targetAddress, Data: data}, res); err != nil {
return "", err return "", err
} }
return resObj.Hash, nil return res.Hash, nil
} }
// GetTransactionTrytesByHash gets the corresponding transaction trytes given the transaction hashes.
func (api *GoShimmerAPI) GetTransactionTrytesByHash(txHashes trinary.Hashes) ([]trinary.Trytes, error) { func (api *GoShimmerAPI) GetTransactionTrytesByHash(txHashes trinary.Hashes) ([]trinary.Trytes, error) {
for _, hash := range txHashes { for _, hash := range txHashes {
if !guards.IsTrytes(hash) { if !guards.IsTrytes(hash) {
...@@ -114,117 +171,82 @@ func (api *GoShimmerAPI) GetTransactionTrytesByHash(txHashes trinary.Hashes) ([] ...@@ -114,117 +171,82 @@ func (api *GoShimmerAPI) GetTransactionTrytesByHash(txHashes trinary.Hashes) ([]
} }
} }
reqBytes, err := json.Marshal(&webapi_getTrytes.Request{Hashes: txHashes}) res := &webapi_getTransactionTrytesByHash.Response{}
if err != nil { if err := api.do(http.MethodPost, routeGetTransactionTrytesByHash,
&webapi_getTransactionTrytesByHash.Request{Hashes: txHashes}, res); err != nil {
return nil, err return nil, err
} }
res, err := api.httpClient.Post(fmt.Sprintf("%s/%s", api.node, routeGetTrytes), contentTypeJSON, bytes.NewReader(reqBytes)) return res.Trytes, nil
if err != nil {
return nil, err
}
resObj := &webapi_getTrytes.Response{}
if err := interpretBody(res, resObj); err != nil {
return nil, err
}
return resObj.Trytes, nil
} }
func (api *GoShimmerAPI) GetTransactionObjectsByHash(txHashes trinary.Hashes) ([]webapi_getTransactions.Transaction, error) { // GetTransactionObjectsByHash gets the transaction objects given the transaction hashes.
func (api *GoShimmerAPI) GetTransactionObjectsByHash(txHashes trinary.Hashes) ([]webapi_getTransactionObjectsByHash.Transaction, error) {
for _, hash := range txHashes { for _, hash := range txHashes {
if !guards.IsTrytes(hash) { if !guards.IsTrytes(hash) {
return nil, fmt.Errorf("%w: invalid hash: %s", consts.ErrInvalidHash, hash) return nil, fmt.Errorf("%w: invalid hash: %s", consts.ErrInvalidHash, hash)
} }
} }
reqBytes, err := json.Marshal(&webapi_getTransactions.Request{Hashes: txHashes}) res := &webapi_getTransactionObjectsByHash.Response{}
if err != nil { if err := api.do(http.MethodPost, routeGetTransactionObjectsByHash,
return nil, err &webapi_getTransactionObjectsByHash.Request{Hashes: txHashes}, res); err != nil {
}
res, err := api.httpClient.Post(fmt.Sprintf("%s/%s", api.node, routeGetTransactions), contentTypeJSON, bytes.NewReader(reqBytes))
if err != nil {
return nil, err return nil, err
} }
resObj := &webapi_getTransactions.Response{} return res.Transactions, nil
if err := interpretBody(res, resObj); err != nil {
return nil, err
}
return resObj.Transactions, nil
} }
func (api *GoShimmerAPI) FindTransactionHashes(query *webapi_findTransactions.Request) ([]trinary.Hashes, error) { // FindTransactionHashes finds the given transaction hashes given the query.
func (api *GoShimmerAPI) FindTransactionHashes(query *webapi_findTransactionHashes.Request) ([]trinary.Hashes, error) {
for _, hash := range query.Addresses { for _, hash := range query.Addresses {
if !guards.IsTrytes(hash) { if !guards.IsTrytes(hash) {
return nil, fmt.Errorf("%w: invalid hash: %s", consts.ErrInvalidHash, hash) return nil, fmt.Errorf("%w: invalid hash: %s", consts.ErrInvalidHash, hash)
} }
} }
reqBytes, err := json.Marshal(&query) res := &webapi_findTransactionHashes.Response{}
if err != nil { if err := api.do(http.MethodPost, routeFindTransactionsHashes, query, res); err != nil {
return nil, err return nil, err
} }
res, err := api.httpClient.Post(fmt.Sprintf("%s/%s", api.node, routeFindTransactions), contentTypeJSON, bytes.NewReader(reqBytes)) return res.Transactions, nil
if err != nil {
return nil, err
}
resObj := &webapi_findTransactions.Response{}
if err := interpretBody(res, resObj); err != nil {
return nil, err
}
return resObj.Transactions, nil
} }
func (api *GoShimmerAPI) GetNeighbors() (*webapi_getNeighbors.Response, error) { // GetNeighbors gets the chosen/accepted neighbors.
res, err := api.httpClient.Get(fmt.Sprintf("%s/%s", api.node, routeGetNeighbors)) // If knownPeers is set, also all known peers to the node are returned additionally.
if err != nil { func (api *GoShimmerAPI) GetNeighbors(knownPeers bool) (*webapi_getNeighbors.Response, error) {
return nil, err res := &webapi_getNeighbors.Response{}
} if err := api.do(http.MethodGet, func() string {
if !knownPeers {
resObj := &webapi_getNeighbors.Response{} return routeGetNeighbors
if err := interpretBody(res, resObj); err != nil { }
return fmt.Sprintf("%s?known=1", routeGetNeighbors)
}(), nil, res); err != nil {
return nil, err return nil, err
} }
return res, nil
return resObj, nil
} }
func (api *GoShimmerAPI) GetTips() (*webapi_gtta.Response, error) { // GetTips executes the tip-selection on the node to retrieve tips to approve.
res, err := api.httpClient.Get(fmt.Sprintf("%s/%s", api.node, routeGetTransactionsToApprove)) func (api *GoShimmerAPI) GetTransactionsToApprove() (*webapi_gtta.Response, error) {
if err != nil { res := &webapi_gtta.Response{}
if err := api.do(http.MethodGet, routeGetTransactionsToApprove, nil, res); err != nil {
return nil, err return nil, err
} }
return res, nil
resObj := &webapi_gtta.Response{}
if err := interpretBody(res, resObj); err != nil {
return nil, err
}
return resObj, nil
} }
// ToggleSpammer toggles the node internal spammer.
func (api *GoShimmerAPI) ToggleSpammer(enable bool) (*webapi_spammer.Response, error) { func (api *GoShimmerAPI) ToggleSpammer(enable bool) (*webapi_spammer.Response, error) {
res, err := api.httpClient.Get(fmt.Sprintf("%s/%s?cmd=%s", api.node, routeSpammer, func() string { res := &webapi_spammer.Response{}
if err := api.do(http.MethodGet, func() string {
if enable { if enable {
return "start" return fmt.Sprintf("%s?cmd=start", routeSpammer)
} }
return "stop" return fmt.Sprintf("%s?cmd=stop", routeSpammer)
}())) }(), nil, res); err != nil {
if err != nil {
return nil, err
}
resObj := &webapi_spammer.Response{}
if err := interpretBody(res, resObj); err != nil {
return nil, err return nil, err
} }
return res, nil
return resObj, nil
} }
...@@ -26,7 +26,7 @@ import ( ...@@ -26,7 +26,7 @@ import (
webapi_getTransactionTrytesByHash "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionTrytesByHash" webapi_getTransactionTrytesByHash "github.com/iotaledger/goshimmer/plugins/webapi/getTransactionTrytesByHash"
webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi/gtta" webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi/gtta"
webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi/spammer" webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi/spammer"
"github.com/iotaledger/goshimmer/plugins/webauth" webapi_auth "github.com/iotaledger/goshimmer/plugins/webauth"
"github.com/iotaledger/goshimmer/plugins/zeromq" "github.com/iotaledger/goshimmer/plugins/zeromq"
"github.com/iotaledger/hive.go/node" "github.com/iotaledger/hive.go/node"
) )
...@@ -54,6 +54,7 @@ func main() { ...@@ -54,6 +54,7 @@ func main() {
statusscreen_tps.PLUGIN, statusscreen_tps.PLUGIN,
webapi.PLUGIN, webapi.PLUGIN,
webapi_auth.PLUGIN,
webapi_gtta.PLUGIN, webapi_gtta.PLUGIN,
webapi_spammer.PLUGIN, webapi_spammer.PLUGIN,
webapi_broadcastData.PLUGIN, webapi_broadcastData.PLUGIN,
...@@ -64,7 +65,6 @@ func main() { ...@@ -64,7 +65,6 @@ func main() {
webapi_spammer.PLUGIN, webapi_spammer.PLUGIN,
ui.PLUGIN, ui.PLUGIN,
webauth.PLUGIN,
graph.PLUGIN, graph.PLUGIN,
), ),
......
package webauth
import (
flag "github.com/spf13/pflag"
)
const (
WEBAPI_AUTH_USERNAME = "webapi.auth.username"
WEBAPI_AUTH_PASSWORD = "webapi.auth.password"
WEBAPI_AUTH_PRIVATE_KEY = "webapi.auth.private_key"
)
func init() {
flag.String(WEBAPI_AUTH_USERNAME, "user", "username for the webapi")
flag.String(WEBAPI_AUTH_PASSWORD, "pass", "password for the webapi")
flag.String(WEBAPI_AUTH_PRIVATE_KEY, "", "private key used to sign the JWTs")
}
...@@ -2,13 +2,11 @@ package webauth ...@@ -2,13 +2,11 @@ package webauth
import ( import (
"net/http" "net/http"
"os"
"strings" "strings"
"time" "time"
"github.com/iotaledger/goshimmer/packages/shutdown" "github.com/iotaledger/goshimmer/packages/parameter"
"github.com/iotaledger/goshimmer/plugins/webapi" "github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/daemon"
"github.com/iotaledger/hive.go/node" "github.com/iotaledger/hive.go/node"
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/labstack/echo/middleware" "github.com/labstack/echo/middleware"
...@@ -16,59 +14,59 @@ import ( ...@@ -16,59 +14,59 @@ import (
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
) )
var secret = "secret" var PLUGIN = node.NewPlugin("WebAPI JWT Auth", node.Disabled, configure)
var privateKey string
func configure(plugin *node.Plugin) { func configure(plugin *node.Plugin) {
jwtKey := os.Getenv("JWT_KEY") privateKey = parameter.NodeConfig.GetString(WEBAPI_AUTH_PRIVATE_KEY)
if jwtKey != "" { if len(privateKey) == 0 {
secret = jwtKey panic("")
} }
webapi.Server.Use(middleware.JWTWithConfig(middleware.JWTConfig{ webapi.Server.Use(middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte(secret), SigningKey: []byte(privateKey),
TokenLookup: "query:token",
Skipper: func(c echo.Context) bool { Skipper: func(c echo.Context) bool {
// if strings.HasPrefix(c.Request().Host, "localhost") {
// return true
// }
if strings.HasPrefix(c.Path(), "/ui") || c.Path() == "/login" { if strings.HasPrefix(c.Path(), "/ui") || c.Path() == "/login" {
return true return true
} }
return false return false
}, },
})) }))
webapi.Server.POST("/login", Handler)
} }
func run(plugin *node.Plugin) { type Request struct {
daemon.BackgroundWorker("webauth", func(shutdownSignal <-chan struct{}) { Username string `json:"username"`
webapi.Server.GET("login", func(c echo.Context) error { Password string `json:"password"`
username := c.FormValue("username") }
password := c.FormValue("password")
uiUser := os.Getenv("UI_USER")
uiPass := os.Getenv("UI_PASS")
// Throws unauthorized error
if username != uiUser || password != uiPass {
return echo.ErrUnauthorized
}
token := jwt.New(jwt.SigningMethodHS256) type Response struct {
claims := token.Claims.(jwt.MapClaims) Token string `json:"token"`
claims["name"] = username }
claims["exp"] = time.Now().Add(time.Hour * 24 * 7).Unix()
t, err := token.SignedString([]byte(secret)) func Handler(c echo.Context) error {
if err != nil { login := &Request{}
return err if err := c.Bind(login); err != nil {
} return echo.ErrBadRequest
}
return c.JSON(http.StatusOK, map[string]string{ if login.Username != parameter.NodeConfig.GetString(WEBAPI_AUTH_USERNAME) ||
"token": t, login.Password != parameter.NodeConfig.GetString(WEBAPI_AUTH_PASSWORD) {
}) return echo.ErrUnauthorized
}) }
}, shutdown.ShutdownPriorityWebAPI)
} token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["name"] = login.Username
claims["exp"] = time.Now().Add(time.Hour * 24 * 7).Unix()
// PLUGIN plugs the UI into the main program t, err := token.SignedString([]byte(privateKey))
var PLUGIN = node.NewPlugin("webauth", node.Disabled, configure, run) if err != nil {
return err
}
return c.JSON(http.StatusOK, &Response{Token: t})
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment