From b39621979260efd35ae7db08c523a5b0490586b6 Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 22:32:08 +0200 Subject: [PATCH 01/13] add api service --- internal/api/lightning.go | 10 ++++++++++ main.go | 3 +++ 2 files changed, 13 insertions(+) create mode 100644 internal/api/lightning.go diff --git a/internal/api/lightning.go b/internal/api/lightning.go new file mode 100644 index 0000000..197c923 --- /dev/null +++ b/internal/api/lightning.go @@ -0,0 +1,10 @@ +package api + +import "net/http" + +type ApiService struct { +} + +func (s ApiService) Handler(w http.ResponseWriter, r *http.Request) { + +} diff --git a/main.go b/main.go index 49384c6..fb7a1a4 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,9 @@ func startApiServer(bot *telegram.TipBot) { s.AppendRoute(`/lndhub/ext/{.*}`, hub.Handle) s.AppendRoute(`/lndhub/ext`, hub.Handle) + apiService := api.ApiService{} + s.AppendRoute(`/api`, apiService.Handler) + // start internal admin server adminService := admin.New(bot) internalAdminServer := api.NewServer("0.0.0.0:6060") From a669ea86ac7c89da1698efc93cd000a334f53b34 Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 22:55:27 +0200 Subject: [PATCH 02/13] add api service --- internal/api/balance.go | 5 +++++ internal/api/invoice.go | 23 +++++++++++++++++++++++ internal/api/lightning.go | 27 ++++++++++++++++++++++++--- internal/api/payment.go | 9 +++++++++ main.go | 9 +++++++-- 5 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 internal/api/balance.go create mode 100644 internal/api/invoice.go create mode 100644 internal/api/payment.go diff --git a/internal/api/balance.go b/internal/api/balance.go new file mode 100644 index 0000000..36f6e2a --- /dev/null +++ b/internal/api/balance.go @@ -0,0 +1,5 @@ +package api + +type BalanceResponse struct { + Balance int64 `json:"balance"` +} diff --git a/internal/api/invoice.go b/internal/api/invoice.go new file mode 100644 index 0000000..35901e2 --- /dev/null +++ b/internal/api/invoice.go @@ -0,0 +1,23 @@ +package api + +type InvoiceStatusResponse struct { + State string `json:"state,omitempty"` + PaymentHash string `json:"payment_hash"` + PreiMage int64 `json:"preiMage"` +} + +type CreateInvoiceResponse struct { + PaymentHash string `json:"payment_hash"` + PayRequest string `json:"pay_request"` + Preimage string `json:"preimage,omitempty"` +} +type CreateInvoiceRequest struct { + Memo string `json:"memo"` + Amount int64 `json:"amount"` + DescriptionHash string `json:"description_hash"` + UnhashedDescription string `json:"unhashed_description"` +} + +type PayInvoice struct { + PayRequest string `json:"pay_request"` +} diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 197c923..05f2498 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -1,10 +1,31 @@ package api -import "net/http" +import ( + "github.com/LightningTipBot/LightningTipBot/internal/telegram" + "net/http" +) -type ApiService struct { +type Service struct { + bot *telegram.TipBot } -func (s ApiService) Handler(w http.ResponseWriter, r *http.Request) { +func (s Service) Balance(w http.ResponseWriter, r *http.Request) { + +} + +func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { + +} + +func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { + +} + +func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { + +} + +// InvoiceStatus +func (s Service) InvoiceStatus(w http.ResponseWriter, r *http.Request) { } diff --git a/internal/api/payment.go b/internal/api/payment.go new file mode 100644 index 0000000..107fb0d --- /dev/null +++ b/internal/api/payment.go @@ -0,0 +1,9 @@ +package api + +type PaymentStatusResponse struct { + State string `json:"state"` + FeeMsat int64 `json:"fee_msat,omitempty"` + Amount int64 `json:"amount,omitempty"` + Preimage string `json:"preimage,omitempty"` + PaymentHash string `json:"payment_hash"` +} diff --git a/main.go b/main.go index fb7a1a4..96ae822 100644 --- a/main.go +++ b/main.go @@ -64,8 +64,13 @@ func startApiServer(bot *telegram.TipBot) { s.AppendRoute(`/lndhub/ext/{.*}`, hub.Handle) s.AppendRoute(`/lndhub/ext`, hub.Handle) - apiService := api.ApiService{} - s.AppendRoute(`/api`, apiService.Handler) + // starting api service + apiService := api.Service{} + s.AppendRoute(`/api/v1/paymentstatus/{payment_hash}`, apiService.PaymentStatus, http.MethodPost) + s.AppendRoute(`/api/v1/invoicestatus/{payment_hash}`, apiService.InvoiceStatus, http.MethodPost) + s.AppendRoute(`/api/v1/payinvoice`, apiService.PayInvoice, http.MethodPost) + s.AppendRoute(`/api/v1/createinvoice`, apiService.CreateInvoice, http.MethodPost) + s.AppendRoute(`/api/v1/balance`, apiService.Balance, http.MethodGet) // start internal admin server adminService := admin.New(bot) From 4bcbd2fefea1de957ae6ec6d8a725721a0c37679 Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 23:35:27 +0200 Subject: [PATCH 03/13] add auth middleware --- internal/api/middleware.go | 75 ++++++++++++++++++++++++++++++++++++++ internal/api/server.go | 7 ++++ main.go | 16 +++++--- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 3bc7670..526ad3a 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -1,8 +1,15 @@ package api import ( + "context" + "encoding/base64" + "fmt" + "github.com/LightningTipBot/LightningTipBot/internal/lnbits" + "github.com/LightningTipBot/LightningTipBot/internal/telegram" + "gorm.io/gorm" "net/http" "net/http/httputil" + "strings" log "github.com/sirupsen/logrus" ) @@ -16,6 +23,74 @@ func LoggingMiddleware(prefix string, next http.HandlerFunc) http.HandlerFunc { } } +type AuthType string + +const ( + AuthTypeBasic AuthType = "Basic" + AuthTypeBearer AuthType = "Bearer" +) + +func AuthorizationMiddleware(database *gorm.DB, authType AuthType, next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + // check if the user is banned + if auth != "" { + _, password, ok := parseAuth(authType, auth) + if !ok { + return + } + // first we make sure that the password is not already "banned_" + if strings.Contains(password, "_") || strings.HasPrefix(password, "banned_") { + log.Warnf("[AuthorizationMiddleware] Banned user %s. Not forwarding request", password) + return + } + // then we check whether the "normal" password provided is in the database (it should be not if the user is banned) + user := &lnbits.User{} + tx := database.Where("wallet_adminkey = ? COLLATE NOCASE", password).First(user) + if tx.Error != nil { + tx = database.Where("wallet_inkey = ? COLLATE NOCASE", password).First(user) + log.Warnf("[AuthorizationMiddleware] Could not get wallet admin key %s: %v", password, tx.Error) + if tx.Error != nil { + log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) + return + } + if r.URL.Path == "/api/v1/payinvoice" { + log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) + return + } + } + r.Context() + log.Debugf("[AuthorizationMiddleware] User: %s", telegram.GetUserStr(user.Telegram)) + r = r.WithContext(context.WithValue(r.Context(), "user", user)) + } + + next.ServeHTTP(w, r) + } +} + +// parseAuth parses an HTTP Basic Authentication string. +// "Bearer QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). +func parseAuth(authType AuthType, auth string) (username, password string, ok bool) { + parse := func(prefix string) (username, password string, ok bool) { + // Case insensitive prefix match. See Issue 22736. + if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { + return + } + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true + } + return parse(fmt.Sprintf("%s ", authType)) + +} + func dump(r *http.Request) string { x, err := httputil.DumpRequest(r, true) if err != nil { diff --git a/internal/api/server.go b/internal/api/server.go index da35696..31ac499 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "gorm.io/gorm" "net/http" "time" @@ -42,6 +43,12 @@ func (w *Server) ListenAndServe() { func (w *Server) PathPrefix(path string, handler http.Handler) { w.router.PathPrefix(path).Handler(handler) } +func (w *Server) AppendAuthorizedRoute(path string, authType AuthType, database *gorm.DB, handler func(http.ResponseWriter, *http.Request), methods ...string) { + r := w.router.HandleFunc(path, LoggingMiddleware("API", AuthorizationMiddleware(database, authType, handler))) + if len(methods) > 0 { + r.Methods(methods...) + } +} func (w *Server) AppendRoute(path string, handler func(http.ResponseWriter, *http.Request), methods ...string) { r := w.router.HandleFunc(path, LoggingMiddleware("API", handler)) if len(methods) > 0 { diff --git a/main.go b/main.go index 96ae822..6ace7b6 100644 --- a/main.go +++ b/main.go @@ -54,7 +54,6 @@ func startApiServer(bot *telegram.TipBot) { // append lnurl ctx functions lnUrl := lnurl.New(bot) s.AppendRoute("/.well-known/lnurlp/{username}", lnUrl.Handle, http.MethodGet) - // userpage server userpage := userpage.New(bot) s.AppendRoute("/@{username}", userpage.UserPageHandler, http.MethodGet) @@ -63,14 +62,16 @@ func startApiServer(bot *telegram.TipBot) { hub := lndhub.New(bot) s.AppendRoute(`/lndhub/ext/{.*}`, hub.Handle) s.AppendRoute(`/lndhub/ext`, hub.Handle) + //s.AppendAuthorizedRoute(`/lndhub/ext/{.*}`, api.AuthTypeBearer, bot.DB.Users, hub.Handle) + //s.AppendAuthorizedRoute(`/lndhub/ext`, api.AuthTypeBearer, bot.DB.Users, hub.Handle) // starting api service apiService := api.Service{} - s.AppendRoute(`/api/v1/paymentstatus/{payment_hash}`, apiService.PaymentStatus, http.MethodPost) - s.AppendRoute(`/api/v1/invoicestatus/{payment_hash}`, apiService.InvoiceStatus, http.MethodPost) - s.AppendRoute(`/api/v1/payinvoice`, apiService.PayInvoice, http.MethodPost) - s.AppendRoute(`/api/v1/createinvoice`, apiService.CreateInvoice, http.MethodPost) - s.AppendRoute(`/api/v1/balance`, apiService.Balance, http.MethodGet) + s.AppendAuthorizedRoute(`/api/v1/paymentstatus/{payment_hash}`, api.AuthTypeBasic, bot.DB.Users, apiService.PaymentStatus, http.MethodPost) + s.AppendAuthorizedRoute(`/api/v1/invoicestatus/{payment_hash}`, api.AuthTypeBasic, bot.DB.Users, apiService.InvoiceStatus, http.MethodPost) + s.AppendAuthorizedRoute(`/api/v1/payinvoice`, api.AuthTypeBasic, bot.DB.Users, apiService.PayInvoice, http.MethodPost) + s.AppendAuthorizedRoute(`/api/v1/createinvoice`, api.AuthTypeBasic, bot.DB.Users, apiService.CreateInvoice, http.MethodPost) + s.AppendAuthorizedRoute(`/api/v1/balance`, api.AuthTypeBasic, bot.DB.Users, apiService.Balance, http.MethodGet) // start internal admin server adminService := admin.New(bot) @@ -83,6 +84,9 @@ func startApiServer(bot *telegram.TipBot) { } +func withMiddleware(mw func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc { + return mw +} func withRecovery() { if r := recover(); r != nil { log.Errorln("Recovered panic: ", r) From 0c29fec19fc249114eed88ac4e43953ef8a81517 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 19 Aug 2022 23:36:11 +0200 Subject: [PATCH 04/13] lnbits stuff --- internal/api/lightning.go | 37 +++++++++++++++++++++++++++++++++++-- internal/lnbits/lnbits.go | 27 ++++++++++++++++++++++++++- internal/lnbits/types.go | 17 +++++++++++------ 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 05f2498..2c92f9a 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -1,8 +1,12 @@ package api import ( - "github.com/LightningTipBot/LightningTipBot/internal/telegram" + "encoding/json" "net/http" + + "github.com/LightningTipBot/LightningTipBot/internal" + "github.com/LightningTipBot/LightningTipBot/internal/lnbits" + "github.com/LightningTipBot/LightningTipBot/internal/telegram" ) type Service struct { @@ -10,11 +14,40 @@ type Service struct { } func (s Service) Balance(w http.ResponseWriter, r *http.Request) { - + user := &lnbits.User{} + balance, err := s.bot.GetUserBalance(user) + if err != nil { + // return ctx, errors.Create("balance check failed") + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(balance) } func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { + var createInvoiceRequest CreateInvoiceRequest + err := json.NewDecoder(r.Body).Decode(&createInvoiceRequest) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + user := &lnbits.User{} + invoice, err := user.Wallet.Invoice( + lnbits.InvoiceParams{ + Amount: createInvoiceRequest.Amount, + Out: false, + DescriptionHash: createInvoiceRequest.DescriptionHash, + UnhashedDescription: createInvoiceRequest.UnhashedDescription, + Webhook: internal.Configuration.Lnbits.WebhookServer}, + s.bot.Client) + if err != nil { + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(invoice) } func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { diff --git a/internal/lnbits/lnbits.go b/internal/lnbits/lnbits.go index c446b9a..64e0a7e 100644 --- a/internal/lnbits/lnbits.go +++ b/internal/lnbits/lnbits.go @@ -1,6 +1,7 @@ package lnbits import ( + "fmt" "time" "github.com/imroc/req" @@ -131,7 +132,7 @@ func (c Client) Info(w Wallet) (wtx Wallet, err error) { return } -// Info returns wallet payments +// Payments returns wallet payments func (c Client) Payments(w Wallet) (wtx Payments, err error) { // custom header with invoice key invoiceHeader := req.Header{ @@ -155,6 +156,30 @@ func (c Client) Payments(w Wallet) (wtx Payments, err error) { return } +// Payment state of a payment +func (c Client) Payment(w Wallet, payment_hash string) (payment Payment, err error) { + // custom header with invoice key + invoiceHeader := req.Header{ + "Content-Type": "application/json", + "Accept": "application/json", + "X-Api-Key": w.Inkey, + } + resp, err := req.Get(c.url+fmt.Sprintf("/api/v1/payments/%s", payment_hash), invoiceHeader, nil) + if err != nil { + return + } + + if resp.Response().StatusCode >= 300 { + var reqErr Error + resp.ToJSON(&reqErr) + err = reqErr + return + } + + err = resp.ToJSON(&payment) + return +} + // Wallets returns all wallets belonging to an user func (c Client) Wallets(w User) (wtx []Wallet, err error) { resp, err := req.Get(c.url+"/usermanager/api/v1/wallets/"+w.ID, c.header, nil) diff --git a/internal/lnbits/types.go b/internal/lnbits/types.go index c15a3a0..37824fc 100644 --- a/internal/lnbits/types.go +++ b/internal/lnbits/types.go @@ -75,11 +75,12 @@ func (u *User) ResetState() { } type InvoiceParams struct { - Out bool `json:"out"` // must be True if invoice is payed, False if invoice is received - Amount int64 `json:"amount"` // amount in Satoshi - Memo string `json:"memo,omitempty"` // the invoice memo. - Webhook string `json:"webhook,omitempty"` // the webhook to fire back to when payment is received. - DescriptionHash string `json:"description_hash,omitempty"` // the invoice description hash. + Out bool `json:"out"` // must be True if invoice is payed, False if invoice is received + Amount int64 `json:"amount"` // amount in Satoshi + Memo string `json:"memo,omitempty"` // the invoice memo. + Webhook string `json:"webhook,omitempty"` // the webhook to fire back to when payment is received. + DescriptionHash string `json:"description_hash,omitempty"` // the invoice description hash. + UnhashedDescription string `json:"unhashed_description,omitempty"` // the unhashed invoice description. } type PaymentParams struct { @@ -117,7 +118,7 @@ type Wallet struct { User string `json:"user"` } -type Payments []struct { +type Payment struct { CheckingID string `json:"checking_id"` Pending bool `json:"pending"` Amount int64 `json:"amount"` @@ -133,6 +134,10 @@ type Payments []struct { WebhookStatus interface{} `json:"webhook_status"` } +type Payments []struct { + Payment +} + type BitInvoice struct { PaymentHash string `json:"payment_hash"` PaymentRequest string `json:"payment_request"` From fbc47c0fb9a420017294e9d5f4b5f5e5b59e2c5e Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 23:37:28 +0200 Subject: [PATCH 05/13] fix payments --- internal/lnbits/types.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/lnbits/types.go b/internal/lnbits/types.go index 37824fc..dc56c2a 100644 --- a/internal/lnbits/types.go +++ b/internal/lnbits/types.go @@ -134,9 +134,7 @@ type Payment struct { WebhookStatus interface{} `json:"webhook_status"` } -type Payments []struct { - Payment -} +type Payments []Payment type BitInvoice struct { PaymentHash string `json:"payment_hash"` From 98401546fafdfbb50ef7465734c6fda24dbc33ab Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 23:59:23 +0200 Subject: [PATCH 06/13] add user auth --- internal/api/middleware.go | 96 +++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 526ad3a..1f1a28f 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -23,47 +23,51 @@ func LoggingMiddleware(prefix string, next http.HandlerFunc) http.HandlerFunc { } } -type AuthType string +type AuthType struct { + Type string + Decoder func(s string) ([]byte, error) +} -const ( - AuthTypeBasic AuthType = "Basic" - AuthTypeBearer AuthType = "Bearer" -) +var AuthTypeBasic = AuthType{Type: "Basic"} +var AuthTypeBearerBase64 = AuthType{Type: "Basic", Decoder: base64.StdEncoding.DecodeString} func AuthorizationMiddleware(database *gorm.DB, authType AuthType, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") // check if the user is banned - if auth != "" { - _, password, ok := parseAuth(authType, auth) - if !ok { - return - } - // first we make sure that the password is not already "banned_" - if strings.Contains(password, "_") || strings.HasPrefix(password, "banned_") { - log.Warnf("[AuthorizationMiddleware] Banned user %s. Not forwarding request", password) - return - } - // then we check whether the "normal" password provided is in the database (it should be not if the user is banned) - user := &lnbits.User{} - tx := database.Where("wallet_adminkey = ? COLLATE NOCASE", password).First(user) - if tx.Error != nil { - tx = database.Where("wallet_inkey = ? COLLATE NOCASE", password).First(user) - log.Warnf("[AuthorizationMiddleware] Could not get wallet admin key %s: %v", password, tx.Error) - if tx.Error != nil { - log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) - return - } - if r.URL.Path == "/api/v1/payinvoice" { - log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) - return - } - } - r.Context() - log.Debugf("[AuthorizationMiddleware] User: %s", telegram.GetUserStr(user.Telegram)) - r = r.WithContext(context.WithValue(r.Context(), "user", user)) + if auth == "" { + w.WriteHeader(401) + log.Warn("[AuthorizationMiddleware] no auth") + return } - + _, password, ok := parseAuth(authType, auth) + if !ok { + w.WriteHeader(401) + return + } + // first we make sure that the password is not already "banned_" + if strings.Contains(password, "_") || strings.HasPrefix(password, "banned_") { + w.WriteHeader(401) + log.Warnf("[AuthorizationMiddleware] Banned user %s. Not forwarding request", password) + return + } + // then we check whether the "normal" password provided is in the database (it should be not if the user is banned) + user := &lnbits.User{} + tx := database.Where("wallet_adminkey = ? COLLATE NOCASE", password).First(user) + if tx.Error != nil { + tx = database.Where("wallet_inkey = ? COLLATE NOCASE", password).First(user) + log.Warnf("[AuthorizationMiddleware] Could not get wallet admin key %s: %v", password, tx.Error) + if tx.Error != nil { + log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) + return + } + if r.URL.Path == "/api/v1/payinvoice" { + log.Warnf("[AuthorizationMiddleware] need admin key to pay invoice %s: %v", password, tx.Error) + return + } + } + log.Debugf("[AuthorizationMiddleware] User: %s", telegram.GetUserStr(user.Telegram)) + r = r.WithContext(context.WithValue(r.Context(), "user", user)) next.ServeHTTP(w, r) } } @@ -76,18 +80,22 @@ func parseAuth(authType AuthType, auth string) (username, password string, ok bo if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { return } - c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) - if err != nil { - return + if authType.Decoder != nil { + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true } - cs := string(c) - s := strings.IndexByte(cs, ':') - if s < 0 { - return - } - return cs[:s], cs[s+1:], true + return auth[len(prefix):], auth[len(prefix):], true + } - return parse(fmt.Sprintf("%s ", authType)) + return parse(fmt.Sprintf("%s ", authType.Type)) } From 127f95b8deaea4a1f0bcc43e8f0a871c6aec0aff Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:17:19 +0200 Subject: [PATCH 07/13] boom --- internal/api/invoice.go | 4 +-- internal/api/lightning.go | 50 ++++++++++++++++++++++++++--- internal/api/middleware.go | 7 ++-- internal/lnbits/lnbits.go | 4 +-- internal/lnbits/types.go | 2 +- internal/telegram/lnurl-withdraw.go | 2 +- internal/telegram/transaction.go | 40 +++++++++++------------ 7 files changed, 76 insertions(+), 33 deletions(-) diff --git a/internal/api/invoice.go b/internal/api/invoice.go index 35901e2..7d1fb56 100644 --- a/internal/api/invoice.go +++ b/internal/api/invoice.go @@ -3,7 +3,7 @@ package api type InvoiceStatusResponse struct { State string `json:"state,omitempty"` PaymentHash string `json:"payment_hash"` - PreiMage int64 `json:"preiMage"` + Preimage int64 `json:"preimage"` } type CreateInvoiceResponse struct { @@ -18,6 +18,6 @@ type CreateInvoiceRequest struct { UnhashedDescription string `json:"unhashed_description"` } -type PayInvoice struct { +type PayInvoiceRequest struct { PayRequest string `json:"pay_request"` } diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 2c92f9a..e00f85c 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -13,11 +13,21 @@ type Service struct { bot *telegram.TipBot } +type ApiError struct { + Message string `json:"error"` +} + +func RespondError(w http.ResponseWriter, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ApiError{Message: message}) +} + func (s Service) Balance(w http.ResponseWriter, r *http.Request) { - user := &lnbits.User{} + user := r.Context().Value("user").(*lnbits.User) balance, err := s.bot.GetUserBalance(user) if err != nil { - // return ctx, errors.Create("balance check failed") + RespondError(w, "balance check failed") return } w.Header().Set("Content-Type", "application/json") @@ -42,6 +52,7 @@ func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { Webhook: internal.Configuration.Lnbits.WebhookServer}, s.bot.Client) if err != nil { + RespondError(w, "could not create invoice") return } @@ -51,14 +62,45 @@ func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { } func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { + var payInvoiceRequest PayInvoiceRequest + err := json.NewDecoder(r.Body).Decode(&payInvoiceRequest) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + user := &lnbits.User{} + invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: payInvoiceRequest.PayRequest}, s.bot.Client) + if err != nil { + RespondError(w, "could not pay invoice: "+err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(invoice) } func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { - + user := &lnbits.User{} + payment, err := s.bot.Client.Payment(*user.Wallet, "") + if err != nil { + RespondError(w, "could not get payment") + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(payment) } // InvoiceStatus func (s Service) InvoiceStatus(w http.ResponseWriter, r *http.Request) { - + user := &lnbits.User{} + user.Wallet = &lnbits.Wallet{} + payment, err := s.bot.Client.Payment(*user.Wallet, "") + if err != nil { + RespondError(w, "could not get payment") + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(payment) } diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 1f1a28f..909f492 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -4,13 +4,14 @@ import ( "context" "encoding/base64" "fmt" - "github.com/LightningTipBot/LightningTipBot/internal/lnbits" - "github.com/LightningTipBot/LightningTipBot/internal/telegram" - "gorm.io/gorm" "net/http" "net/http/httputil" "strings" + "github.com/LightningTipBot/LightningTipBot/internal/lnbits" + "github.com/LightningTipBot/LightningTipBot/internal/telegram" + "gorm.io/gorm" + log "github.com/sirupsen/logrus" ) diff --git a/internal/lnbits/lnbits.go b/internal/lnbits/lnbits.go index 64e0a7e..e8db8f4 100644 --- a/internal/lnbits/lnbits.go +++ b/internal/lnbits/lnbits.go @@ -85,7 +85,7 @@ func (c *Client) CreateWallet(userId, walletName, adminId string) (wal Wallet, e } // Invoice creates an invoice associated with this wallet. -func (w Wallet) Invoice(params InvoiceParams, c *Client) (lntx BitInvoice, err error) { +func (w Wallet) Invoice(params InvoiceParams, c *Client) (lntx Invoice, err error) { // custom header with invoice key invoiceHeader := req.Header{ "Content-Type": "application/json", @@ -199,7 +199,7 @@ func (c Client) Wallets(w User) (wtx []Wallet, err error) { } // Pay pays a given invoice with funds from the wallet. -func (w Wallet) Pay(params PaymentParams, c *Client) (wtx BitInvoice, err error) { +func (w Wallet) Pay(params PaymentParams, c *Client) (wtx Invoice, err error) { // custom header with admin key adminHeader := req.Header{ "Content-Type": "application/json", diff --git a/internal/lnbits/types.go b/internal/lnbits/types.go index dc56c2a..52aa4c7 100644 --- a/internal/lnbits/types.go +++ b/internal/lnbits/types.go @@ -136,7 +136,7 @@ type Payment struct { type Payments []Payment -type BitInvoice struct { +type Invoice struct { PaymentHash string `json:"payment_hash"` PaymentRequest string `json:"payment_request"` } diff --git a/internal/telegram/lnurl-withdraw.go b/internal/telegram/lnurl-withdraw.go index 21e88f9..c1d2f60 100644 --- a/internal/telegram/lnurl-withdraw.go +++ b/internal/telegram/lnurl-withdraw.go @@ -42,7 +42,7 @@ type LnurlWithdrawState struct { Comment string `json:"comment"` LanguageCode string `json:"languagecode"` Success bool `json:"success"` - Invoice lnbits.BitInvoice `json:"invoice"` + Invoice lnbits.Invoice `json:"invoice"` Message string `json:"message"` } diff --git a/internal/telegram/transaction.go b/internal/telegram/transaction.go index fe2d032..afadd4f 100644 --- a/internal/telegram/transaction.go +++ b/internal/telegram/transaction.go @@ -11,26 +11,26 @@ import ( ) type Transaction struct { - ID uint `gorm:"primarykey"` - Time time.Time `json:"time"` - Bot *TipBot `gorm:"-"` - From *lnbits.User `json:"from" gorm:"-"` - To *lnbits.User `json:"to" gorm:"-"` - FromId int64 `json:"from_id" ` - ToId int64 `json:"to_id" ` - FromUser string `json:"from_user"` - ToUser string `json:"to_user"` - Type string `json:"type"` - Amount int64 `json:"amount"` - ChatID int64 `json:"chat_id"` - ChatName string `json:"chat_name"` - Memo string `json:"memo"` - Success bool `json:"success"` - FromWallet string `json:"from_wallet"` - ToWallet string `json:"to_wallet"` - FromLNbitsID string `json:"from_lnbits"` - ToLNbitsID string `json:"to_lnbits"` - Invoice lnbits.BitInvoice `gorm:"embedded;embeddedPrefix:invoice_"` + ID uint `gorm:"primarykey"` + Time time.Time `json:"time"` + Bot *TipBot `gorm:"-"` + From *lnbits.User `json:"from" gorm:"-"` + To *lnbits.User `json:"to" gorm:"-"` + FromId int64 `json:"from_id" ` + ToId int64 `json:"to_id" ` + FromUser string `json:"from_user"` + ToUser string `json:"to_user"` + Type string `json:"type"` + Amount int64 `json:"amount"` + ChatID int64 `json:"chat_id"` + ChatName string `json:"chat_name"` + Memo string `json:"memo"` + Success bool `json:"success"` + FromWallet string `json:"from_wallet"` + ToWallet string `json:"to_wallet"` + FromLNbitsID string `json:"from_lnbits"` + ToLNbitsID string `json:"to_lnbits"` + Invoice lnbits.Invoice `gorm:"embedded;embeddedPrefix:invoice_"` } type TransactionOption func(t *Transaction) From d066fa9e278707394c51efcc995d810b7e25cf19 Mon Sep 17 00:00:00 2001 From: gohumble Date: Sat, 20 Aug 2022 00:17:58 +0200 Subject: [PATCH 08/13] set bot in api service --- internal/api/lightning.go | 8 ++++---- main.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 2c92f9a..0e58892 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -10,12 +10,12 @@ import ( ) type Service struct { - bot *telegram.TipBot + Bot *telegram.TipBot } func (s Service) Balance(w http.ResponseWriter, r *http.Request) { - user := &lnbits.User{} - balance, err := s.bot.GetUserBalance(user) + user := telegram.LoadUser(r.Context()) + balance, err := s.Bot.GetUserBalance(user) if err != nil { // return ctx, errors.Create("balance check failed") return @@ -40,7 +40,7 @@ func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { DescriptionHash: createInvoiceRequest.DescriptionHash, UnhashedDescription: createInvoiceRequest.UnhashedDescription, Webhook: internal.Configuration.Lnbits.WebhookServer}, - s.bot.Client) + s.Bot.Client) if err != nil { return } diff --git a/main.go b/main.go index 6ace7b6..b6a61c5 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ func startApiServer(bot *telegram.TipBot) { //s.AppendAuthorizedRoute(`/lndhub/ext`, api.AuthTypeBearer, bot.DB.Users, hub.Handle) // starting api service - apiService := api.Service{} + apiService := api.Service{Bot: bot} s.AppendAuthorizedRoute(`/api/v1/paymentstatus/{payment_hash}`, api.AuthTypeBasic, bot.DB.Users, apiService.PaymentStatus, http.MethodPost) s.AppendAuthorizedRoute(`/api/v1/invoicestatus/{payment_hash}`, api.AuthTypeBasic, bot.DB.Users, apiService.InvoiceStatus, http.MethodPost) s.AppendAuthorizedRoute(`/api/v1/payinvoice`, api.AuthTypeBasic, bot.DB.Users, apiService.PayInvoice, http.MethodPost) From dbe94b94948e4cf1c2e05bf5d232781d06482e5d Mon Sep 17 00:00:00 2001 From: gohumble Date: Sat, 20 Aug 2022 00:19:12 +0200 Subject: [PATCH 09/13] fix --- internal/api/lightning.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 7cdae17..a1b008e 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -70,7 +70,7 @@ func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { } user := &lnbits.User{} - invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: payInvoiceRequest.PayRequest}, s.bot.Client) + invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: payInvoiceRequest.PayRequest}, s.Bot.Client) if err != nil { RespondError(w, "could not pay invoice: "+err.Error()) return @@ -83,7 +83,7 @@ func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { user := &lnbits.User{} - payment, err := s.bot.Client.Payment(*user.Wallet, "") + payment, err := s.Bot.Client.Payment(*user.Wallet, "") if err != nil { RespondError(w, "could not get payment") } From d0d844fd4fb2d12ea4b85b7e822e89cd14ddb9f0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:21:52 +0200 Subject: [PATCH 10/13] balance check --- internal/api/balance.go | 5 ----- internal/api/invoice.go | 4 ++++ internal/api/lightning.go | 11 ++++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) delete mode 100644 internal/api/balance.go diff --git a/internal/api/balance.go b/internal/api/balance.go deleted file mode 100644 index 36f6e2a..0000000 --- a/internal/api/balance.go +++ /dev/null @@ -1,5 +0,0 @@ -package api - -type BalanceResponse struct { - Balance int64 `json:"balance"` -} diff --git a/internal/api/invoice.go b/internal/api/invoice.go index 7d1fb56..090b714 100644 --- a/internal/api/invoice.go +++ b/internal/api/invoice.go @@ -1,5 +1,9 @@ package api +type BalanceResponse struct { + Balance int64 `json:"balance"` +} + type InvoiceStatusResponse struct { State string `json:"state,omitempty"` PaymentHash string `json:"payment_hash"` diff --git a/internal/api/lightning.go b/internal/api/lightning.go index a1b008e..2fa385f 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -13,14 +13,14 @@ type Service struct { Bot *telegram.TipBot } -type ApiError struct { +type ErrorResponse struct { Message string `json:"error"` } func RespondError(w http.ResponseWriter, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(ApiError{Message: message}) + json.NewEncoder(w).Encode(ErrorResponse{Message: message}) } func (s Service) Balance(w http.ResponseWriter, r *http.Request) { @@ -30,9 +30,14 @@ func (s Service) Balance(w http.ResponseWriter, r *http.Request) { RespondError(w, "balance check failed") return } + + balanceResponse := BalanceResponse{ + Balance: balance, + } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(balance) + json.NewEncoder(w).Encode(balanceResponse) } func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { From df303ae186ba8dad08d4c0589b06ea9443def296 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:32:20 +0200 Subject: [PATCH 11/13] invoice --- internal/api/invoice.go | 2 +- internal/api/lightning.go | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/api/invoice.go b/internal/api/invoice.go index 090b714..0bd4201 100644 --- a/internal/api/invoice.go +++ b/internal/api/invoice.go @@ -23,5 +23,5 @@ type CreateInvoiceRequest struct { } type PayInvoiceRequest struct { - PayRequest string `json:"pay_request"` + PayRequest string `json:"pay_req"` } diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 2fa385f..232c89d 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -41,13 +41,13 @@ func (s Service) Balance(w http.ResponseWriter, r *http.Request) { } func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { + user := telegram.LoadUser(r.Context()) var createInvoiceRequest CreateInvoiceRequest err := json.NewDecoder(r.Body).Decode(&createInvoiceRequest) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - user := &lnbits.User{} invoice, err := user.Wallet.Invoice( lnbits.InvoiceParams{ Amount: createInvoiceRequest.Amount, @@ -67,27 +67,31 @@ func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) { } func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { + user := telegram.LoadUser(r.Context()) var payInvoiceRequest PayInvoiceRequest err := json.NewDecoder(r.Body).Decode(&payInvoiceRequest) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - - user := &lnbits.User{} invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: payInvoiceRequest.PayRequest}, s.Bot.Client) if err != nil { RespondError(w, "could not pay invoice: "+err.Error()) return } + payment, err := s.Bot.Client.Payment(*user.Wallet, invoice.PaymentHash) + if err != nil { + RespondError(w, "could not get payment") + } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(invoice) + json.NewEncoder(w).Encode(payment) } func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { - user := &lnbits.User{} + user := telegram.LoadUser(r.Context()) payment, err := s.Bot.Client.Payment(*user.Wallet, "") if err != nil { RespondError(w, "could not get payment") @@ -99,7 +103,7 @@ func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { // InvoiceStatus func (s Service) InvoiceStatus(w http.ResponseWriter, r *http.Request) { - user := &lnbits.User{} + user := telegram.LoadUser(r.Context()) user.Wallet = &lnbits.Wallet{} payment, err := s.Bot.Client.Payment(*user.Wallet, "") if err != nil { From 250c036c47796ee471a4d032d9bea7653b20c5eb Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 20 Aug 2022 01:03:22 +0200 Subject: [PATCH 12/13] returns --- internal/api/lightning.go | 16 +++++++++++----- internal/lnbits/lnbits.go | 2 +- internal/lnbits/types.go | 6 ++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/internal/api/lightning.go b/internal/api/lightning.go index 232c89d..47f5cbe 100644 --- a/internal/api/lightning.go +++ b/internal/api/lightning.go @@ -7,6 +7,7 @@ import ( "github.com/LightningTipBot/LightningTipBot/internal" "github.com/LightningTipBot/LightningTipBot/internal/lnbits" "github.com/LightningTipBot/LightningTipBot/internal/telegram" + "github.com/gorilla/mux" ) type Service struct { @@ -80,9 +81,10 @@ func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { return } - payment, err := s.Bot.Client.Payment(*user.Wallet, invoice.PaymentHash) + payment, _ := s.Bot.Client.Payment(*user.Wallet, invoice.PaymentHash) if err != nil { - RespondError(w, "could not get payment") + // we assume that it's paid since thre was no error earlier + payment.Paid = true } w.Header().Set("Content-Type", "application/json") @@ -92,9 +94,11 @@ func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) { func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { user := telegram.LoadUser(r.Context()) - payment, err := s.Bot.Client.Payment(*user.Wallet, "") + payment_hash := mux.Vars(r)["payment_hash"] + payment, err := s.Bot.Client.Payment(*user.Wallet, payment_hash) if err != nil { RespondError(w, "could not get payment") + return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -104,10 +108,12 @@ func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) { // InvoiceStatus func (s Service) InvoiceStatus(w http.ResponseWriter, r *http.Request) { user := telegram.LoadUser(r.Context()) + payment_hash := mux.Vars(r)["payment_hash"] user.Wallet = &lnbits.Wallet{} - payment, err := s.Bot.Client.Payment(*user.Wallet, "") + payment, err := s.Bot.Client.Payment(*user.Wallet, payment_hash) if err != nil { - RespondError(w, "could not get payment") + RespondError(w, "could not get invoice") + return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/internal/lnbits/lnbits.go b/internal/lnbits/lnbits.go index e8db8f4..1419af7 100644 --- a/internal/lnbits/lnbits.go +++ b/internal/lnbits/lnbits.go @@ -157,7 +157,7 @@ func (c Client) Payments(w Wallet) (wtx Payments, err error) { } // Payment state of a payment -func (c Client) Payment(w Wallet, payment_hash string) (payment Payment, err error) { +func (c Client) Payment(w Wallet, payment_hash string) (payment LNbitsPayment, err error) { // custom header with invoice key invoiceHeader := req.Header{ "Content-Type": "application/json", diff --git a/internal/lnbits/types.go b/internal/lnbits/types.go index 52aa4c7..770359a 100644 --- a/internal/lnbits/types.go +++ b/internal/lnbits/types.go @@ -134,6 +134,12 @@ type Payment struct { WebhookStatus interface{} `json:"webhook_status"` } +type LNbitsPayment struct { + Paid bool `json:"paid"` + Preimage string `json:"preimage"` + Details Payment `json:"details,omitempty"` +} + type Payments []Payment type Invoice struct { From a87eac37b81d99a7fd42a60afb8cb45d857db026 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 20 Aug 2022 01:04:31 +0200 Subject: [PATCH 13/13] remove --- internal/api/payment.go | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 internal/api/payment.go diff --git a/internal/api/payment.go b/internal/api/payment.go deleted file mode 100644 index 107fb0d..0000000 --- a/internal/api/payment.go +++ /dev/null @@ -1,9 +0,0 @@ -package api - -type PaymentStatusResponse struct { - State string `json:"state"` - FeeMsat int64 `json:"fee_msat,omitempty"` - Amount int64 `json:"amount,omitempty"` - Preimage string `json:"preimage,omitempty"` - PaymentHash string `json:"payment_hash"` -}