robert hace 1 semana
padre
commit
fe4f4df35b
Se han modificado 4 ficheros con 89 adiciones y 12 borrados
  1. 8 2
      cli.go
  2. 37 5
      example/web_admin/index.html
  3. 5 3
      example/web_admin/main.go
  4. 39 2
      tcp_server.go

+ 8 - 2
cli.go

@@ -358,9 +358,15 @@ func (c *CLI) registerDefaultCommands() {
 		switch subCmd {
 		case "init": // Was auth-init
 			if server.AuthManager.IsEnabled() {
-				printBoxed(fmt.Sprintf("%sPermission Denied: Auth system is already initialized.%s\nUse 'login' to access the system or contact administrator.", ColorRed, ColorReset))
-				return
+				fmt.Printf("%sAuth system is already initialized.%s\n", ColorYellow, ColorReset)
+				fmt.Print("Do you want to reset the root user? (y/N): ")
+				var resp string
+				fmt.Scanln(&resp)
+				if strings.ToLower(resp) != "y" {
+					return
+				}
 			}
+			
 			fmt.Print("Enter root password: ")
 			password, err := readPassword()
 			fmt.Println()

+ 37 - 5
example/web_admin/index.html

@@ -351,6 +351,9 @@
                                             <div>
                                                 <span x-text="p.key" class="font-bold text-slate-300"></span> 
                                                 <span class="text-brand-500 text-xs ml-2" x-text="'[' + p.actions.join(',') + ']'"></span>
+                                                <template x-if="p.constraint">
+                                                    <span class="text-yellow-500 text-xs ml-2" x-text="'Range: ' + (p.constraint.min !== null ? p.constraint.min : '-∞') + ' ~ ' + (p.constraint.max !== null ? p.constraint.max : '+∞')"></span>
+                                                </template>
                                             </div>
                                             <div class="flex gap-2 opacity-0 group-hover/perm:opacity-100 transition-opacity">
                                                 <button @click="openAddPermModal(r.name, p)" class="text-blue-400 hover:text-blue-300" title="Edit">
@@ -430,6 +433,18 @@
                         </label>
                     </div>
                 </div>
+                
+                <div x-show="newPerm.actions.includes('write')" x-transition class="grid grid-cols-2 gap-4">
+                    <div>
+                        <label class="block text-sm font-medium mb-2 text-slate-400">Min Value (Optional)</label>
+                        <input x-model="newPerm.min" type="number" step="any" placeholder="No Limit" class="w-full bg-dark-900 border border-dark-700 rounded-lg p-3 text-white outline-none focus:border-brand-500">
+                    </div>
+                    <div>
+                        <label class="block text-sm font-medium mb-2 text-slate-400">Max Value (Optional)</label>
+                        <input x-model="newPerm.max" type="number" step="any" placeholder="No Limit" class="w-full bg-dark-900 border border-dark-700 rounded-lg p-3 text-white outline-none focus:border-brand-500">
+                    </div>
+                </div>
+
                 <div class="flex gap-3 pt-2">
                     <button @click="addPermission" class="flex-1 bg-brand-600 text-white py-2 rounded-lg font-bold hover:bg-brand-500" x-text="isEditing ? 'Save' : 'Add'"></button>
                     <button @click="showAddPermModal = false" class="flex-1 bg-dark-700 text-white py-2 rounded-lg font-bold hover:bg-dark-600">Cancel</button>
@@ -478,7 +493,7 @@
                 showAddPermModal: false,
                 isEditing: false,
                 selectedRole: '',
-                newPerm: { pattern: '', actions: [] },
+                newPerm: { pattern: '', actions: [], min: '', max: '' },
 
                 init() {
                     if (this.token) {
@@ -647,10 +662,15 @@
                     this.selectedRole = roleName;
                     if (perm) {
                         this.isEditing = true;
-                        this.newPerm = { pattern: perm.key, actions: [...perm.actions] };
+                        this.newPerm = { 
+                            pattern: perm.key, 
+                            actions: [...perm.actions],
+                            min: perm.constraint && perm.constraint.min !== undefined ? perm.constraint.min : '',
+                            max: perm.constraint && perm.constraint.max !== undefined ? perm.constraint.max : ''
+                        };
                     } else {
                         this.isEditing = false;
-                        this.newPerm = { pattern: '', actions: [] };
+                        this.newPerm = { pattern: '', actions: [], min: '', max: '' };
                     }
                     this.showAddPermModal = true;
                 },
@@ -671,9 +691,21 @@
                         return;
                     }
                     try {
-                        // Command: ROLE_PERMISSION_ADD <role> <pattern> <actions>
+                        // Command: ROLE_PERMISSION_ADD <role> <pattern> <actions> [min] [max]
                         const actionsStr = this.newPerm.actions.join(',');
-                        await this.api('ROLE_PERMISSION_ADD', [this.selectedRole, this.newPerm.pattern, actionsStr]);
+                        const args = [this.selectedRole, this.newPerm.pattern, actionsStr];
+                        
+                        if (this.newPerm.actions.includes('write')) {
+                            const minVal = (this.newPerm.min !== '' && this.newPerm.min !== null) ? this.newPerm.min : '-';
+                            const maxVal = (this.newPerm.max !== '' && this.newPerm.max !== null) ? this.newPerm.max : '-';
+                            
+                            if (minVal !== '-' || maxVal !== '-') {
+                                args.push(String(minVal));
+                                args.push(String(maxVal));
+                            }
+                        }
+
+                        await this.api('ROLE_PERMISSION_ADD', args);
                         this.showAddPermModal = false;
                         this.fetchRoles();
                     } catch (e) {

+ 5 - 3
example/web_admin/main.go

@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"strings"
 	"sync"
+	"time"
 )
 
 // Configuration
@@ -119,13 +120,13 @@ func handleAPI(w http.ResponseWriter, r *http.Request) {
 	// Helper to execute request with retry logic
 	execute := func(c net.Conn) error {
 		// Set deadline to avoid hanging
-		// c.SetDeadline(time.Now().Add(5 * time.Second))
+		c.SetDeadline(time.Now().Add(5 * time.Second))
 
 		reader := bufio.NewReader(c)
 
 		// 4. Authenticate if token present (Except for LOGIN command itself)
 		if req.Command != "LOGIN" && token != "" {
-			fmt.Fprintf(c, "AUTH %s\n", token)
+			fmt.Fprintf(c, "AUTH %s\n\n", token)
 			authResp, err := reader.ReadString('\n')
 			if err != nil {
 				return err
@@ -145,7 +146,8 @@ func handleAPI(w http.ResponseWriter, r *http.Request) {
 		if len(req.Args) > 0 {
 			cmdStr += " " + strings.Join(req.Args, " ")
 		}
-		fmt.Fprintf(c, "%s\n", cmdStr)
+		// Send command followed by empty line to terminate headers (protocol requirement)
+		fmt.Fprintf(c, "%s\n\n", cmdStr)
 
 		// 6. Read TCP Response
 		respStr, err := reader.ReadString('\n')

+ 39 - 2
tcp_server.go

@@ -133,6 +133,7 @@ func (s *KVServer) handleTCPConnection(conn net.Conn) {
 		// So for now, we just execute.
 		
 		// Execute Command Inline
+		// s.Raft.config.Logger.Debug("TCP Request: %s", line)
 		resp := s.executeCommandWithBody(session, line, body)
 
 		// Write Response
@@ -334,6 +335,10 @@ func (s *KVServer) executeCommand(session *TCPClientSession, line string) string
 	case "INFO":
 		// Check permission (Admin only if auth enabled)
 		if s.AuthManager.IsEnabled() {
+			// Allow if admin OR if root (HasFullAccess)
+			// But IsAdmin is basically check for "admin" on "*"
+			// Let's relax it slightly for dashboard if we want read-only dashboard?
+			// For now, strict: Admin access required.
 			if !s.IsAdmin(session.token) {
 				resp = "ERR Permission Denied: Admin access required"
 				break
@@ -529,15 +534,40 @@ func (s *KVServer) executeCommand(session *TCPClientSession, line string) string
 		}
 
 	case "ROLE_PERMISSION_ADD":
-		// Usage: ROLE_PERMISSION_ADD <role> <pattern> <actions>
+		// Usage: ROLE_PERMISSION_ADD <role> <pattern> <actions> [min] [max]
 		// Actions: comma separated list of actions (read,write,admin,*)
+		// Min/Max: optional numeric constraints for write operations ("-" for no constraint)
 		if len(parts) < 4 {
-			resp = "ERR usage: ROLE_PERMISSION_ADD <role> <pattern> <actions>"
+			resp = "ERR usage: ROLE_PERMISSION_ADD <role> <pattern> <actions> [min] [max]"
 		} else {
 			roleName := parts[1]
 			pattern := parts[2]
 			actionsStr := parts[3]
 			actions := strings.Split(actionsStr, ",")
+			
+			var minVal, maxVal *float64
+			
+			if len(parts) > 4 {
+				if parts[4] != "-" && parts[4] != "null" {
+					if v, err := strconv.ParseFloat(parts[4], 64); err == nil {
+						minVal = &v
+					} else {
+						resp = "ERR invalid min value"
+						break
+					}
+				}
+			}
+			
+			if len(parts) > 5 {
+				if parts[5] != "-" && parts[5] != "null" {
+					if v, err := strconv.ParseFloat(parts[5], 64); err == nil {
+						maxVal = &v
+					} else {
+						resp = "ERR invalid max value"
+						break
+					}
+				}
+			}
 
 			// Check auth logic inside UpdateRole call?
 			// No, UpdateRole in server.go handles Auth check now.
@@ -559,6 +589,13 @@ func (s *KVServer) executeCommand(session *TCPClientSession, line string) string
 					KeyPattern: pattern,
 					Actions:    actions,
 				}
+				
+				if minVal != nil || maxVal != nil {
+					newPerm.Constraint = &Constraint{
+						Min: minVal,
+						Max: maxVal,
+					}
+				}
 
 				// Upsert logic
 				found := false