Skip to content

Commit 05d1c92

Browse files
committed
added zod
1 parent 9228893 commit 05d1c92

File tree

2 files changed

+25
-11
lines changed

2 files changed

+25
-11
lines changed

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { eq } from 'drizzle-orm'
1818
import { nanoid } from 'nanoid'
1919
import { cookies } from 'next/headers'
2020
import { NextResponse } from 'next/server'
21+
import { z } from 'zod'
2122
import { getSession } from '@/lib/auth'
2223
import { applyBonusCredits } from '@/lib/billing/credits/bonus'
2324

@@ -26,11 +27,21 @@ const logger = createLogger('AttributionAPI')
2627
const COOKIE_NAME = 'sim_utm'
2728
const CLOCK_DRIFT_TOLERANCE_MS = 60 * 1000
2829

30+
const UtmCookieSchema = z.object({
31+
utm_source: z.string().optional(),
32+
utm_medium: z.string().optional(),
33+
utm_campaign: z.string().optional(),
34+
utm_content: z.string().optional(),
35+
referrer_url: z.string().optional(),
36+
landing_page: z.string().optional(),
37+
created_at: z.string().min(1),
38+
})
39+
2940
/**
3041
* Finds the most specific active campaign matching the given UTM params.
3142
* Null fields on a campaign act as wildcards. Ties broken by newest campaign.
3243
*/
33-
async function findMatchingCampaign(utmData: Record<string, string>) {
44+
async function findMatchingCampaign(utmData: z.infer<typeof UtmCookieSchema>) {
3445
const campaigns = await db
3546
.select()
3647
.from(referralCampaigns)
@@ -89,25 +100,24 @@ export async function POST() {
89100
return NextResponse.json({ attributed: false, reason: 'no_utm_cookie' })
90101
}
91102

92-
let utmData: Record<string, string>
103+
let utmData: z.infer<typeof UtmCookieSchema>
93104
try {
94-
// Decode first, falling back to raw value if UTM params contain bare %
95105
let decoded: string
96106
try {
97107
decoded = decodeURIComponent(utmCookie.value)
98108
} catch {
99109
decoded = utmCookie.value
100110
}
101-
utmData = JSON.parse(decoded)
111+
utmData = UtmCookieSchema.parse(JSON.parse(decoded))
102112
} catch {
103113
logger.warn('Failed to parse UTM cookie', { userId: session.user.id })
104114
cookieStore.delete(COOKIE_NAME)
105115
return NextResponse.json({ attributed: false, reason: 'invalid_cookie' })
106116
}
107117

108118
const cookieCreatedAt = Number(utmData.created_at)
109-
if (!cookieCreatedAt || !Number.isFinite(cookieCreatedAt)) {
110-
logger.warn('UTM cookie missing created_at timestamp', { userId: session.user.id })
119+
if (!Number.isFinite(cookieCreatedAt)) {
120+
logger.warn('UTM cookie has invalid created_at timestamp', { userId: session.user.id })
111121
cookieStore.delete(COOKIE_NAME)
112122
return NextResponse.json({ attributed: false, reason: 'invalid_cookie' })
113123
}

apps/sim/app/api/referral-code/redeem/route.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ import { createLogger } from '@sim/logger'
2020
import { and, eq } from 'drizzle-orm'
2121
import { nanoid } from 'nanoid'
2222
import { NextResponse } from 'next/server'
23+
import { z } from 'zod'
2324
import { getSession } from '@/lib/auth'
2425
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
2526
import { applyBonusCredits } from '@/lib/billing/credits/bonus'
2627

2728
const logger = createLogger('ReferralCodeRedemption')
2829

30+
const RedeemCodeSchema = z.object({
31+
code: z.string().min(1, 'Code is required'),
32+
})
33+
2934
export async function POST(request: Request) {
3035
try {
3136
const session = await getSession()
@@ -34,11 +39,7 @@ export async function POST(request: Request) {
3439
}
3540

3641
const body = await request.json()
37-
const { code } = body
38-
39-
if (!code || typeof code !== 'string') {
40-
return NextResponse.json({ error: 'Code is required' }, { status: 400 })
41-
}
42+
const { code } = RedeemCodeSchema.parse(body)
4243

4344
const subscription = await getHighestPrioritySubscription(session.user.id)
4445

@@ -160,6 +161,9 @@ export async function POST(request: Request) {
160161
bonusAmount,
161162
})
162163
} catch (error) {
164+
if (error instanceof z.ZodError) {
165+
return NextResponse.json({ error: error.errors[0].message }, { status: 400 })
166+
}
163167
logger.error('Referral code redemption error', { error })
164168
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
165169
}

0 commit comments

Comments
 (0)