Skip to content

Commit 5b2ecc4

Browse files
committed
ack PR comments
1 parent 25e309a commit 5b2ecc4

File tree

29 files changed

+613
-165
lines changed

29 files changed

+613
-165
lines changed

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,22 @@ export async function GET(request: NextRequest) {
4646
// Get user billing (may include organization if they're part of one)
4747
billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined)
4848
} else {
49+
// Get user role in organization for permission checks first
50+
const memberRecord = await db
51+
.select({ role: member.role })
52+
.from(member)
53+
.where(and(eq(member.organizationId, contextId), eq(member.userId, session.user.id)))
54+
.limit(1)
55+
56+
if (memberRecord.length === 0) {
57+
return NextResponse.json(
58+
{ error: 'Access denied - not a member of this organization' },
59+
{ status: 403 }
60+
)
61+
}
62+
4963
// Get organization-specific billing
50-
const rawBillingData = await getOrganizationBillingData(contextId!)
64+
const rawBillingData = await getOrganizationBillingData(contextId)
5165

5266
if (!rawBillingData) {
5367
return NextResponse.json(
@@ -76,14 +90,7 @@ export async function GET(request: NextRequest) {
7690
})),
7791
}
7892

79-
// Get user role in organization for permission checks
80-
const memberRecord = await db
81-
.select({ role: member.role })
82-
.from(member)
83-
.where(and(eq(member.organizationId, contextId!), eq(member.userId, session.user.id)))
84-
.limit(1)
85-
86-
const userRole = memberRecord.length > 0 ? memberRecord[0].role : 'member'
93+
const userRole = memberRecord[0].role
8794

8895
return NextResponse.json({
8996
success: true,

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ export async function POST(request: NextRequest) {
5757
eventType: event.type,
5858
})
5959

60-
// Handle invoice events
61-
if (event.type.startsWith('invoice.')) {
60+
// Handle specific invoice events
61+
const supportedEvents = [
62+
'invoice.payment_succeeded',
63+
'invoice.payment_failed',
64+
'invoice.finalized',
65+
]
66+
67+
if (supportedEvents.includes(event.type)) {
6268
try {
6369
await handleInvoiceWebhook(event)
6470

@@ -79,10 +85,11 @@ export async function POST(request: NextRequest) {
7985
return NextResponse.json({ error: 'Failed to process webhook' }, { status: 500 })
8086
}
8187
} else {
82-
// Not an invoice event, ignore
83-
logger.info('Ignoring non-invoice webhook event', {
88+
// Not a supported invoice event, ignore
89+
logger.info('Ignoring unsupported webhook event', {
8490
eventId: event.id,
8591
eventType: event.type,
92+
supportedEvents,
8693
})
8794

8895
return NextResponse.json({ received: true })

apps/sim/app/api/organizations/invitations/accept/route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ export async function GET(req: NextRequest) {
131131

132132
for (const wsInvitation of workspaceInvitations) {
133133
// Check if invitation hasn't expired
134-
if (wsInvitation.expiresAt && new Date() <= wsInvitation.expiresAt) {
134+
if (
135+
wsInvitation.expiresAt &&
136+
new Date().toISOString() <= wsInvitation.expiresAt.toISOString()
137+
) {
135138
// Check if user isn't already a member of the workspace
136139
const existingWorkspaceMember = await tx
137140
.select()
@@ -304,7 +307,10 @@ export async function POST(req: NextRequest) {
304307
)
305308

306309
for (const wsInvitation of workspaceInvitations) {
307-
if (wsInvitation.expiresAt && new Date() <= wsInvitation.expiresAt) {
310+
if (
311+
wsInvitation.expiresAt &&
312+
new Date().toISOString() <= wsInvitation.expiresAt.toISOString()
313+
) {
308314
const existingWorkspaceMember = await tx
309315
.select()
310316
.from(workspaceMember)

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

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ import { getSession } from '@/lib/auth'
33
import { getUserUsageLimitInfo, updateUserUsageLimit } from '@/lib/billing'
44
import { updateMemberUsageLimit } from '@/lib/billing/core/organization-billing'
55
import { createLogger } from '@/lib/logs/console-logger'
6+
import { isOrganizationOwnerOrAdmin } from '@/lib/permissions/utils'
67

78
const logger = createLogger('UnifiedUsageLimitsAPI')
89

910
/**
1011
* Unified Usage Limits Endpoint
1112
* GET/PUT /api/usage-limits?context=user|member&userId=<id>&organizationId=<id>
1213
*
13-
* Replaces:
14-
* - /api/users/me/usage-limit
15-
* - /api/organizations/[id]/members/[memberId]/usage-limit
1614
*/
1715
export async function GET(request: NextRequest) {
1816
const session = await getSession()
@@ -35,15 +33,42 @@ export async function GET(request: NextRequest) {
3533
)
3634
}
3735

38-
// For member context, require organizationId
39-
if (context === 'member' && !organizationId) {
36+
// For member context, require organizationId and check permissions
37+
if (context === 'member') {
38+
if (!organizationId) {
39+
return NextResponse.json(
40+
{ error: 'Organization ID is required when context=member' },
41+
{ status: 400 }
42+
)
43+
}
44+
45+
// Check if the current user has permission to view member usage info
46+
const hasPermission = await isOrganizationOwnerOrAdmin(session.user.id, organizationId)
47+
if (!hasPermission) {
48+
logger.warn('Unauthorized attempt to view member usage info', {
49+
requesterId: session.user.id,
50+
targetUserId: userId,
51+
organizationId,
52+
})
53+
return NextResponse.json(
54+
{
55+
error:
56+
'Permission denied. Only organization owners and admins can view member usage information',
57+
},
58+
{ status: 403 }
59+
)
60+
}
61+
}
62+
63+
// For user context, ensure they can only view their own info
64+
if (context === 'user' && userId !== session.user.id) {
4065
return NextResponse.json(
41-
{ error: 'Organization ID is required when context=member' },
42-
{ status: 400 }
66+
{ error: "Cannot view other users' usage information" },
67+
{ status: 403 }
4368
)
4469
}
4570

46-
// Get usage limit info (same for both contexts)
71+
// Get usage limit info
4772
const usageLimitInfo = await getUserUsageLimitInfo(userId)
4873

4974
return NextResponse.json({
@@ -101,6 +126,30 @@ export async function PUT(request: NextRequest) {
101126
)
102127
}
103128

129+
// Check if the current user has permission to update member limits
130+
const hasPermission = await isOrganizationOwnerOrAdmin(session.user.id, organizationId)
131+
if (!hasPermission) {
132+
logger.warn('Unauthorized attempt to update member usage limit', {
133+
adminUserId: session.user.id,
134+
targetUserId: userId,
135+
organizationId,
136+
})
137+
return NextResponse.json(
138+
{
139+
error:
140+
'Permission denied. Only organization owners and admins can update member usage limits',
141+
},
142+
{ status: 403 }
143+
)
144+
}
145+
146+
logger.info('Authorized member usage limit update', {
147+
adminUserId: session.user.id,
148+
targetUserId: userId,
149+
organizationId,
150+
newLimit: limit,
151+
})
152+
104153
await updateMemberUsageLimit(organizationId, userId, limit, session.user.id)
105154
} else {
106155
return NextResponse.json(
@@ -125,9 +174,6 @@ export async function PUT(request: NextRequest) {
125174
error,
126175
})
127176

128-
return NextResponse.json(
129-
{ error: error instanceof Error ? error.message : 'Internal server error' },
130-
{ status: 500 }
131-
)
177+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
132178
}
133179
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/settings-navigation/settings-navigation.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface SettingsNavigationProps {
2525
| 'team'
2626
| 'privacy'
2727
) => void
28-
hasOrganization?: boolean
28+
hasOrganization: boolean
2929
}
3030

3131
type NavigationItem = {
@@ -42,7 +42,6 @@ type NavigationItem = {
4242
icon: React.ComponentType<{ className?: string }>
4343
hideInDev?: boolean
4444
requiresTeam?: boolean
45-
requiresOrganization?: boolean
4645
}
4746

4847
const allNavigationItems: NavigationItem[] = [
@@ -94,7 +93,7 @@ const allNavigationItems: NavigationItem[] = [
9493
export function SettingsNavigation({
9594
activeSection,
9695
onSectionChange,
97-
hasOrganization = false,
96+
hasOrganization,
9897
}: SettingsNavigationProps) {
9998
const { getSubscriptionStatus } = useSubscriptionStore()
10099
const subscription = getSubscriptionStatus()
@@ -109,11 +108,6 @@ export function SettingsNavigation({
109108
return false
110109
}
111110

112-
// Hide organization tab if user is not part of an organization
113-
if (item.requiresOrganization && !hasOrganization) {
114-
return false
115-
}
116-
117111
return true
118112
})
119113

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/edit-member-limit-dialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export function EditMemberLimitDialog({
163163
onChange={(e) => setLimitValue(e.target.value)}
164164
className='pl-9'
165165
min={planMinimum}
166+
max={10000}
166167
step='1'
167168
placeholder={planMinimum.toString()}
168169
/>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/team-usage-overview.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,38 @@ export function TeamUsageOverview({ hasAdminAccess }: TeamUsageOverviewProps) {
4242
setEditDialogOpen(true)
4343
}
4444

45-
const handleSaveLimit = async (userId: string, newLimit: number) => {
46-
if (!activeOrg?.id) return
45+
const handleSaveLimit = async (
46+
userId: string,
47+
newLimit: number
48+
): Promise<{ success: boolean; error?: string }> => {
49+
if (!activeOrg?.id) {
50+
return { success: false, error: 'No active organization found' }
51+
}
4752

4853
try {
4954
setIsUpdating(true)
5055
const result = await updateMemberUsageLimit(userId, activeOrg.id, newLimit)
5156

5257
if (!result.success) {
53-
throw new Error(result.error || 'Failed to update usage limit')
58+
logger.error('Failed to update usage limit', { error: result.error, userId, newLimit })
59+
return { success: false, error: result.error || 'Failed to update usage limit' }
5460
}
61+
62+
logger.info('Successfully updated member usage limit', {
63+
userId,
64+
newLimit,
65+
organizationId: activeOrg.id,
66+
})
67+
return { success: true }
5568
} catch (error) {
56-
logger.error('Failed to update usage limit', { error })
57-
throw error
69+
const errorMessage = error instanceof Error ? error.message : 'Failed to update usage limit'
70+
logger.error('Failed to update usage limit', {
71+
error,
72+
userId,
73+
newLimit,
74+
organizationId: activeOrg.id,
75+
})
76+
return { success: false, error: errorMessage }
5877
} finally {
5978
setIsUpdating(false)
6079
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/member-invitation-card.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,11 @@ export function MemberInvitationCard({
220220
{isSelected && (
221221
<div className='flex items-center gap-2'>
222222
<PermissionSelector
223-
value={(selectedWorkspace?.permission || 'read') as PermissionType}
223+
value={
224+
(['read', 'write', 'admin'].includes(selectedWorkspace?.permission)
225+
? selectedWorkspace.permission
226+
: 'read') as PermissionType
227+
}
224228
onChange={(permission) => onWorkspaceToggle(workspace.id, permission)}
225229
disabled={isInviting}
226230
className='h-8'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/no-organization-view.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ export function NoOrganizationView({
5050
<label htmlFor='orgName' className='font-medium text-sm'>
5151
Team Name
5252
</label>
53-
<Input value={orgName} onChange={onOrgNameChange} placeholder='My Team' />
53+
<Input
54+
id='orgName'
55+
value={orgName}
56+
onChange={onOrgNameChange}
57+
placeholder='My Team'
58+
/>
5459
</div>
5560

5661
<div className='space-y-2'>
@@ -62,6 +67,7 @@ export function NoOrganizationView({
6267
simstudio.ai/team/
6368
</div>
6469
<Input
70+
id='orgSlug'
6571
value={orgSlug}
6672
onChange={(e) => setOrgSlug(e.target.value)}
6773
className='rounded-l-none'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/organization-creation-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function OrganizationCreationDialog({
4949
<label htmlFor='orgName' className='font-medium text-sm'>
5050
Team Name
5151
</label>
52-
<Input value={orgName} onChange={onOrgNameChange} placeholder='My Team' />
52+
<Input id='orgName' value={orgName} onChange={onOrgNameChange} placeholder='My Team' />
5353
</div>
5454

5555
<div className='space-y-2'>

0 commit comments

Comments
 (0)