Support changing account settings

This commit is contained in:
Trevor Slocum 2023-12-15 00:23:45 -08:00
parent d8ad756a5c
commit cfb17add3b
8 changed files with 104 additions and 23 deletions

View file

@ -47,6 +47,10 @@ formatted responses are more easily parsed by computers.
- `password <old> <new>`
- Change account password.
- `set <name> <value>`
- Change account setting.
- Available settings: `highlight`, `pips` and `moves`.
- `json <on/off>`
- Turn JSON formatted messages on or off. JSON messages are not sent by default.

View file

@ -9,6 +9,7 @@ const (
CommandRegisterJSON = "registerjson" // Register an account and enable JSON messages.
CommandResetPassword = "resetpassword" // Request password reset link via email.
CommandPassword = "password" // Change password.
CommandSet = "set" // Change account setting.
CommandHelp = "help" // Print help information.
CommandJSON = "json" // Enable or disable JSON formatted messages.
CommandSay = "say" // Send chat message.
@ -48,4 +49,5 @@ const (
EventTypeFailedMove = "failedmove"
EventTypeFailedOk = "failedok"
EventTypeWin = "win"
EventTypeSettings = "settings"
)

View file

@ -110,6 +110,13 @@ type EventWin struct {
Points int
}
type EventSettings struct {
Event
Highlight bool
Pips bool
Moves bool
}
func DecodeEvent(message []byte) (interface{}, error) {
e := &Event{}
err := json.Unmarshal(message, e)
@ -153,6 +160,8 @@ func DecodeEvent(message []byte) (interface{}, error) {
ev = &EventFailedOk{}
case EventTypeWin:
ev = &EventWin{}
case EventTypeSettings:
ev = &EventSettings{}
default:
return nil, fmt.Errorf("failed to decode event: unknown event type: %s", e.Type)
}

View file

@ -1,8 +1,11 @@
package server
type account struct {
id int
email []byte
username []byte
password []byte
id int
email []byte
username []byte
password []byte
highlight bool
pips bool
moves bool
}

View file

@ -63,6 +63,8 @@ func (c *serverClient) sendEvent(e interface{}) {
ev.Type = bgammon.EventTypeFailedOk
case *bgammon.EventWin:
ev.Type = bgammon.EventTypeWin
case *bgammon.EventSettings:
ev.Type = bgammon.EventTypeSettings
default:
log.Panicf("unknown event type %+v", ev)
}

View file

@ -32,7 +32,10 @@ CREATE TABLE account (
reset bigint NOT NULL DEFAULT 0,
email text NOT NULL,
username text NOT NULL,
password text NOT NULL
password text NOT NULL,
highlight smallint NOT NULL DEFAULT 1,
pips smallint NOT NULL DEFAULT 1,
moves smallint NOT NULL DEFAULT 0
);
CREATE TABLE game (
id serial PRIMARY KEY,
@ -105,7 +108,7 @@ func initDB() {
log.Println("Initialized database schema")
}
func registerAccount(a *account) error {
func registerAccount(passwordSalt string, a *account) error {
if db == nil {
return nil
} else if len(bytes.TrimSpace(a.username)) == 0 {
@ -143,7 +146,7 @@ func registerAccount(a *account) error {
return fmt.Errorf("username already in use")
}
passwordHash, err := argon2id.CreateHash(string(a.password), passwordArgon2id)
passwordHash, err := argon2id.CreateHash(string(a.password)+passwordSalt, passwordArgon2id)
if err != nil {
return err
}
@ -192,7 +195,7 @@ func resetAccount(mailServer string, resetSalt string, email []byte) error {
timestamp := time.Now().Unix()
h := sha256.New()
h.Write([]byte(fmt.Sprintf("%d", timestamp) + resetSalt))
h.Write([]byte(fmt.Sprintf("%d/%d", id, timestamp) + resetSalt))
hash := fmt.Sprintf("%x", h.Sum(nil))[0:16]
emailConfig := hermes.Hermes{
@ -274,7 +277,7 @@ func confirmResetAccount(resetSalt string, passwordSalt string, id int, key stri
}
h := sha256.New()
h.Write([]byte(fmt.Sprintf("%d", reset) + resetSalt))
h.Write([]byte(fmt.Sprintf("%d/%d", id, reset) + resetSalt))
hash := fmt.Sprintf("%x", h.Sum(nil))[0:16]
if key != hash {
return "", nil
@ -291,7 +294,7 @@ func confirmResetAccount(resetSalt string, passwordSalt string, id int, key stri
return newPassword, err
}
func loginAccount(username []byte, password []byte) (*account, error) {
func loginAccount(passwordSalt string, username []byte, password []byte) (*account, error) {
if db == nil {
return nil, nil
} else if len(bytes.TrimSpace(username)) == 0 {
@ -306,21 +309,25 @@ func loginAccount(username []byte, password []byte) (*account, error) {
}
defer tx.Commit(context.Background())
account := &account{}
err = tx.QueryRow(context.Background(), "SELECT id, email, username, password FROM account WHERE username = $1 OR email = $2", bytes.ToLower(bytes.TrimSpace(username)), bytes.ToLower(bytes.TrimSpace(username))).Scan(&account.id, &account.email, &account.username, &account.password)
a := &account{}
var highlight, pips, moves int
err = tx.QueryRow(context.Background(), "SELECT id, email, username, password, highlight, pips, moves FROM account WHERE username = $1 OR email = $2", bytes.ToLower(bytes.TrimSpace(username)), bytes.ToLower(bytes.TrimSpace(username))).Scan(&a.id, &a.email, &a.username, &a.password, &highlight, &pips, &moves)
if err != nil {
return nil, nil
} else if len(account.password) == 0 {
} else if len(a.password) == 0 {
return nil, fmt.Errorf("account disabled")
}
a.highlight = highlight == 1
a.pips = pips == 1
a.moves = moves == 1
match, err := argon2id.ComparePasswordAndHash(string(password), string(account.password))
match, err := argon2id.ComparePasswordAndHash(string(password)+passwordSalt, string(a.password))
if err != nil {
return nil, err
} else if !match {
return nil, nil
}
return account, nil
return a, nil
}
func setAccountPassword(passwordSalt string, id int, password string) error {
@ -355,6 +362,31 @@ func setAccountPassword(passwordSalt string, id int, password string) error {
return err
}
func setAccountSetting(id int, name string, value int) error {
if db == nil {
return nil
} else if name == "" {
return fmt.Errorf("no setting name provided")
}
tx, err := begin()
if err != nil {
return err
}
defer tx.Commit(context.Background())
var result int
err = tx.QueryRow(context.Background(), "SELECT COUNT(*) FROM account WHERE id = $1", id).Scan(&result)
if err != nil {
return err
} else if result == 0 {
return nil
}
_, err = tx.Exec(context.Background(), "UPDATE account SET "+name+" = $1 WHERE id = $2", value, id)
return err
}
func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int) error {
if db == nil || g.Started.IsZero() || g.Ended.IsZero() || g.Winner == 0 {
return nil

View file

@ -19,7 +19,7 @@ func testDBConnection() error {
func initDB() {
}
func registerAccount(a *account) error {
func registerAccount(passwordSalt string, a *account) error {
return nil
}
@ -31,7 +31,7 @@ func confirmResetAccount(resetSalt string, passwordSalt string, id int, key stri
return "", nil
}
func loginAccount(username []byte, password []byte) (*account, error) {
func loginAccount(passwordSalt string, username []byte, password []byte) (*account, error) {
return nil, nil
}
@ -39,6 +39,10 @@ func setAccountPassword(passwordSalt string, id int, password string) error {
return nil
}
func setAccountSetting(id int, name string, value int) error {
return nil
}
func recordGameResult(g *bgammon.Game, winType int, account1 int, account2 int) error {
return nil
}

View file

@ -550,13 +550,13 @@ COMMANDS:
cmd.client.Terminate("Failed to register: Invalid username: must contain at least one non-numeric character.")
continue
}
password = bytes.ReplaceAll(password, []byte("_"), []byte(" "))
password = bytes.ReplaceAll(password, []byte(" "), []byte("_"))
a := &account{
email: email,
username: username,
password: append(password, []byte(s.passwordSalt)...),
password: password,
}
err := registerAccount(a)
err := registerAccount(s.passwordSalt, a)
if err != nil {
cmd.client.Terminate(fmt.Sprintf("Failed to register: %s", err))
continue
@ -595,14 +595,14 @@ COMMANDS:
continue
}
if len(params) > 2 {
password = bytes.ReplaceAll(bytes.Join(params[2:], []byte(" ")), []byte("_"), []byte(" "))
password = bytes.ReplaceAll(bytes.Join(params[2:], []byte(" ")), []byte(" "), []byte("_"))
}
s.clientsLock.Unlock()
}
if len(password) > 0 {
a, err := loginAccount(username, append(password, []byte(s.passwordSalt)...))
a, err := loginAccount(s.passwordSalt, username, password)
if err != nil {
cmd.client.Terminate(fmt.Sprintf("Failed to log in: %s", err))
continue
@ -612,6 +612,11 @@ COMMANDS:
}
cmd.client.account = a.id
cmd.client.name = a.username
cmd.client.sendEvent(&bgammon.EventSettings{
Highlight: a.highlight,
Pips: a.pips,
Moves: a.moves,
})
} else {
cmd.client.account = 0
if !randomUsername && !bytes.HasPrefix(username, []byte("BOT_")) && !bytes.HasPrefix(username, []byte("Guest_")) {
@ -1486,7 +1491,7 @@ COMMANDS:
continue
}
a, err := loginAccount(cmd.client.name, append(params[0], []byte(s.passwordSalt)...))
a, err := loginAccount(s.passwordSalt, cmd.client.name, params[0])
if err != nil || a == nil || a.id == 0 {
cmd.client.sendNotice("Failed to change password: incorrect existing password.")
continue
@ -1498,6 +1503,26 @@ COMMANDS:
continue
}
cmd.client.sendNotice("Password changed successfully.")
case bgammon.CommandSet:
if cmd.client.account == 0 {
continue
} else if len(params) < 2 {
cmd.client.sendNotice("Please specify the setting name and value as follows: set <name> <value>")
continue
}
name := string(bytes.ToLower(params[0]))
if name != "highlight" && name != "pips" && name != "moves" {
cmd.client.sendNotice("Please specify the setting name and value as follows: set <name> <value>")
continue
}
value, err := strconv.Atoi(string(params[1]))
if err != nil || value < 0 {
cmd.client.sendNotice("Invalid setting value provided.")
continue
}
_ = setAccountSetting(cmd.client.account, name, value)
case bgammon.CommandDisconnect:
if clientGame != nil {
clientGame.removeClient(cmd.client)