diff options
| author | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
| commit | 8fe9e3c3fef0049df82281a62afe35dda516dfe2 (patch) | |
| tree | dd269f2c0710f334e19f00a191260ebc46284a90 /internal/game/core_login_account.go | |
| parent | 194164a77eb4f2479b94c19e6f0e1278ab608781 (diff) | |
| download | thehouseoficarus-8fe9e3c3fef0049df82281a62afe35dda516dfe2.tar.gz | |
fix: close combat ghost, admin traversal, login throttle, journal double-pay, and quality/economy gaps
Diffstat (limited to 'internal/game/core_login_account.go')
| -rw-r--r-- | internal/game/core_login_account.go | 10 |
1 files changed, 10 insertions, 0 deletions
diff --git a/internal/game/core_login_account.go b/internal/game/core_login_account.go index a5106ea..e9fa67f 100644 --- a/internal/game/core_login_account.go +++ b/internal/game/core_login_account.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "strings" + "time" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" @@ -156,6 +157,8 @@ func (g *Game) handlePasswordOffOwner(sess *net.Session, input string) { }) return } + throttle := g.ensureLoginThrottle() + key := loginThrottleKey(name, sess.RemoteAddr()) auth := passwordAuth{} acc, err := g.AccountStore.LoadAccount(name) switch { @@ -163,6 +166,7 @@ func (g *Game) handlePasswordOffOwner(sess *net.Session, input string) { auth.loadErr = true case player.CheckPassword(input, acc.PasswordHash): auth.ok, auth.stored = true, acc.PasswordHash + throttle.clear(key) // Transparent upgrade: legacy single-pass SHA-256 hashes rehash to // Argon2id after a successful verification. A failed rehash keeps // the legacy hash and retries on the next login. @@ -173,6 +177,12 @@ func (g *Game) handlePasswordOffOwner(sess *net.Session, input string) { log.Printf("login: password rehash for %s: %v", acc.Name, hashErr) } } + default: + // Wrong password: apply an escalating per-(account, IP) delay so a + // brute-force attempt costs real wall-clock time. This is the + // connection's own goroutine, never the owner, so the world is not + // stalled by an attacker. + time.Sleep(throttle.delayFor(key)) } g.RunOnOwner(func() { if g.isShuttingDown() { |
