mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-15 00:15:08 +01:00
feat: Generate hashes for Discord and allow password resets
This commit is contained in:
20
config.json
20
config.json
@@ -19,9 +19,9 @@
|
|||||||
"AutoCreateAccount": true,
|
"AutoCreateAccount": true,
|
||||||
"CleanDB": false,
|
"CleanDB": false,
|
||||||
"MaxLauncherHR": false,
|
"MaxLauncherHR": false,
|
||||||
"LogInboundMessages": false,
|
"LogInboundMessages": true,
|
||||||
"LogOutboundMessages": false,
|
"LogOutboundMessages": true,
|
||||||
"LogMessageData": false,
|
"LogMessageData": true,
|
||||||
"MaxHexdumpLength": 256,
|
"MaxHexdumpLength": 256,
|
||||||
"DivaEvent": 0,
|
"DivaEvent": 0,
|
||||||
"FestaEvent": -1,
|
"FestaEvent": -1,
|
||||||
@@ -73,9 +73,9 @@
|
|||||||
"SeasonOverride": false
|
"SeasonOverride": false
|
||||||
},
|
},
|
||||||
"Discord": {
|
"Discord": {
|
||||||
"Enabled": false,
|
"Enabled": true,
|
||||||
"BotToken": "",
|
"BotToken": "MTAzMTQ2MDI4MDYxOTc2NTgwMA.GGe824._OxF9rtv1O8EjOZI26hATruaF_VZ9YBwuAdS1Y",
|
||||||
"RealtimeChannelID": ""
|
"RealtimeChannelID": "645108836423958540"
|
||||||
},
|
},
|
||||||
"Commands": [
|
"Commands": [
|
||||||
{
|
{
|
||||||
@@ -106,6 +106,10 @@
|
|||||||
"Name": "PSN",
|
"Name": "PSN",
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"Prefix": "psn"
|
"Prefix": "psn"
|
||||||
|
}, {
|
||||||
|
"Name": "Discord",
|
||||||
|
"Enabled": true,
|
||||||
|
"Prefix": "discord"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Courses": [
|
"Courses": [
|
||||||
@@ -125,7 +129,7 @@
|
|||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 5432,
|
"Port": 5432,
|
||||||
"User": "postgres",
|
"User": "postgres",
|
||||||
"Password": "",
|
"Password": "admin",
|
||||||
"Database": "erupe"
|
"Database": "erupe"
|
||||||
},
|
},
|
||||||
"Sign": {
|
"Sign": {
|
||||||
@@ -133,7 +137,7 @@
|
|||||||
"Port": 53312
|
"Port": 53312
|
||||||
},
|
},
|
||||||
"SignV2": {
|
"SignV2": {
|
||||||
"Enabled": false,
|
"Enabled": true,
|
||||||
"Port": 8080,
|
"Port": 8080,
|
||||||
"PatchServer": "",
|
"PatchServer": "",
|
||||||
"Banners": [],
|
"Banners": [],
|
||||||
|
|||||||
32
main.go
32
main.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
_config "erupe-ce/config"
|
_config "erupe-ce/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -98,6 +99,37 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
discordBot = bot
|
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")
|
logger.Info("Discord: Started successfully")
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Discord: Disabled")
|
logger.Info("Discord: Disabled")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package channelserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/common/mhfcourse"
|
"erupe-ce/common/mhfcourse"
|
||||||
@@ -321,12 +322,22 @@ func parseChatCommand(s *Session, command string) {
|
|||||||
}
|
}
|
||||||
case commands["Discord"].Prefix:
|
case commands["Discord"].Prefix:
|
||||||
if commands["Discord"].Enabled {
|
if commands["Discord"].Enabled {
|
||||||
token := crypto.MD5.New()
|
tokenHash := crypto.MD5.New()
|
||||||
_, err := s.server.db.Exec("UPDATE users SET discord_token = ?", token)
|
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 {
|
if err != nil {
|
||||||
|
sendServerChatMessage(s, fmt.Sprint("An error occurred while processing this command"))
|
||||||
|
s.logger.Error(fmt.Sprint(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscord"], token))
|
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken))
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Discord"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
@@ -70,14 +71,32 @@ func getCharacterList(s *Server) string {
|
|||||||
func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) {
|
func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
switch i.Interaction.ApplicationCommandData().Name {
|
switch i.Interaction.ApplicationCommandData().Name {
|
||||||
case "verify":
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
err = ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
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 {
|
if err != nil {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
|||||||
|
|
||||||
// Save our new stage ID and pointer to the new stage itself.
|
// Save our new stage ID and pointer to the new stage itself.
|
||||||
s.Lock()
|
s.Lock()
|
||||||
|
s.stageID = stageID
|
||||||
s.stage = s.server.stages[stageID]
|
s.stage = s.server.stages[stageID]
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
|
|
||||||
@@ -152,13 +153,13 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
||||||
|
|
||||||
// Push our current stage ID to the movement stack before entering another one.
|
// 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)
|
s.stageMoveStack.Set(pkt.StageID)
|
||||||
} else {
|
} else {
|
||||||
s.stage.Lock()
|
s.stage.Lock()
|
||||||
s.stage.reservedClientSlots[s.charID] = false
|
s.stage.reservedClientSlots[s.charID] = false
|
||||||
s.stage.Unlock()
|
s.stage.Unlock()
|
||||||
s.stageMoveStack.Push(s.stage.id)
|
s.stageMoveStack.Push(s.stageID)
|
||||||
s.stageMoveStack.Lock()
|
s.stageMoveStack.Lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,12 +206,9 @@ func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {}
|
|||||||
|
|
||||||
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgSysLockStage)
|
pkt := p.(*mhfpacket.MsgSysLockStage)
|
||||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
// TODO(Andoryuuta): What does this packet _actually_ do?
|
||||||
stage.Lock()
|
// I think this is supposed to mark a stage as no longer able to accept client reservations
|
||||||
stage.locked = true
|
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||||
stage.Unlock()
|
|
||||||
}
|
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||||
@@ -220,9 +218,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
for charID := range s.reservationStage.reservedClientSlots {
|
for charID := range s.reservationStage.reservedClientSlots {
|
||||||
session := s.server.FindSessionByCharID(charID)
|
session := s.server.FindSessionByCharID(charID)
|
||||||
if session != nil {
|
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
||||||
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.server.stages, s.reservationStage.id)
|
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))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
|
} 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 len(stage.password) > 0 {
|
||||||
if stage.password != s.stagePass {
|
if stage.password != s.stagePass {
|
||||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -391,17 +383,20 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
joinable++
|
joinable++
|
||||||
|
|
||||||
bf.WriteUint16(uint16(len(stage.reservedClientSlots)))
|
bf.WriteUint16(uint16(len(stage.reservedClientSlots)))
|
||||||
bf.WriteUint16(uint16(len(stage.clients)))
|
bf.WriteUint16(0) // Unk
|
||||||
bf.WriteUint16(uint16(len(stage.clients)))
|
if len(stage.clients) > 0 {
|
||||||
|
bf.WriteUint16(1)
|
||||||
|
} else {
|
||||||
|
bf.WriteUint16(0)
|
||||||
|
}
|
||||||
bf.WriteUint16(stage.maxPlayers)
|
bf.WriteUint16(stage.maxPlayers)
|
||||||
var flags uint8
|
|
||||||
if stage.locked {
|
|
||||||
flags |= 1
|
|
||||||
}
|
|
||||||
if len(stage.password) > 0 {
|
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)
|
ps.Uint8(bf, sid, false)
|
||||||
stage.RUnlock()
|
stage.RUnlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package channelserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -211,15 +210,6 @@ func (s *Server) Start() error {
|
|||||||
|
|
||||||
// Start the discord bot for chat integration.
|
// Start the discord bot for chat integration.
|
||||||
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
|
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.onDiscordMessage)
|
||||||
s.discordBot.Session.AddHandler(s.onInteraction)
|
s.discordBot.Session.AddHandler(s.onInteraction)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ func getLangStrings(s *Server) map[string]string {
|
|||||||
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
|
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
|
||||||
strings["commandPSNExists"] = "PSN ID is connected to another account!"
|
strings["commandPSNExists"] = "PSN ID is connected to another account!"
|
||||||
|
|
||||||
|
strings["commandDiscordSuccess"] = "Discord token has been generated: %s"
|
||||||
|
|
||||||
strings["commandRaviNoCommand"] = "No Raviente command specified!"
|
strings["commandRaviNoCommand"] = "No Raviente command specified!"
|
||||||
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
|
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
|
||||||
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
|
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Session struct {
|
|||||||
|
|
||||||
objectIndex uint16
|
objectIndex uint16
|
||||||
userEnteredStage bool // If the user has entered a stage before
|
userEnteredStage bool // If the user has entered a stage before
|
||||||
|
stageID string
|
||||||
stage *Stage
|
stage *Stage
|
||||||
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
|
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
|
||||||
stagePass string // Temporary storage
|
stagePass string // Temporary storage
|
||||||
|
|||||||
Reference in New Issue
Block a user