lightningtipbot/internal/api/middleware.go
callebtc 5e4c045312 log
2022-08-22 09:35:43 +02:00

127 lines
3.5 KiB
Go

package api
import (
"context"
"encoding/base64"
"fmt"
"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"
)
func LoggingMiddleware(prefix string, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Tracef("[%s] %s %s", prefix, r.Method, r.URL.Path)
log.Tracef("[%s]\n%s", prefix, dump(r))
r.BasicAuth()
next.ServeHTTP(w, r)
}
}
type AuthType struct {
Type string
Decoder func(s string) ([]byte, error)
}
var AuthTypeBasic = AuthType{Type: "Basic"}
var AuthTypeBearerBase64 = AuthType{Type: "Bearer", Decoder: base64.StdEncoding.DecodeString}
var AuthTypeNone = AuthType{}
// invoice key or admin key requirement
type AccessKeyType struct {
Type string
}
var AccessKeyTypeInvoice = AccessKeyType{Type: "invoice"}
var AccessKeyTypeAdmin = AccessKeyType{Type: "admin"}
var AccessKeyTypeNone = AccessKeyType{Type: "none"} // no authorization required
func AuthorizationMiddleware(database *gorm.DB, authType AuthType, accessType AccessKeyType, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if accessType.Type == "none" {
next.ServeHTTP(w, r)
return
}
auth := r.Header.Get("Authorization")
// check if the user is banned
if auth == "" {
w.WriteHeader(401)
log.Warn("[api] 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("[api] 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{}
var tx *gorm.DB
if accessType.Type == "admin" {
tx = database.Where("wallet_adminkey = ? COLLATE NOCASE", password).First(user)
} else if accessType.Type == "invoice" {
tx = database.Where("wallet_inkey = ? COLLATE NOCASE", password).First(user)
} else {
log.Errorf("[api] route without access type")
w.WriteHeader(401)
return
}
if tx.Error != nil {
log.Warnf("[api] could not load access key: %v", tx.Error)
w.WriteHeader(401)
return
}
log.Debugf("[api] User: %s Endpoint: %s %s %s", telegram.GetUserStr(user.Telegram), r.Method, r.URL.Path, r.URL.RawQuery)
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
}
if authType.Decoder != nil {
c, err := authType.Decoder(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 auth[len(prefix):], auth[len(prefix):], true
}
return parse(fmt.Sprintf("%s ", authType.Type))
}
func dump(r *http.Request) string {
x, err := httputil.DumpRequest(r, true)
if err != nil {
return ""
}
return string(x)
}