aboutsummaryrefslogtreecommitdiff
path: root/internal/game/core_login_account.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/core_login_account.go')
-rw-r--r--internal/game/core_login_account.go82
1 files changed, 72 insertions, 10 deletions
diff --git a/internal/game/core_login_account.go b/internal/game/core_login_account.go
index 921af82..a5106ea 100644
--- a/internal/game/core_login_account.go
+++ b/internal/game/core_login_account.go
@@ -16,6 +16,14 @@ func (g *Game) handleAccountName(sess *net.Session, input string) {
sess.Write("Account name: ")
return
}
+ // Validate BEFORE any filesystem access: the name is joined into an
+ // account (and later character) path, so an invalid name must never
+ // reach FindAccount or anything downstream that reuses it.
+ if verr := player.ValidName(name); verr != nil {
+ sess.WriteLine(verr.Error())
+ sess.Write("Account name: ")
+ return
+ }
actualName, err := g.AccountStore.FindAccount(name)
if err == nil {
sess.Account = &player.Account{Name: actualName}
@@ -24,11 +32,6 @@ func (g *Game) handleAccountName(sess *net.Session, input string) {
sess.SetEcho(false)
sess.Write("Password: ")
} else {
- if verr := player.ValidName(name); verr != nil {
- sess.WriteLine(verr.Error())
- sess.Write("Account name: ")
- return
- }
sess.Account = &player.Account{Name: name}
sess.SetAuthName(name)
sess.SetState(net.StateNewAccountPass)
@@ -38,6 +41,22 @@ func (g *Game) handleAccountName(sess *net.Session, input string) {
}
}
+// resetToAccountName returns a session that has no usable account left
+// (purged, or republished out from under it) to the account-name prompt. A
+// menu-state handler must call it instead of dereferencing sess.Account:
+// a queued-mode Close only drops the transport after the writer drains, so
+// an already-buffered input line can still be dispatched to the owner
+// after the account is gone.
+func (g *Game) resetToAccountName(sess *net.Session) {
+ sess.Account = nil
+ sess.PendingChar = ""
+ sess.PendingMenu = nil
+ sess.PendingPass = ""
+ sess.SetEcho(true)
+ sess.SetState(net.StateAccountName)
+ sess.Write("Account name: ")
+}
+
func (g *Game) handlePassword(sess *net.Session, input string) {
if input == "" {
sess.Write("Password: ")
@@ -171,6 +190,13 @@ func (g *Game) applyPasswordAuth(sess *net.Session, auth passwordAuth) {
sess.SetState(net.StateAccountName)
sess.Write("Account name: ")
}
+ if sess.Account == nil {
+ // The account vanished while the password was being verified (a
+ // purge or republish from another session): there is nothing to
+ // authorize against, so start over from the account-name prompt.
+ g.resetToAccountName(sess)
+ return
+ }
if auth.loadErr {
reject("Error loading account.")
return
@@ -235,6 +261,10 @@ func (g *Game) handleNewPass(sess *net.Session, input string) {
}
func (g *Game) handleNewAccount(sess *net.Session, input string) {
+ if sess.Account == nil {
+ g.resetToAccountName(sess)
+ return
+ }
input = strings.TrimSpace(input)
if input == "" {
sess.SetEcho(true)
@@ -283,6 +313,13 @@ func (g *Game) handleNewAccountOffOwner(sess *net.Session, input string) {
// applyNewAccount creates the account on the owner. The password was
// already hashed by the caller (off the owner where possible).
func (g *Game) applyNewAccount(sess *net.Session, hash string, hashErr error) {
+ if sess.Account == nil {
+ // The account vanished while the password was being hashed off the
+ // owner (a purge or republish from another session): there is nothing
+ // to create against, and sess.Account is dereferenced below.
+ g.resetToAccountName(sess)
+ return
+ }
if hashErr != nil {
sess.SetEcho(true)
sess.WriteLine(fmt.Sprintf("Error creating account: %v", hashErr))
@@ -332,6 +369,10 @@ func (g *Game) applyNewAccount(sess *net.Session, hash string, hashErr error) {
}
func (g *Game) handleColorChoice(sess *net.Session, input string) {
+ if sess.Account == nil {
+ g.resetToAccountName(sess)
+ return
+ }
input = strings.ToLower(strings.TrimSpace(input))
if input == "y" || input == "yes" {
sess.Account.Options["color"] = "none"
@@ -359,6 +400,13 @@ func menuColorMode(sess *net.Session) string {
}
func (g *Game) showMenu(sess *net.Session) {
+ if sess.Account == nil {
+ // The account is gone (purged or republished from another session)
+ // while this session was mid-flow: there is no menu to show for a
+ // deleted account.
+ g.resetToAccountName(sess)
+ return
+ }
sess.SetState(net.StateMenu)
mode := menuColorMode(sess)
lines := []string{
@@ -387,6 +435,10 @@ func (g *Game) showMenu(sess *net.Session) {
}
func (g *Game) handleMenu(sess *net.Session, input string) {
+ if sess.Account == nil {
+ g.resetToAccountName(sess)
+ return
+ }
switch strings.ToLower(strings.TrimSpace(input)) {
case "":
sess.Write("> ")
@@ -483,6 +535,10 @@ func (g *Game) handleMenu(sess *net.Session, input string) {
}
func (g *Game) handleRenameAccount(sess *net.Session, input string) {
+ if sess.Account == nil {
+ g.resetToAccountName(sess)
+ return
+ }
newName := strings.TrimSpace(input)
if newName == "" {
sess.Write("New account name: ")
@@ -524,6 +580,10 @@ func (g *Game) handleRenameAccount(sess *net.Session, input string) {
}
func (g *Game) handlePurgeAccount(sess *net.Session, input string) {
+ if sess.Account == nil {
+ g.resetToAccountName(sess)
+ return
+ }
input = strings.TrimSpace(input)
expected := "PURGE " + sess.Account.Name
if !strings.EqualFold(input, expected) {
@@ -551,17 +611,19 @@ func (g *Game) handlePurgeAccount(sess *net.Session, input string) {
}
sess.WriteLine(fmt.Sprintf("Account %s has been purged. Thanks for playing.", sess.Account.Name))
- // Other authenticated sessions of this account are stale now; tell
- // them and close them (they cannot safely keep using cached state).
+ // Every session authenticated to this account is stale now: the account
+ // file is gone, so none of them may keep a live protocol state (a menu
+ // handler dereferencing the cached account, or a buffered input line
+ // already inside the transport, would fault). Reset each to the
+ // account-name prompt before closing it.
for _, other := range g.allSessionsOfAccount(sess.Account.Name) {
if other == sess {
continue
}
- other.Account = nil
- other.PendingChar = ""
- other.PendingMenu = nil
other.WriteLine("\nYour account has been purged from another session.")
+ g.resetToAccountName(other)
other.Close()
}
+ g.resetToAccountName(sess)
sess.Close()
}