Get preferred network IP and refactor server package to increase reusability (#2626)
* Added function to get preffered network IP Also done some refactor work oon server package to make a lot more reusable * Added server.Option and simplified handler funcs * Added serial number deterministically generated from pk * Debugging TLS server connection * Implemented configurable server ip When accessing over the network the server needs to listen on the network port and not localhost or 127.0.0.1 . Also the cert can now have a dedicated IP * Refactor of URL funcs to use the url package * Removed redundant Options pattern in favour of config param * Added full server test using GetOutboundIP * Remove references and usage of Server.port The application does not need to set the port, we rely on the net.Listener to pick a port. * Version bump * Added ToECDSA func and improved cert testing * Added error check in test * Split Server types, embedding raw Server funcs into specialised server types * localhost * Implemented DNS and IP based cert gen ios doesn't allow for restricted ip addresses to be used in a valid tls cert * Replace listener handling with original port store Also added handlers as a parameter of the Server
This commit is contained in:
parent
efa14805bd
commit
7f149f93c1
|
@ -82,7 +82,7 @@ type StatusNode struct {
|
||||||
rpcClient *rpc.Client // reference to an RPC client
|
rpcClient *rpc.Client // reference to an RPC client
|
||||||
|
|
||||||
downloader *ipfs.Downloader
|
downloader *ipfs.Downloader
|
||||||
httpServer *server.Server
|
httpServer *server.MediaServer
|
||||||
|
|
||||||
discovery discovery.Discovery
|
discovery discovery.Discovery
|
||||||
register *peers.Register
|
register *peers.Register
|
||||||
|
@ -152,7 +152,7 @@ func (n *StatusNode) GethNode() *node.Node {
|
||||||
return n.gethNode
|
return n.gethNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *StatusNode) HTTPServer() *server.Server {
|
func (n *StatusNode) HTTPServer() *server.MediaServer {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
defer n.mu.RUnlock()
|
defer n.mu.RUnlock()
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ func (n *StatusNode) startWithDB(config *params.NodeConfig, accs *accounts.Manag
|
||||||
|
|
||||||
n.downloader = ipfs.NewDownloader(config.RootDataDir)
|
n.downloader = ipfs.NewDownloader(config.RootDataDir)
|
||||||
|
|
||||||
httpServer, err := server.NewServer(n.appDB, n.downloader)
|
httpServer, err := server.NewMediaServer(n.appDB, n.downloader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/images"
|
"github.com/status-im/status-go/images"
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
|
"github.com/status-im/status-go/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QuotedMessage contains the original text of the message replied to
|
// QuotedMessage contains the original text of the message replied to
|
||||||
|
@ -35,10 +36,6 @@ type QuotedMessage struct {
|
||||||
CommunityID string `json:"communityId,omitempty"`
|
CommunityID string `json:"communityId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *QuotedMessage) PrepareImageURL(port int) {
|
|
||||||
m.ImageLocalURL = fmt.Sprintf("https://localhost:%d/messages/images?messageId=%s", port, m.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandState int
|
type CommandState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -177,20 +174,20 @@ type Message struct {
|
||||||
ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"`
|
ContactRequestState ContactRequestState `json:"contactRequestState,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) PrepareServerURLs(port int) {
|
func (m *Message) PrepareServerURLs(s *server.MediaServer) {
|
||||||
m.Identicon = fmt.Sprintf("https://localhost:%d/messages/identicons?publicKey=%s", port, m.From)
|
m.Identicon = s.MakeIdenticonURL(m.From)
|
||||||
|
|
||||||
if m.QuotedMessage != nil && m.QuotedMessage.ContentType == int64(protobuf.ChatMessage_IMAGE) {
|
if m.QuotedMessage != nil && m.QuotedMessage.ContentType == int64(protobuf.ChatMessage_IMAGE) {
|
||||||
m.QuotedMessage.PrepareImageURL(port)
|
m.QuotedMessage.ImageLocalURL = s.MakeImageURL(m.QuotedMessage.ID)
|
||||||
}
|
}
|
||||||
if m.ContentType == protobuf.ChatMessage_IMAGE {
|
if m.ContentType == protobuf.ChatMessage_IMAGE {
|
||||||
m.ImageLocalURL = fmt.Sprintf("https://localhost:%d/messages/images?messageId=%s", port, m.ID)
|
m.ImageLocalURL = s.MakeImageURL(m.ID)
|
||||||
}
|
}
|
||||||
if m.ContentType == protobuf.ChatMessage_AUDIO {
|
if m.ContentType == protobuf.ChatMessage_AUDIO {
|
||||||
m.AudioLocalURL = fmt.Sprintf("https://localhost:%d/messages/audio?messageId=%s", port, m.ID)
|
m.AudioLocalURL = s.MakeAudioURL(m.ID)
|
||||||
}
|
}
|
||||||
if m.ContentType == protobuf.ChatMessage_STICKER {
|
if m.ContentType == protobuf.ChatMessage_STICKER {
|
||||||
m.StickerLocalURL = fmt.Sprintf("https://localhost:%d/ipfs?hash=%s", port, m.GetSticker().Hash)
|
m.StickerLocalURL = s.MakeStickerURL(m.GetSticker().Hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,22 +15,18 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/status-im/status-go/contracts"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
"github.com/status-im/status-go/appmetrics"
|
"github.com/status-im/status-go/appmetrics"
|
||||||
"github.com/status-im/status-go/connection"
|
"github.com/status-im/status-go/connection"
|
||||||
|
"github.com/status-im/status-go/contracts"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
userimage "github.com/status-im/status-go/images"
|
userimage "github.com/status-im/status-go/images"
|
||||||
|
@ -56,9 +52,9 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"github.com/status-im/status-go/protocol/transport"
|
||||||
v1protocol "github.com/status-im/status-go/protocol/v1"
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
||||||
"github.com/status-im/status-go/server"
|
"github.com/status-im/status-go/server"
|
||||||
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
"github.com/status-im/status-go/services/ext/mailservers"
|
||||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||||
|
|
||||||
"github.com/status-im/status-go/telemetry"
|
"github.com/status-im/status-go/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -128,7 +124,7 @@ type Messenger struct {
|
||||||
account *multiaccounts.Account
|
account *multiaccounts.Account
|
||||||
mailserversDatabase *mailserversDB.Database
|
mailserversDatabase *mailserversDB.Database
|
||||||
browserDatabase *browsers.Database
|
browserDatabase *browsers.Database
|
||||||
httpServer *server.Server
|
httpServer *server.MediaServer
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
|
|
||||||
requestedCommunitiesLock sync.RWMutex
|
requestedCommunitiesLock sync.RWMutex
|
||||||
|
@ -4220,7 +4216,7 @@ func (m *Messenger) MessageByChatID(chatID, cursor string, limit int) ([]*common
|
||||||
}
|
}
|
||||||
if m.httpServer != nil {
|
if m.httpServer != nil {
|
||||||
for idx := range msgs {
|
for idx := range msgs {
|
||||||
msgs[idx].PrepareServerURLs(m.httpServer.Port)
|
msgs[idx].PrepareServerURLs(m.httpServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4230,7 +4226,7 @@ func (m *Messenger) MessageByChatID(chatID, cursor string, limit int) ([]*common
|
||||||
func (m *Messenger) prepareMessages(messages map[string]*common.Message) {
|
func (m *Messenger) prepareMessages(messages map[string]*common.Message) {
|
||||||
if m.httpServer != nil {
|
if m.httpServer != nil {
|
||||||
for idx := range messages {
|
for idx := range messages {
|
||||||
messages[idx].PrepareServerURLs(m.httpServer.Port)
|
messages[idx].PrepareServerURLs(m.httpServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ type config struct {
|
||||||
clusterConfig params.ClusterConfig
|
clusterConfig params.ClusterConfig
|
||||||
browserDatabase *browsers.Database
|
browserDatabase *browsers.Database
|
||||||
torrentConfig *params.TorrentConfig
|
torrentConfig *params.TorrentConfig
|
||||||
httpServer *server.Server
|
httpServer *server.MediaServer
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
|
|
||||||
verifyTransactionClient EthClient
|
verifyTransactionClient EthClient
|
||||||
|
@ -280,7 +280,7 @@ func WithTorrentConfig(tc *params.TorrentConfig) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithHTTPServer(s *server.Server) Option {
|
func WithHTTPServer(s *server.MediaServer) Option {
|
||||||
return func(c *config) error {
|
return func(c *config) error {
|
||||||
c.httpServer = s
|
c.httpServer = s
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (m *Messenger) ImageServerURL() string {
|
func (m *Messenger) ImageServerURL() string {
|
||||||
return fmt.Sprintf("https://localhost:%d/messages/", m.httpServer.Port)
|
return m.httpServer.MakeImageServerURL()
|
||||||
}
|
}
|
||||||
|
|
110
server/certs.go
110
server/certs.go
|
@ -2,35 +2,53 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateX509Cert(from, to time.Time) (*x509.Certificate, error) {
|
var globalCertificate *tls.Certificate = nil
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
var globalPem string
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
|
|
||||||
if err != nil {
|
func makeRandomSerialNumber() (*big.Int, error) {
|
||||||
return nil, err
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
return rand.Int(rand.Reader, serialNumberLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := &x509.Certificate{
|
func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
|
||||||
SerialNumber: serialNumber,
|
h := sha256.New()
|
||||||
|
h.Write(append(pk.D.Bytes(), append(pk.Y.Bytes(), pk.X.Bytes()...)...))
|
||||||
|
|
||||||
|
return new(big.Int).SetBytes(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateX509Cert(sn *big.Int, from, to time.Time, hostname string) *x509.Certificate {
|
||||||
|
c := &x509.Certificate{
|
||||||
|
SerialNumber: sn,
|
||||||
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
|
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
|
||||||
NotBefore: from,
|
NotBefore: from,
|
||||||
NotAfter: to,
|
NotAfter: to,
|
||||||
DNSNames: []string{"localhost"},
|
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return template, nil
|
ip := net.ParseIP(hostname)
|
||||||
|
if ip != nil {
|
||||||
|
c.IPAddresses = []net.IP{ip}
|
||||||
|
} else {
|
||||||
|
c.DNSNames = []string{hostname}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
|
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
|
||||||
|
@ -48,3 +66,77 @@ func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, k
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateTLSCert() error {
|
||||||
|
if globalCertificate != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
||||||
|
|
||||||
|
sn, err := makeRandomSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := GenerateX509Cert(sn, notBefore, notAfter, localhost)
|
||||||
|
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalCert, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCertificate = &finalCert
|
||||||
|
globalPem = string(certPem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicTLSCert() (string, error) {
|
||||||
|
err := generateTLSCert()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalPem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateCertFromKey(pk *ecdsa.PrivateKey, ttl time.Duration, hostname string) (tls.Certificate, []byte, error) {
|
||||||
|
// TODO fix, this isn't deterministic,
|
||||||
|
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(ttl)
|
||||||
|
|
||||||
|
cert := GenerateX509Cert(makeSerialNumberFromKey(pk), notBefore, notAfter, hostname)
|
||||||
|
certPem, keyPem, err := GenerateX509PEMs(cert, pk)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tlsCert, certPem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToECDSA takes a []byte of D and uses it to create an ecdsa.PublicKey on the elliptic.P256 curve
|
||||||
|
// this function is basically a P256 curve version of eth-node/crypto.ToECDSA without all the nice validation
|
||||||
|
func ToECDSA(d []byte) *ecdsa.PrivateKey {
|
||||||
|
k := new(ecdsa.PrivateKey)
|
||||||
|
k.D = new(big.Int).SetBytes(d)
|
||||||
|
k.PublicKey.Curve = elliptic.P256()
|
||||||
|
|
||||||
|
k.PublicKey.X, k.PublicKey.Y = k.PublicKey.Curve.ScalarBaseMult(d)
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil/base58"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCerts(t *testing.T) {
|
||||||
|
suite.Run(t, new(CertsSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
X = "7744735542292224619198421067303535767629647588258222392379329927711683109548"
|
||||||
|
Y = "6855516769916529066379811647277920115118980625614889267697023742462401590771"
|
||||||
|
D = "38564357061962143106230288374146033267100509055924181407058066820384455255240"
|
||||||
|
DB58 = "6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh"
|
||||||
|
SN = "91849736469742262272885892667727604096707836853856473239722372976236128900962"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertsSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
D *big.Int
|
||||||
|
DBytes []byte
|
||||||
|
SN *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertsSuite) SetupSuite() {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
s.X, ok = new(big.Int).SetString(X, 10)
|
||||||
|
s.Require().True(ok)
|
||||||
|
|
||||||
|
s.Y, ok = new(big.Int).SetString(Y, 10)
|
||||||
|
s.Require().True(ok)
|
||||||
|
|
||||||
|
s.D, ok = new(big.Int).SetString(D, 10)
|
||||||
|
s.Require().True(ok)
|
||||||
|
|
||||||
|
s.DBytes = base58.Decode(DB58)
|
||||||
|
s.Require().Exactly(s.D.Bytes(), s.DBytes)
|
||||||
|
|
||||||
|
s.SN, ok = new(big.Int).SetString(SN, 10)
|
||||||
|
s.Require().True(ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertsSuite) Test_makeSerialNumberFromKey() {
|
||||||
|
pk := &ecdsa.PrivateKey{
|
||||||
|
PublicKey: ecdsa.PublicKey{
|
||||||
|
Curve: elliptic.P256(),
|
||||||
|
X: s.X,
|
||||||
|
Y: s.Y,
|
||||||
|
},
|
||||||
|
D: s.D,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Require().Zero(makeSerialNumberFromKey(pk).Cmp(s.SN))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertsSuite) TestToECDSA() {
|
||||||
|
k := ToECDSA(base58.Decode(DB58))
|
||||||
|
s.Require().NotNil(k.PublicKey.X)
|
||||||
|
s.Require().NotNil(k.PublicKey.Y)
|
||||||
|
|
||||||
|
s.Require().Zero(k.PublicKey.X.Cmp(s.X))
|
||||||
|
s.Require().Zero(k.PublicKey.Y.Cmp(s.Y))
|
||||||
|
s.Require().Zero(k.D.Cmp(s.D))
|
||||||
|
|
||||||
|
b58 := base58.Encode(s.D.Bytes())
|
||||||
|
s.Require().Equal(DB58, b58)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertsSuite) TestGenerateX509Cert() {
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(time.Hour)
|
||||||
|
|
||||||
|
c1 := GenerateX509Cert(s.SN, notBefore, notAfter, localhost)
|
||||||
|
s.Require().Exactly([]string{localhost}, c1.DNSNames)
|
||||||
|
s.Require().Nil(c1.IPAddresses)
|
||||||
|
|
||||||
|
c2 := GenerateX509Cert(s.SN, notBefore, notAfter, defaultIP.String())
|
||||||
|
s.Require().Len(c2.IPAddresses, 1)
|
||||||
|
s.Require().Equal(defaultIP.String(), c2.IPAddresses[0].String())
|
||||||
|
s.Require().Nil(c2.DNSNames)
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/ipfs"
|
||||||
|
"github.com/status-im/status-go/protocol/identity/identicon"
|
||||||
|
"github.com/status-im/status-go/protocol/images"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
basePath = "/messages"
|
||||||
|
identiconsPath = basePath + "/identicons"
|
||||||
|
imagesPath = basePath + "/images"
|
||||||
|
audioPath = basePath + "/audio"
|
||||||
|
ipfsPath = "/ipfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerPatternMap map[string]http.HandlerFunc
|
||||||
|
|
||||||
|
func handleIdenticon(logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pks, ok := r.URL.Query()["publicKey"]
|
||||||
|
if !ok || len(pks) == 0 {
|
||||||
|
logger.Error("no publicKey")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pk := pks[0]
|
||||||
|
image, err := identicon.Generate(pk)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("could not generate identicon")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
w.Header().Set("Cache-Control", "max-age:290304000, public")
|
||||||
|
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
|
||||||
|
|
||||||
|
_, err = w.Write(image)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write image", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleImage(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
messageIDs, ok := r.URL.Query()["messageId"]
|
||||||
|
if !ok || len(messageIDs) == 0 {
|
||||||
|
logger.Error("no messageID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messageID := messageIDs[0]
|
||||||
|
var image []byte
|
||||||
|
err := db.QueryRow(`SELECT image_payload FROM user_messages WHERE id = ?`, messageID).Scan(&image)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find image", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(image) == 0 {
|
||||||
|
logger.Error("empty image")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mime, err := images.ImageMime(image)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get mime", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", mime)
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
|
||||||
|
_, err = w.Write(image)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write image", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAudio(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
messageIDs, ok := r.URL.Query()["messageId"]
|
||||||
|
if !ok || len(messageIDs) == 0 {
|
||||||
|
logger.Error("no messageID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messageID := messageIDs[0]
|
||||||
|
var audio []byte
|
||||||
|
err := db.QueryRow(`SELECT audio_payload FROM user_messages WHERE id = ?`, messageID).Scan(&audio)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find image", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(audio) == 0 {
|
||||||
|
logger.Error("empty audio")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "audio/aac")
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
|
||||||
|
_, err = w.Write(audio)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write audio", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
hashes, ok := r.URL.Query()["hash"]
|
||||||
|
if !ok || len(hashes) == 0 {
|
||||||
|
logger.Error("no hash")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, download := r.URL.Query()["download"]
|
||||||
|
|
||||||
|
content, err := downloader.Get(hashes[0], download)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("could not download hash", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Cache-Control", "max-age:290304000, public")
|
||||||
|
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
|
||||||
|
|
||||||
|
_, err = w.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write ipfs resource", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultIP = net.IP{127, 0, 0, 1}
|
||||||
|
localhost = "localhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOutboundIP() (net.IP, error) {
|
||||||
|
conn, err := net.Dial("udp", "255.255.255.255:8080")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||||
|
|
||||||
|
return localAddr.IP, nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
say, ok := r.URL.Query()["say"]
|
||||||
|
if !ok || len(say) == 0 {
|
||||||
|
say = append(say, "nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.Write([]byte("Hello I like to be a tls server. You said: `" + say[0] + "` " + time.Now().String()))
|
||||||
|
if err != nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOutboundIPWithFullServerE2e(t *testing.T) {
|
||||||
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ip, err := GetOutboundIP()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cert, certPem, err := GenerateCertFromKey(pk, time.Hour, ip.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s := NewPairingServer(&Config{&cert, ip.String()})
|
||||||
|
|
||||||
|
s.SetHandlers(HandlerPatternMap{"/hello": testHandler(t)})
|
||||||
|
|
||||||
|
err = s.Start()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Give time for the sever to be ready, hacky I know
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
spew.Dump(s.MakeBaseURL().String())
|
||||||
|
|
||||||
|
rootCAs, err := x509.SystemCertPool()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ok := rootCAs.AppendCertsFromPEM(certPem)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
InsecureSkipVerify: false, // MUST BE FALSE, or the test is meaningless
|
||||||
|
RootCAs: rootCAs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err = rand.Read(b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
thing := hex.EncodeToString(b)
|
||||||
|
|
||||||
|
response, err := client.Get(s.MakeBaseURL().String() + "/hello?say=" + thing)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(response.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "Hello I like to be a tls server. You said: `"+thing+"`", string(content[:109]))
|
||||||
|
}
|
247
server/server.go
247
server/server.go
|
@ -2,225 +2,44 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"net/url"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/status-im/status-go/ipfs"
|
|
||||||
"github.com/status-im/status-go/logutils"
|
"github.com/status-im/status-go/logutils"
|
||||||
"github.com/status-im/status-go/protocol/identity/identicon"
|
|
||||||
"github.com/status-im/status-go/protocol/images"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalCertificate *tls.Certificate = nil
|
|
||||||
var globalPem string
|
|
||||||
|
|
||||||
func generateTLSCert() error {
|
|
||||||
if globalCertificate != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
notBefore := time.Now()
|
|
||||||
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
|
||||||
|
|
||||||
cert, err := GenerateX509Cert(notBefore, notAfter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
finalCert, err := tls.X509KeyPair(certPem, keyPem)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
globalCertificate = &finalCert
|
|
||||||
globalPem = string(certPem)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func PublicTLSCert() (string, error) {
|
|
||||||
err := generateTLSCert()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return globalPem, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type imageHandler struct {
|
|
||||||
db *sql.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type audioHandler struct {
|
|
||||||
db *sql.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type identiconHandler struct {
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipfsHandler struct {
|
|
||||||
logger *zap.Logger
|
|
||||||
downloader *ipfs.Downloader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *identiconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
pks, ok := r.URL.Query()["publicKey"]
|
|
||||||
if !ok || len(pks) == 0 {
|
|
||||||
s.logger.Error("no publicKey")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pk := pks[0]
|
|
||||||
image, err := identicon.Generate(pk)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("could not generate identicon")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "image/png")
|
|
||||||
w.Header().Set("Cache-Control", "max-age:290304000, public")
|
|
||||||
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
|
|
||||||
|
|
||||||
_, err = w.Write(image)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write image", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *imageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
messageIDs, ok := r.URL.Query()["messageId"]
|
|
||||||
if !ok || len(messageIDs) == 0 {
|
|
||||||
s.logger.Error("no messageID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
messageID := messageIDs[0]
|
|
||||||
var image []byte
|
|
||||||
err := s.db.QueryRow(`SELECT image_payload FROM user_messages WHERE id = ?`, messageID).Scan(&image)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to find image", zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(image) == 0 {
|
|
||||||
s.logger.Error("empty image")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mime, err := images.ImageMime(image)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to get mime", zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", mime)
|
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
|
||||||
|
|
||||||
_, err = w.Write(image)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write image", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *audioHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
messageIDs, ok := r.URL.Query()["messageId"]
|
|
||||||
if !ok || len(messageIDs) == 0 {
|
|
||||||
s.logger.Error("no messageID")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
messageID := messageIDs[0]
|
|
||||||
var audio []byte
|
|
||||||
err := s.db.QueryRow(`SELECT audio_payload FROM user_messages WHERE id = ?`, messageID).Scan(&audio)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to find image", zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(audio) == 0 {
|
|
||||||
s.logger.Error("empty audio")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "audio/aac")
|
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
|
||||||
|
|
||||||
_, err = w.Write(audio)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write audio", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
hashes, ok := r.URL.Query()["hash"]
|
|
||||||
if !ok || len(hashes) == 0 {
|
|
||||||
s.logger.Error("no hash")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, download := r.URL.Query()["download"]
|
|
||||||
|
|
||||||
content, err := s.downloader.Get(hashes[0], download)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("could not download hash", zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Cache-Control", "max-age:290304000, public")
|
|
||||||
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
|
|
||||||
|
|
||||||
_, err = w.Write(content)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write ipfs resource", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Port int
|
|
||||||
run bool
|
run bool
|
||||||
server *http.Server
|
server *http.Server
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
db *sql.DB
|
|
||||||
cert *tls.Certificate
|
cert *tls.Certificate
|
||||||
downloader *ipfs.Downloader
|
hostname string
|
||||||
|
port int
|
||||||
|
handlers HandlerPatternMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(db *sql.DB, downloader *ipfs.Downloader) (*Server, error) {
|
func NewServer(cert *tls.Certificate, hostname string) Server {
|
||||||
err := generateTLSCert()
|
return Server{logger: logutils.ZapLogger(), cert: cert, hostname: hostname}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Server{db: db, logger: logutils.ZapLogger(), cert: globalCertificate, Port: 0, downloader: downloader}, nil
|
func (s *Server) getHost() string {
|
||||||
|
// TODO consider returning an error if s.getPort returns `0`, as this means that the listener is not ready
|
||||||
|
return fmt.Sprintf("%s:%d", s.hostname, s.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) listenAndServe() {
|
func (s *Server) listenAndServe() {
|
||||||
cfg := &tls.Config{Certificates: []tls.Certificate{*s.cert}, ServerName: "localhost", MinVersion: tls.VersionTLS12}
|
cfg := &tls.Config{Certificates: []tls.Certificate{*s.cert}, ServerName: s.hostname, MinVersion: tls.VersionTLS12}
|
||||||
|
|
||||||
// in case of restart, we should use the same port as the first start in order not to break existing links
|
// in case of restart, we should use the same port as the first start in order not to break existing links
|
||||||
addr := fmt.Sprintf("localhost:%d", s.Port)
|
listener, err := tls.Listen("tcp", s.getHost(), cfg)
|
||||||
|
|
||||||
listener, err := tls.Listen("tcp", addr, cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("failed to start server, retrying", zap.Error(err))
|
s.logger.Error("failed to start server, retrying", zap.Error(err))
|
||||||
s.Port = 0
|
s.port = 0
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("server start failed, giving up", zap.Error(err))
|
s.logger.Error("server start failed, giving up", zap.Error(err))
|
||||||
|
@ -228,8 +47,9 @@ func (s *Server) listenAndServe() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Port = listener.Addr().(*net.TCPAddr).Port
|
s.port = listener.Addr().(*net.TCPAddr).Port
|
||||||
s.run = true
|
s.run = true
|
||||||
|
|
||||||
err = s.server.Serve(listener)
|
err = s.server.Serve(listener)
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
s.logger.Error("server failed unexpectedly, restarting", zap.Error(err))
|
s.logger.Error("server failed unexpectedly, restarting", zap.Error(err))
|
||||||
|
@ -243,17 +63,27 @@ func (s *Server) listenAndServe() {
|
||||||
s.run = false
|
s.run = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) resetServer() {
|
||||||
|
s.server = new(http.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) applyHandlers() {
|
||||||
|
if s.server == nil {
|
||||||
|
s.server = new(http.Server)
|
||||||
|
}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
for p, h := range s.handlers {
|
||||||
|
mux.HandleFunc(p, h)
|
||||||
|
}
|
||||||
|
s.server.Handler = mux
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
handler := http.NewServeMux()
|
// Once Shutdown has been called on a server, it may not be reused;
|
||||||
handler.Handle("/messages/images", &imageHandler{db: s.db, logger: s.logger})
|
s.resetServer()
|
||||||
handler.Handle("/messages/audio", &audioHandler{db: s.db, logger: s.logger})
|
s.applyHandlers()
|
||||||
handler.Handle("/messages/identicons", &identiconHandler{logger: s.logger})
|
|
||||||
handler.Handle("/ipfs", &ipfsHandler{logger: s.logger, downloader: s.downloader})
|
|
||||||
|
|
||||||
s.server = &http.Server{Handler: handler}
|
|
||||||
|
|
||||||
go s.listenAndServe()
|
go s.listenAndServe()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,3 +112,14 @@ func (s *Server) ToBackground() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) SetHandlers(handlers HandlerPatternMap) {
|
||||||
|
s.handlers = handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) MakeBaseURL() *url.URL {
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: s.getHost(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/ipfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MediaServer struct {
|
||||||
|
Server
|
||||||
|
|
||||||
|
db *sql.DB
|
||||||
|
downloader *ipfs.Downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMediaServer returns a *MediaServer
|
||||||
|
func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader) (*MediaServer, error) {
|
||||||
|
err := generateTLSCert()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &MediaServer{
|
||||||
|
Server: NewServer(globalCertificate, localhost),
|
||||||
|
db: db,
|
||||||
|
downloader: downloader,
|
||||||
|
}
|
||||||
|
s.SetHandlers(HandlerPatternMap{
|
||||||
|
imagesPath: handleImage(s.db, s.logger),
|
||||||
|
audioPath: handleAudio(s.db, s.logger),
|
||||||
|
identiconsPath: handleIdenticon(s.logger),
|
||||||
|
ipfsPath: handleIPFS(s.downloader, s.logger),
|
||||||
|
})
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeImageServerURL() string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = basePath + "/"
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeIdenticonURL(from string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = identiconsPath
|
||||||
|
u.RawQuery = url.Values{"publicKey": {from}}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeImageURL(id string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = imagesPath
|
||||||
|
u.RawQuery = url.Values{"messageId": {id}}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeAudioURL(id string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = audioPath
|
||||||
|
u.RawQuery = url.Values{"messageId": {id}}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeStickerURL(stickerHash string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = ipfsPath
|
||||||
|
u.RawQuery = url.Values{"hash": {stickerHash}}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PairingServer struct {
|
||||||
|
Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Cert *tls.Certificate
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPairingServer returns a *NewPairingServer init from the given *Config
|
||||||
|
func NewPairingServer(config *Config) *PairingServer {
|
||||||
|
return &PairingServer{Server: NewServer(
|
||||||
|
config.Cert,
|
||||||
|
config.Hostname,
|
||||||
|
)}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServerURLSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ServerURLSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerURLSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
server *MediaServer
|
||||||
|
serverNoPort *MediaServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) SetupSuite() {
|
||||||
|
s.server = &MediaServer{Server: Server{
|
||||||
|
hostname: defaultIP.String(),
|
||||||
|
port: 1337,
|
||||||
|
}}
|
||||||
|
s.serverNoPort = &MediaServer{Server: Server{
|
||||||
|
hostname: defaultIP.String(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeBaseURL() {
|
||||||
|
s.Require().Equal("https://127.0.0.1:1337", s.server.MakeBaseURL().String())
|
||||||
|
s.Require().Equal("https://127.0.0.1:0", s.serverNoPort.MakeBaseURL().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeImageServerURL() {
|
||||||
|
s.Require().Equal("https://127.0.0.1:1337/messages/", s.server.MakeImageServerURL())
|
||||||
|
s.Require().Equal("https://127.0.0.1:0/messages/", s.serverNoPort.MakeImageServerURL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeIdenticonURL() {
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:1337/messages/identicons?publicKey=0xdaff0d11decade",
|
||||||
|
s.server.MakeIdenticonURL("0xdaff0d11decade"))
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:0/messages/identicons?publicKey=0xdaff0d11decade",
|
||||||
|
s.serverNoPort.MakeIdenticonURL("0xdaff0d11decade"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeImageURL() {
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:1337/messages/images?messageId=0x10aded70ffee",
|
||||||
|
s.server.MakeImageURL("0x10aded70ffee"))
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:0/messages/images?messageId=0x10aded70ffee",
|
||||||
|
s.serverNoPort.MakeImageURL("0x10aded70ffee"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeAudioURL() {
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:1337/messages/audio?messageId=0xde1e7ebee71e",
|
||||||
|
s.server.MakeAudioURL("0xde1e7ebee71e"))
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:0/messages/audio?messageId=0xde1e7ebee71e",
|
||||||
|
s.serverNoPort.MakeAudioURL("0xde1e7ebee71e"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerURLSuite) TestServer_MakeStickerURL() {
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:1337/ipfs?hash=0xdeadbeef4ac0",
|
||||||
|
s.server.MakeStickerURL("0xdeadbeef4ac0"))
|
||||||
|
s.Require().Equal(
|
||||||
|
"https://127.0.0.1:0/ipfs?hash=0xdeadbeef4ac0",
|
||||||
|
s.serverNoPort.MakeStickerURL("0xdeadbeef4ac0"))
|
||||||
|
}
|
|
@ -11,10 +11,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/status-im/status-go/server"
|
|
||||||
"github.com/status-im/status-go/services/browsers"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
commongethtypes "github.com/ethereum/go-ethereum/common"
|
commongethtypes "github.com/ethereum/go-ethereum/common"
|
||||||
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
@ -24,7 +22,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/status-im/status-go/rpc"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/connection"
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/db"
|
"github.com/status-im/status-go/db"
|
||||||
|
@ -39,12 +36,13 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
||||||
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"github.com/status-im/status-go/protocol/transport"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
"github.com/status-im/status-go/server"
|
||||||
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
"github.com/status-im/status-go/services/ext/mailservers"
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||||
"github.com/status-im/status-go/services/wallet/transfer"
|
"github.com/status-im/status-go/services/wallet/transfer"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvelopeEventsHandler used for two different event types.
|
// EnvelopeEventsHandler used for two different event types.
|
||||||
|
@ -109,7 +107,7 @@ func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
|
||||||
return enode.ParseV4(rawURL)
|
return enode.ParseV4(rawURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, httpServer *server.Server, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, logger *zap.Logger) error {
|
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, httpServer *server.MediaServer, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, logger *zap.Logger) error {
|
||||||
var err error
|
var err error
|
||||||
if !s.config.ShhextConfig.PFSEnabled {
|
if !s.config.ShhextConfig.PFSEnabled {
|
||||||
return nil
|
return nil
|
||||||
|
@ -394,7 +392,7 @@ func buildMessengerOptions(
|
||||||
config params.NodeConfig,
|
config params.NodeConfig,
|
||||||
identity *ecdsa.PrivateKey,
|
identity *ecdsa.PrivateKey,
|
||||||
db *sql.DB,
|
db *sql.DB,
|
||||||
httpServer *server.Server,
|
httpServer *server.MediaServer,
|
||||||
rpcClient *rpc.Client,
|
rpcClient *rpc.Client,
|
||||||
multiAccounts *multiaccounts.Database,
|
multiAccounts *multiaccounts.Database,
|
||||||
account *multiaccounts.Account,
|
account *multiaccounts.Account,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package stickers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/zenthangplus/goccm"
|
"github.com/zenthangplus/goccm"
|
||||||
|
@ -44,7 +43,7 @@ type API struct {
|
||||||
|
|
||||||
keyStoreDir string
|
keyStoreDir string
|
||||||
downloader *ipfs.Downloader
|
downloader *ipfs.Downloader
|
||||||
httpServer *server.Server
|
httpServer *server.MediaServer
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
@ -85,7 +84,7 @@ type ednStickerPackInfo struct {
|
||||||
Meta ednStickerPack
|
Meta ednStickerPack
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPI(ctx context.Context, acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, keyStoreDir string, downloader *ipfs.Downloader, httpServer *server.Server) *API {
|
func NewAPI(ctx context.Context, acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, keyStoreDir string, downloader *ipfs.Downloader, httpServer *server.MediaServer) *API {
|
||||||
result := &API{
|
result := &API{
|
||||||
contractMaker: &contracts.ContractMaker{
|
contractMaker: &contracts.ContractMaker{
|
||||||
RPCClient: rpcClient,
|
RPCClient: rpcClient,
|
||||||
|
@ -327,7 +326,7 @@ func (api *API) downloadPackData(stickerPack *StickerPack, contentHash []byte, t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) hashToURL(hash string) string {
|
func (api *API) hashToURL(hash string) string {
|
||||||
return fmt.Sprintf("https://localhost:%d/ipfs?hash=%s", api.httpServer.Port, hash)
|
return api.httpServer.MakeStickerURL(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error {
|
func (api *API) populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService initializes service instance.
|
// NewService initializes service instance.
|
||||||
func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig, downloader *ipfs.Downloader, httpServer *server.Server) *Service {
|
func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig, downloader *ipfs.Downloader, httpServer *server.MediaServer) *Service {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
|
@ -39,7 +39,7 @@ type Service struct {
|
||||||
rpcFiltersSrvc *rpcfilters.Service
|
rpcFiltersSrvc *rpcfilters.Service
|
||||||
downloader *ipfs.Downloader
|
downloader *ipfs.Downloader
|
||||||
keyStoreDir string
|
keyStoreDir string
|
||||||
httpServer *server.Server
|
httpServer *server.MediaServer
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue