234 lines
5.0 KiB
Go
234 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"server/internal/db"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type LoginServer struct {
|
|
listener net.Listener
|
|
database *db.Database
|
|
worldURL string
|
|
serverID string
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Type string `json:"type"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type LoginResponse struct {
|
|
Type string `json:"type"`
|
|
Success bool `json:"success"`
|
|
Message string `json:"message,omitempty"`
|
|
Token string `json:"token,omitempty"`
|
|
WorldURL string `json:"worldUrl,omitempty"`
|
|
PlayerID int64 `json:"playerId,omitempty"`
|
|
}
|
|
|
|
type RegisterRequest struct {
|
|
Type string `json:"type"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type RegisterResponse struct {
|
|
Type string `json:"type"`
|
|
Success bool `json:"success"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
port = flag.String("port", "8081", "Login server port")
|
|
dbDSN = flag.String("db", "user:password@tcp(localhost:3306)/game", "Database DSN")
|
|
worldURL = flag.String("world", "localhost:8082", "World server URL")
|
|
)
|
|
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,
|
|
worldURL: *worldURL,
|
|
serverID: generateServerID(),
|
|
}
|
|
|
|
listener, err := net.Listen("tcp", ":"+*port)
|
|
if err != nil {
|
|
log.Fatalf("Failed to start login server: %v", err)
|
|
}
|
|
server.listener = listener
|
|
|
|
log.Printf("Login server started on port %s (ID: %s)", *port, server.serverID)
|
|
|
|
go server.cleanupSessions()
|
|
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Printf("Accept error: %v", err)
|
|
continue
|
|
}
|
|
go server.handleConnection(conn)
|
|
}
|
|
}
|
|
|
|
func (s *LoginServer) handleConnection(conn net.Conn) {
|
|
defer conn.Close()
|
|
|
|
decoder := json.NewDecoder(conn)
|
|
encoder := json.NewEncoder(conn)
|
|
|
|
for {
|
|
var msg json.RawMessage
|
|
if err := decoder.Decode(&msg); err != nil {
|
|
return
|
|
}
|
|
|
|
var baseMsg struct {
|
|
Type string `json:"type"`
|
|
}
|
|
if err := json.Unmarshal(msg, &baseMsg); err != nil {
|
|
continue
|
|
}
|
|
|
|
switch baseMsg.Type {
|
|
case "login":
|
|
var req LoginRequest
|
|
if err := json.Unmarshal(msg, &req); err != nil {
|
|
continue
|
|
}
|
|
s.handleLogin(req, encoder)
|
|
|
|
case "register":
|
|
var req RegisterRequest
|
|
if err := json.Unmarshal(msg, &req); err != nil {
|
|
continue
|
|
}
|
|
s.handleRegister(req, encoder)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *LoginServer) handleLogin(req LoginRequest, encoder *json.Encoder) {
|
|
player, err := s.database.GetPlayerByUsername(req.Username)
|
|
if err != nil {
|
|
encoder.Encode(LoginResponse{
|
|
Type: "loginResponse",
|
|
Success: false,
|
|
Message: "Invalid username or password",
|
|
})
|
|
return
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(player.PasswordHash), []byte(req.Password)); err != nil {
|
|
encoder.Encode(LoginResponse{
|
|
Type: "loginResponse",
|
|
Success: false,
|
|
Message: "Invalid username or password",
|
|
})
|
|
return
|
|
}
|
|
|
|
token := generateToken()
|
|
if err := s.database.CreateSession(player.ID, token, "world", s.serverID, 24*time.Hour); err != nil {
|
|
encoder.Encode(LoginResponse{
|
|
Type: "loginResponse",
|
|
Success: false,
|
|
Message: "Failed to create session",
|
|
})
|
|
return
|
|
}
|
|
|
|
s.database.UpdateLastLogin(player.ID)
|
|
|
|
encoder.Encode(LoginResponse{
|
|
Type: "loginResponse",
|
|
Success: true,
|
|
Token: token,
|
|
WorldURL: s.worldURL,
|
|
PlayerID: player.ID,
|
|
})
|
|
}
|
|
|
|
func (s *LoginServer) handleRegister(req RegisterRequest, encoder *json.Encoder) {
|
|
if len(req.Username) < 3 || len(req.Username) > 20 {
|
|
encoder.Encode(RegisterResponse{
|
|
Type: "registerResponse",
|
|
Success: false,
|
|
Message: "Username must be between 3 and 20 characters",
|
|
})
|
|
return
|
|
}
|
|
|
|
if len(req.Password) < 6 {
|
|
encoder.Encode(RegisterResponse{
|
|
Type: "registerResponse",
|
|
Success: false,
|
|
Message: "Password must be at least 6 characters",
|
|
})
|
|
return
|
|
}
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
encoder.Encode(RegisterResponse{
|
|
Type: "registerResponse",
|
|
Success: false,
|
|
Message: "Failed to process password",
|
|
})
|
|
return
|
|
}
|
|
|
|
_, err = s.database.CreatePlayer(req.Username, string(hashedPassword))
|
|
if err != nil {
|
|
encoder.Encode(RegisterResponse{
|
|
Type: "registerResponse",
|
|
Success: false,
|
|
Message: "Username already exists",
|
|
})
|
|
return
|
|
}
|
|
|
|
encoder.Encode(RegisterResponse{
|
|
Type: "registerResponse",
|
|
Success: true,
|
|
Message: "Registration successful",
|
|
})
|
|
}
|
|
|
|
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 generateToken() string {
|
|
b := make([]byte, 32)
|
|
rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
func generateServerID() string {
|
|
return fmt.Sprintf("login-%d", time.Now().Unix())
|
|
} |