diff --git a/internal/commands/users.go b/internal/commands/users.go index 0a8b88218..b5f331b73 100644 --- a/internal/commands/users.go +++ b/internal/commands/users.go @@ -29,6 +29,7 @@ var UsersCommand = cli.Command{ UsersShowCommand, UsersModCommand, UsersRemoveCommand, + UsersResetCommand, }, } diff --git a/internal/commands/users_ls.go b/internal/commands/users_list.go similarity index 90% rename from internal/commands/users_ls.go rename to internal/commands/users_list.go index 35ac8df67..9b7b3cff9 100644 --- a/internal/commands/users_ls.go +++ b/internal/commands/users_list.go @@ -40,8 +40,8 @@ func usersListAction(ctx *cli.Context) error { user.Email(), user.AclRole().String(), report.Bool(user.SuperAdmin, report.Yes, report.No), - report.Bool(user.CanLogin, report.Enabled, report.Disabled), - report.Bool(user.CanSync, report.Enabled, report.Disabled), + report.Bool(user.LoginAllowed(), report.Enabled, report.Disabled), + report.Bool(user.SyncAllowed(), report.Enabled, report.Disabled), user.Attr(), } } diff --git a/internal/commands/users_rm.go b/internal/commands/users_remove.go similarity index 100% rename from internal/commands/users_rm.go rename to internal/commands/users_remove.go diff --git a/internal/commands/users_reset.go b/internal/commands/users_reset.go new file mode 100644 index 000000000..9dfffcc75 --- /dev/null +++ b/internal/commands/users_reset.go @@ -0,0 +1,84 @@ +package commands + +import ( + "fmt" + + "github.com/manifoldco/promptui" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/internal/entity" +) + +// UsersResetCommand configures the command name, flags, and action. +var UsersResetCommand = cli.Command{ + Name: "reset", + Usage: "Resets the user database to a clean state", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "trace, t", + Usage: "show trace logs for debugging", + }, + cli.BoolFlag{ + Name: "yes, y", + Usage: "assume \"yes\" and run non-interactively", + }, + }, + Action: usersResetAction, +} + +// usersResetAction deletes recreates the user management database tables. +func usersResetAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + confirmed := ctx.Bool("yes") + + // Show prompt? + if !confirmed { + actionPrompt := promptui.Prompt{ + Label: fmt.Sprintf("Reset the user database to a clean state?"), + IsConfirm: true, + } + + if _, err := actionPrompt.Run(); err != nil { + return nil + } + } + + if ctx.Bool("trace") { + log.SetLevel(logrus.TraceLevel) + log.Infoln("reset: enabled trace mode") + } + + db := conf.Db() + + // Drop existing user management tables. + if err := db.DropTableIfExists(entity.User{}, entity.UserDetails{}, entity.UserSettings{}, entity.UserShare{}).Error; err != nil { + return err + } + + // Re-create auth_users. + if err := db.CreateTable(entity.User{}).Error; err != nil { + return err + } + + // Re-create auth_users_details. + if err := db.CreateTable(entity.UserDetails{}).Error; err != nil { + return err + } + + // Re-create auth_users_settings. + if err := db.CreateTable(entity.UserSettings{}).Error; err != nil { + return err + } + + // Re-create auth_users_shares. + if err := db.CreateTable(entity.UserShare{}).Error; err != nil { + return err + } + + log.Infof("the user database has been recreated and is now in a clean state") + + return nil + }) +} diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index e97809d36..8dd45515c 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -298,7 +298,7 @@ func (m *User) Disabled() bool { // LoginAllowed checks if the user is allowed to log in and use the web UI. func (m *User) LoginAllowed() bool { - if role := m.AclRole(); m.Disabled() || !m.CanLogin || m.UserName == "" || role == acl.RoleUnauthorized { + if role := m.AclRole(); m.Disabled() || !m.CanLogin || m.ID <= 0 || m.UserName == "" || role == acl.RoleUnauthorized { return false } else { return acl.Resources.Allow(acl.ResourceConfig, role, acl.AccessOwn) @@ -308,7 +308,7 @@ func (m *User) LoginAllowed() bool { // SyncAllowed checks whether the user is allowed to use WebDAV to synchronize files. func (m *User) SyncAllowed() bool { - if role := m.AclRole(); m.Disabled() || !m.CanSync || m.UserName == "" || role == acl.RoleUnauthorized { + if role := m.AclRole(); m.Disabled() || !m.CanSync || m.ID <= 0 || m.UserName == "" || role == acl.RoleUnauthorized { return false } else { return acl.Resources.Allow(acl.ResourcePhotos, role, acl.ActionUpload)