package db import ( "crypto/sha256" "database/sql" "encoding/hex" "errors" "fmt" "sync" "time" _ "github.com/mattn/go-sqlite3" ) var db *sql.DB var ( ErrUserExists = errors.New("username already exists") ErrInvalidCredentials = errors.New("invalid username or password") ) const ( maxRegistrationsPerIP = 3 // Maximum registrations allowed per IP registrationWindow = 24 * time.Hour // Time window for rate limiting ) type registrationAttempt struct { count int firstTry time.Time } var ( registrationAttempts = make(map[string]*registrationAttempt) rateLimitMutex sync.RWMutex ) func CleanupOldAttempts() { rateLimitMutex.Lock() defer rateLimitMutex.Unlock() now := time.Now() for ip, attempt := range registrationAttempts { if now.Sub(attempt.firstTry) > registrationWindow { delete(registrationAttempts, ip) } } } func CheckRegistrationLimit(ip string) error { rateLimitMutex.Lock() defer rateLimitMutex.Unlock() now := time.Now() attempt, exists := registrationAttempts[ip] if !exists { registrationAttempts[ip] = ®istrationAttempt{ count: 1, firstTry: now, } return nil } // Reset if window has passed if now.Sub(attempt.firstTry) > registrationWindow { attempt.count = 1 attempt.firstTry = now return nil } if attempt.count >= maxRegistrationsPerIP { return fmt.Errorf("registration limit reached for this IP. Please try again in %v", registrationWindow-now.Sub(attempt.firstTry)) } attempt.count++ return nil } func InitDB(dbPath string) error { var err error db, err = sql.Open("sqlite3", dbPath) if err != nil { return err } // Create tables if they don't exist _, err = db.Exec(` CREATE TABLE IF NOT EXISTS players ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at DATETIME NOT NULL ); CREATE TABLE IF NOT EXISTS player_states ( player_id INTEGER PRIMARY KEY, x INTEGER NOT NULL, y INTEGER NOT NULL, last_seen DATETIME NOT NULL, FOREIGN KEY(player_id) REFERENCES players(id) ); `) return err } func hashPassword(password string) string { hash := sha256.Sum256([]byte(password)) return hex.EncodeToString(hash[:]) } func RegisterPlayer(username, password string) (int, error) { // Check if username exists var exists bool err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM players WHERE username = ?)", username).Scan(&exists) if err != nil { return 0, err } if exists { return 0, ErrUserExists } // Create new player result, err := db.Exec(` INSERT INTO players (username, password_hash, created_at) VALUES (?, ?, ?)`, username, hashPassword(password), time.Now().UTC(), ) if err != nil { return 0, err } id, err := result.LastInsertId() return int(id), err } func AuthenticatePlayer(username, password string) (int, error) { var id int var storedHash string err := db.QueryRow(` SELECT id, password_hash FROM players WHERE username = ?`, username, ).Scan(&id, &storedHash) if err == sql.ErrNoRows { return 0, ErrInvalidCredentials } if err != nil { return 0, err } if storedHash != hashPassword(password) { return 0, ErrInvalidCredentials } return id, nil } func SavePlayerState(playerID int, x, y int) error { _, err := db.Exec(` INSERT OR REPLACE INTO player_states ( player_id, x, y, last_seen ) VALUES (?, ?, ?, ?)`, playerID, x, y, time.Now().UTC(), ) return err } func LoadPlayerState(playerID int) (x, y int, err error) { err = db.QueryRow(` SELECT x, y FROM player_states WHERE player_id = ?`, playerID, ).Scan(&x, &y) if err == sql.ErrNoRows { // Return default position for new players return 5, 5, nil } return x, y, err } func GetUsername(playerID int) (string, error) { var username string err := db.QueryRow("SELECT username FROM players WHERE id = ?", playerID).Scan(&username) return username, err }