231 lines
5.6 KiB
Go
231 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"server/internal/db"
|
|
"server/internal/net/packets"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type LoginServer struct {
|
|
conn *net.UDPConn
|
|
database *db.Database
|
|
worldHost string
|
|
worldPort uint16
|
|
serverID string
|
|
clients map[string]*LoginClient // Map of addr string to client
|
|
clientsLock sync.RWMutex
|
|
}
|
|
|
|
type LoginClient struct {
|
|
addr *net.UDPAddr
|
|
lastSeen time.Time
|
|
username string
|
|
authed bool
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
port = flag.String("port", "9998", "Login server port")
|
|
dbDSN = flag.String("db", "user:password@tcp(localhost:3306)/game", "Database DSN")
|
|
worldHost = flag.String("worldhost", "localhost", "World server host")
|
|
worldPort = flag.Uint("worldport", 9999, "World server port")
|
|
)
|
|
flag.Parse()
|
|
|
|
database, err := db.New(*dbDSN)
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to database: %v", err)
|
|
}
|
|
defer database.Close()
|
|
|
|
server := &LoginServer{
|
|
database: database,
|
|
worldHost: *worldHost,
|
|
worldPort: uint16(*worldPort),
|
|
serverID: generateServerID(),
|
|
clients: make(map[string]*LoginClient),
|
|
}
|
|
|
|
addr, err := net.ResolveUDPAddr("udp", ":"+*port)
|
|
if err != nil {
|
|
log.Fatalf("Failed to resolve address: %v", err)
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp", addr)
|
|
if err != nil {
|
|
log.Fatalf("Failed to start login server: %v", err)
|
|
}
|
|
server.conn = conn
|
|
|
|
log.Printf("Login server started on UDP port %s (ID: %s)", *port, server.serverID)
|
|
log.Printf("World server configured at %s:%d", server.worldHost, server.worldPort)
|
|
|
|
go server.cleanupSessions()
|
|
go server.cleanupClients()
|
|
|
|
// Main packet processing loop
|
|
buffer := make([]byte, 1024)
|
|
for {
|
|
n, clientAddr, err := conn.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
log.Printf("Read error: %v", err)
|
|
continue
|
|
}
|
|
|
|
go server.handlePacket(buffer[:n], clientAddr)
|
|
}
|
|
}
|
|
|
|
func (s *LoginServer) handlePacket(data []byte, addr *net.UDPAddr) {
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
|
|
msgType := data[0]
|
|
|
|
switch msgType {
|
|
case packets.MSG_LOGIN_REQUEST:
|
|
s.handleLogin(data, addr)
|
|
case packets.MSG_REGISTER_REQUEST:
|
|
s.handleRegister(data, addr)
|
|
}
|
|
}
|
|
|
|
func (s *LoginServer) handleLogin(data []byte, addr *net.UDPAddr) {
|
|
username, password, ok := packets.DecodeLoginRequest(data)
|
|
if !ok {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Invalid request")
|
|
return
|
|
}
|
|
|
|
// Update client tracking
|
|
s.clientsLock.Lock()
|
|
s.clients[addr.String()] = &LoginClient{
|
|
addr: addr,
|
|
lastSeen: time.Now(),
|
|
username: username,
|
|
authed: false,
|
|
}
|
|
s.clientsLock.Unlock()
|
|
|
|
player, err := s.database.GetPlayerByUsername(username)
|
|
if err != nil {
|
|
// Try to register if user doesn't exist
|
|
s.handleRegister(data, addr)
|
|
return
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(player.PasswordHash), []byte(password)); err != nil {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Invalid username or password")
|
|
return
|
|
}
|
|
|
|
// Generate session token
|
|
token := make([]byte, 32)
|
|
rand.Read(token)
|
|
tokenHex := hex.EncodeToString(token)
|
|
|
|
if err := s.database.CreateSession(player.ID, tokenHex, "world", s.serverID, 24*time.Hour); err != nil {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Failed to create session")
|
|
return
|
|
}
|
|
|
|
s.database.UpdateLastLogin(player.ID)
|
|
|
|
// Mark client as authenticated
|
|
s.clientsLock.Lock()
|
|
if client, exists := s.clients[addr.String()]; exists {
|
|
client.authed = true
|
|
}
|
|
s.clientsLock.Unlock()
|
|
|
|
s.sendLoginResponse(addr, true, token, uint32(player.ID), "Login successful")
|
|
}
|
|
|
|
func (s *LoginServer) handleRegister(data []byte, addr *net.UDPAddr) {
|
|
username, password, ok := packets.DecodeRegisterRequest(data)
|
|
if !ok {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Invalid request")
|
|
return
|
|
}
|
|
|
|
if len(username) < 3 || len(username) > 20 {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Username must be 3-20 characters")
|
|
return
|
|
}
|
|
|
|
if len(password) < 6 {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Password must be at least 6 characters")
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Failed to process password")
|
|
return
|
|
}
|
|
|
|
playerID, err := s.database.CreatePlayer(username, string(hashedPassword))
|
|
if err != nil {
|
|
// Player might already exist, try login instead
|
|
s.handleLogin(data, addr)
|
|
return
|
|
}
|
|
|
|
// Auto-login after registration
|
|
token := make([]byte, 32)
|
|
rand.Read(token)
|
|
tokenHex := hex.EncodeToString(token)
|
|
|
|
if err := s.database.CreateSession(playerID, tokenHex, "world", s.serverID, 24*time.Hour); err != nil {
|
|
s.sendLoginResponse(addr, false, nil, 0, "Registration successful but login failed")
|
|
return
|
|
}
|
|
|
|
s.sendLoginResponse(addr, true, token, uint32(playerID), "Registration successful")
|
|
}
|
|
|
|
func (s *LoginServer) sendLoginResponse(addr *net.UDPAddr, success bool, token []byte, playerID uint32, message string) {
|
|
response := packets.EncodeLoginResponse(success, token, s.worldHost, s.worldPort, playerID, message)
|
|
s.conn.WriteToUDP(response, addr)
|
|
}
|
|
|
|
func (s *LoginServer) cleanupSessions() {
|
|
ticker := time.NewTicker(1 * time.Hour)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
if err := s.database.CleanExpiredSessions(); err != nil {
|
|
log.Printf("Failed to clean expired sessions: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *LoginServer) cleanupClients() {
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
s.clientsLock.Lock()
|
|
now := time.Now()
|
|
for addr, client := range s.clients {
|
|
if now.Sub(client.lastSeen) > 5*time.Minute {
|
|
delete(s.clients, addr)
|
|
}
|
|
}
|
|
s.clientsLock.Unlock()
|
|
}
|
|
}
|
|
|
|
func generateServerID() string {
|
|
return fmt.Sprintf("login-%d", time.Now().Unix())
|
|
} |