Skip to content

Commit 76889fd

Browse files
authored
fix(permissions): remove permissions granted by org membership (#1206)
* fix(permissions): remove cross-functional permissions granted by org membership * code hygiene
1 parent 7780d9b commit 76889fd

File tree

31 files changed

+549
-379
lines changed

31 files changed

+549
-379
lines changed

apps/sim/app/api/billing/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { getSession } from '@/lib/auth'
44
import { getSimplifiedBillingSummary } from '@/lib/billing/core/billing'
5-
import { getOrganizationBillingData } from '@/lib/billing/core/organization-billing'
5+
import { getOrganizationBillingData } from '@/lib/billing/core/organization'
66
import { createLogger } from '@/lib/logs/console/logger'
77
import { db } from '@/db'
88
import { member, userStats } from '@/db/schema'

apps/sim/app/api/usage-limits/route.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { getSession } from '@/lib/auth'
33
import { getUserUsageLimitInfo, updateUserUsageLimit } from '@/lib/billing'
4-
import { getOrganizationBillingData } from '@/lib/billing/core/organization-billing'
4+
import {
5+
getOrganizationBillingData,
6+
isOrganizationOwnerOrAdmin,
7+
} from '@/lib/billing/core/organization'
58
import { createLogger } from '@/lib/logs/console/logger'
6-
import { isOrganizationOwnerOrAdmin } from '@/lib/permissions/utils'
79

810
const logger = createLogger('UnifiedUsageLimitsAPI')
911

@@ -25,23 +27,20 @@ export async function GET(request: NextRequest) {
2527
const userId = searchParams.get('userId') || session.user.id
2628
const organizationId = searchParams.get('organizationId')
2729

28-
// Validate context
2930
if (!['user', 'organization'].includes(context)) {
3031
return NextResponse.json(
3132
{ error: 'Invalid context. Must be "user" or "organization"' },
3233
{ status: 400 }
3334
)
3435
}
3536

36-
// For user context, ensure they can only view their own info
3737
if (context === 'user' && userId !== session.user.id) {
3838
return NextResponse.json(
3939
{ error: "Cannot view other users' usage information" },
4040
{ status: 403 }
4141
)
4242
}
4343

44-
// Get usage limit info
4544
if (context === 'organization') {
4645
if (!organizationId) {
4746
return NextResponse.json(
@@ -107,10 +106,8 @@ export async function PUT(request: NextRequest) {
107106
}
108107

109108
if (context === 'user') {
110-
// Update user's own usage limit
111109
await updateUserUsageLimit(userId, limit)
112110
} else if (context === 'organization') {
113-
// context === 'organization'
114111
if (!organizationId) {
115112
return NextResponse.json(
116113
{ error: 'Organization ID is required when context=organization' },
@@ -123,10 +120,7 @@ export async function PUT(request: NextRequest) {
123120
return NextResponse.json({ error: 'Permission denied' }, { status: 403 })
124121
}
125122

126-
// Use the dedicated function to update org usage limit
127-
const { updateOrganizationUsageLimit } = await import(
128-
'@/lib/billing/core/organization-billing'
129-
)
123+
const { updateOrganizationUsageLimit } = await import('@/lib/billing/core/organization')
130124
const result = await updateOrganizationUsageLimit(organizationId, limit)
131125

132126
if (!result.success) {
@@ -137,7 +131,6 @@ export async function PUT(request: NextRequest) {
137131
return NextResponse.json({ success: true, context, userId, organizationId, data: updated })
138132
}
139133

140-
// Return updated limit info
141134
const updatedInfo = await getUserUsageLimitInfo(userId)
142135

143136
return NextResponse.json({

apps/sim/app/api/workspaces/[id]/permissions/route.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import crypto from 'crypto'
22
import { and, eq } from 'drizzle-orm'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { getSession } from '@/lib/auth'
5+
import { createLogger } from '@/lib/logs/console/logger'
56
import { getUsersWithPermissions, hasWorkspaceAdminAccess } from '@/lib/permissions/utils'
67
import { db } from '@/db'
78
import { permissions, type permissionTypeEnum } from '@/db/schema'
89

10+
const logger = createLogger('WorkspacesPermissionsAPI')
11+
912
type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
1013

1114
interface UpdatePermissionsRequest {
1215
updates: Array<{
1316
userId: string
14-
permissions: PermissionType // Single permission type instead of object with booleans
17+
permissions: PermissionType
1518
}>
1619
}
1720

@@ -33,7 +36,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
3336
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
3437
}
3538

36-
// Verify the current user has access to this workspace
3739
const userPermission = await db
3840
.select()
3941
.from(permissions)
@@ -57,7 +59,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
5759
total: result.length,
5860
})
5961
} catch (error) {
60-
console.error('Error fetching workspace permissions:', error)
62+
logger.error('Error fetching workspace permissions:', error)
6163
return NextResponse.json({ error: 'Failed to fetch workspace permissions' }, { status: 500 })
6264
}
6365
}
@@ -81,7 +83,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
8183
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
8284
}
8385

84-
// Verify the current user has admin access to this workspace (either direct or through organization)
8586
const hasAdminAccess = await hasWorkspaceAdminAccess(session.user.id, workspaceId)
8687

8788
if (!hasAdminAccess) {
@@ -91,10 +92,8 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
9192
)
9293
}
9394

94-
// Parse and validate request body
9595
const body: UpdatePermissionsRequest = await request.json()
9696

97-
// Prevent users from modifying their own admin permissions
9897
const selfUpdate = body.updates.find((update) => update.userId === session.user.id)
9998
if (selfUpdate && selfUpdate.permissions !== 'admin') {
10099
return NextResponse.json(
@@ -103,10 +102,8 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
103102
)
104103
}
105104

106-
// Process updates in a transaction
107105
await db.transaction(async (tx) => {
108106
for (const update of body.updates) {
109-
// Delete existing permissions for this user and workspace
110107
await tx
111108
.delete(permissions)
112109
.where(
@@ -117,7 +114,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
117114
)
118115
)
119116

120-
// Insert the single new permission
121117
await tx.insert(permissions).values({
122118
id: crypto.randomUUID(),
123119
userId: update.userId,
@@ -138,7 +134,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
138134
total: updatedUsers.length,
139135
})
140136
} catch (error) {
141-
console.error('Error updating workspace permissions:', error)
137+
logger.error('Error updating workspace permissions:', error)
142138
return NextResponse.json({ error: 'Failed to update workspace permissions' }, { status: 500 })
143139
}
144140
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/chat.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ import {
1212
extractPathFromOutputId,
1313
parseOutputContentSafely,
1414
} from '@/lib/response-format'
15-
import { ChatMessage } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/chat-message/chat-message'
16-
import { OutputSelect } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select'
15+
import {
16+
ChatFileUpload,
17+
ChatMessage,
18+
OutputSelect,
19+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components'
1720
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution'
1821
import type { BlockLog, ExecutionResult } from '@/executor/types'
1922
import { useExecutionStore } from '@/stores/execution/store'
2023
import { useChatStore } from '@/stores/panel/chat/store'
2124
import { useConsoleStore } from '@/stores/panel/console/store'
2225
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
23-
import { ChatFileUpload } from './components/chat-file-upload'
2426

2527
const logger = createLogger('ChatPanel')
2628

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/chat-file-upload.tsx renamed to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/chat-file-upload/chat-file-upload.tsx

File renamed without changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { ChatFileUpload } from './chat-file-upload/chat-file-upload'
2+
export { ChatMessage } from './chat-message/chat-message'
3+
export { OutputSelect } from './output-select/output-select'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/console/components/console-entry/console-entry.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const ImagePreview = ({
155155
className='h-auto w-full rounded-lg border'
156156
unoptimized
157157
onError={(e) => {
158-
console.error('Image failed to load:', imageSrc)
158+
logger.error('Image failed to load:', imageSrc)
159159
setLoadError(true)
160160
onLoadError?.(true)
161161
}}
@@ -333,7 +333,7 @@ export function ConsoleEntry({ entry, consoleWidth }: ConsoleEntryProps) {
333333
// Clean up the URL
334334
setTimeout(() => URL.revokeObjectURL(url), 100)
335335
} catch (error) {
336-
console.error('Error downloading image:', error)
336+
logger.error('Error downloading image:', error)
337337
alert('Failed to download image. Please try again later.')
338338
}
339339
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '@/components/ui/dropdown-menu'
1010
import { ScrollArea } from '@/components/ui/scroll-area'
1111
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
12+
import { createLogger } from '@/lib/logs/console/logger'
1213
import { useCopilotStore } from '@/stores/copilot/store'
1314
import { useChatStore } from '@/stores/panel/chat/store'
1415
import { useConsoleStore } from '@/stores/panel/console/store'
@@ -19,6 +20,8 @@ import { Console } from './components/console/console'
1920
import { Copilot } from './components/copilot/copilot'
2021
import { Variables } from './components/variables/variables'
2122

23+
const logger = createLogger('Panel')
24+
2225
export function Panel() {
2326
const [chatMessage, setChatMessage] = useState<string>('')
2427
const [isHistoryDropdownOpen, setIsHistoryDropdownOpen] = useState(false)
@@ -67,7 +70,7 @@ export function Panel() {
6770
try {
6871
await deleteChat(chatId)
6972
} catch (error) {
70-
console.error('Error deleting chat:', error)
73+
logger.error('Error deleting chat:', error)
7174
}
7275
},
7376
[deleteChat]
@@ -101,7 +104,7 @@ export function Panel() {
101104
lastLoadedWorkflowRef.current = activeWorkflowId
102105
}
103106
} catch (error) {
104-
console.error('Failed to load copilot data:', error)
107+
logger.error('Failed to load copilot data:', error)
105108
}
106109
},
107110
[
@@ -134,14 +137,14 @@ export function Panel() {
134137
if (!areChatsFresh(activeWorkflowId)) {
135138
// Don't await - let it load in background while dropdown is already open
136139
ensureCopilotDataLoaded(false).catch((error) => {
137-
console.error('Failed to load chat history:', error)
140+
logger.error('Failed to load chat history:', error)
138141
})
139142
}
140143
}
141144

142145
// If streaming, just log that we're showing cached data
143146
if (open && isSendingMessage) {
144-
console.log('Chat history opened during stream - showing cached data only')
147+
logger.info('Chat history opened during stream - showing cached data only')
145148
}
146149
},
147150
[ensureCopilotDataLoaded, activeWorkflowId, areChatsFresh, isSendingMessage]
@@ -278,7 +281,7 @@ export function Panel() {
278281
// This is a real workflow change, not just a tab switch
279282
if (copilotWorkflowId !== activeWorkflowId || !copilotWorkflowId) {
280283
ensureCopilotDataLoaded().catch((error) => {
281-
console.error('Failed to auto-load copilot data on workflow change:', error)
284+
logger.error('Failed to auto-load copilot data on workflow change:', error)
282285
})
283286
}
284287
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-upload.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export function FileUpload({
235235
})
236236
}
237237
} catch (error) {
238-
console.error(`Error uploading ${file.name}:`, error)
238+
logger.error(`Error uploading ${file.name}:`, error)
239239
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
240240
uploadErrors.push(`${file.name}: ${errorMessage}`)
241241
}
@@ -428,7 +428,7 @@ export function FileUpload({
428428
deletionResults.failures.push(`${file.name}: ${errorMessage}`)
429429
}
430430
} catch (error) {
431-
console.error(`Failed to delete file ${file.name}:`, error)
431+
logger.error(`Failed to delete file ${file.name}:`, error)
432432
deletionResults.failures.push(
433433
`${file.name}: ${error instanceof Error ? error.message : 'Unknown error'}`
434434
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ export function ToolInput({
483483
try {
484484
return block.tools.config.tool({ operation })
485485
} catch (error) {
486-
console.error('Error selecting tool for operation:', error)
486+
logger.error('Error selecting tool for operation:', error)
487487
}
488488
}
489489

0 commit comments

Comments
 (0)