Skip to content

Feature/else access control permissions#529

Merged
icecrasher321 merged 2 commits into
mainfrom
feature/else-access-control-permissions
Jun 21, 2025
Merged

Feature/else access control permissions#529
icecrasher321 merged 2 commits into
mainfrom
feature/else-access-control-permissions

Conversation

@SHARKYBOY1248
Copy link
Copy Markdown
Contributor

PR thats built on feature/migration-access-control-permissions for all the rest of the changes. We merge this after the migration PR.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 21, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
sim ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 21, 2025 10:17pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
docs ⬜️ Skipped (Inspect) Jun 21, 2025 10:17pm

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Implements comprehensive access control system across the application, transitioning from role-based to permission-based (admin/write/read) access with proper UI feedback and security controls.

  • Added new permission management system in apps/sim/db/migrations/0046_loose_blizzard.sql with dedicated permissions table and enum types
  • Integrated permission checks across UI components with new hooks useUserPermissions and useWorkspacePermissions to enforce read/write/admin access levels
  • Added disabled states to all interactive components (SliderInput, Dropdown, DateInput etc.) with consistent visual feedback and tooltips
  • Secured workspace member management with atomic transactions in apps/sim/app/api/workspaces/members/route.ts and invitation flow
  • Enhanced workspace API endpoints with permission validation in apps/sim/app/api/workspaces/[id]/permissions/route.ts, preventing admins from removing their own rights

48 files reviewed, 23 comments
Edit PR Review Bot Settings | Greptile

Comment on lines 22 to +23
const handleClick = useCallback(() => {
if (config.type === 'connectionBlock') return
if (config.type === 'connectionBlock' || disabled) return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: 'disabled' check needed in the dependency array of useCallback since it's used in the function

Suggested change
const handleClick = useCallback(() => {
if (config.type === 'connectionBlock') return
if (config.type === 'connectionBlock' || disabled) return
const handleClick = useCallback(() => {
if (config.type === 'connectionBlock' || disabled) return
// Dispatch a custom event to be caught by the workflow component
const event = new CustomEvent('add-block-from-toolbar', {
detail: {
type: config.type,
},
})
window.dispatchEvent(event)
}, [config.type, disabled])

Comment on lines +18 to +19
CREATE INDEX "permissions_user_entity_idx" ON "permissions" USING btree ("user_id","entity_type","entity_id");--> statement-breakpoint
CREATE UNIQUE INDEX "permissions_unique_constraint" ON "permissions" USING btree ("user_id","entity_type","entity_id"); No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Potential redundancy between permissions_user_entity_idx and permissions_unique_constraint - both cover the same columns

Suggested change
CREATE INDEX "permissions_user_entity_idx" ON "permissions" USING btree ("user_id","entity_type","entity_id");--> statement-breakpoint
CREATE UNIQUE INDEX "permissions_unique_constraint" ON "permissions" USING btree ("user_id","entity_type","entity_id");
CREATE UNIQUE INDEX "permissions_unique_constraint" ON "permissions" USING btree ("user_id","entity_type","entity_id");

Comment on lines 68 to 71
const updateField = (id: string, field: keyof InputField, value: any) => {
if (isPreview) return
if (isPreview || disabled) return
setStoreValue(fields.map((f: InputField) => (f.id === id ? { ...f, [field]: value } : f)))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type 'any' used for value parameter. Consider using more specific types for type safety.

Suggested change
const updateField = (id: string, field: keyof InputField, value: any) => {
if (isPreview) return
if (isPreview || disabled) return
setStoreValue(fields.map((f: InputField) => (f.id === id ? { ...f, [field]: value } : f)))
}
const updateField = <K extends keyof InputField>(id: string, field: K, value: InputField[K]) => {
if (isPreview || disabled) return
setStoreValue(fields.map((f: InputField) => (f.id === id ? { ...f, [field]: value } : f)))
}

Comment on lines 93 to +94
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (isPreview) return
if (isPreview || disabled) return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Early return for disabled state placed before e.stopPropagation(). Consider moving it after to ensure event propagation is always handled consistently.

toggleBlockEnabled(blockId)
}
}}
className={cn('text-gray-500', disabled && 'cursor-not-allowed opacity-50')}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider consolidating disabled styles into a reusable className to avoid repetition across buttons

Comment on lines +388 to +391
const updates = Object.entries(existingUserPermissionChanges).map(([userId, changes]) => ({
userId,
permissions: changes.permissionType || 'read',
}))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: 'permissions' field name in API request doesn't match the singular 'permission' used in invitation body. This inconsistency may cause issues.

Comment on lines +20 to 21
if (userPermission !== 'read') {
return NextResponse.json({ error: 'Workspace not found or access denied' }, { status: 404 })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Check should be 'if (!userPermission)' since getUserEntityPermissions returns null for no access. Current check allows write/admin access but denies read.

Comment on lines +102 to +110
await db.insert(permissions).values({
id: crypto.randomUUID(),
entityType: 'workspace' as const,
entityId: workspaceId,
userId: userId,
permissionType: 'admin' as const,
createdAt: new Date(),
updatedAt: new Date(),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using a transaction to ensure atomicity between workspace, membership, and permission creation. If any step fails, all should be rolled back.

Comment on lines +1562 to +1592
"name": "permissions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"entity_type": {
"name": "entity_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"entity_id": {
"name": "entity_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"permission_type": {
"name": "permission_type",
"type": "permission_type",
"typeSchema": "public",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Consider using a compound primary key (user_id, entity_type, entity_id) instead of a separate id field since these columns already have a unique constraint

// Core permission checks
const canAdmin = userPerms === 'admin'
const canEdit = userPerms === 'write' || userPerms === 'admin'
const canRead = true // If user is found in workspace permissions, they have read access
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This hardcoded canRead=true is problematic. Even if a user is found in workspace permissions, they might have their read access explicitly revoked. Consider checking against userPerms === 'read' || userPerms === 'write' || userPerms === 'admin'

@icecrasher321 icecrasher321 merged commit 190f3f9 into main Jun 21, 2025
5 checks passed
@waleedlatif1 waleedlatif1 deleted the feature/else-access-control-permissions branch June 28, 2025 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants