Răsfoiți Sursa

feat: real-time account suspension via WebSocket kick

unknown 5 zile în urmă
părinte
comite
624695e0a5

+ 5 - 0
backend/routers/auth.py

@@ -175,6 +175,11 @@ async def admin_update_user(target_id: int, data: schemas.UserUpdate, token: str
         params.append(target_id)
         db.execute_commit(query, tuple(params))
         
+        # If user was deactivated, kick from active sessions
+        update_dict = data.dict(exclude_unset=True)
+        if update_dict.get("is_active") is False:
+            await global_manager.kick_user(target_id)
+        
     user = db.execute_query("SELECT id, email, first_name, last_name, phone, shipping_address, preferred_language, role, can_chat, is_active, is_company, company_name, company_pib, company_address, ip_address, created_at FROM users WHERE id = %s", (target_id,))
     if not user: raise HTTPException(status_code=404, detail="User not found")
     return user[0]

+ 19 - 0
backend/scratch/inspect_routes.py

@@ -0,0 +1,19 @@
+import sys
+import os
+# Adjust sys.path to include the backend directory
+backend_dir = r"d:\radionica3d\backend"
+sys.path.append(backend_dir)
+os.chdir(backend_dir) # Change to backend dir to avoid import issues
+
+from main import app
+
+print("--- REGISTERED ROUTES ---")
+for route in app.routes:
+    path = getattr(route, "path", None)
+    name = getattr(route, "name", None)
+    if path:
+        print(f"Path: {path}, Name: {name}")
+    elif hasattr(route, "routes"): # For mounted apps or groups
+         for subroute in route.routes:
+             subpath = getattr(subroute, "path", None)
+             print(f"Group Path: {subpath}")

+ 11 - 0
backend/services/global_manager.py

@@ -53,4 +53,15 @@ class GlobalConnectionManager:
                     except:
                         pass
 
+    async def kick_user(self, user_id: int):
+        if user_id in self.active_connections:
+            payload = json.dumps({"type": "account_suspended"})
+            for ws in self.active_connections[user_id]:
+                try:
+                    await ws.send_text(payload)
+                    await ws.close(code=4003) # Custom code for kick
+                except:
+                    pass
+            # Connections will be removed via disconnect() called on close
+
 global_manager = GlobalConnectionManager()

+ 12 - 5
src/stores/auth.ts

@@ -1,6 +1,7 @@
 import { defineStore } from "pinia";
 import { ref } from "vue";
 import { getCurrentUser } from "@/lib/api";
+import { toast } from "vue-sonner";
 
 export const useAuthStore = defineStore("auth", () => {
   const user = ref<any>(null);
@@ -65,7 +66,7 @@ export const useAuthStore = defineStore("auth", () => {
     const token = localStorage.getItem("token");
     if (!token || wsAuthFailed.value) return;
 
-    globalWs = new WebSocket(`${WS_BASE_URL}/api/auth/ws/global?token=${encodeURIComponent(token)}`);
+    globalWs = new WebSocket(`${WS_BASE_URL}/auth/ws/global?token=${encodeURIComponent(token)}`);
 
     globalWs.onopen = () => {
       // Send ping every 25 seconds to keep connection alive and update online status in Redis
@@ -84,6 +85,9 @@ export const useAuthStore = defineStore("auth", () => {
             playNotificationSound();
           }
           unreadMessagesCount.value = msg.count;
+        } else if (msg.type === "account_suspended") {
+          toast.error("Your account has been suspended by an administrator.", { duration: 10000 });
+          logout();
         }
       } catch (e) {
         console.error("WS Parse error", e);
@@ -93,11 +97,14 @@ export const useAuthStore = defineStore("auth", () => {
     globalWs.onclose = (event) => {
       if (pingInterval) clearInterval(pingInterval);
       
-      // 4001 is our custom code for Auth failure
-      // 1008 is Policy Violation (often used for Auth fail in generic WS)
-      if (event.code === 4001 || event.code === 1008) {
-        console.warn("WS Authentication failed (403/401). Stopping reconnection until next login.");
+      // 4001: Auth fail, 4003: Suspended, 1008: Policy
+      if (event.code === 4001 || event.code === 1008 || event.code === 4003) {
+        console.warn(`WS Connection terminated (code ${event.code}).`);
         wsAuthFailed.value = true;
+        if (event.code === 4003) {
+          user.value = null; // Instant clear
+          localStorage.removeItem("token");
+        }
         return;
       }