From 226adddc43b5c639ae230ff46b0681dc9fcb55fd Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 26 Nov 2023 16:47:54 -0500 Subject: [PATCH] feat: Generate hashes for Discord and allow password resets --- config.json | 20 ++++++---- main.go | 32 +++++++++++++++ server/channelserver/handlers_cast_binary.go | 17 ++++++-- server/channelserver/handlers_discord.go | 23 ++++++++++- server/channelserver/handlers_stage.go | 41 +++++++++----------- server/channelserver/sys_channel_server.go | 10 ----- server/channelserver/sys_language.go | 2 + server/channelserver/sys_session.go | 1 + 8 files changed, 100 insertions(+), 46 deletions(-) diff --git a/config.json b/config.json index da75463ee..4063a6df2 100644 --- a/config.json +++ b/config.json @@ -19,9 +19,9 @@ "AutoCreateAccount": true, "CleanDB": false, "MaxLauncherHR": false, - "LogInboundMessages": false, - "LogOutboundMessages": false, - "LogMessageData": false, + "LogInboundMessages": true, + "LogOutboundMessages": true, + "LogMessageData": true, "MaxHexdumpLength": 256, "DivaEvent": 0, "FestaEvent": -1, @@ -73,9 +73,9 @@ "SeasonOverride": false }, "Discord": { - "Enabled": false, - "BotToken": "", - "RealtimeChannelID": "" + "Enabled": true, + "BotToken": "MTAzMTQ2MDI4MDYxOTc2NTgwMA.GGe824._OxF9rtv1O8EjOZI26hATruaF_VZ9YBwuAdS1Y", + "RealtimeChannelID": "645108836423958540" }, "Commands": [ { @@ -106,6 +106,10 @@ "Name": "PSN", "Enabled": true, "Prefix": "psn" + }, { + "Name": "Discord", + "Enabled": true, + "Prefix": "discord" } ], "Courses": [ @@ -125,7 +129,7 @@ "Host": "localhost", "Port": 5432, "User": "postgres", - "Password": "", + "Password": "admin", "Database": "erupe" }, "Sign": { @@ -133,7 +137,7 @@ "Port": 53312 }, "SignV2": { - "Enabled": false, + "Enabled": true, "Port": 8080, "PatchServer": "", "Banners": [], diff --git a/main.go b/main.go index c56d90b0f..7a21a4b7b 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( _config "erupe-ce/config" "fmt" + "github.com/bwmarrin/discordgo" "net" "os" "os/signal" @@ -98,6 +99,37 @@ func main() { } discordBot = bot + + _, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ + { + Name: "verify", + Description: "Verify your account with Discord", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "token", + Description: "The access token provided by !discord command within the game client.", + Required: true, + }, + }, + }, + { + Name: "passwordreset", + Description: "Reset your account password on Erupe", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "password", + Description: "The password to change your account to.", + Required: true, + }, + }, + }, + }) + if err != nil { + preventClose(fmt.Sprintf("Discord: Failed to start, %s", err.Error())) + } + logger.Info("Discord: Started successfully") } else { logger.Info("Discord: Disabled") diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 36edbbadb..ea3b291d2 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -2,6 +2,7 @@ package channelserver import ( "crypto" + "encoding/binary" "encoding/hex" "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" @@ -321,12 +322,22 @@ func parseChatCommand(s *Session, command string) { } case commands["Discord"].Prefix: if commands["Discord"].Enabled { - token := crypto.MD5.New() - _, err := s.server.db.Exec("UPDATE users SET discord_token = ?", token) + tokenHash := crypto.MD5.New() + tokenSalt := fmt.Sprint(s.charID) + fmt.Sprint(s.server.ID) + tokenData := make([]byte, 4) + binary.LittleEndian.PutUint32(tokenData, uint32(time.Now().Second())) + tokenHash.Write([]byte(fmt.Sprintf("%s%s", tokenSalt, tokenData))) + discordToken := fmt.Sprint(tokenHash)[4:12] + s.logger.Info(discordToken) + _, err := s.server.db.Exec("UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", discordToken, s.charID) if err != nil { + sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command")) + s.logger.Error(fmt.Sprint(err)) return } - sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscord"], token)) + sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken)) + } else { + sendDisabledCommandMessage(s, commands["Discord"]) } } } diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 551c74146..7482332e0 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -3,6 +3,7 @@ package channelserver import ( "fmt" "github.com/bwmarrin/discordgo" + "golang.org/x/crypto/bcrypt" "sort" "strings" "unicode" @@ -70,14 +71,32 @@ func getCharacterList(s *Server) string { func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) { switch i.Interaction.ApplicationCommandData().Name { case "verify": - _, err := s.db.Exec("UPDATE users SET discord_id = ? WHERE discord_token = ?", i.User.ID, i.Interaction.ApplicationCommandData().Options[0].StringValue()) + _, err := s.db.Exec("UPDATE users SET discord_id = $1 WHERE discord_token = $2", i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()) if err != nil { return } err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: "Account successfully linked", + Content: "Erupe account successfully linked to Discord account.", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + if err != nil { + return + } + break + case "passwordreset": + password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10) + _, err := s.db.Exec("UPDATE users SET password = $1 WHERE discord_id = $2", password, i.Member.User.ID) + if err != nil { + return + } + err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Password has been reset, you may login now.", + Flags: discordgo.MessageFlagsEphemeral, }, }) if err != nil { diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 7a12d453e..cf8757b00 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -55,6 +55,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { // Save our new stage ID and pointer to the new stage itself. s.Lock() + s.stageID = stageID s.stage = s.server.stages[stageID] s.Unlock() @@ -152,13 +153,13 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) // Push our current stage ID to the movement stack before entering another one. - if s.stage.id == "" { + if s.stageID == "" { s.stageMoveStack.Set(pkt.StageID) } else { s.stage.Lock() s.stage.reservedClientSlots[s.charID] = false s.stage.Unlock() - s.stageMoveStack.Push(s.stage.id) + s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Lock() } @@ -205,12 +206,9 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysLockStage) - if stage, exists := s.server.stages[pkt.StageID]; exists { - stage.Lock() - stage.locked = true - stage.Unlock() - } - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + // TODO(Andoryuuta): What does this packet _actually_ do? + // I think this is supposed to mark a stage as no longer able to accept client reservations + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { @@ -220,9 +218,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) { for charID := range s.reservationStage.reservedClientSlots { session := s.server.FindSessionByCharID(charID) - if session != nil { - session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) - } + session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) } delete(s.server.stages, s.reservationStage.id) @@ -245,10 +241,6 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { - if stage.locked { - doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) - return - } if len(stage.password) > 0 { if stage.password != s.stagePass { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) @@ -391,17 +383,20 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { joinable++ bf.WriteUint16(uint16(len(stage.reservedClientSlots))) - bf.WriteUint16(uint16(len(stage.clients))) - bf.WriteUint16(uint16(len(stage.clients))) + bf.WriteUint16(0) // Unk + if len(stage.clients) > 0 { + bf.WriteUint16(1) + } else { + bf.WriteUint16(0) + } bf.WriteUint16(stage.maxPlayers) - var flags uint8 - if stage.locked { - flags |= 1 - } if len(stage.password) > 0 { - flags |= 2 + // This byte has also been seen as 1 + // The quest is also recognised as locked when this is 2 + bf.WriteUint8(2) + } else { + bf.WriteUint8(0) } - bf.WriteUint8(flags) ps.Uint8(bf, sid, false) stage.RUnlock() } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index f7658c5cd..119cbf420 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -2,7 +2,6 @@ package channelserver import ( "fmt" - "github.com/bwmarrin/discordgo" "net" "strings" "sync" @@ -211,15 +210,6 @@ func (s *Server) Start() error { // Start the discord bot for chat integration. if s.erupeConfig.Discord.Enabled && s.discordBot != nil { - _, err := s.discordBot.Session.ApplicationCommandBulkOverwrite(s.discordBot.Session.State.User.ID, "", []*discordgo.ApplicationCommand{ - { - Name: "verify", - Description: "Verify your account with Discord", - }, - }) - if err != nil { - return err - } s.discordBot.Session.AddHandler(s.onDiscordMessage) s.discordBot.Session.AddHandler(s.onInteraction) } diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index dbd48cfb8..cc17ee6a1 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -76,6 +76,8 @@ func getLangStrings(s *Server) map[string]string { strings["commandPSNSuccess"] = "Connected PSN ID: %s" strings["commandPSNExists"] = "PSN ID is connected to another account!" + strings["commandDiscordSuccess"] = "Discord token has been generated: %s" + strings["commandRaviNoCommand"] = "No Raviente command specified!" strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment" strings["commandRaviStartError"] = "The Great Slaying has already begun!" diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 5034f38c2..32004151d 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -36,6 +36,7 @@ type Session struct { objectIndex uint16 userEnteredStage bool // If the user has entered a stage before + stageID string stage *Stage reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. stagePass string // Temporary storage