diff --git a/api/api.go b/api/api.go index 3ab3fe31..35565c7a 100644 --- a/api/api.go +++ b/api/api.go @@ -106,6 +106,14 @@ func (h *Handler) setupRouter(address string) (*echo.Echo, error) { e.Use(middleware.Recover()) } + if h.conf.HttpBasicAuth.Password != "" { + username := h.conf.HttpBasicAuth.Username + password := h.conf.HttpBasicAuth.Password + e.Use(middleware.BasicAuth(func(u, p string, _ echo.Context) (bool, error) { + return u == username && p == password, nil + })) + } + // Routes // Metrics @@ -205,10 +213,15 @@ func (cv *customValidator) Validate(i interface{}) error { type Error struct { Message string `json:"error"` + // AuthMessage is an error from Echo framework, particularly from Basic Auth middleware + AuthMessage string `json:"message"` } func (e Error) Error() string { - return e.Message + if e.Message != "" && e.AuthMessage != "" { + return fmt.Sprintf("error: %s; message: %s", e.Message, e.AuthMessage) + } + return e.Message + e.AuthMessage } func ErrorMessage(message string) Error { diff --git a/api/apiclient/client.go b/api/apiclient/client.go index fe2140ec..d00f9aec 100644 --- a/api/apiclient/client.go +++ b/api/apiclient/client.go @@ -3,6 +3,7 @@ package apiclient import ( "bytes" "encoding/json" + "fmt" "io" "net/http" "net/url" @@ -30,6 +31,30 @@ func New(address string) *Client { } } +func NewWithAuth(address, username, password string) *Client { + c := New(address) + if password != "" { + c.cli.Transport = &basicAuthTransport{ + username: username, + password: password, + inner: c.cli.Transport, + } + } + return c +} + +type basicAuthTransport struct { + username string + password string + inner http.RoundTripper +} + +func (t *basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req = req.Clone(req.Context()) + req.SetBasicAuth(t.username, t.password) + return t.inner.RoundTrip(req) +} + func (c *Client) KnownPeers() ([]entity.KnownPeersResponse, error) { knownPeers := make([]entity.KnownPeersResponse, 0) err := c.sendGetRequest(api.GetKnownPeersPath, &knownPeers) @@ -206,9 +231,9 @@ func (c *Client) readResponseBody(resp *http.Response, responseRef interface{}) apiError := api.Error{} err := json.NewDecoder(resp.Body).Decode(&apiError) if err != nil { - return err + return fmt.Errorf("status code: %d, error decoding json: %w", resp.StatusCode, err) } - return apiError + return fmt.Errorf("status code: %d, error: %w", resp.StatusCode, apiError) } else if responseRef != nil { return json.NewDecoder(resp.Body).Decode(responseRef) } diff --git a/application_test.go b/application_test.go index 976b3f31..8e85f133 100644 --- a/application_test.go +++ b/application_test.go @@ -45,7 +45,7 @@ func TestRemovePeer(t *testing.T) { ts.NoError(err) peer2From1, err := peer1.api.KnownPeerConfig(peer2.PeerID()) - ts.EqualError(err, "peer not found") + ts.EqualError(err, "status code: 404, error: peer not found") ts.Nil(peer2From1) _, blockedPeerExists := peer1.app.Conf.GetBlockedPeer(peer2.PeerID()) ts.True(blockedPeerExists) @@ -262,7 +262,7 @@ func TestUniquePeerAlias(t *testing.T) { time.Sleep(200 * time.Millisecond) err = peer1.api.SendFriendRequest(peer3.PeerID(), "peer", "") - ts.EqualError(err, api.ErrorPeerAliasIsNotUniq) + ts.EqualError(err, "status code: 400, error: "+api.ErrorPeerAliasIsNotUniq) } func TestUpdateUseAsExitNodeConfig(t *testing.T) { diff --git a/cli/cli.go b/cli/cli.go index 9dcc1ea0..63f87390 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -108,6 +108,16 @@ func (a *Application) init() { Usage: fmt.Sprintf("awl api address, example: %s", defaultApiAddr), Required: false, }, + &cli.StringFlag{ + Name: "api_user", + Usage: "username for api basic auth", + Required: false, + }, + &cli.StringFlag{ + Name: "api_password", + Usage: "password for api basic auth", + Required: false, + }, }, Commands: []*cli.Command{ { @@ -466,17 +476,24 @@ func (a *Application) init() { } func (a *Application) initApiConnection(c *cli.Context) error { + username := c.String("api_user") + password := c.String("api_password") + apiAddr := c.String("api_addr") if apiAddr != "" { - return a.initApiFromAddr(apiAddr) + return a.initApiFromAddr(apiAddr, username, password) } conf, errConfig := config.LoadConfig(eventbus.NewBus()) if errConfig == nil { - return a.initApiFromAddr(conf.HttpListenAddress) + if username == "" && password == "" { + username = conf.HttpBasicAuth.Username + password = conf.HttpBasicAuth.Password + } + return a.initApiFromAddr(conf.HttpListenAddress, username, password) } - errDefault := a.initApiFromAddr(defaultApiAddr) + errDefault := a.initApiFromAddr(defaultApiAddr, username, password) if errDefault == nil { return nil } @@ -487,8 +504,8 @@ func (a *Application) initApiConnection(c *cli.Context) error { return errors.New("no connection to api server") } -func (a *Application) initApiFromAddr(addr string) error { - api := apiclient.New(addr) +func (a *Application) initApiFromAddr(addr, username, password string) error { + api := apiclient.NewWithAuth(addr, username, password) _, err := api.PeerInfo() if err != nil { return fmt.Errorf("could not access api on address %s: %v", addr, err) diff --git a/config/config.go b/config/config.go index fcf97442..64aab7e0 100644 --- a/config/config.go +++ b/config/config.go @@ -48,6 +48,7 @@ type ( LoggerLevel string `json:"loggerLevel"` HttpListenAddress string `json:"httpListenAddress"` HttpListenOnAdminHost bool `json:"httpListenOnAdminHost"` + HttpBasicAuth HttpBasicAuthConfig `json:"httpBasicAuth"` P2pNode P2pNodeConfig `json:"p2pNode"` VPNConfig VPNConfig `json:"vpn"` SOCKS5 SOCKS5Config `json:"socks5"` @@ -123,6 +124,10 @@ type ( TrayAutoCheckEnabled bool `json:"trayAutoCheckEnabled"` TrayAutoCheckInterval string `json:"trayAutoCheckInterval"` } + HttpBasicAuthConfig struct { + Username string `json:"username"` + Password string `json:"password"` + } ) func (c *Config) Save() { diff --git a/test_suite_test.go b/test_suite_test.go index e183c071..94e76b4c 100644 --- a/test_suite_test.go +++ b/test_suite_test.go @@ -23,7 +23,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/host/eventbus" "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" - simlibp2p "github.com/libp2p/go-libp2p/x/simlibp2p" + "github.com/libp2p/go-libp2p/x/simlibp2p" "github.com/marcopolo/simnet" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" @@ -87,7 +87,7 @@ func (ts *TestSuite) NewTestPeer(disableLogging bool) TestPeer { multiaddr.StringCast("/ip4/127.0.0.1/tcp/0"), multiaddr.StringCast("/ip4/127.0.0.1/udp/0/quic-v1"), } - return ts.newTestPeer(disableLogging, listenAddrs, nil) + return ts.newTestPeerWithConfig(disableLogging, listenAddrs, nil, nil) } type ConfigModifier func(*config.Config) @@ -106,10 +106,6 @@ type SOCKS5PeerConfig struct { ProxyingEnabled bool } -func (ts *TestSuite) newTestPeer(disableLogging bool, listenAddrs []multiaddr.Multiaddr, extraLibp2pOpts []libp2p.Option) TestPeer { - return ts.newTestPeerWithConfig(disableLogging, listenAddrs, extraLibp2pOpts, nil) -} - func (ts *TestSuite) newTestPeerWithSOCKS5(disableLogging bool, listenAddrs []multiaddr.Multiaddr, extraLibp2pOpts []libp2p.Option, socks5Conf *SOCKS5PeerConfig) TestPeer { return ts.newTestPeerWithConfig(disableLogging, listenAddrs, extraLibp2pOpts, func(c *config.Config) { if socks5Conf != nil { @@ -178,7 +174,7 @@ func (ts *TestSuite) newTestPeerWithConfig(disableLogging bool, listenAddrs []mu tp := TestPeer{ app: app, - api: apiclient.New(app.Api.Address()), + api: apiclient.NewWithAuth(app.Api.Address(), app.Conf.HttpBasicAuth.Username, app.Conf.HttpBasicAuth.Password), tun: testTUN, }