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 <lngohumble@gmail.com>
This commit is contained in:
LightningTipBot 2021-10-24 20:31:52 +02:00 committed by GitHub
parent bb00253122
commit fd9ad39b0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 37 deletions

View file

@ -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
}

View file

@ -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 (

View file

@ -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)

View file

@ -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()
}

View file

@ -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),

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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)