diff --git a/doc/soju.1.scd b/doc/soju.1.scd index 93f8ff4..a756302 100644 --- a/doc/soju.1.scd +++ b/doc/soju.1.scd @@ -138,6 +138,12 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just Connect with the specified nickname. By default, the account's username is used. +*network delete* + Disconnect and delete a network. + +*network status* + Show a list of saved networks and their current status. + *certfp generate* *[options...]* Generate self-signed certificate and use it for authentication. @@ -159,15 +165,12 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just *certfp reset* Disable SASL EXTERNAL authentication and remove stored certificate. +*user create* -username -password [-admin] + Create a new soju user. Only admin users can create new accounts. + *change-password* Change current user password. -*network delete* - Disconnect and delete a network. - -*network status* - Show a list of saved networks and their current status. - # AUTHORS Maintained by Simon Ser , who is assisted by other diff --git a/server.go b/server.go index 6636757..8fb7874 100644 --- a/server.go +++ b/server.go @@ -88,6 +88,26 @@ func (s *Server) Run() error { select {} } +func (s *Server) createUser(user *User) (*user, error) { + s.lock.Lock() + defer s.lock.Unlock() + + if _, ok := s.users[user.Username]; ok { + return nil, fmt.Errorf("user %q already exists", user.Username) + } + + err := s.db.StoreUser(user) + if err != nil { + return nil, fmt.Errorf("could not create user in db: %v", err) + } + + s.Logger.Printf("starting bouncer for new user %q", user.Username) + u := newUser(s, user) + s.users[u.Username] = u + go u.run() + return u, nil +} + func (s *Server) getUser(name string) *user { s.lock.Lock() u := s.users[name] diff --git a/service.go b/service.go index 9894668..ef708e1 100644 --- a/service.go +++ b/service.go @@ -162,6 +162,17 @@ func init() { }, }, }, + "user": { + children: serviceCommandSet{ + "create": { + usage: "-username -password [-admin]", + desc: "create a new soju user", + handle: handleUserCreate, + admin: true, + }, + }, + admin: true, + }, "change-password": { usage: "", desc: "change your password", @@ -567,3 +578,37 @@ func handlePasswordChange(dc *downstreamConn, params []string) error { sendServicePRIVMSG(dc, "password updated") return nil } + +func handleUserCreate(dc *downstreamConn, params []string) error { + fs := newFlagSet() + username := fs.String("username", "", "") + password := fs.String("password", "", "") + admin := fs.Bool("admin", false, "") + + if err := fs.Parse(params); err != nil { + return err + } + if *username == "" { + return fmt.Errorf("flag -username is required") + } + if *password == "" { + return fmt.Errorf("flag -password is required") + } + + hashed, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("failed to hash password: %v", err) + } + + user := &User{ + Username: *username, + Password: string(hashed), + Admin: *admin, + } + if _, err := dc.srv.createUser(user); err != nil { + return fmt.Errorf("could not create user: %v", err) + } + + sendServicePRIVMSG(dc, fmt.Sprintf("created user %q", *username)) + return nil +}