-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Record fake click for demo workspace #4444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors"; | ||
| import { parseRequestBody } from "@/lib/api/utils"; | ||
| import { prefixWorkspaceId } from "@/lib/api/workspaces/workspace-id"; | ||
| import { withAxiom } from "@/lib/axiom/server"; | ||
| import { getLinkWithPartner } from "@/lib/planetscale/get-link-with-partner"; | ||
| import { recordFakeClick } from "@/lib/tinybird/record-fake-click"; | ||
| import { | ||
| COUNTRY_CODES, | ||
| DEMO_PROGRAM_ID, | ||
| DEMO_WORKSPACE_ID, | ||
| getDomainWithoutWWW, | ||
| } from "@dub/utils"; | ||
| import { NextResponse } from "next/server"; | ||
| import * as z from "zod/v4"; | ||
|
|
||
| const demoClickSchema = z.object({ | ||
| domain: z.preprocess( | ||
| (val) => getDomainWithoutWWW(val as string), | ||
| z.string({ error: "domain is required." }), | ||
| ), | ||
| key: z.string({ error: "key is required." }), | ||
| country: z.enum(COUNTRY_CODES), | ||
| region: z.string().nullish(), | ||
| city: z.string().nullish(), | ||
| continent: z.string().nullish(), | ||
| referrer: z.string().nullish(), | ||
| userAgent: z.string().nullish(), | ||
| }); | ||
|
|
||
| function verifyDemoClickSecret(req: Request) { | ||
| const secret = process.env.DEMO_CLICK_SECRET; | ||
| const authorization = req.headers.get("authorization"); | ||
|
|
||
| if (!secret || authorization !== `Bearer ${secret}`) { | ||
| throw new DubApiError({ | ||
| code: "unauthorized", | ||
| message: "Invalid or missing DEMO_CLICK_SECRET.", | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // POST /api/demo/click – mint a geo-accurate click for the LoopWork demo workspace only | ||
| export const POST = withAxiom(async (req) => { | ||
| try { | ||
| verifyDemoClickSecret(req); | ||
|
|
||
| const { | ||
| domain, | ||
| key, | ||
| country, | ||
| region, | ||
| city, | ||
| continent, | ||
| referrer, | ||
| userAgent, | ||
| } = demoClickSchema.parse(await parseRequestBody(req)); | ||
|
|
||
| const link = await getLinkWithPartner({ domain, key }); | ||
|
|
||
| if (!link) { | ||
| throw new DubApiError({ | ||
| code: "not_found", | ||
| message: `Link not found for domain: ${domain} and key: ${key}.`, | ||
| }); | ||
| } | ||
|
|
||
| if ( | ||
| prefixWorkspaceId(link.projectId) !== prefixWorkspaceId(DEMO_WORKSPACE_ID) | ||
| ) { | ||
| throw new DubApiError({ | ||
| code: "forbidden", | ||
| message: "This endpoint can only record clicks for the demo workspace.", | ||
| }); | ||
| } | ||
|
|
||
| if (link.programId && link.programId !== DEMO_PROGRAM_ID) { | ||
| throw new DubApiError({ | ||
| code: "forbidden", | ||
| message: "This endpoint can only record clicks for the demo program.", | ||
| }); | ||
| } | ||
|
|
||
| const clickEvent = await recordFakeClick({ | ||
| link: { | ||
| id: link.id, | ||
| url: link.url, | ||
| domain: link.domain, | ||
| key: link.key, | ||
| projectId: link.projectId, | ||
| programId: link.programId, | ||
| partnerId: link.partnerId, | ||
| }, | ||
| customer: { | ||
| country, | ||
| region, | ||
| city, | ||
| continent, | ||
| }, | ||
| referrer, | ||
| userAgent, | ||
| }); | ||
|
|
||
| return NextResponse.json({ clickId: clickEvent.click_id }); | ||
| } catch (error) { | ||
| return handleAndReturnErrorResponse(error); | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -447,9 +447,13 @@ async function recordEvents(args: RecordEventsArgs) { | |
| timestamp: clickedAt.toISOString(), | ||
| identity_hash: targetCustomer.externalId || targetCustomer.id, | ||
| click_id: clickId, | ||
| workspace_id: workspace.id, | ||
| link_id: targetLink.id, | ||
| domain: targetLink.domain, | ||
| key: targetLink.key, | ||
| url: targetLink.url, | ||
| ip: "127.0.0.1", | ||
| country: targetCustomer.country || "Unknown", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Normalize The code copies 🤖 Prompt for AI Agents |
||
| continent: targetCustomer.country | ||
| ? COUNTRIES_TO_CONTINENTS[targetCustomer.country.toUpperCase()] || "" | ||
| : "", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { nanoid } from "@dub/utils"; | ||
| import { COUNTRIES_TO_CONTINENTS, nanoid } from "@dub/utils"; | ||
| import { Link } from "@prisma/client"; | ||
| import { clickEventSchemaTB } from "../zod/schemas/clicks"; | ||
| import { recordClick } from "./record-click"; | ||
|
|
@@ -25,22 +25,50 @@ export async function recordFakeClick({ | |
| link, | ||
| customer, | ||
| timestamp, | ||
| referrer, | ||
| userAgent, | ||
| }: { | ||
| link: Pick<Link, "id" | "url" | "domain" | "key" | "projectId">; | ||
| link: Pick<Link, "id" | "url" | "domain" | "key" | "projectId"> & { | ||
| programId?: string | null; | ||
| partnerId?: string | null; | ||
| }; | ||
| customer?: { | ||
| country?: string | null; | ||
| region?: string | null; | ||
| continent?: string | null; | ||
| city?: string | null; | ||
| latitude?: string | null; | ||
| longitude?: string | null; | ||
| }; | ||
| timestamp?: string | number; | ||
| referrer?: string | null; | ||
| userAgent?: string | null; | ||
| }) { | ||
| const country = toSafeHeaderValue(customer?.country) || "US"; | ||
| const continent = | ||
| toSafeHeaderValue(customer?.continent) || | ||
| COUNTRIES_TO_CONTINENTS[country] || | ||
| "NA"; | ||
|
|
||
| const dummyRequest = new Request(link.url, { | ||
| headers: new Headers({ | ||
| "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", | ||
| "user-agent": | ||
| userAgent || "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", | ||
|
Comment on lines
+55
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applicable repository conventions ---'
find /tmp/coderabbit-repo-knowledge/dubinc-dub-890dfd63 -type f -name '*.md' -maxdepth 3 -print
printf '%s\n' '--- file outline ---'
ast-grep outline apps/web/lib/tinybird/record-fake-click.ts
printf '%s\n' '--- changed hunk ---'
git diff -- apps/web/lib/tinybird/record-fake-click.ts
printf '%s\n' '--- implementation and directly bound definitions ---'
cat -n apps/web/lib/tinybird/record-fake-click.tsRepository: dubinc/dub Length of output: 6296 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- demoClickSchema and userAgent bindings ---'
rg -n -C 4 'demoClickSchema|userAgent' apps/web --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- relevant learning scopes ---'
cat /tmp/coderabbit-repo-knowledge/dubinc-dub-890dfd63/learnings/apps-web-lib.md
cat /tmp/coderabbit-repo-knowledge/dubinc-dub-890dfd63/learnings/apps-web.mdRepository: dubinc/dub Length of output: 33490 🏁 Script executed: #!/bin/bash
set -eu
node - <<'JS'
const value = "Mozilla/5.0 🤖";
try {
new Headers({ "user-agent": value });
console.log("Headers accepted the Unicode value");
} catch (error) {
console.log(`${error.constructor.name}: ${error.message}`);
}
JSRepository: dubinc/dub Length of output: 444 Sanitize
🤖 Prompt for AI Agents |
||
| "x-forwarded-for": "127.0.0.1", | ||
| "x-vercel-ip-country": toSafeHeaderValue(customer?.country) || "US", | ||
| "x-vercel-ip-country": country, | ||
| "x-vercel-ip-country-region": toSafeHeaderValue(customer?.region) || "CA", | ||
| "x-vercel-ip-continent": toSafeHeaderValue(customer?.continent) || "NA", | ||
| "x-vercel-ip-continent": continent, | ||
| ...(customer?.city && { | ||
| "x-vercel-ip-city": toSafeHeaderValue(customer.city) || "Unknown", | ||
| }), | ||
| ...(customer?.latitude && { | ||
| "x-vercel-ip-latitude": | ||
| toSafeHeaderValue(customer.latitude) || "Unknown", | ||
| }), | ||
| ...(customer?.longitude && { | ||
| "x-vercel-ip-longitude": | ||
| toSafeHeaderValue(customer.longitude) || "Unknown", | ||
| }), | ||
| }), | ||
| }); | ||
|
|
||
|
|
@@ -52,8 +80,11 @@ export async function recordFakeClick({ | |
| domain: link.domain, | ||
| key: link.key, | ||
| url: link.url, | ||
| programId: link.programId ?? undefined, | ||
| partnerId: link.partnerId ?? undefined, | ||
| skipRatelimit: true, | ||
| shouldCacheClickId: true, | ||
| ...(referrer && { referrer }), | ||
| ...(timestamp && { timestamp: new Date(timestamp).toISOString() }), | ||
| }); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Authorization Bypass (CWE-862): Missing Authorization
Reachability: External · Exploitability: Difficult
Reject links that have no demo program.
When
link.programIdisnull, the current condition allowsrecordFakeClickto run. Requirelink.programId === DEMO_PROGRAM_IDand add a test that expects403with no recording.🤖 Prompt for AI Agents