From e4a71725176fa0d7e5c858d1c49cafd7665c7f9a Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Wed, 21 Jun 2023 18:26:08 +0100 Subject: [PATCH] messages: assign tokens to jf users on userpage pins generated on the user page are assigned to that user, no other jellyifn user can verify them. --- api-userpage.go | 8 ++++---- discord.go | 43 +++++++++++++++++++++++++++++++++---------- storage.go | 1 + telegram.go | 47 +++++++++++++++++++++++++++++++++++++---------- 4 files changed, 75 insertions(+), 24 deletions(-) diff --git a/api-userpage.go b/api-userpage.go index bada208..93f856a 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -301,10 +301,10 @@ func (app *appContext) GetMyPIN(gc *gin.Context) { resp := GetMyPINDTO{} switch service { case "discord": - resp.PIN = app.discord.NewAuthToken() + resp.PIN = app.discord.NewAssignedAuthToken(gc.GetString("jfId")) break case "telegram": - resp.PIN = app.telegram.NewAuthToken() + resp.PIN = app.telegram.NewAssignedAuthToken(gc.GetString("jfId")) break default: respond(400, "invalid service", gc) @@ -322,7 +322,7 @@ 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.UserVerified(pin) + dcUser, ok := app.discord.AssignedUserVerified(pin, gc.GetString("jfId")) app.discord.DeleteVerifiedUser(pin) if !ok { respondBool(200, false, gc) @@ -350,7 +350,7 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) { // @tags User Page func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) { pin := gc.Param("pin") - token, ok := app.telegram.TokenVerified(pin) + token, ok := app.telegram.AssignedTokenVerified(pin, gc.GetString("jfId")) app.telegram.DeleteVerifiedToken(pin) if !ok { respondBool(200, false, gc) diff --git a/discord.go b/discord.go index 1c2c58e..607696c 100644 --- a/discord.go +++ b/discord.go @@ -13,8 +13,8 @@ type DiscordDaemon struct { ShutdownChannel chan string bot *dg.Session username string - tokens map[string]time.Time // Map of tokens to expiry times. - verifiedTokens map[string]DiscordUser // Map of tokens to discord users. + tokens map[string]VerifToken // Map of pins to tokens. + verifiedTokens map[string]DiscordUser // Map of token pins to discord users. channelID, channelName, inviteChannelID, inviteChannelName string guildID string serverChannelName, serverName string @@ -38,7 +38,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { Stopped: false, ShutdownChannel: make(chan string), bot: bot, - tokens: map[string]time.Time{}, + tokens: map[string]VerifToken{}, verifiedTokens: map[string]DiscordUser{}, users: map[string]DiscordUser{}, app: app, @@ -59,7 +59,15 @@ 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[pin] = time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second) + d.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: ""} + return pin +} + +// NewAssignedAuthToken generates an 8-character pin in the form "A1-2B-CD", +// and assigns it for access only with the given Jellyfin ID. +func (d *DiscordDaemon) NewAssignedAuthToken(id string) string { + pin := genAuthToken() + d.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: id} return pin } @@ -432,8 +440,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() - expiry, ok := d.tokens[pin] - if !ok || time.Now().After(expiry) { + user, ok := d.tokens[pin] + if !ok || time.Now().After(user.Expiry) { err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{ // Type: dg.InteractionResponseChannelMessageWithSource, Type: dg.InteractionResponseChannelMessageWithSource, @@ -459,7 +467,9 @@ 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) } - d.verifiedTokens[pin] = d.users[i.Interaction.Member.User.ID] + dcUser := d.users[i.Interaction.Member.User.ID] + dcUser.JellyfinID = user.JellyfinID + d.verifiedTokens[pin] = dcUser delete(d.tokens, pin) } @@ -601,8 +611,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 } - expiry, ok := d.tokens[sects[0]] - if !ok || time.Now().After(expiry) { + user, ok := d.tokens[sects[0]] + if !ok || time.Now().After(user.Expiry) { _, err := s.ChannelMessageSend( m.ChannelID, d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"), @@ -620,7 +630,9 @@ 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) } - d.verifiedTokens[sects[0]] = d.users[m.Author.ID] + dcUser := d.users[m.Author.ID] + dcUser.JellyfinID = user.JellyfinID + d.verifiedTokens[sects[0]] = dcUser delete(d.tokens, sects[0]) } @@ -683,6 +695,17 @@ func (d *DiscordDaemon) UserVerified(pin string) (user DiscordUser, ok bool) { return } +// AssignedUserVerified returns whether or not a user with the given PIN has been verified, and the token itself. +// Returns false if the given Jellyfin ID does not match the one in the user. +func (d *DiscordDaemon) AssignedUserVerified(pin string, jfID string) (user DiscordUser, ok bool) { + user, ok = d.verifiedTokens[pin] + if ok && user.JellyfinID != jfID { + ok = false + } + // delete(d.verifiedUsers, pin) + return +} + // UserExists returns whether or not a user with the given ID exists. func (d *DiscordDaemon) UserExists(id string) (ok bool) { ok = false diff --git a/storage.go b/storage.go index de49b5d..5d66d0b 100644 --- a/storage.go +++ b/storage.go @@ -181,6 +181,7 @@ type DiscordUser struct { Discriminator string Lang string Contact bool + JellyfinID string `json:"-"` // Used internally in discord.go } type EmailAddress struct { diff --git a/telegram.go b/telegram.go index 52d9291..a50292c 100644 --- a/telegram.go +++ b/telegram.go @@ -14,8 +14,15 @@ const ( ) type TelegramVerifiedToken struct { - ChatID int64 - Username string + ChatID int64 + Username string + JellyfinID string // optional, for ensuring a user-requested change is only accessed by them. +} + +// VerifToken stores details about a pending user verification token. +type VerifToken struct { + Expiry time.Time + JellyfinID string // optional, for ensuring a user-requested change is only accessed by them. } type TelegramDaemon struct { @@ -23,8 +30,8 @@ type TelegramDaemon struct { ShutdownChannel chan string bot *tg.BotAPI username string - tokens map[string]time.Time // Map of tokens to their expiry time. - verifiedTokens map[string]TelegramVerifiedToken // Map of tokens to the responsible ChatID+Username. + tokens map[string]VerifToken // Map of pins to tokens. + verifiedTokens map[string]TelegramVerifiedToken // Map of token pins 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 @@ -43,7 +50,7 @@ func newTelegramDaemon(app *appContext) (*TelegramDaemon, error) { ShutdownChannel: make(chan string), bot: bot, username: bot.Self.UserName, - tokens: map[string]time.Time{}, + tokens: map[string]VerifToken{}, verifiedTokens: map[string]TelegramVerifiedToken{}, languages: map[int64]string{}, link: "https://t.me/" + bot.Self.UserName, @@ -75,7 +82,15 @@ 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[pin] = time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second) + t.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: ""} + return pin +} + +// NewAssignedAuthToken generates an 8-character pin in the form "A1-2B-CD", +// and assigns it for access only with the given Jellyfin ID. +func (t *TelegramDaemon) NewAssignedAuthToken(id string) string { + pin := genAuthToken() + t.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: id} return pin } @@ -215,8 +230,8 @@ func (t *TelegramDaemon) commandLang(upd *tg.Update, sects []string, lang string } func (t *TelegramDaemon) commandPIN(upd *tg.Update, sects []string, lang string) { - expiry, ok := t.tokens[upd.Message.Text] - if !ok || time.Now().After(expiry) { + token, ok := t.tokens[upd.Message.Text] + if !ok || time.Now().After(token.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) @@ -229,8 +244,9 @@ func (t *TelegramDaemon) commandPIN(upd *tg.Update, sects []string, lang string) t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err) } t.verifiedTokens[upd.Message.Text] = TelegramVerifiedToken{ - ChatID: upd.Message.Chat.ID, - Username: upd.Message.Chat.UserName, + ChatID: upd.Message.Chat.ID, + Username: upd.Message.Chat.UserName, + JellyfinID: token.JellyfinID, } delete(t.tokens, upd.Message.Text) } @@ -242,6 +258,17 @@ func (t *TelegramDaemon) TokenVerified(pin string) (token TelegramVerifiedToken, return } +// AssignedTokenVerified returns whether or not a token with the given PIN has been verified, and the token itself. +// Returns false if the given Jellyfin ID does not match the one in the token. +func (t *TelegramDaemon) AssignedTokenVerified(pin string, jfID string) (token TelegramVerifiedToken, ok bool) { + token, ok = t.verifiedTokens[pin] + if ok && token.JellyfinID != jfID { + ok = false + } + // 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