|
@@ -78,7 +78,7 @@ func printBoxed(content string) {
|
|
|
|
|
|
|
|
// Add padding
|
|
// Add padding
|
|
|
contentWidth := maxWidth + 2
|
|
contentWidth := maxWidth + 2
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
fmt.Println() // Start new line before box
|
|
fmt.Println() // Start new line before box
|
|
|
// Top Border
|
|
// Top Border
|
|
|
fmt.Printf("%s╭%s╮%s\n", ColorDim, strings.Repeat("─", contentWidth), ColorReset)
|
|
fmt.Printf("%s╭%s╮%s\n", ColorDim, strings.Repeat("─", contentWidth), ColorReset)
|
|
@@ -340,121 +340,377 @@ func (c *CLI) registerDefaultCommands() {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- c.RegisterCommand("login", "Login to system (login <user> [code])", func(parts []string, server *KVServer) {
|
|
|
|
|
|
|
+ // User Command Group
|
|
|
|
|
+ c.RegisterCommand("user", "User management commands (type 'user help' for more)", func(parts []string, server *KVServer) {
|
|
|
if len(parts) < 2 {
|
|
if len(parts) < 2 {
|
|
|
- printBoxed("Usage: login <username> [mfa_code]")
|
|
|
|
|
|
|
+ printBoxed("Usage: user <subcommand> [args...]\nType 'user help' for list of subcommands.")
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- username := parts[1]
|
|
|
|
|
- code := ""
|
|
|
|
|
- if len(parts) > 2 {
|
|
|
|
|
- code = parts[2]
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Prompt for password
|
|
|
|
|
- fmt.Print("Password: ")
|
|
|
|
|
- password, err := readPassword()
|
|
|
|
|
- fmt.Println() // Newline after input
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("Error reading password: %v", err))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- token, err := server.AuthManager.Login(username, password, code, "cli")
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("%sLogin Failed:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
- } else {
|
|
|
|
|
- c.mu.Lock()
|
|
|
|
|
- c.token = token
|
|
|
|
|
- c.mu.Unlock()
|
|
|
|
|
- printBoxed(fmt.Sprintf("%sLogin Success! Token saved.%s", ColorGreen, ColorReset))
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- c.RegisterCommand("logout", "Logout from system", func(parts []string, server *KVServer) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ subCmd := strings.ToLower(parts[1])
|
|
|
|
|
+ args := parts[2:]
|
|
|
|
|
+
|
|
|
|
|
+ // Common vars
|
|
|
c.mu.RLock()
|
|
c.mu.RLock()
|
|
|
token := c.token
|
|
token := c.token
|
|
|
c.mu.RUnlock()
|
|
c.mu.RUnlock()
|
|
|
-
|
|
|
|
|
- if token != "" {
|
|
|
|
|
- if err := server.Logout(token); err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("%sError logging out:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
|
|
+
|
|
|
|
|
+ switch subCmd {
|
|
|
|
|
+ case "init": // Was auth-init
|
|
|
|
|
+ if server.AuthManager.IsEnabled() && !server.IsRoot(token) {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sPermission Denied: Auth already enabled. Login as root to re-init.%s", ColorRed, ColorReset))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Print("Enter root password: ")
|
|
|
|
|
+ password, err := readPassword()
|
|
|
|
|
+ fmt.Println()
|
|
|
|
|
+ if err != nil || password == "" {
|
|
|
|
|
+ printBoxed("Error reading password")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.AuthManager.CreateRootUser(password); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("Failed to create root user: %v", err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.AuthManager.EnableAuth(); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("Failed to enable auth: %v", err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ printBoxed("Auth Initialized. Please login as 'root'.")
|
|
|
|
|
+
|
|
|
|
|
+ case "login": // Was login
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user login <username> [code]")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ username := args[0]
|
|
|
|
|
+ code := ""
|
|
|
|
|
+ if len(args) > 1 {
|
|
|
|
|
+ code = args[1]
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Print("Password: ")
|
|
|
|
|
+ password, err := readPassword()
|
|
|
|
|
+ fmt.Println()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ printBoxed("Error reading password")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ t, err := server.AuthManager.Login(username, password, code, "cli")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sLogin Failed:%s %v", ColorRed, ColorReset, err))
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ c.mu.Lock()
|
|
|
|
|
+ c.token = t
|
|
|
|
|
+ c.mu.Unlock()
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sLogin Success!%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "logout": // Was logout
|
|
|
|
|
+ if token != "" {
|
|
|
|
|
+ server.Logout(token)
|
|
|
|
|
+ c.mu.Lock()
|
|
|
|
|
+ c.token = ""
|
|
|
|
|
+ c.mu.Unlock()
|
|
|
printBoxed("Logged out.")
|
|
printBoxed("Logged out.")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed("Not logged in.")
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- printBoxed("Not logged in.")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- c.mu.Lock()
|
|
|
|
|
- c.token = ""
|
|
|
|
|
- c.mu.Unlock()
|
|
|
|
|
- })
|
|
|
|
|
|
|
|
|
|
- c.RegisterCommand("auth-init", "Initialize Auth System (auth-init)", func(parts []string, server *KVServer) {
|
|
|
|
|
- if len(parts) > 1 {
|
|
|
|
|
- printBoxed("Usage: auth-init (prompts for password securely)")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // If auth enabled, require root
|
|
|
|
|
- c.mu.RLock()
|
|
|
|
|
- token := c.token
|
|
|
|
|
- c.mu.RUnlock()
|
|
|
|
|
|
|
+ case "who": // Was whoami
|
|
|
|
|
+ if token == "" {
|
|
|
|
|
+ printBoxed("Not logged in.")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ session, err := server.GetSessionInfo(token)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("Error: %v", err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ printBoxed(fmt.Sprintf("Logged in as: %s%s%s", ColorCyan, session.Username, ColorReset))
|
|
|
|
|
|
|
|
- if server.AuthManager.IsEnabled() && !server.IsRoot(token) {
|
|
|
|
|
- printBoxed(fmt.Sprintf("%sPermission Denied: Auth already enabled. Login as root to re-init.%s", ColorRed, ColorReset))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ case "add": // Was user-add
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user add <username> [role1,role2...]")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ username := args[0]
|
|
|
|
|
+ var roles []string
|
|
|
|
|
+ if len(args) > 1 {
|
|
|
|
|
+ roles = strings.Split(args[1], ",")
|
|
|
|
|
+ }
|
|
|
|
|
+ fmt.Printf("Enter password for %s: ", username)
|
|
|
|
|
+ password, err := readPassword()
|
|
|
|
|
+ fmt.Println()
|
|
|
|
|
+ if err != nil || password == "" {
|
|
|
|
|
+ printBoxed("Error reading password")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.CreateUser(username, password, roles, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sUser %s created%s", ColorGreen, username, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Prompt for password
|
|
|
|
|
- fmt.Print("Enter root password: ")
|
|
|
|
|
- password, err := readPassword()
|
|
|
|
|
- fmt.Println() // Newline after input
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("Error reading password: %v", err))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if password == "" {
|
|
|
|
|
- printBoxed("Error: Password cannot be empty")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 1. Create Root User
|
|
|
|
|
- if err := server.AuthManager.CreateRootUser(password); err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("Failed to create root user: %v", err))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 2. Enable Auth
|
|
|
|
|
- if err := server.AuthManager.EnableAuth(); err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("Failed to enable auth: %v", err))
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- printBoxed("Auth Initialized. Please login as 'root'.")
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- c.RegisterCommand("whoami", "Show current user info", func(parts []string, server *KVServer) {
|
|
|
|
|
- c.mu.RLock()
|
|
|
|
|
- token := c.token
|
|
|
|
|
- c.mu.RUnlock()
|
|
|
|
|
-
|
|
|
|
|
- if token == "" {
|
|
|
|
|
- printBoxed("Not logged in.")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- session, err := server.GetSessionInfo(token)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- printBoxed(fmt.Sprintf("Error: %v (Session might have expired)", err))
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ case "del": // Was user-del
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user del <username>")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ username := args[0]
|
|
|
|
|
+ fmt.Printf("Delete user '%s'? (y/N): ", username)
|
|
|
|
|
+ var resp string
|
|
|
|
|
+ fmt.Scanln(&resp)
|
|
|
|
|
+ if strings.ToLower(resp) != "y" {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.DeleteUser(username, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sUser deleted%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "list":
|
|
|
|
|
+ users, err := server.ListUsers(token)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ var buf bytes.Buffer
|
|
|
|
|
+ w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
|
|
|
|
|
+ fmt.Fprintln(w, "Username\tRoles\tStatus")
|
|
|
|
|
+ fmt.Fprintln(w, "--------\t-----\t------")
|
|
|
|
|
+ for _, u := range users {
|
|
|
|
|
+ status := "Offline"
|
|
|
|
|
+ if u.IsOnline { status = "Online" }
|
|
|
|
|
+ fmt.Fprintf(w, "%s\t%s\t%s\n", u.Username, strings.Join(u.Roles, ","), status)
|
|
|
|
|
+ }
|
|
|
|
|
+ w.Flush()
|
|
|
|
|
+ printBoxed(buf.String())
|
|
|
|
|
+
|
|
|
|
|
+ case "update":
|
|
|
|
|
+ // user update <username> [roles=r1,r2]
|
|
|
|
|
+ // Currently only password update via prompt, maybe roles later
|
|
|
|
|
+ // The prompt implies a generic update but also mentions "user update"
|
|
|
|
|
+ // Let's implement password update here or allow changing roles.
|
|
|
|
|
+ // Ideally arguments: user update <username> roles=admin,dev
|
|
|
|
|
+ // If no args (other than username), prompt for password?
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user update <username> [roles=r1,r2...]")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ username := args[0]
|
|
|
|
|
+
|
|
|
|
|
+ // If args present, update roles
|
|
|
|
|
+ if len(args) > 1 {
|
|
|
|
|
+ // Parse options
|
|
|
|
|
+ updated := false
|
|
|
|
|
+ // user, err := server.AuthManager.GetUser(username) // Use internal GetUser helper or logic?
|
|
|
|
|
+ // We need authenticated way. server.ListUsers gives us access if admin.
|
|
|
|
|
+ // But we need the User object to modify.
|
|
|
|
|
+ // Let's rely on server.UpdateUser taking a struct.
|
|
|
|
|
+ // We first need to GET the user to avoid overwriting fields.
|
|
|
|
|
+ // Since we don't have GetUser exposed in server (only List), let's add one or iterate List.
|
|
|
|
|
+ // Iterating List is inefficient but works for now.
|
|
|
|
|
+ users, _ := server.ListUsers(token)
|
|
|
|
|
+ var targetUser *User
|
|
|
|
|
+ for _, u := range users {
|
|
|
|
|
+ if u.Username == username {
|
|
|
|
|
+ targetUser = u
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if targetUser == nil {
|
|
|
|
|
+ printBoxed("User not found or permission denied")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Apply updates
|
|
|
|
|
+ for _, arg := range args[1:] {
|
|
|
|
|
+ if strings.HasPrefix(arg, "roles=") {
|
|
|
|
|
+ roleStr := strings.TrimPrefix(arg, "roles=")
|
|
|
|
|
+ targetUser.Roles = strings.Split(roleStr, ",")
|
|
|
|
|
+ updated = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if updated {
|
|
|
|
|
+ if err := server.UpdateUser(*targetUser, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sUser updated%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // No args -> Change Password
|
|
|
|
|
+ fmt.Printf("Enter new password for %s: ", username)
|
|
|
|
|
+ pass, err := readPassword()
|
|
|
|
|
+ fmt.Println()
|
|
|
|
|
+ if err != nil || pass == "" {
|
|
|
|
|
+ printBoxed("Error reading password")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.ChangeUserPassword(username, pass, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sPassword updated%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "passwd": // Keep alias or subcommand
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user passwd <username>")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // Reuse logic
|
|
|
|
|
+ username := args[0]
|
|
|
|
|
+ fmt.Printf("Enter new password for %s: ", username)
|
|
|
|
|
+ pass, err := readPassword()
|
|
|
|
|
+ fmt.Println()
|
|
|
|
|
+ if err != nil || pass == "" {
|
|
|
|
|
+ printBoxed("Error reading password")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.ChangeUserPassword(username, pass, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sPassword updated%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "role":
|
|
|
|
|
+ if len(args) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user role <list|add|delete|update> ...")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ roleCmd := strings.ToLower(args[0])
|
|
|
|
|
+ roleArgs := args[1:]
|
|
|
|
|
+
|
|
|
|
|
+ switch roleCmd {
|
|
|
|
|
+ case "list":
|
|
|
|
|
+ roles, err := server.ListRoles(token)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ var buf bytes.Buffer
|
|
|
|
|
+ w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
|
|
|
|
|
+ fmt.Fprintln(w, "Role\tParents\tPerms")
|
|
|
|
|
+ fmt.Fprintln(w, "----\t-------\t-----")
|
|
|
|
|
+ for _, r := range roles {
|
|
|
|
|
+ parents := strings.Join(r.ParentRoles, ",")
|
|
|
|
|
+ permCount := len(r.Permissions)
|
|
|
|
|
+ fmt.Fprintf(w, "%s\t%s\t%d rules\n", r.Name, parents, permCount)
|
|
|
|
|
+ }
|
|
|
|
|
+ w.Flush()
|
|
|
|
|
+ printBoxed(buf.String())
|
|
|
|
|
+
|
|
|
|
|
+ case "add":
|
|
|
|
|
+ if len(roleArgs) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user role add <name>")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.CreateRole(roleArgs[0], token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sRole %s created%s", ColorGreen, roleArgs[0], ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "delete":
|
|
|
|
|
+ if len(roleArgs) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user role delete <name>")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := server.DeleteRole(roleArgs[0], token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sRole %s deleted%s", ColorGreen, roleArgs[0], ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "update":
|
|
|
|
|
+ // Simplistic update: user role update <name> parents=p1,p2
|
|
|
|
|
+ // Complex permissions editing is hard in CLI without JSON
|
|
|
|
|
+ if len(roleArgs) < 1 {
|
|
|
|
|
+ printBoxed("Usage: user role update <name> [parents=p1,p2]")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ name := roleArgs[0]
|
|
|
|
|
+ roles, _ := server.ListRoles(token)
|
|
|
|
|
+ var targetRole *Role
|
|
|
|
|
+ for _, r := range roles {
|
|
|
|
|
+ if r.Name == name {
|
|
|
|
|
+ targetRole = r
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if targetRole == nil {
|
|
|
|
|
+ printBoxed("Role not found")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ updated := false
|
|
|
|
|
+ for _, arg := range roleArgs[1:] {
|
|
|
|
|
+ if strings.HasPrefix(arg, "parents=") {
|
|
|
|
|
+ pStr := strings.TrimPrefix(arg, "parents=")
|
|
|
|
|
+ targetRole.ParentRoles = strings.Split(pStr, ",")
|
|
|
|
|
+ updated = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if updated {
|
|
|
|
|
+ if err := server.UpdateRole(*targetRole, token); err != nil {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sError:%s %v", ColorRed, ColorReset, err))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed(fmt.Sprintf("%sRole updated%s", ColorGreen, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ printBoxed("No changes specified.")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ printBoxed("Unknown sub-command. Options: list, add, delete, update")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ case "help":
|
|
|
|
|
+ var sb strings.Builder
|
|
|
|
|
+ sb.WriteString(fmt.Sprintf("%sUser Management Commands:%s\n", ColorCyan, ColorReset))
|
|
|
|
|
+
|
|
|
|
|
+ // Define subcommands for formatting
|
|
|
|
|
+ type subCmd struct {
|
|
|
|
|
+ Name string
|
|
|
|
|
+ Desc string
|
|
|
|
|
+ }
|
|
|
|
|
+ cmds := []subCmd{
|
|
|
|
|
+ {"user init", "Initialize auth system (Root)"},
|
|
|
|
|
+ {"user login <user> [code]", "Login"},
|
|
|
|
|
+ {"user logout", "Logout"},
|
|
|
|
|
+ {"user who", "Current user info"},
|
|
|
|
|
+ {"user add <user> [roles]", "Create user (Root)"},
|
|
|
|
|
+ {"user del <user>", "Delete user (Root)"},
|
|
|
|
|
+ {"user update <user> [opts]", "Update user/pass (Root)"},
|
|
|
|
|
+ {"user list", "List users (Root)"},
|
|
|
|
|
+ {"user role list", "List roles (Root)"},
|
|
|
|
|
+ {"user role add <name>", "Create role (Root)"},
|
|
|
|
|
+ {"user role delete <name>", "Delete role (Root)"},
|
|
|
|
|
+ {"user role update <name> ...", "Update role (Root)"},
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Find max length
|
|
|
|
|
+ maxLen := 0
|
|
|
|
|
+ for _, c := range cmds {
|
|
|
|
|
+ if len(c.Name) > maxLen {
|
|
|
|
|
+ maxLen = len(c.Name)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, c := range cmds {
|
|
|
|
|
+ padding := strings.Repeat(" ", maxLen-len(c.Name))
|
|
|
|
|
+ sb.WriteString(fmt.Sprintf(" %s%s%s%s %s%s%s\n", ColorGreen, c.Name, ColorReset, padding, ColorDim, c.Desc, ColorReset))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ printBoxed(sb.String())
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ printBoxed(fmt.Sprintf("Unknown subcommand '%s'. Type 'user help'.", subCmd))
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- printBoxed(fmt.Sprintf("Logged in as: %s%s%s (Token: %s...)", ColorCyan, session.Username, ColorReset, token[:8]))
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
c.RegisterCommand("join", "Add node to cluster (join <id> <addr>)", func(parts []string, server *KVServer) {
|
|
c.RegisterCommand("join", "Add node to cluster (join <id> <addr>)", func(parts []string, server *KVServer) {
|