feat: harden tenant access and production operations
This commit is contained in:
+15
-8
@@ -114,7 +114,8 @@ function accessibleProjects(workspaceId, userId, organizationId) {
|
||||
FROM projects p
|
||||
LEFT JOIN project_members pm ON pm.project_id = p.id AND pm.user_id = ? AND pm.status = 'active'
|
||||
JOIN workspace_members wm ON wm.workspace_id = p.workspace_id AND wm.user_id = ? AND wm.status = 'active'
|
||||
WHERE p.workspace_id = ? AND (pm.user_id IS NOT NULL OR wm.user_id IS NOT NULL)
|
||||
WHERE p.workspace_id = ?
|
||||
AND (wm.access_mode IS NULL OR wm.access_mode = 'all' OR (wm.access_mode = 'project-only' AND pm.user_id IS NOT NULL))
|
||||
ORDER BY CASE WHEN p.status = 'archived' THEN 1 ELSE 0 END, p.updated_at DESC`,
|
||||
[userId, userId, workspaceId]
|
||||
);
|
||||
@@ -179,7 +180,8 @@ function ensureProject(userId, organization, workspace, projectId) {
|
||||
[projectId, userId]
|
||||
);
|
||||
const workspaceMember = dbGet("SELECT * FROM workspace_members WHERE workspace_id = ? AND user_id = ? AND status = 'active'", [workspace.id, userId]);
|
||||
if (!membership && !workspaceMember && !canSeeAllWorkspaceData(organization.id, userId)) {
|
||||
const workspaceCanSeeProject = workspaceMember && (workspaceMember.access_mode !== "project-only" || Boolean(membership));
|
||||
if (!membership && !workspaceCanSeeProject && !canSeeAllWorkspaceData(organization.id, userId)) {
|
||||
throw httpError(403, "project_forbidden", "当前用户没有该项目访问权", { projectId, userId });
|
||||
}
|
||||
return { project, membership };
|
||||
@@ -304,7 +306,7 @@ export function orgMembers(organizationId) {
|
||||
|
||||
export function workspaceMembers(workspaceId) {
|
||||
return dbAll(
|
||||
`SELECT wm.id, wm.user_id, u.display_name, u.email, u.avatar_color, wm.role_key, r.name AS role_name, wm.status
|
||||
`SELECT wm.id, wm.user_id, u.display_name, u.email, u.avatar_color, wm.role_key, r.name AS role_name, wm.access_mode, wm.status
|
||||
FROM workspace_members wm
|
||||
JOIN users u ON u.id = wm.user_id
|
||||
LEFT JOIN roles r ON r.key = wm.role_key
|
||||
@@ -486,7 +488,10 @@ export function previewInvitation(token) {
|
||||
}
|
||||
|
||||
function invitationRoleForWorkspace(roleKey) {
|
||||
return roleKey === "writer" || roleKey === "voice_editor" || roleKey === "art_director" ? roleKey : "producer";
|
||||
if (["producer", "writer", "art_director", "voice_editor", "reviewer"].includes(roleKey)) return roleKey;
|
||||
// Project invitations need a workspace membership for context resolution,
|
||||
// but must not inherit workspace-wide production permissions.
|
||||
return "project_guest";
|
||||
}
|
||||
|
||||
function applyInvitationMembership(invitation, userId, timestamp) {
|
||||
@@ -497,15 +502,17 @@ function applyInvitationMembership(invitation, userId, timestamp) {
|
||||
[`om-${invitation.organization_id}-${userId}`, invitation.organization_id, userId, organizationRole, timestamp, timestamp, timestamp]
|
||||
);
|
||||
if (invitation.workspace_id) {
|
||||
const accessMode = invitation.project_id ? "project-only" : "all";
|
||||
dbRun(
|
||||
"INSERT INTO workspace_members(id, workspace_id, user_id, role_key, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'active', ?, ?) ON CONFLICT(workspace_id, user_id) DO UPDATE SET role_key = excluded.role_key, status = 'active', updated_at = excluded.updated_at",
|
||||
[`wm-${invitation.workspace_id}-${userId}`, invitation.workspace_id, userId, invitationRoleForWorkspace(invitation.role_key), timestamp, timestamp]
|
||||
"INSERT INTO workspace_members(id, workspace_id, user_id, role_key, access_mode, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'active', ?, ?) ON CONFLICT(workspace_id, user_id) DO UPDATE SET role_key = excluded.role_key, access_mode = CASE WHEN workspace_members.access_mode = 'all' THEN 'all' ELSE excluded.access_mode END, status = 'active', updated_at = excluded.updated_at",
|
||||
[`wm-${invitation.workspace_id}-${userId}`, invitation.workspace_id, userId, invitationRoleForWorkspace(invitation.role_key), accessMode, timestamp, timestamp]
|
||||
);
|
||||
}
|
||||
if (invitation.project_id) {
|
||||
const projectRole = ["project_editor", "project_viewer"].includes(invitation.role_key) ? invitation.role_key : "project_editor";
|
||||
dbRun(
|
||||
"INSERT INTO project_members(id, project_id, user_id, role_key, status, created_at, updated_at) VALUES (?, ?, ?, 'project_editor', 'active', ?, ?) ON CONFLICT(project_id, user_id) DO UPDATE SET status = 'active', updated_at = excluded.updated_at",
|
||||
[`pm-${invitation.project_id}-${userId}`, invitation.project_id, userId, timestamp, timestamp]
|
||||
"INSERT INTO project_members(id, project_id, user_id, role_key, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'active', ?, ?) ON CONFLICT(project_id, user_id) DO UPDATE SET role_key = excluded.role_key, status = 'active', updated_at = excluded.updated_at",
|
||||
[`pm-${invitation.project_id}-${userId}`, invitation.project_id, userId, projectRole, timestamp, timestamp]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user