diff --git a/controllers_v2/balance.ctrl.go b/controllers_v2/balance.ctrl.go new file mode 100644 index 0000000..e637fbc --- /dev/null +++ b/controllers_v2/balance.ctrl.go @@ -0,0 +1,47 @@ +package v2controllers + +import ( + "net/http" + + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +// BalanceController : BalanceController struct +type BalanceController struct { + svc *service.LndhubService +} + +func NewBalanceController(svc *service.LndhubService) *BalanceController { + return &BalanceController{svc: svc} +} + +type BalanceResponse struct { + BTC struct { + AvailableBalance int64 + } +} + +// Balance godoc +// @Summary Retrieve balance +// @Description Current user's balance in satoshi +// @Accept json +// @Produce json +// @Tags Account +// @Success 200 {object} BalanceResponse +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /balance [get] +// @Security OAuth2Password +func (controller *BalanceController) Balance(c echo.Context) error { + userId := c.Get("UserID").(int64) + balance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userId) + if err != nil { + return err + } + return c.JSON(http.StatusOK, &BalanceResponse{ + BTC: struct{ AvailableBalance int64 }{ + AvailableBalance: balance, + }, + }) +} diff --git a/controllers_v2/create.ctrl.go b/controllers_v2/create.ctrl.go new file mode 100644 index 0000000..a81cdaf --- /dev/null +++ b/controllers_v2/create.ctrl.go @@ -0,0 +1,59 @@ +package v2controllers + +import ( + "net/http" + + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +// CreateUserController : Create user controller struct +type CreateUserController struct { + svc *service.LndhubService +} + +func NewCreateUserController(svc *service.LndhubService) *CreateUserController { + return &CreateUserController{svc: svc} +} + +type CreateUserResponseBody struct { + Username string `json:"username"` + Password string `json:"password"` +} +type CreateUserRequestBody struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// CreateUser godoc +// @Summary Create an account +// @Description Create a new account with a username and password +// @Accept json +// @Produce json +// @Tags Account +// @Param account body CreateUserRequestBody false "Create User" +// @Success 200 {object} CreateUserResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /v2/users [post] +func (controller *CreateUserController) CreateUser(c echo.Context) error { + + var body CreateUserRequestBody + + if err := c.Bind(&body); err != nil { + c.Logger().Errorf("Failed to load create user request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + user, err := controller.svc.CreateUser(c.Request().Context(), body.Username, body.Password) + if err != nil { + c.Logger().Errorf("Failed to create user: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + var ResponseBody CreateUserResponseBody + ResponseBody.Username = user.Login + ResponseBody.Password = user.Password + + return c.JSON(http.StatusOK, &ResponseBody) +} diff --git a/controllers_v2/invoice.ctrl.go b/controllers_v2/invoice.ctrl.go new file mode 100644 index 0000000..bbfbb28 --- /dev/null +++ b/controllers_v2/invoice.ctrl.go @@ -0,0 +1,188 @@ +package v2controllers + +import ( + "net/http" + + "github.com/getAlby/lndhub.go/common" + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/getsentry/sentry-go" + "github.com/labstack/echo/v4" +) + +// InvoiceController : Add invoice controller struct +type InvoiceController struct { + svc *service.LndhubService +} + +func NewInvoiceController(svc *service.LndhubService) *InvoiceController { + return &InvoiceController{svc: svc} +} + +type OutgoingInvoice struct { + RHash interface{} `json:"r_hash,omitempty"` + PaymentHash interface{} `json:"payment_hash"` + PaymentPreimage string `json:"payment_preimage"` + Value int64 `json:"value"` + Type string `json:"type"` + Fee int64 `json:"fee"` + Timestamp int64 `json:"timestamp"` + Memo string `json:"memo"` + Keysend bool `json:"keysend"` + CustomRecords map[uint64][]byte `json:"custom_records"` +} + +type IncomingInvoice struct { + RHash interface{} `json:"r_hash,omitempty"` + PaymentHash interface{} `json:"payment_hash"` + PaymentRequest string `json:"payment_request"` + Description string `json:"description"` + PayReq string `json:"pay_req"` + Timestamp int64 `json:"timestamp"` + Type string `json:"type"` + ExpireTime int64 `json:"expire_time"` + Amount int64 `json:"amt"` + IsPaid bool `json:"ispaid"` + Keysend bool `json:"keysend"` + CustomRecords map[uint64][]byte `json:"custom_records"` +} + +// GetTXS godoc +// @Summary Retrieve outgoing payments +// @Description Returns a list of outgoing payments for a user +// @Accept json +// @Produce json +// @Tags Account +// @Success 200 {object} []OutgoingInvoice +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /gettxs [get] +// @Security OAuth2Password +func (controller *InvoiceController) GetOutgoingInvoices(c echo.Context) error { + userId := c.Get("UserID").(int64) + + invoices, err := controller.svc.InvoicesFor(c.Request().Context(), userId, common.InvoiceTypeOutgoing) + if err != nil { + return err + } + + response := make([]OutgoingInvoice, len(invoices)) + for i, invoice := range invoices { + rhash, _ := lib.ToJavaScriptBuffer(invoice.RHash) + response[i] = OutgoingInvoice{ + RHash: rhash, + PaymentHash: rhash, + PaymentPreimage: invoice.Preimage, + Value: invoice.Amount, + Type: common.InvoiceTypePaid, + Fee: invoice.Fee, + Timestamp: invoice.CreatedAt.Unix(), + Memo: invoice.Memo, + Keysend: invoice.Keysend, + CustomRecords: invoice.DestinationCustomRecords, + } + } + return c.JSON(http.StatusOK, &response) +} + +// GetUserInvoices godoc +// @Summary Retrieve incoming invoices +// @Description Returns a list of incoming invoices for a user +// @Accept json +// @Produce json +// @Tags Account +// @Success 200 {object} []IncomingInvoice +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /getuserinvoices [get] +// @Security OAuth2Password +func (controller *InvoiceController) GetIncomingInvoices(c echo.Context) error { + userId := c.Get("UserID").(int64) + + invoices, err := controller.svc.InvoicesFor(c.Request().Context(), userId, common.InvoiceTypeIncoming) + if err != nil { + return err + } + + response := make([]IncomingInvoice, len(invoices)) + for i, invoice := range invoices { + rhash, _ := lib.ToJavaScriptBuffer(invoice.RHash) + response[i] = IncomingInvoice{ + RHash: rhash, + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + ExpireTime: 3600 * 24, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + Keysend: invoice.Keysend, + CustomRecords: invoice.DestinationCustomRecords, + } + } + return c.JSON(http.StatusOK, &response) +} + +type AddInvoiceRequestBody struct { + Amount interface{} `json:"amt"` // amount in Satoshi + Memo string `json:"memo"` + DescriptionHash string `json:"description_hash" validate:"omitempty,hexadecimal,len=64"` +} + +type AddInvoiceResponseBody struct { + RHash string `json:"r_hash"` + PaymentRequest string `json:"payment_request"` + PayReq string `json:"pay_req"` +} + +// AddInvoice godoc +// @Summary Generate a new invoice +// @Description Returns a new bolt11 invoice +// @Accept json +// @Produce json +// @Tags Invoice +// @Param invoice body AddInvoiceRequestBody True "Add Invoice" +// @Success 200 {object} AddInvoiceResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /addinvoice [post] +// @Security OAuth2Password +func (controller *InvoiceController) AddInvoice(c echo.Context) error { + userID := c.Get("UserID").(int64) + var body AddInvoiceRequestBody + + if err := c.Bind(&body); err != nil { + c.Logger().Errorf("Failed to load addinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + if err := c.Validate(&body); err != nil { + c.Logger().Errorf("Invalid addinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + amount, err := controller.svc.ParseInt(body.Amount) + if err != nil || amount < 0 { + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + c.Logger().Infof("Adding invoice: user_id:%v memo:%s value:%v description_hash:%s", userID, body.Memo, amount, body.DescriptionHash) + + invoice, err := controller.svc.AddIncomingInvoice(c.Request().Context(), userID, amount, body.Memo, body.DescriptionHash) + if err != nil { + c.Logger().Errorf("Error creating invoice: user_id:%v error: %v", userID, err) + sentry.CaptureException(err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + responseBody := AddInvoiceResponseBody{} + responseBody.RHash = invoice.RHash + responseBody.PaymentRequest = invoice.PaymentRequest + responseBody.PayReq = invoice.PaymentRequest + + return c.JSON(http.StatusOK, &responseBody) +} +func (controller *InvoiceController) GetInvoice(c echo.Context) error { + return nil +} diff --git a/controllers_v2/keysend.ctrl.go b/controllers_v2/keysend.ctrl.go new file mode 100644 index 0000000..35c18eb --- /dev/null +++ b/controllers_v2/keysend.ctrl.go @@ -0,0 +1,127 @@ +package v2controllers + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lnd" + "github.com/getsentry/sentry-go" + "github.com/labstack/echo/v4" + "github.com/lightningnetwork/lnd/lnrpc" +) + +// KeySendController : Key send controller struct +type KeySendController struct { + svc *service.LndhubService +} + +func NewKeySendController(svc *service.LndhubService) *KeySendController { + return &KeySendController{svc: svc} +} + +type KeySendRequestBody struct { + Amount int64 `json:"amount" validate:"required,gt=0"` + Destination string `json:"destination" validate:"required"` + Memo string `json:"memo" validate:"omitempty"` + CustomRecords map[string]string `json:"customRecords" validate:"omitempty"` +} + +type KeySendResponseBody struct { + RHash *lib.JavaScriptBuffer `json:"payment_hash,omitempty"` + Amount int64 `json:"num_satoshis,omitempty"` + Description string `json:"description,omitempty"` + Destination string `json:"destination,omitempty"` + DescriptionHashStr string `json:"description_hash,omitempty"` + PaymentError string `json:"payment_error,omitempty"` + PaymentPreimage *lib.JavaScriptBuffer `json:"payment_preimage,omitempty"` + PaymentRoute *service.Route `json:"payment_route,omitempty"` +} + +//// KeySend godoc +// @Summary Make a keysend payment +// @Description Pay a node without an invoice using it's public key +// @Accept json +// @Produce json +// @Tags Payment +// @Param KeySendRequestBody body KeySendRequestBody True "Invoice to pay" +// @Success 200 {object} KeySendResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /keysend [post] +// @Security OAuth2Password +func (controller *KeySendController) KeySend(c echo.Context) error { + userID := c.Get("UserID").(int64) + reqBody := KeySendRequestBody{} + if err := c.Bind(&reqBody); err != nil { + c.Logger().Errorf("Failed to load keysend request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + if err := c.Validate(&reqBody); err != nil { + c.Logger().Errorf("Invalid keysend request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + lnPayReq := &lnd.LNPayReq{ + PayReq: &lnrpc.PayReq{ + Destination: reqBody.Destination, + NumSatoshis: reqBody.Amount, + Description: reqBody.Memo, + }, + Keysend: true, + } + + invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, "", lnPayReq) + if err != nil { + return err + } + + currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID) + if err != nil { + return err + } + + minimumBalance := invoice.Amount + if controller.svc.Config.FeeReserve { + minimumBalance += invoice.CalcFeeLimit() + } + if currentBalance < minimumBalance { + c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) + return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) + } + + invoice.DestinationCustomRecords = map[uint64][]byte{} + for key, value := range reqBody.CustomRecords { + intKey, err := strconv.Atoi(key) + if err != nil { + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + invoice.DestinationCustomRecords[uint64(intKey)] = []byte(value) + } + sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) + if err != nil { + c.Logger().Errorf("Payment failed: user_id:%v error: %v", userID, err) + sentry.CaptureException(err) + return c.JSON(http.StatusBadRequest, echo.Map{ + "error": true, + "code": 10, + "message": fmt.Sprintf("Payment failed. Does the receiver have enough inbound capacity? (%v)", err), + }) + } + + responseBody := &KeySendResponseBody{} + responseBody.RHash = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentHash} + responseBody.Amount = invoice.Amount + responseBody.Destination = invoice.DestinationPubkeyHex + responseBody.Description = invoice.Memo + responseBody.DescriptionHashStr = invoice.DescriptionHash + responseBody.PaymentError = sendPaymentResponse.PaymentError + responseBody.PaymentPreimage = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentPreimage} + responseBody.PaymentRoute = sendPaymentResponse.PaymentRoute + + return c.JSON(http.StatusOK, responseBody) +} diff --git a/controllers_v2/payinvoice.ctrl.go b/controllers_v2/payinvoice.ctrl.go new file mode 100644 index 0000000..441583a --- /dev/null +++ b/controllers_v2/payinvoice.ctrl.go @@ -0,0 +1,136 @@ +package v2controllers + +import ( + "fmt" + "net/http" + "strings" + + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lnd" + "github.com/getsentry/sentry-go" + sentryecho "github.com/getsentry/sentry-go/echo" + "github.com/labstack/echo/v4" +) + +// PayInvoiceController : Pay invoice controller struct +type PayInvoiceController struct { + svc *service.LndhubService +} + +func NewPayInvoiceController(svc *service.LndhubService) *PayInvoiceController { + return &PayInvoiceController{svc: svc} +} + +type PayInvoiceRequestBody struct { + Invoice string `json:"invoice" validate:"required"` + Amount interface{} `json:"amount" validate:"omitempty"` +} +type PayInvoiceResponseBody struct { + RHash *lib.JavaScriptBuffer `json:"payment_hash,omitempty"` + PaymentRequest string `json:"payment_request,omitempty"` + PayReq string `json:"pay_req,omitempty"` + Amount int64 `json:"num_satoshis,omitempty"` + Description string `json:"description,omitempty"` + DescriptionHashStr string `json:"description_hash,omitempty"` + PaymentError string `json:"payment_error,omitempty"` + PaymentPreimage *lib.JavaScriptBuffer `json:"payment_preimage,omitempty"` + PaymentRoute *service.Route `json:"payment_route,omitempty"` +} + +// PayInvoice godoc +// @Summary Pay an invoice +// @Description Pay a bolt11 invoice +// @Accept json +// @Produce json +// @Tags Payment +// @Param PayInvoiceRequest body PayInvoiceRequestBody True "Invoice to pay" +// @Success 200 {object} PayInvoiceResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /payinvoice [post] +// @Security OAuth2Password +func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { + userID := c.Get("UserID").(int64) + reqBody := PayInvoiceRequestBody{} + if err := c.Bind(&reqBody); err != nil { + c.Logger().Errorf("Failed to load payinvoice request body: user_id:%v error: %v", userID, err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + if err := c.Validate(&reqBody); err != nil { + c.Logger().Errorf("Invalid payinvoice request body user_id:%v error: %v", userID, err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + paymentRequest := reqBody.Invoice + paymentRequest = strings.ToLower(paymentRequest) + decodedPaymentRequest, err := controller.svc.DecodePaymentRequest(c.Request().Context(), paymentRequest) + if err != nil { + c.Logger().Errorf("Invalid payment request user_id:%v error: %v", userID, err) + sentry.CaptureException(err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + lnPayReq := &lnd.LNPayReq{ + PayReq: decodedPaymentRequest, + Keysend: false, + } + if decodedPaymentRequest.NumSatoshis == 0 { + amt, err := controller.svc.ParseInt(reqBody.Amount) + if err != nil || amt <= 0 { + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + lnPayReq.PayReq.NumSatoshis = amt + } + + invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, paymentRequest, lnPayReq) + if err != nil { + return err + } + + currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID) + if err != nil { + return err + } + + minimumBalance := invoice.Amount + if controller.svc.Config.FeeReserve { + minimumBalance += invoice.CalcFeeLimit() + } + if currentBalance < minimumBalance { + c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) + return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) + } + + sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) + if err != nil { + c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err) + if hub := sentryecho.GetHubFromContext(c); hub != nil { + hub.WithScope(func(scope *sentry.Scope) { + scope.SetExtra("invoice_id", invoice.ID) + scope.SetExtra("destination_pubkey_hex", invoice.DestinationPubkeyHex) + scope.SetExtra("payment_request", invoice.PaymentRequest) + hub.CaptureException(err) + }) + } + return c.JSON(http.StatusBadRequest, echo.Map{ + "error": true, + "code": 10, + "message": fmt.Sprintf("Payment failed. Does the receiver have enough inbound capacity? (%v)", err), + }) + } + responseBody := &PayInvoiceResponseBody{} + responseBody.RHash = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentHash} + responseBody.PaymentRequest = paymentRequest + responseBody.PayReq = paymentRequest + responseBody.Amount = invoice.Amount + responseBody.Description = invoice.Memo + responseBody.DescriptionHashStr = invoice.DescriptionHash + responseBody.PaymentError = sendPaymentResponse.PaymentError + responseBody.PaymentPreimage = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentPreimage} + responseBody.PaymentRoute = sendPaymentResponse.PaymentRoute + + return c.JSON(http.StatusOK, responseBody) +} diff --git a/legacy_endpoints.go b/legacy_endpoints.go new file mode 100644 index 0000000..0ef7a15 --- /dev/null +++ b/legacy_endpoints.go @@ -0,0 +1,44 @@ +package main + +import ( + "net/http" + + "github.com/getAlby/lndhub.go/controllers" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "golang.org/x/time/rate" +) + +func RegisterLegacyEndpoints(svc *service.LndhubService, e *echo.Echo, secured *echo.Group, securedWithStrictRateLimit *echo.Group, strictRateLimitMiddleware echo.MiddlewareFunc) { + // Public endpoints for account creation and authentication + e.POST("/auth", controllers.NewAuthController(svc).Auth) + e.POST("/create", controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware) + e.POST("/invoice/:user_login", controllers.NewInvoiceController(svc).Invoice, middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(svc.Config.DefaultRateLimit)))) + + // Secured endpoints which require a Authorization token (JWT) + secured.POST("/addinvoice", controllers.NewAddInvoiceController(svc).AddInvoice) + securedWithStrictRateLimit.POST("/payinvoice", controllers.NewPayInvoiceController(svc).PayInvoice) + secured.GET("/gettxs", controllers.NewGetTXSController(svc).GetTXS) + secured.GET("/getuserinvoices", controllers.NewGetTXSController(svc).GetUserInvoices) + secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment) + secured.GET("/balance", controllers.NewBalanceController(svc).Balance) + secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo, createCacheClient().Middleware()) + securedWithStrictRateLimit.POST("/keysend", controllers.NewKeySendController(svc).KeySend) + + // These endpoints are currently not supported and we return a blank response for backwards compatibility + blankController := controllers.NewBlankController(svc) + secured.GET("/getbtc", blankController.GetBtc) + secured.GET("/getpending", blankController.GetPending) + + //Index page endpoints, no Authorization required + homeController := controllers.NewHomeController(svc, indexHtml) + e.GET("/", homeController.Home, createCacheClient().Middleware()) + e.GET("/qr", homeController.QR) + //workaround, just adding /static would make a request to these resources hit the authorized group + e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) + e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) + e.Pre(middleware.Rewrite(map[string]string{ + "/favicon.ico": "/static/img/favicon.png", + })) +} diff --git a/main.go b/main.go index 8f1a26e..ac125be 100644 --- a/main.go +++ b/main.go @@ -146,38 +146,11 @@ func main() { } strictRateLimitMiddleware := createRateLimitMiddleware(c.StrictRateLimit, c.BurstRateLimit) - // Public endpoints for account creation and authentication - e.POST("/auth", controllers.NewAuthController(svc).Auth) - e.POST("/create", controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware) - e.POST("/invoice/:user_login", controllers.NewInvoiceController(svc).Invoice, middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(c.DefaultRateLimit)))) - - // Secured endpoints which require a Authorization token (JWT) secured := e.Group("", tokens.Middleware(c.JWTSecret), middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(c.DefaultRateLimit)))) securedWithStrictRateLimit := e.Group("", tokens.Middleware(c.JWTSecret), strictRateLimitMiddleware) - secured.POST("/addinvoice", controllers.NewAddInvoiceController(svc).AddInvoice) - securedWithStrictRateLimit.POST("/payinvoice", controllers.NewPayInvoiceController(svc).PayInvoice) - secured.GET("/gettxs", controllers.NewGetTXSController(svc).GetTXS) - secured.GET("/getuserinvoices", controllers.NewGetTXSController(svc).GetUserInvoices) - secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment) - secured.GET("/balance", controllers.NewBalanceController(svc).Balance) - secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo, createCacheClient().Middleware()) - securedWithStrictRateLimit.POST("/keysend", controllers.NewKeySendController(svc).KeySend) - // These endpoints are currently not supported and we return a blank response for backwards compatibility - blankController := controllers.NewBlankController(svc) - secured.GET("/getbtc", blankController.GetBtc) - secured.GET("/getpending", blankController.GetPending) - - //Index page endpoints, no Authorization required - homeController := controllers.NewHomeController(svc, indexHtml) - e.GET("/", homeController.Home, createCacheClient().Middleware()) - e.GET("/qr", homeController.QR) - //workaround, just adding /static would make a request to these resources hit the authorized group - e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) - e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) - e.Pre(middleware.Rewrite(map[string]string{ - "/favicon.ico": "/static/img/favicon.png", - })) + RegisterLegacyEndpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware) + RegisterV2Endpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware) //invoice streaming //Authentication should be done through the query param because this is a websocket diff --git a/v2_endpoints.go b/v2_endpoints.go new file mode 100644 index 0000000..55f4758 --- /dev/null +++ b/v2_endpoints.go @@ -0,0 +1,21 @@ +package main + +import ( + v2controllers "github.com/getAlby/lndhub.go/controllers_v2" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +func RegisterV2Endpoints(svc *service.LndhubService, e *echo.Echo, secured *echo.Group, securedWithStrictRateLimit *echo.Group, strictRateLimitMiddleware echo.MiddlewareFunc) { + // TODO: v2 auth endpoint: generalized oauth token generation + // e.POST("/auth", controllers.NewAuthController(svc).Auth) + e.POST("/v2/users", v2controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware) + invoiceCtrl := v2controllers.NewInvoiceController(svc) + secured.POST("/v2/invoices", invoiceCtrl.AddInvoice) + secured.GET("/v2/invoices/incoming", invoiceCtrl.GetIncomingInvoices) + secured.GET("/v2/invoices/outgoing", invoiceCtrl.GetOutgoingInvoices) + secured.GET("/v2/invoices/:payment_hash", invoiceCtrl.GetInvoice) + securedWithStrictRateLimit.POST("/v2/payments/bolt11", v2controllers.NewPayInvoiceController(svc).PayInvoice) + securedWithStrictRateLimit.POST("/v2/payments/keysend", v2controllers.NewKeySendController(svc).KeySend) + secured.GET("/v2/balance", v2controllers.NewBalanceController(svc).Balance) +}