From 4bcbd2fefea1de957ae6ec6d8a725721a0c37679 Mon Sep 17 00:00:00 2001 From: gohumble Date: Fri, 19 Aug 2022 23:35:27 +0200 Subject: [PATCH] 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)