From fd9ad39b0c2ac1bcdff2285e64971963d80ab88d Mon Sep 17 00:00:00 2001 From: LightningTipBot <88730856+LightningTipBot@users.noreply.github.com> Date: Sun, 24 Oct 2021 20:31:52 +0200 Subject: [PATCH] anonymous user ID for lnurl (#107) * anonymous user ID for lnurl * anonymous lnurl * migrate at every start, not good * anon id at user creation * migrations file * fix database migration task * remove comment * anon label Co-authored-by: lngohumble --- internal/database/migrations.go | 26 +++++++++++++++++++ internal/lnbits/types.go | 1 + internal/lnurl/lnurl.go | 2 +- internal/str/strings.go | 13 ++++++++++ internal/telegram/bot.go | 4 +-- internal/telegram/database.go | 44 ++++++++++++++++++++++++--------- internal/telegram/help.go | 12 +++++---- internal/telegram/lnurl.go | 34 +++++++++++++++---------- internal/telegram/start.go | 5 +++- internal/telegram/users.go | 9 ++++--- 10 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 internal/database/migrations.go diff --git a/internal/database/migrations.go b/internal/database/migrations.go new file mode 100644 index 0000000..3853680 --- /dev/null +++ b/internal/database/migrations.go @@ -0,0 +1,26 @@ +package database + +import ( + "fmt" + + "github.com/LightningTipBot/LightningTipBot/internal/lnbits" + "github.com/LightningTipBot/LightningTipBot/internal/str" + log "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func MigrateAnonIdHash(db *gorm.DB) error { + users := []lnbits.User{} + _ = db.Find(&users) + for _, u := range users { + log.Info(u.ID, str.Int32Hash(u.ID)) + u.AnonID = fmt.Sprint(str.Int32Hash(u.ID)) + tx := db.Save(u) + if tx.Error != nil { + errmsg := fmt.Sprintf("[MigrateAnonIdHash] Error: Couldn't migrate user %s (%d)", u.Telegram.Username, u.Telegram.ID) + log.Errorln(errmsg) + return tx.Error + } + } + return nil +} diff --git a/internal/lnbits/types.go b/internal/lnbits/types.go index ea792ad..ab67141 100644 --- a/internal/lnbits/types.go +++ b/internal/lnbits/types.go @@ -24,6 +24,7 @@ type User struct { StateData string `json:"stateData"` CreatedAt time.Time `json:"created"` UpdatedAt time.Time `json:"updated"` + AnonID string `jsin:"anonid"` } const ( diff --git a/internal/lnurl/lnurl.go b/internal/lnurl/lnurl.go index 2fd840f..129974f 100644 --- a/internal/lnurl/lnurl.go +++ b/internal/lnurl/lnurl.go @@ -131,7 +131,7 @@ func (w Server) serveLNURLpSecond(username string, amount int64, comment string) tx := w.database if err == nil { // asume it's a user ID - tx = w.database.Where("telegram_id = ?", fmt.Sprint(id)).First(user) + tx = w.database.Where("anon_id = ?", fmt.Sprint(id)).First(user) } else { // assume it's a string @username tx = w.database.Where("telegram_username = ?", strings.ToLower(username)).First(user) diff --git a/internal/str/strings.go b/internal/str/strings.go index 07c8fde..c1e5d4b 100644 --- a/internal/str/strings.go +++ b/internal/str/strings.go @@ -2,6 +2,7 @@ package str import ( "fmt" + "hash/fnv" "strings" ) @@ -25,3 +26,15 @@ func MarkdownEscape(s string) string { } return s } + +func Int32Hash(s string) uint32 { + h := fnv.New32a() + h.Write([]byte(s)) + return h.Sum32() +} + +func Int64Hash(s string) uint64 { + h := fnv.New64a() + h.Write([]byte(s)) + return h.Sum64() +} diff --git a/internal/telegram/bot.go b/internal/telegram/bot.go index 4eeb035..d201e8d 100644 --- a/internal/telegram/bot.go +++ b/internal/telegram/bot.go @@ -2,10 +2,10 @@ package telegram import ( "fmt" - "github.com/LightningTipBot/LightningTipBot/internal" "sync" "time" + "github.com/LightningTipBot/LightningTipBot/internal" "github.com/LightningTipBot/LightningTipBot/internal/lnbits" "github.com/LightningTipBot/LightningTipBot/internal/storage" log "github.com/sirupsen/logrus" @@ -30,7 +30,7 @@ var ( // NewBot migrates data and creates a new bot func NewBot() TipBot { // create sqlite databases - db, txLogger := migration() + db, txLogger := AutoMigration() return TipBot{ Database: db, Client: lnbits.NewClient(internal.Configuration.Lnbits.AdminKey, internal.Configuration.Lnbits.Url), diff --git a/internal/telegram/database.go b/internal/telegram/database.go index 1e929fa..2a504a2 100644 --- a/internal/telegram/database.go +++ b/internal/telegram/database.go @@ -2,14 +2,16 @@ package telegram import ( "fmt" - "github.com/LightningTipBot/LightningTipBot/internal" - "github.com/LightningTipBot/LightningTipBot/internal/storage" - "github.com/tidwall/buntdb" "reflect" "strconv" "strings" "time" + "github.com/LightningTipBot/LightningTipBot/internal" + "github.com/LightningTipBot/LightningTipBot/internal/database" + "github.com/LightningTipBot/LightningTipBot/internal/storage" + "github.com/tidwall/buntdb" + log "github.com/sirupsen/logrus" "github.com/LightningTipBot/LightningTipBot/internal/lnbits" @@ -33,21 +35,41 @@ func createBunt() *storage.DB { } return bunt } -func migration() (db *gorm.DB, txLogger *gorm.DB) { - txLogger, err := gorm.Open(sqlite.Open(internal.Configuration.Database.TransactionsPath), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true, FullSaveAssociations: true}) - if err != nil { - panic("Initialize orm failed.") - } +func ColumnMigrationTasks(db *gorm.DB) error { + var err error + if !db.Migrator().HasColumn(&lnbits.User{}, "anon_id") { + // first we need to auto migrate the user. This will create anon_id column + err = db.AutoMigrate(&lnbits.User{}) + if err != nil { + panic(err) + } + log.Info("Running ano_id database migrations ...") + // run the migration on anon_id + err = database.MigrateAnonIdHash(db) + } + // todo -- add more database field migrations here in the future + return err +} + +func AutoMigration() (db *gorm.DB, txLogger *gorm.DB) { orm, err := gorm.Open(sqlite.Open(internal.Configuration.Database.DbPath), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true, FullSaveAssociations: true}) if err != nil { panic("Initialize orm failed.") } - + err = ColumnMigrationTasks(orm) + if err != nil { + panic(err) + } err = orm.AutoMigrate(&lnbits.User{}) if err != nil { panic(err) } + + txLogger, err = gorm.Open(sqlite.Open(internal.Configuration.Database.TransactionsPath), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true, FullSaveAssociations: true}) + if err != nil { + panic("Initialize orm failed.") + } err = txLogger.AutoMigrate(&Transaction{}) if err != nil { panic(err) @@ -93,7 +115,7 @@ func GetUser(u *tb.User, bot TipBot) (*lnbits.User, error) { return user, err } go func() { - userCopy := bot.copyLowercaseUser(u) + userCopy := bot.CopyLowercaseUser(u) if !reflect.DeepEqual(userCopy, user.Telegram) { // update possibly changed user details in Database user.Telegram = userCopy @@ -107,7 +129,7 @@ func GetUser(u *tb.User, bot TipBot) (*lnbits.User, error) { } func UpdateUserRecord(user *lnbits.User, bot TipBot) error { - user.Telegram = bot.copyLowercaseUser(user.Telegram) + user.Telegram = bot.CopyLowercaseUser(user.Telegram) user.UpdatedAt = time.Now() tx := bot.Database.Save(user) if tx.Error != nil { diff --git a/internal/telegram/help.go b/internal/telegram/help.go index be7b5f4..91f4958 100644 --- a/internal/telegram/help.go +++ b/internal/telegram/help.go @@ -8,13 +8,14 @@ import ( ) func (bot TipBot) makeHelpMessage(ctx context.Context, m *tb.Message) string { + fromUser := LoadUser(ctx) dynamicHelpMessage := "" // user has no username set if len(m.Sender.Username) == 0 { // return fmt.Sprintf(helpMessage, fmt.Sprintf("%s\n\n", helpNoUsernameMessage)) dynamicHelpMessage = dynamicHelpMessage + "\n" + Translate(ctx, "helpNoUsernameMessage") } - lnaddr, _ := bot.UserGetLightningAddress(m.Sender) + lnaddr, _ := bot.UserGetLightningAddress(fromUser) if len(lnaddr) > 0 { dynamicHelpMessage = dynamicHelpMessage + "\n" + fmt.Sprintf(Translate(ctx, "infoYourLightningAddress"), lnaddr) } @@ -48,18 +49,19 @@ func (bot TipBot) basicsHandler(ctx context.Context, m *tb.Message) { } func (bot TipBot) makeAdvancedHelpMessage(ctx context.Context, m *tb.Message) string { - + fromUser := LoadUser(ctx) dynamicHelpMessage := "ℹ️ *Info*\n" // user has no username set if len(m.Sender.Username) == 0 { // return fmt.Sprintf(helpMessage, fmt.Sprintf("%s\n\n", helpNoUsernameMessage)) dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("%s", Translate(ctx, "helpNoUsernameMessage")) + "\n" } - lnaddr, err := bot.UserGetLightningAddress(m.Sender) + // we print the anonymous ln address in the advanced help + lnaddr, err := bot.UserGetAnonLightningAddress(fromUser) if err == nil { - dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("Lightning Address: `%s`\n", lnaddr) + dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("Anonymous lightning address: `%s`\n", lnaddr) } - lnurl, err := UserGetLNURL(m.Sender) + lnurl, err := UserGetLNURL(fromUser) if err == nil { dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("LNURL: `%s`", lnurl) } diff --git a/internal/telegram/lnurl.go b/internal/telegram/lnurl.go index 328591d..612bc5d 100644 --- a/internal/telegram/lnurl.go +++ b/internal/telegram/lnurl.go @@ -114,23 +114,30 @@ func (bot TipBot) lnurlHandler(ctx context.Context, m *tb.Message) { } } -func (bot *TipBot) UserGetLightningAddress(user *tb.User) (string, error) { - if len(user.Username) > 0 { - return fmt.Sprintf("%s@%s", strings.ToLower(user.Username), strings.ToLower(internal.Configuration.Bot.LNURLHostUrl.Hostname())), nil +func (bot *TipBot) UserGetLightningAddress(user *lnbits.User) (string, error) { + if len(user.Telegram.Username) > 0 { + return fmt.Sprintf("%s@%s", strings.ToLower(user.Telegram.Username), strings.ToLower(internal.Configuration.Bot.LNURLHostUrl.Hostname())), nil } else { - return fmt.Sprintf("%s@%s", fmt.Sprint(user.ID), strings.ToLower(internal.Configuration.Bot.LNURLHostUrl.Hostname())), nil - // return "", fmt.Errorf("user has no username") + lnaddr, err := bot.UserGetAnonLightningAddress(user) + return lnaddr, err } } -func UserGetLNURL(user *tb.User) (string, error) { - name := strings.ToLower(strings.ToLower(user.Username)) - if len(name) == 0 { - name = fmt.Sprint(user.ID) - // return "", fmt.Errorf("user has no username.") - } +func (bot *TipBot) UserGetAnonLightningAddress(user *lnbits.User) (string, error) { + return fmt.Sprintf("%s@%s", fmt.Sprint(user.AnonID), strings.ToLower(internal.Configuration.Bot.LNURLHostUrl.Hostname())), nil +} + +func UserGetLNURL(user *lnbits.User) (string, error) { + // before: we used the username for the LNURL + // name := strings.ToLower(strings.ToLower(user.Telegram.Username)) + // if len(name) == 0 { + // name = fmt.Sprint(user.AnonID) + // // return "", fmt.Errorf("user has no username.") + // } + // now: use only the anon ID as LNURL + name := fmt.Sprint(user.AnonID) callback := fmt.Sprintf("%s/.well-known/lnurlp/%s", internal.Configuration.Bot.LNURLHostName, name) - log.Debugf("[lnurlReceiveHandler] %s's LNURL: %s", GetUserStr(user), callback) + log.Debugf("[lnurlReceiveHandler] %s's LNURL: %s", GetUserStr(user.Telegram), callback) lnurlEncode, err := lnurl.LNURLEncode(callback) if err != nil { @@ -141,7 +148,8 @@ func UserGetLNURL(user *tb.User) (string, error) { // lnurlReceiveHandler outputs the LNURL of the user func (bot TipBot) lnurlReceiveHandler(ctx context.Context, m *tb.Message) { - lnurlEncode, err := UserGetLNURL(m.Sender) + fromUser := LoadUser(ctx) + lnurlEncode, err := UserGetLNURL(fromUser) if err != nil { errmsg := fmt.Sprintf("[lnurlReceiveHandler] Failed to get LNURL: %s", err) log.Errorln(errmsg) diff --git a/internal/telegram/start.go b/internal/telegram/start.go index fbc880f..ed5df46 100644 --- a/internal/telegram/start.go +++ b/internal/telegram/start.go @@ -4,13 +4,15 @@ import ( "context" "errors" "fmt" - "github.com/LightningTipBot/LightningTipBot/internal" "strconv" "time" + "github.com/LightningTipBot/LightningTipBot/internal" + log "github.com/sirupsen/logrus" "github.com/LightningTipBot/LightningTipBot/internal/lnbits" + "github.com/LightningTipBot/LightningTipBot/internal/str" tb "gopkg.in/tucnak/telebot.v2" "gorm.io/gorm" ) @@ -97,6 +99,7 @@ func (bot TipBot) createWallet(user *lnbits.User) error { return err } user.Wallet = &wallet[0] + user.AnonID = fmt.Sprint(str.Int32Hash(user.ID)) user.Initialized = false user.CreatedAt = time.Now() err = UpdateUserRecord(user, bot) diff --git a/internal/telegram/users.go b/internal/telegram/users.go index 51abbc2..53e25c9 100644 --- a/internal/telegram/users.go +++ b/internal/telegram/users.go @@ -3,9 +3,10 @@ package telegram import ( "errors" "fmt" - "github.com/LightningTipBot/LightningTipBot/internal/str" "strings" + "github.com/LightningTipBot/LightningTipBot/internal/str" + "github.com/LightningTipBot/LightningTipBot/internal/lnbits" log "github.com/sirupsen/logrus" @@ -78,15 +79,15 @@ func (bot *TipBot) GetUserBalance(user *lnbits.User) (amount int, err error) { return } -// copyLowercaseUser will create a coy user and cast username to lowercase. -func (bot *TipBot) copyLowercaseUser(u *tb.User) *tb.User { +// CopyLowercaseUser will create a coy user and cast username to lowercase. +func (bot *TipBot) CopyLowercaseUser(u *tb.User) *tb.User { userCopy := *u userCopy.Username = strings.ToLower(u.Username) return &userCopy } func (bot *TipBot) CreateWalletForTelegramUser(tbUser *tb.User) (*lnbits.User, error) { - userCopy := bot.copyLowercaseUser(tbUser) + userCopy := bot.CopyLowercaseUser(tbUser) user := &lnbits.User{Telegram: userCopy} userStr := GetUserStr(tbUser) log.Printf("[CreateWalletForTelegramUser] Creating wallet for user %s ... ", userStr)