package cmd import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" "heckel.io/ntfy/v2/server" "heckel.io/ntfy/v2/test" "heckel.io/ntfy/v2/user" ) func TestCLI_User_Add(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) require.Contains(t, stdout.String(), "user phil added with role user") } func TestCLI_User_Add_Exists(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) require.Contains(t, stdout.String(), "user phil added with role user") app, stdin, _, _ = newTestApp() stdin.WriteString("mypass\nmypass") err := runUserCommand(app, conf, "add", "phil") require.Error(t, err) require.Contains(t, err.Error(), "user phil already exists") } func TestCLI_User_Add_Admin(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "--role=admin", "phil")) require.Contains(t, stdout.String(), "user phil added with role admin") } func TestCLI_User_Add_Password_Mismatch(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, _, _ := newTestApp() stdin.WriteString("mypass\nNOTMATCH") err := runUserCommand(app, conf, "add", "phil") require.Error(t, err) require.Contains(t, err.Error(), "passwords do not match: try it again, but this time type slooowwwlly") } func TestCLI_User_ChangePass(t *testing.T) { s, conf, port := newTestServerWithAuth(t) conf.AuthUsers = []*user.User{ {Name: "philuser", Hash: "$2a$10$U4WSIYY6evyGmZaraavM2e2JeVG6EMGUKN1uUwufUeeRd4Jpg6cGC", Role: user.RoleUser}, // philuser:philpass } defer test.StopServer(t, s, port) // Add user app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) require.Contains(t, stdout.String(), "user phil added with role user") // Change pass app, stdin, stdout, _ = newTestApp() stdin.WriteString("newpass\nnewpass") require.Nil(t, runUserCommand(app, conf, "change-pass", "phil")) require.Contains(t, stdout.String(), "changed password for user phil") // Cannot change provisioned user's pass app, stdin, _, _ = newTestApp() stdin.WriteString("newpass\nnewpass") require.Error(t, runUserCommand(app, conf, "change-pass", "philuser")) } func TestCLI_User_ChangeRole(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) // Add user app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) require.Contains(t, stdout.String(), "user phil added with role user") // Change role app, _, stdout, _ = newTestApp() require.Nil(t, runUserCommand(app, conf, "change-role", "phil", "admin")) require.Contains(t, stdout.String(), "changed role for user phil to admin") } func TestCLI_User_Delete(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) // Add user app, stdin, stdout, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) require.Contains(t, stdout.String(), "user phil added with role user") // Delete user app, _, stdout, _ = newTestApp() require.Nil(t, runUserCommand(app, conf, "del", "phil")) require.Contains(t, stdout.String(), "user phil removed") // Delete user again (does not exist) app, _, _, _ = newTestApp() err := runUserCommand(app, conf, "del", "phil") require.Error(t, err) require.Contains(t, err.Error(), "user phil does not exist") } func TestCLI_User_ResetPass(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, _, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) // Prints a working-looking reset link when base-url is set app, _, stdout, _ := newTestApp() require.Nil(t, runUserCommand(app, conf, "--base-url=https://ntfy.example.com", "reset-pass", "phil")) require.Contains(t, stdout.String(), "https://ntfy.example.com/account/password/reset/") } func TestCLI_User_ResetPass_NoBaseURL(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, _, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) app, _, _, _ = newTestApp() err := runUserCommand(app, conf, "reset-pass", "phil") require.Error(t, err) require.Contains(t, err.Error(), "base-url") } func TestCLI_User_ResetPass_SendEmailNoPrimary(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) app, stdin, _, _ := newTestApp() stdin.WriteString("mypass\nmypass") require.Nil(t, runUserCommand(app, conf, "add", "phil")) // --send-email requires a primary email; phil has none app, _, _, _ = newTestApp() err := runUserCommand(app, conf, "--base-url=https://ntfy.example.com", "reset-pass", "--send-email", "phil") require.Error(t, err) require.Contains(t, err.Error(), "no primary email") } func TestCLI_User_ResetPass_ProvisionedRejected(t *testing.T) { s, conf, port := newTestServerWithAuth(t) defer test.StopServer(t, s, port) // Seed a provisioned user into the auth database via config provisioning m, err := user.NewSQLiteManager(conf.AuthFile, "", &user.Config{ ProvisionEnabled: true, Users: []*user.User{ {Name: "provuser", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: user.RoleUser}, }, }) require.Nil(t, err) require.Nil(t, m.Close()) app, _, _, _ := newTestApp() err = runUserCommand(app, conf, "--base-url=https://ntfy.example.com", "reset-pass", "provuser") require.Error(t, err) require.Contains(t, err.Error(), "provisioned") } func newTestServerWithAuth(t *testing.T) (s *server.Server, conf *server.Config, port int) { configFile := filepath.Join(t.TempDir(), "server-dummy.yml") require.Nil(t, os.WriteFile(configFile, []byte(""), 0600)) // Dummy config file to avoid lookup of real server.yml conf = server.NewConfig() conf.File = configFile conf.AuthFile = filepath.Join(t.TempDir(), "user.db") conf.AuthDefault = user.PermissionDenyAll conf.AuthAccessCacheEnabled = false s, port = test.StartServerWithConfig(t, conf) return } func runUserCommand(app *cli.App, conf *server.Config, args ...string) error { userArgs := []string{ "ntfy", "--log-level=ERROR", "user", "--config=" + conf.File, // Dummy config file to avoid lookups of real file "--auth-file=" + conf.AuthFile, "--auth-default-access=" + conf.AuthDefault.String(), } return app.Run(append(userArgs, args...)) }