feat: Generate hashes for Discord and allow password resets

This commit is contained in:
Matthew
2023-11-26 16:47:54 -05:00
parent 50946b9c68
commit 226adddc43
8 changed files with 100 additions and 46 deletions

View File

@@ -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": [],

32
main.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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!"

View File

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