189 lines
3.9 KiB
Go
189 lines
3.9 KiB
Go
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
|
|
}
|