Support changing account settings
This commit is contained in:
parent
d8ad756a5c
commit
cfb17add3b
8 changed files with 104 additions and 23 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
9
event.go
9
event.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue