Skip to content

Commit fc3b1a4

Browse files
HotFix: Simplify teamIdOrSlug schema validation (#185)
The previous TeamIdOrSlugSchema regex did not match the possible slugs in the database 100% of the time. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Loosens teamIdOrSlug validation, verifies UUIDs against DB to avoid slug/ID collisions, and adds structured warning logs in dashboard and team middleware. > > - **Team resolution/backend**: > - `TeamIdOrSlugSchema` now accepts any `string` (removed slug regex) alongside `uuid`. > - `getTeamIdFromSegment` verifies UUIDs against `teams` table and ensures they aren’t treated as IDs when they match a slug; retains slug lookup and uses `server-only`. > - **Logging**: > - Adds warning logs in `app/dashboard/[teamIdOrSlug]/layout.tsx` when team cannot be resolved (with serialized error). > - Adds warning logs in `withTeamIdResolution` middleware for invalid `teamIdOrSlug` and unauthorized access. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bbb083f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4d66499 commit fc3b1a4

File tree

4 files changed

+50
-8
lines changed

4 files changed

+50
-8
lines changed

src/app/dashboard/[teamIdOrSlug]/layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { AUTH_URLS } from '@/configs/urls'
44
import { DashboardContextProvider } from '@/features/dashboard/context'
55
import DashboardLayoutView from '@/features/dashboard/layout/layout'
66
import Sidebar from '@/features/dashboard/sidebar/sidebar'
7+
import { l } from '@/lib/clients/logger/logger'
78
import { getSessionInsecure } from '@/server/auth/get-session'
89
import getUserByToken from '@/server/auth/get-user-by-token'
910
import { getTeam } from '@/server/team/get-team'
1011
import { SidebarInset, SidebarProvider } from '@/ui/primitives/sidebar'
1112
import { cookies } from 'next/headers'
1213
import { redirect, unauthorized } from 'next/navigation'
1314
import { Metadata } from 'next/types'
15+
import { serializeError } from 'serialize-error'
1416

1517
export const metadata: Metadata = {
1618
title: 'Dashboard - E2B',
@@ -48,6 +50,17 @@ export default async function DashboardLayout({
4850
const team = teamRes?.data
4951

5052
if (!team) {
53+
l.warn(
54+
{
55+
key: 'dashboard_layout:team_not_resolved',
56+
user_id: data.user.id,
57+
error: serializeError(teamRes?.serverError),
58+
context: {
59+
teamIdOrSlug,
60+
},
61+
},
62+
`dashboard_layout:team_not_resolved - team not resolved for user (${data.user.id}) when accessing team (${teamIdOrSlug}) in dashboard layout`
63+
)
5164
throw unauthorized()
5265
}
5366

src/lib/clients/action.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,32 @@ export const withTeamIdResolution = createMiddleware<{
215215
const teamId = await getTeamIdFromSegment(clientInput.teamIdOrSlug as string)
216216

217217
if (!teamId) {
218+
l.warn(
219+
{
220+
key: 'with_team_id_resolution:invalid_team_id_or_slug',
221+
context: {
222+
teamIdOrSlug: clientInput.teamIdOrSlug,
223+
},
224+
},
225+
`with_team_id_resolution:invalid_team_id_or_slug - invalid team id or slug provided through withTeamIdResolution middleware: ${clientInput.teamIdOrSlug}`
226+
)
227+
218228
throw unauthorized()
219229
}
220230

221231
const isAuthorized = await checkUserTeamAuthorization(ctx.user.id, teamId)
222232

223233
if (!isAuthorized) {
234+
l.warn(
235+
{
236+
key: 'with_team_id_resolution:user_not_authorized',
237+
context: {
238+
teamIdOrSlug: clientInput.teamIdOrSlug,
239+
},
240+
},
241+
`with_team_id_resolution:user_not_authorized - user not authorized to access team: ${clientInput.teamIdOrSlug}`
242+
)
243+
224244
throw unauthorized()
225245
}
226246

src/lib/schemas/team.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { z } from 'zod'
22

33
export const TeamIdOrSlugSchema = z.union([
44
z.uuid(),
5-
z
6-
.string()
7-
.regex(
8-
/^[a-z0-9]+(-[a-z0-9]+)*$/i,
9-
'Must be a valid slug (words separated by hyphens)'
10-
),
5+
z.string(),
6+
// FIXME: Add correct team regex as in db slug generation
7+
// .regex(
8+
// /^[a-z0-9]+(-[a-z0-9]+)*$/i,
9+
// 'Must be a valid slug (words separated by hyphens)'
10+
// ),
1111
])

src/server/team/get-team-id-from-segment.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'server-cli-only'
1+
import 'server-only'
22

33
import { CACHE_TAGS } from '@/configs/cache'
44
import { l } from '@/lib/clients/logger/logger'
@@ -29,7 +29,16 @@ export const getTeamIdFromSegment = async (segment: string) => {
2929
}
3030

3131
if (z.uuid().safeParse(segment).success) {
32-
return segment
32+
// make sure this uuid is a valid teamId and is not it's slug
33+
const { data } = await supabaseAdmin
34+
.from('teams')
35+
.select('id')
36+
.not('slug', 'eq', segment)
37+
.eq('id', segment)
38+
39+
if (data?.length) {
40+
return data[0]!.id
41+
}
3342
}
3443

3544
const { data, error } = await supabaseAdmin

0 commit comments

Comments
 (0)