diff --git a/server/dev-server.mjs b/server/dev-server.mjs index 936742f..dd60111 100644 --- a/server/dev-server.mjs +++ b/server/dev-server.mjs @@ -1449,6 +1449,16 @@ app.post("/api/admin/access/user-service", requireLauncherAdmin, asyncRoute(asyn return; } + const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user); + if (isPublicTaskManagerGuestServiceAssignment(snapshot.data, req.body)) { + res.status(400).json({ + ok: false, + error: "task_manager_public_guest_not_assignable", + message: "Workspace Guest выдаётся только через настройки Operational Core.", + }); + return; + } + const result = await controlPlaneStore.setUserServiceAccess(req.body, req.nodedcSession.user); const syncResult = await syncUsersToAuthentik(result.data, [req.body?.userId], req.nodedcSession.user); publishControlPlaneEvent("admin.access.user-service.updated", syncResult.userIds); @@ -2181,6 +2191,24 @@ function normalizeTaskManagerRole(value) { return value === "guest" || value === "admin" || value === "member" ? value : null; } +function isPublicTaskManagerGuestServiceAssignment(data, payload) { + if (payload?.value !== "viewer") return false; + + const service = data.services.find((candidate) => candidate.id === payload?.serviceId); + if (!service || !isTaskManagerService(service)) return false; + + return data.memberships.some( + (membership) => + membership.userId === payload?.userId && + membership.clientId === publicPoolClientId && + membership.status === "active" + ); +} + +function isTaskManagerService(service) { + return service?.slug === "task-manager" || service?.authentikApplicationSlug === "task-manager"; +} + function resolveTaskManagerRoleForMembership(role) { return role === "client_owner" || role === "client_admin" ? "admin" : "member"; } diff --git a/src/widgets/admin-overlay/AdminOverlay.tsx b/src/widgets/admin-overlay/AdminOverlay.tsx index a89e118..437f46b 100644 --- a/src/widgets/admin-overlay/AdminOverlay.tsx +++ b/src/widgets/admin-overlay/AdminOverlay.tsx @@ -1347,7 +1347,7 @@ const accessAssignmentOptions: Array> const publicOperationalCoreAccessOptions: Array> = [ { value: "unset", label: "—", description: "Не назначен", hidden: true }, - { value: "viewer", label: "Workspace Guest", description: "Доступ к приглашённому workspace", tone: "green" }, + { value: "viewer", label: "Workspace Guest", description: "Выдаётся только через Tasker", tone: "green", hidden: true }, { value: "member", label: "Workspace Member", description: "Доступ к приглашённому workspace", tone: "green" }, { value: "admin", label: "Service Admin", description: "Self-service", tone: "green" }, { value: "deny", label: "Заблокирован", description: "Запрет доступа", tone: "red" },