service: allow updating other users

This commit is contained in:
Simon Ser 2021-10-12 09:11:14 +02:00
parent 96d4111be2
commit 4e9ddf78ab
3 changed files with 99 additions and 16 deletions

View File

@ -322,16 +322,24 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just
Other options are: Other options are:
*-admin* *-admin* true|false
Make the new user an administrator. Make the new user an administrator.
*-realname* <realname> *-realname* <realname>
Set the user's realname. This is used as a fallback if there is no Set the user's realname. This is used as a fallback if there is no
realname set for a network. realname set for a network.
*user update* [-password <password>] [-realname <realname>] *user update* [username] [options...]
Update the current user. The options are the same as the _user create_ Update a user. The options are the same as the _user create_ command.
command.
If _username_ is omitted, the current user is updated. Only admins can
update other users.
Not all flags are valid in all contexts:
- The _-username_ flag is never valid, usernames are immutable.
- The _-realname_ flag is only valid when updating the current user.
- The _-admin_ flag is only valid when updating another user.
*user delete* <username> *user delete* <username>
Delete a soju user. Only admins can delete accounts. Delete a soju user. Only admins can delete accounts.

View File

@ -125,7 +125,7 @@ func handleServicePRIVMSG(dc *downstreamConn, text string) {
return return
} }
if cmd.admin && !dc.user.Admin { if cmd.admin && !dc.user.Admin {
sendServicePRIVMSG(dc, fmt.Sprintf(`error: you must be an admin to use this command`)) sendServicePRIVMSG(dc, "error: you must be an admin to use this command")
return return
} }
@ -766,35 +766,84 @@ func handleUserCreate(dc *downstreamConn, params []string) error {
return nil return nil
} }
func popArg(params []string) (string, []string) {
if len(params) > 0 && !strings.HasPrefix(params[0], "-") {
return params[0], params[1:]
}
return "", params
}
func handleUserUpdate(dc *downstreamConn, params []string) error { func handleUserUpdate(dc *downstreamConn, params []string) error {
var password, realname *string var password, realname *string
var admin *bool
fs := newFlagSet() fs := newFlagSet()
fs.Var(stringPtrFlag{&password}, "password", "") fs.Var(stringPtrFlag{&password}, "password", "")
fs.Var(stringPtrFlag{&realname}, "realname", "") fs.Var(stringPtrFlag{&realname}, "realname", "")
fs.Var(boolPtrFlag{&admin}, "admin", "")
username, params := popArg(params)
if err := fs.Parse(params); err != nil { if err := fs.Parse(params); err != nil {
return err return err
} }
if len(fs.Args()) > 0 {
return fmt.Errorf("unexpected argument")
}
// copy the user record because we'll mutate it var hashed *string
record := dc.user.User
if password != nil { if password != nil {
hashed, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost) hashedBytes, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return fmt.Errorf("failed to hash password: %v", err) return fmt.Errorf("failed to hash password: %v", err)
} }
record.Password = string(hashed) hashedStr := string(hashedBytes)
} hashed = &hashedStr
if realname != nil {
record.Realname = *realname
} }
if err := dc.user.updateUser(&record); err != nil { if username != "" && username != dc.user.Username {
return err if !dc.user.Admin {
return fmt.Errorf("you must be an admin to update other users")
}
if realname != nil {
return fmt.Errorf("cannot update -realname of other user")
}
u := dc.srv.getUser(username)
if u == nil {
return fmt.Errorf("unknown username %q", username)
}
done := make(chan error, 1)
u.events <- eventUserUpdate{
password: hashed,
admin: admin,
done: done,
}
if err := <-done; err != nil {
return err
}
sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", username))
} else {
// copy the user record because we'll mutate it
record := dc.user.User
if hashed != nil {
record.Password = *hashed
}
if realname != nil {
record.Realname = *realname
}
if admin != nil {
return fmt.Errorf("cannot update -admin of own user")
}
if err := dc.user.updateUser(&record); err != nil {
return err
}
sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
} }
sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
return nil return nil
} }

26
user.go
View File

@ -59,6 +59,12 @@ type eventBroadcast struct {
type eventStop struct{} type eventStop struct{}
type eventUserUpdate struct {
password *string
admin *bool
done chan error
}
type deliveredClientMap map[string]string // client name -> msg ID type deliveredClientMap map[string]string // client name -> msg ID
type deliveredStore struct { type deliveredStore struct {
@ -642,6 +648,26 @@ func (u *user) run() {
u.forEachDownstream(func(dc *downstreamConn) { u.forEachDownstream(func(dc *downstreamConn) {
dc.SendMessage(msg) dc.SendMessage(msg)
}) })
case eventUserUpdate:
// copy the user record because we'll mutate it
record := u.User
if e.password != nil {
record.Password = *e.password
}
if e.admin != nil {
record.Admin = *e.admin
}
e.done <- u.updateUser(&record)
// If the password was updated, kill all downstream connections to
// force them to re-authenticate with the new credentials.
if e.password != nil {
u.forEachDownstream(func(dc *downstreamConn) {
dc.Close()
})
}
case eventStop: case eventStop:
u.forEachDownstream(func(dc *downstreamConn) { u.forEachDownstream(func(dc *downstreamConn) {
dc.Close() dc.Close()