diff --git a/api-messages.go b/api-messages.go index dcaecc4..d4ec8ff 100644 --- a/api-messages.go +++ b/api-messages.go @@ -332,18 +332,12 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) { respondBool(400, false, gc) return } - tokenIndex := -1 - for i, v := range app.telegram.verifiedTokens { - if v.Token == req.Token { - tokenIndex = i - break - } - } - if tokenIndex == -1 { + tgToken, ok := app.telegram.TokenVerified(req.Token) + app.telegram.DeleteVerifiedToken(req.Token) + if !ok { respondBool(500, false, gc) return } - tgToken := app.telegram.verifiedTokens[tokenIndex] tgUser := TelegramUser{ ChatID: tgToken.ChatID, Username: tgToken.Username, @@ -352,17 +346,7 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) { if lang, ok := app.telegram.languages[tgToken.ChatID]; ok { tgUser.Lang = lang } - if app.storage.GetTelegram() == nil { - app.storage.telegram = telegramStore{} - } app.storage.SetTelegramKey(req.ID, tgUser) - err := app.storage.storeTelegramUsers() - if err != nil { - app.err.Printf("Failed to store Telegram users: %v", err) - } else { - app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1] - app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1] - } linkExistingOmbiDiscordTelegram(app) respondBool(200, true, gc) } @@ -462,19 +446,8 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte // @tags Other func (app *appContext) TelegramVerified(gc *gin.Context) { pin := gc.Param("pin") - tokenIndex := -1 - for i, v := range app.telegram.verifiedTokens { - if v.Token == pin { - tokenIndex = i - break - } - } - // if tokenIndex != -1 { - // length := len(app.telegram.verifiedTokens) - // app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1] - // app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1] - // } - respondBool(200, tokenIndex != -1, gc) + _, ok := app.telegram.TokenVerified(pin) + respondBool(200, ok, gc) } // @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code. @@ -492,27 +465,13 @@ func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) { return } pin := gc.Param("pin") - tokenIndex := -1 - for i, v := range app.telegram.verifiedTokens { - if v.Token == pin { - tokenIndex = i - break - } - } - if app.config.Section("telegram").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetTelegram() { - if app.telegram.verifiedTokens[tokenIndex].Username == u.Username { - respondBool(400, false, gc) - return - } - } + token, ok := app.telegram.TokenVerified(pin) + if ok && app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(token.Username) { + app.discord.DeleteVerifiedUser(pin) + respondBool(400, false, gc) + return } - // if tokenIndex != -1 { - // length := len(app.telegram.verifiedTokens) - // app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1] - // app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1] - // } - respondBool(200, tokenIndex != -1, gc) + respondBool(200, ok, gc) } // @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code. @@ -530,15 +489,11 @@ func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) { return } pin := gc.Param("pin") - _, ok := app.discord.verifiedTokens[pin] - if app.config.Section("discord").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetDiscord() { - if app.discord.verifiedTokens[pin].ID == u.ID { - delete(app.discord.verifiedTokens, pin) - respondBool(400, false, gc) - return - } - } + user, ok := app.discord.UserVerified(pin) + if ok && app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(user.ID) { + delete(app.discord.verifiedTokens, pin) + respondBool(400, false, gc) + return } respondBool(200, ok, gc) } diff --git a/api-userpage.go b/api-userpage.go index dd908d5..bada208 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -322,19 +322,15 @@ func (app *appContext) GetMyPIN(gc *gin.Context) { // @tags User Page func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) { pin := gc.Param("pin") - dcUser, ok := app.discord.verifiedTokens[pin] + dcUser, ok := app.discord.UserVerified(pin) + app.discord.DeleteVerifiedUser(pin) if !ok { respondBool(200, false, gc) return } - if app.config.Section("discord").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetDiscord() { - if app.discord.verifiedTokens[pin].ID == u.ID { - delete(app.discord.verifiedTokens, pin) - respondBool(400, false, gc) - return - } - } + if app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(dcUser.ID) { + respondBool(400, false, gc) + return } existingUser, ok := app.storage.GetDiscordKey(gc.GetString("jfId")) if ok { @@ -354,30 +350,24 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) { // @tags User Page func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) { pin := gc.Param("pin") - tokenIndex := -1 - for i, v := range app.telegram.verifiedTokens { - if v.Token == pin { - tokenIndex = i - break - } - } - if tokenIndex == -1 { + token, ok := app.telegram.TokenVerified(pin) + app.telegram.DeleteVerifiedToken(pin) + if !ok { respondBool(200, false, gc) return } - if app.config.Section("telegram").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetTelegram() { - if app.telegram.verifiedTokens[tokenIndex].Username == u.Username { - respondBool(400, false, gc) - return - } - } + if app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(token.Username) { + respondBool(400, false, gc) + return } tgUser := TelegramUser{ - ChatID: app.telegram.verifiedTokens[tokenIndex].ChatID, - Username: app.telegram.verifiedTokens[tokenIndex].Username, + ChatID: token.ChatID, + Username: token.Username, Contact: true, } + if lang, ok := app.telegram.languages[tgUser.ChatID]; ok { + tgUser.Lang = lang + } existingUser, ok := app.storage.GetTelegramKey(gc.GetString("jfId")) if ok { diff --git a/api-users.go b/api-users.go index 343139d..94d5b59 100644 --- a/api-users.go +++ b/api-users.go @@ -193,7 +193,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc } } - telegramTokenIndex := -1 + var tgToken TelegramVerifiedToken + telegramVerified := false if telegramEnabled { if req.TelegramPIN == "" { if app.config.Section("telegram").Key("required").MustBool(false) { @@ -205,13 +206,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc return } } else { - for i, v := range app.telegram.verifiedTokens { - if v.Token == req.TelegramPIN { - telegramTokenIndex = i - break - } - } - if telegramTokenIndex == -1 { + tgToken, telegramVerified = app.telegram.TokenVerified(req.TelegramPIN) + if telegramVerified { f = func(gc *gin.Context) { app.debug.Printf("%s: New user failed: Telegram PIN was invalid", req.Code) respond(401, "errorInvalidPIN", gc) @@ -219,17 +215,13 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc success = false return } - if app.config.Section("telegram").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetTelegram() { - if app.telegram.verifiedTokens[telegramTokenIndex].Username == u.Username { - f = func(gc *gin.Context) { - app.debug.Printf("%s: New user failed: Telegram user already linked", req.Code) - respond(400, "errorAccountLinked", gc) - } - success = false - return - } + if app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(tgToken.Username) { + f = func(gc *gin.Context) { + app.debug.Printf("%s: New user failed: Telegram user already linked", req.Code) + respond(400, "errorAccountLinked", gc) } + success = false + return } } } @@ -352,7 +344,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc app.err.Printf("Failed to store user duration: %v", err) } } - if discordEnabled && discordVerified { + if discordVerified { discordUser.Contact = req.DiscordContact if app.storage.discord == nil { app.storage.discord = discordStore{} @@ -364,8 +356,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc delete(app.discord.verifiedTokens, req.DiscordPIN) } } - if telegramEnabled && telegramTokenIndex != -1 { - tgToken := app.telegram.verifiedTokens[telegramTokenIndex] + if telegramVerified { tgUser := TelegramUser{ ChatID: tgToken.ChatID, Username: tgToken.Username, @@ -377,13 +368,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc if app.storage.telegram == nil { app.storage.telegram = telegramStore{} } + app.telegram.DeleteVerifiedToken(req.TelegramPIN) app.storage.SetTelegramKey(user.ID, tgUser) - if err := app.storage.storeTelegramUsers(); err != nil { - app.err.Printf("Failed to store Telegram users: %v", err) - } else { - app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[telegramTokenIndex] = app.telegram.verifiedTokens[telegramTokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1] - app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1] - } } if invite.Profile != "" && app.config.Section("ombi").Key("enabled").MustBool(false) { if profile.Ombi != nil && len(profile.Ombi) != 0 { @@ -394,17 +380,17 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) } else { app.info.Println("Created Ombi user") - if (discordEnabled && discordVerified) || (telegramEnabled && telegramTokenIndex != -1) { + if discordVerified || telegramVerified { ombiUser, status, err := app.getOmbiUser(id) if status != 200 || err != nil { app.err.Printf("Failed to get Ombi user (%d): %v", status, err) } else { dID := "" tUser := "" - if discordEnabled && discordVerified { + if discordVerified { dID = discordUser.ID } - if telegramEnabled && telegramTokenIndex != -1 { + if telegramVerified { u, _ := app.storage.GetTelegramKey(user.ID) tUser = u.Username } @@ -431,7 +417,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc app.err.Printf("Failed to store Matrix users: %v", err) } } - if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramTokenIndex != -1 || discordVerified { + if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified { name := app.getAddressOrName(user.ID) app.debug.Printf("%s: Sending welcome message to %s", req.Username, name) msg, err := app.email.constructWelcome(req.Username, expiry, app, false) diff --git a/discord.go b/discord.go index 4fc98be..1c2c58e 100644 --- a/discord.go +++ b/discord.go @@ -3,6 +3,7 @@ package main import ( "fmt" "strings" + "time" dg "github.com/bwmarrin/discordgo" ) @@ -12,7 +13,7 @@ type DiscordDaemon struct { ShutdownChannel chan string bot *dg.Session username string - tokens []string + tokens map[string]time.Time // Map of tokens to expiry times. verifiedTokens map[string]DiscordUser // Map of tokens to discord users. channelID, channelName, inviteChannelID, inviteChannelName string guildID string @@ -37,7 +38,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { Stopped: false, ShutdownChannel: make(chan string), bot: bot, - tokens: []string{}, + tokens: map[string]time.Time{}, verifiedTokens: map[string]DiscordUser{}, users: map[string]DiscordUser{}, app: app, @@ -58,7 +59,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { // NewAuthToken generates an 8-character pin in the form "A1-2B-CD". func (d *DiscordDaemon) NewAuthToken() string { pin := genAuthToken() - d.tokens = append(d.tokens, pin) + d.tokens[pin] = time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second) return pin } @@ -431,14 +432,8 @@ func (d *DiscordDaemon) cmdStart(s *dg.Session, i *dg.InteractionCreate, lang st func (d *DiscordDaemon) cmdPIN(s *dg.Session, i *dg.InteractionCreate, lang string) { pin := i.ApplicationCommandData().Options[0].StringValue() - tokenIndex := -1 - for i, token := range d.tokens { - if pin == token { - tokenIndex = i - break - } - } - if tokenIndex == -1 { + expiry, ok := d.tokens[pin] + if !ok || time.Now().After(expiry) { err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{ // Type: dg.InteractionResponseChannelMessageWithSource, Type: dg.InteractionResponseChannelMessageWithSource, @@ -450,6 +445,7 @@ func (d *DiscordDaemon) cmdPIN(s *dg.Session, i *dg.InteractionCreate, lang stri if err != nil { d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", i.Interaction.Member.User.Username, err) } + delete(d.tokens, pin) return } err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{ @@ -464,8 +460,7 @@ func (d *DiscordDaemon) cmdPIN(s *dg.Session, i *dg.InteractionCreate, lang stri d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", i.Interaction.Member.User.Username, err) } d.verifiedTokens[pin] = d.users[i.Interaction.Member.User.ID] - d.tokens[len(d.tokens)-1], d.tokens[tokenIndex] = d.tokens[tokenIndex], d.tokens[len(d.tokens)-1] - d.tokens = d.tokens[:len(d.tokens)-1] + delete(d.tokens, pin) } func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang string) { @@ -606,14 +601,8 @@ func (d *DiscordDaemon) msgPIN(s *dg.Session, m *dg.MessageCreate, sects []strin d.app.debug.Println("Discord: Ignoring message as user was not found") return } - tokenIndex := -1 - for i, token := range d.tokens { - if sects[0] == token { - tokenIndex = i - break - } - } - if tokenIndex == -1 { + expiry, ok := d.tokens[sects[0]] + if !ok || time.Now().After(expiry) { _, err := s.ChannelMessageSend( m.ChannelID, d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"), @@ -621,6 +610,7 @@ func (d *DiscordDaemon) msgPIN(s *dg.Session, m *dg.MessageCreate, sects []strin if err != nil { d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err) } + delete(d.tokens, sects[0]) return } _, err := s.ChannelMessageSend( @@ -631,8 +621,7 @@ func (d *DiscordDaemon) msgPIN(s *dg.Session, m *dg.MessageCreate, sects []strin d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err) } d.verifiedTokens[sects[0]] = d.users[m.Author.ID] - d.tokens[len(d.tokens)-1], d.tokens[tokenIndex] = d.tokens[tokenIndex], d.tokens[len(d.tokens)-1] - d.tokens = d.tokens[:len(d.tokens)-1] + delete(d.tokens, sects[0]) } func (d *DiscordDaemon) SendDM(message *Message, userID ...string) error { @@ -686,3 +675,27 @@ func (d *DiscordDaemon) Send(message *Message, channelID ...string) error { } return nil } + +// UserVerified returns whether or not a token with the given PIN has been verified, and the user itself. +func (d *DiscordDaemon) UserVerified(pin string) (user DiscordUser, ok bool) { + user, ok = d.verifiedTokens[pin] + // delete(d.verifiedTokens, pin) + return +} + +// UserExists returns whether or not a user with the given ID exists. +func (d *DiscordDaemon) UserExists(id string) (ok bool) { + ok = false + for _, u := range d.app.storage.GetDiscord() { + if u.ID == id { + ok = true + break + } + } + return +} + +// DeleteVerifiedUser removes the token with the given PIN. +func (d *DiscordDaemon) DeleteVerifiedUser(pin string) { + delete(d.verifiedTokens, pin) +} diff --git a/storage.go b/storage.go index 095075d..de49b5d 100644 --- a/storage.go +++ b/storage.go @@ -70,6 +70,9 @@ func (st *Storage) DeleteEmailsKey(k string) { // GetDiscord returns a copy of the store. func (st *Storage) GetDiscord() discordStore { + if st.discord == nil { + st.discord = discordStore{} + } return st.discord } @@ -82,6 +85,9 @@ func (st *Storage) GetDiscordKey(k string) (DiscordUser, bool) { // SetDiscordKey stores value v in key k. func (st *Storage) SetDiscordKey(k string, v DiscordUser) { st.discordLock.Lock() + if st.discord == nil { + st.discord = discordStore{} + } st.discord[k] = v st.storeDiscordUsers() st.discordLock.Unlock() @@ -97,6 +103,9 @@ func (st *Storage) DeleteDiscordKey(k string) { // GetTelegram returns a copy of the store. func (st *Storage) GetTelegram() telegramStore { + if st.telegram == nil { + st.telegram = telegramStore{} + } return st.telegram } @@ -109,6 +118,9 @@ func (st *Storage) GetTelegramKey(k string) (TelegramUser, bool) { // SetTelegramKey stores value v in key k. func (st *Storage) SetTelegramKey(k string, v TelegramUser) { st.telegramLock.Lock() + if st.telegram == nil { + st.telegram = telegramStore{} + } st.telegram[k] = v st.storeTelegramUsers() st.telegramLock.Unlock() @@ -124,6 +136,9 @@ func (st *Storage) DeleteTelegramKey(k string) { // GetMatrix returns a copy of the store. func (st *Storage) GetMatrix() matrixStore { + if st.matrix == nil { + st.matrix = matrixStore{} + } return st.matrix } @@ -136,6 +151,9 @@ func (st *Storage) GetMatrixKey(k string) (MatrixUser, bool) { // SetMatrixKey stores value v in key k. func (st *Storage) SetMatrixKey(k string, v MatrixUser) { st.matrixLock.Lock() + if st.matrix == nil { + st.matrix = matrixStore{} + } st.matrix[k] = v st.storeMatrixUsers() st.matrixLock.Unlock() diff --git a/telegram.go b/telegram.go index 288d95d..52d9291 100644 --- a/telegram.go +++ b/telegram.go @@ -9,8 +9,11 @@ import ( tg "github.com/go-telegram-bot-api/telegram-bot-api" ) +const ( + VERIF_TOKEN_EXPIRY_SEC = 10 * 60 +) + type TelegramVerifiedToken struct { - Token string ChatID int64 Username string } @@ -20,9 +23,9 @@ type TelegramDaemon struct { ShutdownChannel chan string bot *tg.BotAPI username string - tokens []string - verifiedTokens []TelegramVerifiedToken - languages map[int64]string // Store of languages for chatIDs. Added to on first interaction, and loaded from app.storage.telegram on start. + tokens map[string]time.Time // Map of tokens to their expiry time. + verifiedTokens map[string]TelegramVerifiedToken // Map of tokens to the responsible ChatID+Username. + languages map[int64]string // Store of languages for chatIDs. Added to on first interaction, and loaded from app.storage.telegram on start. link string app *appContext } @@ -40,8 +43,8 @@ func newTelegramDaemon(app *appContext) (*TelegramDaemon, error) { ShutdownChannel: make(chan string), bot: bot, username: bot.Self.UserName, - tokens: []string{}, - verifiedTokens: []TelegramVerifiedToken{}, + tokens: map[string]time.Time{}, + verifiedTokens: map[string]TelegramVerifiedToken{}, languages: map[int64]string{}, link: "https://t.me/" + bot.Self.UserName, app: app, @@ -72,7 +75,7 @@ var runes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") // NewAuthToken generates an 8-character pin in the form "A1-2B-CD". func (t *TelegramDaemon) NewAuthToken() string { pin := genAuthToken() - t.tokens = append(t.tokens, pin) + t.tokens[pin] = time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second) return pin } @@ -212,29 +215,46 @@ func (t *TelegramDaemon) commandLang(upd *tg.Update, sects []string, lang string } func (t *TelegramDaemon) commandPIN(upd *tg.Update, sects []string, lang string) { - tokenIndex := -1 - for i, token := range t.tokens { - if upd.Message.Text == token { - tokenIndex = i - break - } - } - if tokenIndex == -1 { + expiry, ok := t.tokens[upd.Message.Text] + if !ok || time.Now().After(expiry) { err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN")) if err != nil { t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err) } + delete(t.tokens, upd.Message.Text) return } err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("pinSuccess")) if err != nil { t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err) } - t.verifiedTokens = append(t.verifiedTokens, TelegramVerifiedToken{ - Token: upd.Message.Text, + t.verifiedTokens[upd.Message.Text] = TelegramVerifiedToken{ ChatID: upd.Message.Chat.ID, Username: upd.Message.Chat.UserName, - }) - t.tokens[len(t.tokens)-1], t.tokens[tokenIndex] = t.tokens[tokenIndex], t.tokens[len(t.tokens)-1] - t.tokens = t.tokens[:len(t.tokens)-1] + } + delete(t.tokens, upd.Message.Text) +} + +// TokenVerified returns whether or not a token with the given PIN has been verified, and the token itself. +func (t *TelegramDaemon) TokenVerified(pin string) (token TelegramVerifiedToken, ok bool) { + token, ok = t.verifiedTokens[pin] + // delete(t.verifiedTokens, pin) + return +} + +// UserExists returns whether or not a user with the given username exists. +func (t *TelegramDaemon) UserExists(username string) (ok bool) { + ok = false + for _, u := range t.app.storage.GetTelegram() { + if u.Username == username { + ok = true + break + } + } + return +} + +// DeleteVerifiedToken removes the token with the given PIN. +func (t *TelegramDaemon) DeleteVerifiedToken(pin string) { + delete(t.verifiedTokens, pin) }