Add per-user realname setting
This allows users to set a default realname used if the per-network realname isn't set. A new "user update" command is introduced and can be extended to edit other user properties and other users in the future.
This commit is contained in:
parent
9a53d4cd08
commit
a14f646135
6
db.go
6
db.go
@ -30,6 +30,7 @@ type User struct {
|
|||||||
ID int64
|
ID int64
|
||||||
Username string
|
Username string
|
||||||
Password string // hashed
|
Password string // hashed
|
||||||
|
Realname string
|
||||||
Admin bool
|
Admin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,10 +93,13 @@ func (net *Network) GetUsername() string {
|
|||||||
return net.Nick
|
return net.Nick
|
||||||
}
|
}
|
||||||
|
|
||||||
func (net *Network) GetRealname() string {
|
func GetRealname(user *User, net *Network) string {
|
||||||
if net.Realname != "" {
|
if net.Realname != "" {
|
||||||
return net.Realname
|
return net.Realname
|
||||||
}
|
}
|
||||||
|
if user.Realname != "" {
|
||||||
|
return user.Realname
|
||||||
|
}
|
||||||
return net.Nick
|
return net.Nick
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
db_sqlite.go
20
db_sqlite.go
@ -16,7 +16,8 @@ CREATE TABLE User (
|
|||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
username VARCHAR(255) NOT NULL UNIQUE,
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
password VARCHAR(255),
|
password VARCHAR(255),
|
||||||
admin INTEGER NOT NULL DEFAULT 0
|
admin INTEGER NOT NULL DEFAULT 0,
|
||||||
|
realname VARCHAR(255)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE Network (
|
CREATE TABLE Network (
|
||||||
@ -133,6 +134,7 @@ var sqliteMigrations = []string{
|
|||||||
`,
|
`,
|
||||||
"ALTER TABLE Channel ADD COLUMN detached_internal_msgid VARCHAR(255)",
|
"ALTER TABLE Channel ADD COLUMN detached_internal_msgid VARCHAR(255)",
|
||||||
"ALTER TABLE Network ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1",
|
"ALTER TABLE Network ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1",
|
||||||
|
"ALTER TABLE User ADD COLUMN realname VARCHAR(255)",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SqliteDB struct {
|
type SqliteDB struct {
|
||||||
@ -242,12 +244,13 @@ func (db *SqliteDB) GetUser(username string) (*User, error) {
|
|||||||
|
|
||||||
user := &User{Username: username}
|
user := &User{Username: username}
|
||||||
|
|
||||||
var password sql.NullString
|
var password, realname sql.NullString
|
||||||
row := db.db.QueryRow("SELECT id, password, admin FROM User WHERE username = ?", username)
|
row := db.db.QueryRow("SELECT id, password, admin, realname FROM User WHERE username = ?", username)
|
||||||
if err := row.Scan(&user.ID, &password, &user.Admin); err != nil {
|
if err := row.Scan(&user.ID, &password, &user.Admin, &realname); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user.Password = password.String
|
user.Password = password.String
|
||||||
|
user.Realname = realname.String
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,15 +259,16 @@ func (db *SqliteDB) StoreUser(user *User) error {
|
|||||||
defer db.lock.Unlock()
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
password := toNullString(user.Password)
|
password := toNullString(user.Password)
|
||||||
|
realname := toNullString(user.Realname)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if user.ID != 0 {
|
if user.ID != 0 {
|
||||||
_, err = db.db.Exec("UPDATE User SET password = ?, admin = ? WHERE username = ?",
|
_, err = db.db.Exec("UPDATE User SET password = ?, admin = ?, realname = ? WHERE username = ?",
|
||||||
password, user.Admin, user.Username)
|
password, user.Admin, realname, user.Username)
|
||||||
} else {
|
} else {
|
||||||
var res sql.Result
|
var res sql.Result
|
||||||
res, err = db.db.Exec("INSERT INTO User(username, password, admin) VALUES (?, ?, ?)",
|
res, err = db.db.Exec("INSERT INTO User(username, password, admin, realname) VALUES (?, ?, ?, ?)",
|
||||||
user.Username, password, user.Admin)
|
user.Username, password, user.Admin, realname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,8 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just
|
|||||||
Connect with the specified server password.
|
Connect with the specified server password.
|
||||||
|
|
||||||
*-realname* <realname>
|
*-realname* <realname>
|
||||||
Connect with the specified real name. By default, the nickname is used.
|
Connect with the specified real name. By default, the account's realname
|
||||||
|
is used if set, otherwise the network's nickname is used.
|
||||||
|
|
||||||
*-nick* <nickname>
|
*-nick* <nickname>
|
||||||
Connect with the specified nickname. By default, the account's username
|
Connect with the specified nickname. By default, the account's username
|
||||||
@ -296,9 +297,21 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just
|
|||||||
*sasl reset* <network name>
|
*sasl reset* <network name>
|
||||||
Disable SASL authentication and remove stored credentials.
|
Disable SASL authentication and remove stored credentials.
|
||||||
|
|
||||||
*user create* -username <username> -password <password> [-admin]
|
*user create* -username <username> -password <password> [options...]
|
||||||
Create a new soju user. Only admin users can create new accounts.
|
Create a new soju user. Only admin users can create new accounts.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
*-admin*
|
||||||
|
Make the new user an administrator.
|
||||||
|
|
||||||
|
*-realname* <realname>
|
||||||
|
Set the user's realname. This is used as a fallback if there is no
|
||||||
|
realname set for a network.
|
||||||
|
|
||||||
|
*user update* [-realname <realname>]
|
||||||
|
Update the current 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.
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ func getNetworkAttrs(network *network) irc.Tags {
|
|||||||
if network.Username != "" {
|
if network.Username != "" {
|
||||||
attrs["username"] = irc.TagValue(network.Username)
|
attrs["username"] = irc.TagValue(network.Username)
|
||||||
}
|
}
|
||||||
if network.Realname != "" {
|
if realname := GetRealname(&network.user.User, &network.Network); realname != "" {
|
||||||
attrs["realname"] = irc.TagValue(network.Realname)
|
attrs["realname"] = irc.TagValue(realname)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := network.URL(); err == nil {
|
if u, err := network.URL(); err == nil {
|
||||||
@ -1387,6 +1387,13 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the client just resets to the default, just wipe the per-network
|
||||||
|
// preference
|
||||||
|
storeRealname := realname
|
||||||
|
if realname == dc.user.Realname {
|
||||||
|
storeRealname = ""
|
||||||
|
}
|
||||||
|
|
||||||
var storeErr error
|
var storeErr error
|
||||||
var needUpdate []Network
|
var needUpdate []Network
|
||||||
dc.forEachNetwork(func(n *network) {
|
dc.forEachNetwork(func(n *network) {
|
||||||
@ -1398,7 +1405,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
Params: []string{realname},
|
Params: []string{realname},
|
||||||
})
|
})
|
||||||
|
|
||||||
n.Realname = realname
|
n.Realname = storeRealname
|
||||||
if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
|
if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
|
||||||
dc.logger.Printf("failed to store network realname: %v", err)
|
dc.logger.Printf("failed to store network realname: %v", err)
|
||||||
storeErr = err
|
storeErr = err
|
||||||
@ -1407,7 +1414,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record := n.Network // copy network record because we'll mutate it
|
record := n.Network // copy network record because we'll mutate it
|
||||||
record.Realname = realname
|
record.Realname = storeRealname
|
||||||
needUpdate = append(needUpdate, record)
|
needUpdate = append(needUpdate, record)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2223,6 +2230,10 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
realname, _ := attrs.GetTag("realname")
|
realname, _ := attrs.GetTag("realname")
|
||||||
pass, _ := attrs.GetTag("pass")
|
pass, _ := attrs.GetTag("pass")
|
||||||
|
|
||||||
|
if realname == dc.user.Realname {
|
||||||
|
realname = ""
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: reject unknown attributes
|
// TODO: reject unknown attributes
|
||||||
|
|
||||||
record := &Network{
|
record := &Network{
|
||||||
|
26
service.go
26
service.go
@ -254,11 +254,16 @@ func init() {
|
|||||||
"user": {
|
"user": {
|
||||||
children: serviceCommandSet{
|
children: serviceCommandSet{
|
||||||
"create": {
|
"create": {
|
||||||
usage: "-username <username> -password <password> [-admin]",
|
usage: "-username <username> -password <password> [-realname <realname>] [-admin]",
|
||||||
desc: "create a new soju user",
|
desc: "create a new soju user",
|
||||||
handle: handleUserCreate,
|
handle: handleUserCreate,
|
||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
|
"update": {
|
||||||
|
usage: "[-realname <realname>]",
|
||||||
|
desc: "update the current user",
|
||||||
|
handle: handleUserUpdate,
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
usage: "<username>",
|
usage: "<username>",
|
||||||
desc: "delete a user",
|
desc: "delete a user",
|
||||||
@ -266,7 +271,6 @@ func init() {
|
|||||||
admin: true,
|
admin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
admin: true,
|
|
||||||
},
|
},
|
||||||
"change-password": {
|
"change-password": {
|
||||||
usage: "<new password>",
|
usage: "<new password>",
|
||||||
@ -751,6 +755,7 @@ func handleUserCreate(dc *downstreamConn, params []string) error {
|
|||||||
fs := newFlagSet()
|
fs := newFlagSet()
|
||||||
username := fs.String("username", "", "")
|
username := fs.String("username", "", "")
|
||||||
password := fs.String("password", "", "")
|
password := fs.String("password", "", "")
|
||||||
|
realname := fs.String("realname", "", "")
|
||||||
admin := fs.Bool("admin", false, "")
|
admin := fs.Bool("admin", false, "")
|
||||||
|
|
||||||
if err := fs.Parse(params); err != nil {
|
if err := fs.Parse(params); err != nil {
|
||||||
@ -771,6 +776,7 @@ func handleUserCreate(dc *downstreamConn, params []string) error {
|
|||||||
user := &User{
|
user := &User{
|
||||||
Username: *username,
|
Username: *username,
|
||||||
Password: string(hashed),
|
Password: string(hashed),
|
||||||
|
Realname: *realname,
|
||||||
Admin: *admin,
|
Admin: *admin,
|
||||||
}
|
}
|
||||||
if _, err := dc.srv.createUser(user); err != nil {
|
if _, err := dc.srv.createUser(user); err != nil {
|
||||||
@ -781,6 +787,22 @@ func handleUserCreate(dc *downstreamConn, params []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleUserUpdate(dc *downstreamConn, params []string) error {
|
||||||
|
fs := newFlagSet()
|
||||||
|
realname := fs.String("realname", "", "")
|
||||||
|
|
||||||
|
if err := fs.Parse(params); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dc.user.updateRealname(*realname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sendServicePRIVMSG(dc, fmt.Sprintf("updated user %q", dc.user.Username))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleUserDelete(dc *downstreamConn, params []string) error {
|
func handleUserDelete(dc *downstreamConn, params []string) error {
|
||||||
if len(params) != 1 {
|
if len(params) != 1 {
|
||||||
return fmt.Errorf("expected exactly one argument")
|
return fmt.Errorf("expected exactly one argument")
|
||||||
|
@ -1671,7 +1671,7 @@ func (uc *upstreamConn) register() {
|
|||||||
uc.nick = uc.network.Nick
|
uc.nick = uc.network.Nick
|
||||||
uc.nickCM = uc.network.casemap(uc.nick)
|
uc.nickCM = uc.network.casemap(uc.nick)
|
||||||
uc.username = uc.network.GetUsername()
|
uc.username = uc.network.GetUsername()
|
||||||
uc.realname = uc.network.GetRealname()
|
uc.realname = GetRealname(&uc.user.User, &uc.network.Network)
|
||||||
|
|
||||||
uc.SendMessage(&irc.Message{
|
uc.SendMessage(&irc.Message{
|
||||||
Command: "CAP",
|
Command: "CAP",
|
||||||
|
30
user.go
30
user.go
@ -763,6 +763,12 @@ func (u *user) updateNetwork(record *Network) (*network, error) {
|
|||||||
panic("tried updating a new network")
|
panic("tried updating a new network")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the realname is reset to the default, just wipe the per-network
|
||||||
|
// setting
|
||||||
|
if record.Realname == u.Realname {
|
||||||
|
record.Realname = ""
|
||||||
|
}
|
||||||
|
|
||||||
if err := u.checkNetwork(record); err != nil {
|
if err := u.checkNetwork(record); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -855,6 +861,30 @@ func (u *user) updatePassword(hashed string) error {
|
|||||||
return u.srv.db.StoreUser(&u.User)
|
return u.srv.db.StoreUser(&u.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *user) updateRealname(realname string) error {
|
||||||
|
u.User.Realname = realname
|
||||||
|
if err := u.srv.db.StoreUser(&u.User); err != nil {
|
||||||
|
return fmt.Errorf("failed to update user %q: %v", u.Username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-connect to networks which use the default realname
|
||||||
|
var needUpdate []Network
|
||||||
|
u.forEachNetwork(func(net *network) {
|
||||||
|
if net.Realname == "" {
|
||||||
|
needUpdate = append(needUpdate, net.Network)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var netErr error
|
||||||
|
for _, net := range needUpdate {
|
||||||
|
if _, err := u.updateNetwork(&net); err != nil {
|
||||||
|
netErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netErr
|
||||||
|
}
|
||||||
|
|
||||||
func (u *user) stop() {
|
func (u *user) stop() {
|
||||||
u.events <- eventStop{}
|
u.events <- eventStop{}
|
||||||
<-u.done
|
<-u.done
|
||||||
|
Loading…
Reference in New Issue
Block a user