M doc/soju.1.scd => doc/soju.1.scd +9 -6
@@ 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* <name>
+ Disconnect and delete a network.
+
+*network status*
+ Show a list of saved networks and their current status.
+
*certfp generate* *[options...]* <network name>
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* <network name>
Disable SASL EXTERNAL authentication and remove stored certificate.
+*user create* -username <username> -password <password> [-admin]
+ Create a new soju user. Only admin users can create new accounts.
+
*change-password* <new password>
Change current user password.
-*network delete* <name>
- Disconnect and delete a network.
-
-*network status*
- Show a list of saved networks and their current status.
-
# AUTHORS
Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other
M server.go => server.go +20 -0
@@ 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]
M service.go => service.go +45 -0
@@ 162,6 162,17 @@ func init() {
},
},
},
+ "user": {
+ children: serviceCommandSet{
+ "create": {
+ usage: "-username <username> -password <password> [-admin]",
+ desc: "create a new soju user",
+ handle: handleUserCreate,
+ admin: true,
+ },
+ },
+ admin: true,
+ },
"change-password": {
usage: "<new password>",
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
+}