From 88c59ac00b5729560a4a933d8af87756b0d4d564 Mon Sep 17 00:00:00 2001 From: Geno Date: Tue, 14 Sep 2021 01:32:37 +0200 Subject: [PATCH] implement JWT for anti spam fix #1 --- gateway/go.mod | 1 + gateway/go.sum | 2 ++ gateway/main.go | 10 +++++---- gateway/token.go | 49 +++++++++++++++++++++++++++++++++++++++++++ gateway/token_test.go | 25 ++++++++++++++++++++++ gateway/web.go | 6 +++--- gateway/web_post.go | 14 +++++++++---- gateway/xmpp.go | 25 ++++++++++++++-------- 8 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 gateway/token.go create mode 100644 gateway/token_test.go diff --git a/gateway/go.mod b/gateway/go.mod index 022c92a..aa3daf9 100644 --- a/gateway/go.mod +++ b/gateway/go.mod @@ -25,6 +25,7 @@ require ( github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect diff --git a/gateway/go.sum b/gateway/go.sum index 23c9927..009da1a 100644 --- a/gateway/go.sum +++ b/gateway/go.sum @@ -123,6 +123,8 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/gateway/main.go b/gateway/main.go index 2a4f884..c9b8a1c 100644 --- a/gateway/main.go +++ b/gateway/main.go @@ -13,9 +13,10 @@ import ( var VERSION = "development" type configData struct { - EndpointURL string `toml:"endpoint_url"` - XMPP XMPPService `toml:"xmpp"` - Webserver web.Service `toml:"webserver"` + JWTSecret JWTSecret `toml"jwt_secret"` + EndpointURL string `toml:"endpoint_url"` + XMPP XMPPService `toml:"xmpp"` + Webserver web.Service `toml:"webserver"` } func main() { @@ -42,6 +43,7 @@ func main() { } // just for more beautiful config file - jere config.XMPP.EndpointURL = config.EndpointURL + config.XMPP.JWTSecret = config.JWTSecret go func() { if err := config.XMPP.Run(); err != nil { @@ -49,7 +51,7 @@ func main() { } }() - config.Webserver.ModuleRegister(Bind(&config.XMPP)) + config.Webserver.ModuleRegister(Bind(&config.XMPP, config.JWTSecret)) log.Info("startup") if err := config.Webserver.Run(); err != nil { diff --git a/gateway/token.go b/gateway/token.go new file mode 100644 index 0000000..563c18c --- /dev/null +++ b/gateway/token.go @@ -0,0 +1,49 @@ +package main + +import ( + "github.com/golang-jwt/jwt" + "mellium.im/xmpp/jid" +) + +// JWTSecret the secret +type JWTSecret string + +// JWTToken data field +type JWTToken struct { + jwt.StandardClaims + Token string `json:"token"` + JID string `json:"jid"` +} + +// Generate an jwt token by token and jid +func (s JWTSecret) Generate(jid jid.JID, token string) (string, error) { + jwtToken := JWTToken{ + Token: token, + JID: jid.String(), + } + claim := jwt.NewWithClaims(jwt.SigningMethodHS512, jwtToken) + t, err := claim.SignedString([]byte(s)) + if err != nil { + return "", err + } + return t, nil +} + +// Read token to token and jid +func (s JWTSecret) Read(jwtToken string) (jid.JID, string, error) { + token, err := jwt.ParseWithClaims(jwtToken, &JWTToken{}, func(token *jwt.Token) (interface{}, error) { + return []byte(s), nil + }) + if err != nil { + return jid.JID{}, "", err + } + claims, ok := token.Claims.(*JWTToken) + if !ok { + return jid.JID{}, "", jwt.ErrInvalidKey + } + addr, err := jid.Parse(claims.JID) + if err != nil { + return jid.JID{}, "", err + } + return addr, claims.Token, nil +} diff --git a/gateway/token_test.go b/gateway/token_test.go new file mode 100644 index 0000000..7e2a3ef --- /dev/null +++ b/gateway/token_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "mellium.im/xmpp/jid" +) + +func TestJWT(t *testing.T) { + assert := assert.New(t) + + addr := "a@example.org" + token := "pushtoken" + + secret := JWTSecret("CHANGEME") + jwt, err := secret.Generate(jid.MustParse(addr), token) + assert.NoError(err) + assert.NoEqual("", jwt) + + jid, t, err := secret.Read(jwt) + assert.NoError(err) + assert.Equal(addr, jid.String()) + assert.Equal(t, token) +} diff --git a/gateway/web.go b/gateway/web.go index 257fd15..d34682c 100644 --- a/gateway/web.go +++ b/gateway/web.go @@ -1,10 +1,10 @@ package main import ( - "github.com/gin-gonic/gin" "dev.sum7.eu/genofire/golang-lib/web" "dev.sum7.eu/genofire/golang-lib/web/api/status" "dev.sum7.eu/genofire/golang-lib/web/metrics" + "github.com/gin-gonic/gin" ) // Bind to webservice @@ -18,13 +18,13 @@ import ( // @securityDefinitions.apikey ApiKeyAuth // @in header // @name Authorization -func Bind(xmpp *XMPPService) web.ModuleRegisterFunc { +func Bind(xmpp *XMPPService, jwt JWTSecret) web.ModuleRegisterFunc { return func(r *gin.Engine, ws *web.Service) { // docs.Bind(r, ws) status.Register(r, ws) metrics.Register(r, ws) Get(r, ws) - Post(r, ws, xmpp) + Post(r, ws, xmpp, jwt) } } diff --git a/gateway/web_post.go b/gateway/web_post.go index 1c26f9a..31bacd0 100644 --- a/gateway/web_post.go +++ b/gateway/web_post.go @@ -2,16 +2,22 @@ package main import ( "github.com/gin-gonic/gin" - "net/http" "io/ioutil" + "net/http" "dev.sum7.eu/genofire/golang-lib/web" ) -func Post(r *gin.Engine, ws *web.Service, xmpp *XMPPService) { +func Post(r *gin.Engine, ws *web.Service, xmpp *XMPPService, jwtsecret JWTSecret) { r.POST("/UP", func(c *gin.Context) { - to := c.Query("to") - token := c.Query("token") + to, token, err := jwtsecret.Read(c.Query("token")) + if err != nil { + c.JSON(http.StatusUnauthorized, web.HTTPError{ + Message: "jwt token unauthoried - or not given", + Error: err.Error(), + }) + return + } b, err := ioutil.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, web.HTTPError{ diff --git a/gateway/xmpp.go b/gateway/xmpp.go index 8f599ca..1eb0564 100644 --- a/gateway/xmpp.go +++ b/gateway/xmpp.go @@ -18,12 +18,13 @@ import ( ) type XMPPService struct { - Addr string `toml:"address"` - JID string `toml:"jid"` - Secret string `toml:"secret"` + Addr string `toml:"address"` + JID string `toml:"jid"` + Secret string `toml:"secret"` // hidden here for beautiful config file - EndpointURL string `toml:"-"` - session *xmpp.Session + EndpointURL string `toml:"-"` + JWTSecret JWTSecret `toml"-"` + session *xmpp.Session } func (s *XMPPService) Run() error { @@ -102,7 +103,13 @@ func (s *XMPPService) handleRegister(iq stanza.IQ, t xmlstream.TokenReadEncoder, reply.Register.Error = &messages.ErrorData{Body: "no token"} return nil } - endpoint := s.EndpointURL+"/UP?token=" + token + "&to=" + iq.From.String() + jwt, err := s.JWTSecret.Generate(iq.From, token) + if err != nil { + log.Errorf("unable jwt generation: %v", err) + reply.Register.Error = &messages.ErrorData{Body: "jwt error on gateway"} + return nil + } + endpoint := s.EndpointURL + "/UP?token=" + jwt reply.IQ.Type = stanza.ResultIQ reply.Register.Endpoint = &messages.EndpointData{Body: endpoint} log.Infof("generate respone: %v", endpoint) @@ -145,14 +152,14 @@ func (s *XMPPService) handleDisco(iq stanza.IQ, t xmlstream.TokenReadEncoder, st } // SendMessage of an UP Notification -func (s *XMPPService) SendMessage(to, token, content string) error { +func (s *XMPPService) SendMessage(to jid.JID, token, content string) error { log.WithFields(map[string]interface{}{ - "to": to, + "to": to, "token": token, }).Info("forward message to xmpp") return s.session.Encode(context.TODO(), messages.Message{ Message: stanza.Message{ - To: jid.MustParse(to), + To: to, From: jid.MustParse(s.JID), // Type: stanza.ChatMessage, Type: stanza.NormalMessage,