From 0341276f4dc17414e1adbdcba25cd0b351f7a49a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 24 Mar 2022 13:55:11 +0100 Subject: [PATCH] use websocket instead of sse because more client support --- controllers/invoicestream.ctrl.go | 65 ++++++++++++++----------------- go.mod | 1 + go.sum | 3 +- lib/tokens/jwt.go | 28 +++++++++++++ main.go | 3 +- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 94b50fe..6dd6476 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -1,13 +1,13 @@ package controllers import ( - "encoding/json" "net/http" - "time" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lib/tokens" + "github.com/gorilla/websocket" "github.com/labstack/echo/v4" ) @@ -20,49 +20,44 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro return &InvoiceStreamController{svc: svc} } -type InvoiceEvent struct { - Invoice *IncomingInvoice `json:"invoice,omitempty"` - Type string -} - // Stream invoices streams incoming payments to the client func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { - userId := c.Get("UserID").(int64) - c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - c.Response().WriteHeader(http.StatusOK) - enc := json.NewEncoder(c.Response()) + userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token"))) + if err != nil { + return err + } invoiceChan := make(chan models.Invoice) controller.svc.InvoiceSubscribers[userId] = invoiceChan ctx := c.Request().Context() - ticker := time.NewTicker(30 * time.Second) + upgrader := websocket.Upgrader{} + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + if err != nil { + return err + } + defer ws.Close() +SocketLoop: for { select { - case <-ticker.C: - if err := enc.Encode( - InvoiceEvent{ - Type: "keepalive", - }); err != nil { - return err - } case <-ctx.Done(): - return nil + break SocketLoop case invoice := <-invoiceChan: - if err := enc.Encode( - InvoiceEvent{ - Type: "invoice", - Invoice: &IncomingInvoice{ - PaymentHash: invoice.RHash, - PaymentRequest: invoice.PaymentRequest, - Description: invoice.Memo, - PayReq: invoice.PaymentRequest, - Timestamp: invoice.CreatedAt.Unix(), - Type: common.InvoiceTypeUser, - Amount: invoice.Amount, - IsPaid: invoice.State == common.InvoiceStateSettled, - }}); err != nil { - return err + err := ws.WriteJSON( + &IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }) + if err != nil { + controller.svc.Logger.Error(err) + break SocketLoop } } - c.Response().Flush() } + return nil } diff --git a/go.mod b/go.mod index af8d6e3..b4080db 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( require ( github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 + github.com/gorilla/websocket v1.5.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect diff --git a/go.sum b/go.sum index 1e937f6..f5d3a3d 100644 --- a/go.sum +++ b/go.sum @@ -338,8 +338,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= diff --git a/lib/tokens/jwt.go b/lib/tokens/jwt.go index 46b7c67..117453b 100644 --- a/lib/tokens/jwt.go +++ b/lib/tokens/jwt.go @@ -81,6 +81,34 @@ func GenerateRefreshToken(secret []byte, expiryInSeconds int, u *models.User) (s return t, nil } +func ParseToken(secret []byte, token string) (int64, error) { + userIdClaim := "id" + claims := jwt.MapClaims{} + parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + return secret, nil + }) + + if err != nil { + return -1, err + } + + if !parsedToken.Valid { + return -1, errors.New("Token is invalid") + } + + var userId interface{} + for k, v := range claims { + if k == userIdClaim { + userId = v.(float64) + } + } + + if userId == nil { + return -1, errors.New("User id claim not found") + } + + return int64(userId.(float64)), nil +} func GetUserIdFromToken(secret []byte, token string) (int64, error) { userIdClaim := "id" diff --git a/main.go b/main.go index d14a0b8..56bd7af 100644 --- a/main.go +++ b/main.go @@ -157,7 +157,8 @@ func main() { e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) //invoice streaming - secured.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) + //Authentication should be done through the query param because this is a websocket + e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) // Subscribe to LND invoice updates in the background go svc.InvoiceUpdateSubscription(context.Background())