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