Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Nov 26, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added device fingerprinting for enhanced partner identity verification during onboarding
    • Introduced geolocation mismatch detection in fraud risk assessment
    • Added duplicate account detection to prevent fraudulent activity
  • Chores

    • Restricted advanced fraud risk assessment features to Advanced and Enterprise plans
    • Standardized inactive partner enrollment status management

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Nov 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Error Error Dec 2, 2025 6:11pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

This PR introduces fingerprint-based device identification for partners during onboarding and login, refactors fraud risk detection with new device- and geolocation-based rules, consolidates hardcoded enrollment status arrays into shared constants, and adds plan-based gating to risk evaluation endpoints.

Changes

Cohort / File(s) Change Summary
Fingerprint Integration (Provider & Components)
apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx, apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx, apps/web/app/(ee)/partners.dub.co/(auth)/login/page.tsx, apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/page.tsx
New FingerprintProvider wraps login and onboarding forms with FingerprintJS Pro context; new PartnerLoginForm component replaces LoginForm on login page; onboarding page wrapped with FingerprintProvider.
Fingerprint Data Storage & Fetching
apps/web/lib/api/fraud/fingerprint.ts, apps/web/lib/actions/partners/onboard-partner.ts, apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx
New fetchVisitorFingerprint utility validates and retrieves visitor ID and country from Fingerprint API; onboarding flow integrates visitorId/visitorCountry into partner payload; form captures requestId and propagates visitor data.
Risk Detection Refactoring
apps/web/lib/api/fraud/get-application-risk-signals.ts, apps/web/lib/api/fraud/get-partner-high-risk-signals.ts, apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts
New getApplicationRiskSignals helper replaces getPartnerHighRiskSignals with comprehensive risk signal mapping; old file removed; new country-mismatch fraud check added.
Fraud Rules & Types
apps/web/lib/api/fraud/constants.ts, apps/web/lib/types.ts
Email-domain mismatch rule replaced with duplicate-account detection (severity: low → high); masked-email rule replaced with geolocation-mismatch rule (severity: low → medium); legacy rules re-added as separate entries; ExtendedFraudRuleType updated with new rule types.
Enrollment Status Constants
apps/web/lib/zod/schemas/partners.ts, apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts, apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts, apps/web/lib/actions/partners/reject-partner-application.ts, apps/web/ui/partners/partner-info-cards.tsx
New INACTIVE_PROGRAM_ENROLLMENT_STATUSES constant exported from partners schema; replaces hardcoded ["banned", "deactivated", "rejected"] arrays across multiple files.
Auto-Approval & Risk Gating
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
Replaced getPartnerHighRiskSignals with getApplicationRiskSignals; added plan-capability gating via getPlanCapabilities; conditional risk evaluation only when canManageFraudEvents is true; skip auto-approval if risk severity is "high".
Risk Endpoint Gating
apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts
GET handler now wrapped with requiredPlan constraint to ["advanced", "enterprise"]; simplified response using getApplicationRiskSignals; removed per-rule validators and intermediate aggregation logic.
Risk UI Updates
apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
PartnerApplicationRiskSummaryUpsell signature changed to parameterless function with hard-coded severity "high"; severity prop removed from call site; overlay styling refactored.
Database Schema
packages/prisma/schema/partner.prisma
Added optional visitorId and visitorCountry fields to Partner model; added index on visitorId.
Onboarding Schema & Env
apps/web/lib/zod/schemas/partners.ts, apps/web/.env.example
onboardPartnerSchema extended with required requestId field; environment variables added: NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY and FINGERPRINT_SECRET_KEY.
Dependencies
apps/web/package.json
Added @fingerprintjs/fingerprintjs-pro-react@^2.7.1 as runtime dependency.

Sequence Diagram(s)

sequenceDiagram
    actor Partner
    participant LoginPage as Login Page
    participant FingerprintJS as FingerprintJS Pro
    participant Backend as Backend API
    participant DB as Database
    
    Partner->>LoginPage: Visit partner login
    LoginPage->>FingerprintJS: Initialize FingerprintProvider
    FingerprintJS->>FingerprintJS: Generate device fingerprint
    Partner->>LoginPage: Submit login (email/password)
    LoginPage->>Backend: POST /auth/login with requestId
    Backend->>DB: Fetch partner by email
    DB-->>Backend: Partner record (if exists)
    Backend->>Backend: Generate session token
    Backend-->>LoginPage: Redirect to dashboard
    
    Partner->>Partner: Navigate to onboarding
    participant OnboardingPage as Onboarding Page
    Partner->>OnboardingPage: View onboarding form
    OnboardingPage->>FingerprintJS: Get visitor data
    FingerprintJS-->>OnboardingPage: visitorId, visitorCountry
    OnboardingPage->>OnboardingPage: Store in form state
    Partner->>OnboardingPage: Submit onboarding form
    OnboardingPage->>Backend: POST /onboard-partner with requestId
    Backend->>Backend: fetchVisitorFingerprint(requestId)
    Backend->>FingerprintJS: Query Fingerprint API for visitor data
    FingerprintJS-->>Backend: visitorId, visitorCountry
    Backend->>DB: Store Partner with fingerprint data
    Backend->>Backend: getApplicationRiskSignals(program, partner)
    Backend->>DB: Query for cross-program bans, duplicates
    DB-->>Backend: Risk signal results
    Backend->>Backend: Compute risk severity
    Backend-->>OnboardingPage: Success with risk assessment
Loading
sequenceDiagram
    participant Cron as Auto-Approve Cron
    participant API as Backend API
    participant DB as Database
    participant FraudAPI as Fraud Detection
    
    Cron->>API: Trigger auto-approve-partner
    API->>DB: Fetch pending program enrollments
    DB-->>API: List of pending enrollments
    loop For each enrollment
        API->>API: getPlanCapabilities(workspace)
        alt canManageFraudEvents = true
            API->>FraudAPI: getApplicationRiskSignals(program, partner)
            FraudAPI->>DB: Query fraud indicators
            DB-->>FraudAPI: Risk data
            FraudAPI-->>API: riskSignals, severity
            alt severity = "high"
                API->>API: Skip auto-approval, log warning
            else
                API->>DB: Update enrollment to approved
                DB-->>API: Success
            end
        else
            API->>DB: Auto-approve (no fraud checks)
            DB-->>API: Success
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Fraud rule refactoring (lib/api/fraud/constants.ts, lib/api/fraud/get-application-risk-signals.ts): Verify that new risk signal logic correctly identifies all previously-detected fraud cases; ensure severity mapping aligns with intended gating behavior (e.g., "high" severity blocks auto-approval).
  • Fingerprint API integration (lib/api/fraud/fingerprint.ts, lib/actions/partners/onboard-partner.ts): Confirm error handling doesn't block onboarding if Fingerprint API is unavailable; validate requestId threading through form → action → database.
  • Database schema changes (packages/prisma/schema/partner.prisma): Verify migration strategy for adding optional visitorId/visitorCountry fields and index; confirm no existing data or queries break.
  • Plan-capability gating (app/(ee)/api/cron/auto-approve-partner/route.ts, app/(ee)/api/partners/[partnerId]/application-risks/route.ts): Ensure getPlanCapabilities is called correctly and that requiredPlan constraint on the GET endpoint blocks non-qualifying plans as intended.
  • Enrollment status constant rollout (six files updating to use INACTIVE_PROGRAM_ENROLLMENT_STATUSES): Verify all hardcoded status arrays were replaced and no edge cases missed; confirm constant value matches previous literals.

Possibly related PRs

Suggested reviewers

  • steven-tey

🐰 Hop, hop—now we've got fingerprints to spare,
Device IDs floating through the network air,
Duplicate accounts beware, we've found your scheme,
Geolocation checks fulfill our fraud-free dream,
With plans in place and risks now clear—
Partner safety reigns supreme here!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fingerprint integration' accurately and concisely summarizes the main change in the changeset, which introduces Fingerprint device identification across partner authentication and onboarding flows.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fraud-fingerprint

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7acff8 and af66903.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (4 hunks)
  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (2)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-22)
apps/web/lib/api/fraud/get-application-risk-signals.ts (1)
  • getApplicationRiskSignals (12-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (3)

40-40: LGTM - consistent with component refactor.

The removal of the severity prop is consistent with the refactored PartnerApplicationRiskSummaryUpsell component that no longer accepts this parameter. This makes sense for the upsell view, where actual risk data should not be displayed to users without access.


156-161: LGTM - improved upsell overlay structure.

The UI restructuring improves the upsell overlay by:

  • Adding a dedicated non-blurred header area for better readability
  • Using proper layering with z-index to position the header above the blurred content
  • Maintaining visual hierarchy with the negative margins

The removal of backdrop-blur and the new header container enhance the visual clarity of the upsell message.


175-176: LGTM - improved clarity.

The text update improves grammatical flow by explicitly stating the features are "available on the Advanced plan" rather than just "Advanced plan".

apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (4)

2-2: LGTM!

Clean addition of required imports for the new risk evaluation and plan capability features.

Also applies to: 5-5


68-74: LGTM!

The explicit null check for partner correctly addresses previous review feedback and ensures safe access to partner data in subsequent operations. The check ordering is now correct with programEnrollment validated first.


54-54: I'll verify that the Partner model includes all required fields for the risk evaluation. Let me search the codebase to confirm this.
<function_calls>


#!/bin/bash

Find the Prisma schema file

fd -t f "schema.prisma" --exec cat {} ; | head -200


</function_calls>

Executing...

<function_calls>


#!/bin/bash

Search for Partner model definition in the repository

rg "model Partner" -A 60 --type prisma


</function_calls>

Executing...

<function_calls>


#!/bin/bash

Find where getApplicationRiskSignals is defined to see what fields it requires

rg -n "getApplicationRiskSignals" --type ts --type tsx -A 20


</function_calls>

Executing...


96-113: Design decisions verified as intentional and consistent.

After thorough verification of the codebase:

  1. Plan gating (enterprise/advanced only): Confirmed intentional. canManageFraudEvents is explicitly gated in getPlanCapabilities to only ["enterprise", "advanced"] plans, consistent with other premium features. Lower-tier plans bypass fraud checks during auto-approval.

  2. Severity threshold (only "high" blocks): Confirmed intentional. The code explicitly checks if (severity === "high") with clear logging. Multiple fraud rules are evaluated across high, medium, and low severity levels, but only the highest severity of "high" triggers the auto-approval block.

Both design decisions are well-documented through clear code comments and follow established patterns throughout the codebase. No TODOs or FIXMEs indicate incomplete work.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Introduced `checkPartnerCountryMismatch` and `checkPartnerDuplicateAccount` functions to enhance fraud detection.
- Updated the `FRAUD_RULES` to include new checks for country mismatch and duplicate accounts.
- Modified the onboarding process to capture visitor country and adjust payload accordingly.
- Refactored visitor fingerprint response to include visitor country information.
- Adjusted Prisma schema to accommodate new visitor country field.
- Replaced `getPartnerHighRiskSignals` with `getApplicationRiskSignals` to streamline risk assessment for partners.
- Updated the auto-approval logic to evaluate risk signals based on workspace plan capabilities.
- Removed deprecated functions and adjusted related API routes to enhance clarity and maintainability.
- Introduced new risk signal checks for improved fraud detection accuracy.
@devkiran devkiran marked this pull request as ready for review November 28, 2025 07:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (5)
apps/web/.env.example (1)

160-162: Address formatting issues flagged by static analysis.

Two minor formatting issues to fix:

  1. Add a trailing newline at the end of the file
  2. Consider reordering keys (FINGERPRINT_SECRET_KEY before NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY) for consistency with the pattern of non-public keys before public ones

Apply this diff to fix:

 # Fingerprint
-NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY=
 FINGERPRINT_SECRET_KEY=
+NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY=
+
apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts (1)

4-16: Logic is sound, consider adding country code validation.

The function correctly handles missing data and performs case-insensitive comparison. However, consider validating that both country codes are valid ISO 3166-1 codes before comparison to catch data quality issues early.

Example enhancement:

import { COUNTRY_CODES } from "@dub/utils";

export function checkPartnerCountryMismatch(
  partner: Pick<Partner, "country" | "visitorCountry">,
) {
  if (!partner.visitorCountry || !partner.country) {
    return false;
  }

  const normalizedVisitorCountry = partner.visitorCountry.toUpperCase().trim();
  const normalizedCountry = partner.country.toUpperCase().trim();
  
  // Optionally validate against COUNTRY_CODES if you want to catch invalid codes
  // if (!COUNTRY_CODES.includes(normalizedVisitorCountry) || !COUNTRY_CODES.includes(normalizedCountry)) {
  //   return false;
  // }

  return normalizedVisitorCountry !== normalizedCountry;
}
apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx (1)

1-21: FingerprintProvider wiring looks correct; consider guarding the API key

The FpjsProvider setup (apiKey + default endpoints) looks aligned with the library’s pattern. To avoid accidentally passing "undefined" when the env var is missing, you may want to normalize/guard:

-export function FingerprintProvider({ children }: { children: ReactNode }) {
-  return (
-    <FpjsProvider
-      loadOptions={{
-        apiKey: `${process.env.NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY}`,
+export function FingerprintProvider({ children }: { children: ReactNode }) {
+  const apiKey = process.env.NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY ?? "";
+
+  return (
+    <FpjsProvider
+      loadOptions={{
+        apiKey,
         endpoint: [FingerprintJSPro.defaultEndpoint],
         scriptUrlPattern: [FingerprintJSPro.defaultScriptUrlPattern],
       }}
     >
       {children}
     </FpjsProvider>
   );
 }

You can also add a dev-time warning if apiKey is empty.

apps/web/app/(ee)/partners.dub.co/(auth)/login/page.tsx (1)

7-7: Login page now wraps PartnerLoginForm with FingerprintProvider, but form still has TODO

The provider integration here looks fine, but PartnerLoginForm (per its TODO) still doesn’t actually use the fingerprint context/hooks, so this change alone won’t collect any fingerprint data on login. Either wire PartnerLoginForm up to useVisitorData (or similar) in this PR or explicitly track that as a follow-up.

Also applies to: 9-9, 66-70

apps/web/lib/api/fraud/fingerprint.ts (1)

77-84: Document error status handling behavior.

The catch block returns { visitorId: null, status: "error" }, which based on the onboarding action code, currently allows onboarding to proceed (only not_found blocks). This is inconsistent with the explicit throw for server errors (lines 58-60).

Consider adding a comment explaining this behavior:

 } catch (error) {
   console.error("[Fingerprint] Error fetching visitor ID.", error);
 
+  // Return error status to allow onboarding to proceed despite fingerprint service issues
+  // This prevents legitimate users from being blocked by transient service failures
   return {
     visitorId: null,
     status: "error",
   };
 }

Alternatively, if "error" should also block onboarding, update the onboarding action to check for status !== "valid" instead of just status === "not_found".

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d126bfc and 00ae03c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • apps/web/.env.example (1 hunks)
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (3 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (2 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (2 hunks)
  • apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(auth)/login/page.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx (6 hunks)
  • apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/page.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx (1 hunks)
  • apps/web/lib/actions/partners/onboard-partner.ts (5 hunks)
  • apps/web/lib/actions/partners/reject-partner-application.ts (2 hunks)
  • apps/web/lib/api/fraud/constants.ts (3 hunks)
  • apps/web/lib/api/fraud/fingerprint.ts (1 hunks)
  • apps/web/lib/api/fraud/get-application-risk-signals.ts (1 hunks)
  • apps/web/lib/api/fraud/get-partner-high-risk-signals.ts (0 hunks)
  • apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts (1 hunks)
  • apps/web/lib/types.ts (2 hunks)
  • apps/web/lib/zod/schemas/partners.ts (2 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/ui/partners/partner-info-cards.tsx (2 hunks)
  • packages/prisma/schema/partner.prisma (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/lib/api/fraud/get-partner-high-risk-signals.ts
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
📚 Learning: 2025-09-24T16:13:00.387Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: packages/prisma/schema/partner.prisma:151-153
Timestamp: 2025-09-24T16:13:00.387Z
Learning: In the Dub codebase, Prisma schemas use single-column indexes without brackets (e.g., `@index(partnerId)`) and multi-column indexes with brackets (e.g., `@index([programId, partnerId])`). This syntax pattern is consistently used throughout their schema files and works correctly with their Prisma version.

Applied to files:

  • packages/prisma/schema/partner.prisma
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/ui/partners/partner-info-cards.tsx
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
  • apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts
  • apps/web/lib/api/fraud/get-application-risk-signals.ts
  • apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts
  • apps/web/lib/api/fraud/fingerprint.ts
  • apps/web/lib/types.ts
  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
  • apps/web/lib/api/fraud/constants.ts
  • apps/web/lib/actions/partners/onboard-partner.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
  • apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/ui/partners/partner-info-cards.tsx
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/zod/schemas/partners.ts
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/ui/partners/partner-info-cards.tsx
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx
  • apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx
  • apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx
📚 Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.

Applied to files:

  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
  • apps/web/lib/api/fraud/rules/check-partner-country-mismatch.ts
  • apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/lib/types.ts
  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/lib/api/fraud/constants.ts
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/ui/partners/partner-info-cards.tsx
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/actions/partners/reject-partner-application.ts
  • apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx
📚 Learning: 2025-06-04T15:09:51.562Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2471
File: apps/web/ui/auth/reset-password-form.tsx:52-65
Timestamp: 2025-06-04T15:09:51.562Z
Learning: In the Dub codebase, server-side validation errors for password fields are handled via toast notifications rather than using react-hook-form's setError method to display field-specific errors. This architectural pattern separates client-side validation feedback (inline) from server-side error handling (toast).

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx
📚 Learning: 2025-08-14T05:00:23.224Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2735
File: apps/web/app/api/og/program/route.tsx:63-64
Timestamp: 2025-08-14T05:00:23.224Z
Learning: In Dub's partner program system, the default partner group will always exist. When programs are created, a default partner group is automatically upserted using DEFAULT_PARTNER_GROUP constant, so accessing program.groups[0] in contexts where the default group is queried is safe.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
🧬 Code graph analysis (12)
apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx (1)
apps/web/ui/auth/login/login-form.tsx (1)
  • LoginForm (72-207)
apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_PROGRAM_ENROLLMENT_STATUSES (26-30)
apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/page.tsx (2)
apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx (1)
  • FingerprintProvider (9-21)
apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx (1)
  • OnboardingForm (33-327)
apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (2)
apps/web/lib/api/fraud/get-application-risk-signals.ts (1)
  • getApplicationRiskSignals (12-85)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-22)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_PROGRAM_ENROLLMENT_STATUSES (26-30)
apps/web/lib/actions/partners/reject-partner-application.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_PROGRAM_ENROLLMENT_STATUSES (26-30)
apps/web/ui/partners/partner-info-cards.tsx (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_PROGRAM_ENROLLMENT_STATUSES (26-30)
apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx (1)
apps/web/lib/actions/parse-action-errors.ts (1)
  • parseActionError (3-29)
apps/web/app/(ee)/partners.dub.co/(auth)/login/page.tsx (2)
apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx (1)
  • FingerprintProvider (9-21)
apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx (1)
  • PartnerLoginForm (5-10)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (2)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-22)
apps/web/lib/api/fraud/get-application-risk-signals.ts (1)
  • getApplicationRiskSignals (12-85)
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_PROGRAM_ENROLLMENT_STATUSES (26-30)
apps/web/lib/actions/partners/onboard-partner.ts (2)
apps/web/lib/api/fraud/fingerprint.ts (1)
  • fetchVisitorFingerprint (26-85)
apps/web/lib/partners/complete-program-applications.ts (1)
  • completeProgramApplications (19-230)
🪛 dotenv-linter (4.0.0)
apps/web/.env.example

[warning] 162-162: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 162-162: [UnorderedKey] The FINGERPRINT_SECRET_KEY key should go before the NEXT_PUBLIC_FINGERPRINT_PUBLIC_KEY key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (21)
packages/prisma/schema/partner.prisma (2)

65-67: LGTM! Fingerprint fields added appropriately.

The optional visitorId and visitorCountry fields enable device-based fraud detection while allowing gradual rollout. Making them nullable ensures backward compatibility with existing partners.


91-91: Index on visitorId will support fraud detection queries.

The index follows the single-column syntax convention and will optimize lookups when checking for duplicate accounts from the same device.

apps/web/lib/types.ts (1)

683-685: New fraud rule types added correctly.

The partnerDuplicateAccount and partnerCountryMismatch types align with the corresponding fraud rule definitions in constants.ts and support the Fingerprint integration.

apps/web/lib/api/fraud/constants.ts (4)

38-38: Comment clarifies severity-based ordering.

The updated comment accurately reflects that partner rules are now ordered by severity (high → medium → low).


67-74: High severity assignment for duplicate account detection is appropriate.

Detecting multiple accounts from the same device (partnerDuplicateAccount) is correctly classified as high severity since it strongly indicates fraudulent behavior.


76-83: Medium severity for geolocation mismatch is reasonable.

The partnerCountryMismatch rule is appropriately set to medium severity since location mismatches could result from VPNs, proxies, or legitimate travel scenarios, making it less definitive than device-based duplicate detection.


93-109: Original rules correctly preserved.

Good that partnerEmailDomainMismatch and partnerEmailMasked were re-added to maintain existing fraud detection capabilities alongside the new device-based rules.

apps/web/ui/partners/partner-info-cards.tsx (1)

10-10: Centralizing inactive-status logic via shared constant looks good

Using INACTIVE_PROGRAM_ENROLLMENT_STATUSES.includes(partner.status) keeps UI behavior aligned with backend/business rules and avoids drift from ad-hoc status lists.

Also applies to: 300-302

apps/web/package.json (1)

32-32: Dependency addition for FingerprintJS Pro React

Adding @fingerprintjs/fingerprintjs-pro-react here matches the new provider/hooks usage and looks correct; just double-check this version’s compatibility with your current React (19.1.1) and Next (15.5.4) stack.

apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx (1)

3-3: Onboarding form: fingerprint requestId + error handling wiring looks solid

Using useVisitorData to populate requestId into the form and switching to parseActionError for toasts fits cleanly with the new fraud/fingerprint pipeline; the async submit wrapper is also fine.

Also applies to: 20-20, 54-61, 97-101, 116-117, 127-129

apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1)

9-12: Using INACTIVE_PROGRAM_ENROLLMENT_STATUSES for link-creation guard is a good centralization

Switching the forbidden check to INACTIVE_PROGRAM_ENROLLMENT_STATUSES.includes(status) keeps this API aligned with the single source of truth for inactive enrollment statuses and avoids future drift if new inactive states are introduced.

Also applies to: 68-72

apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (1)

88-105: LGTM! Clean refactor to centralized risk evaluation.

The plan-gated risk evaluation approach is well-structured. It correctly:

  • Gates risk checks behind the canManageFraudEvents capability
  • Uses the centralized getApplicationRiskSignals function
  • Blocks auto-approval only for high-severity risks
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)

8-11: LGTM! Good consolidation of shared constants.

Replacing the hardcoded status array with INACTIVE_PROGRAM_ENROLLMENT_STATUSES improves maintainability and ensures consistency across enrollment status checks throughout the codebase.

Also applies to: 26-26

apps/web/lib/actions/partners/onboard-partner.ts (2)

41-47: Verify handling of fingerprint "error" status.

The code blocks onboarding when result.status === "not_found" but allows it to proceed when status is "error". Based on the fingerprint API implementation, "error" status indicates a caught exception during the fetch.

Should onboarding be blocked for "error" status as well, or is it intentional to allow onboarding to proceed when fingerprint verification fails due to technical errors (to avoid blocking legitimate users)?

If "error" should also block onboarding, apply this diff:

-if (result.status === "not_found") {
+if (result.status !== "valid") {
   throw new Error(
     "We're having trouble verifying your request. Please refresh the page and try again.",
   );
 }

Otherwise, consider adding a comment explaining why "error" doesn't block onboarding:

+// Allow onboarding to proceed on "error" status to avoid blocking legitimate users
+// when fingerprint service is unavailable
 if (result.status === "not_found") {

59-60: LGTM! Clean conditional payload enrichment.

The fingerprint data is correctly added to the partner payload only when available. The conditional spread syntax properly handles the optional nature of visitorId and visitorCountry.

apps/web/lib/api/fraud/get-application-risk-signals.ts (3)

33-62: LGTM! Efficient parallel risk data collection.

The database queries are well-structured:

  • Uses Promise.all for parallel execution
  • Properly filters by partnerId and programId
  • Conditionally executes duplicate account query only when visitorId is present
  • Returns appropriate defaults (0) when data is unavailable

64-73: LGTM! Risk signal thresholds are correctly implemented.

The risk signal logic correctly uses:

  • > 0 for cross-program bans (any ban is a risk)
  • > 1 for duplicate payout/account (count of 1 = current partner only, >1 = actual duplicates)
  • Direct boolean checks for partner validation rules

75-84: LGTM! Clean severity computation and return value.

The function correctly:

  • Filters triggered rules based on risk signals
  • Computes the highest severity from triggered rules
  • Returns both the detailed signals and overall severity for downstream use
apps/web/lib/api/fraud/fingerprint.ts (2)

5-22: LGTM! Well-designed schema and type definitions.

The Zod schema properly validates the nested Fingerprint API response structure, and the FingerprintVisitorResponse discriminated union provides excellent type safety with the status discriminator and appropriate field types for each variant.


50-67: Verify error handling strategy for fingerprint failures.

The function throws errors for status >= 500 (server errors) but returns not_found for 4xx errors. This means:

  • Server errors will propagate and block onboarding
  • Client errors (e.g., invalid requestId) return gracefully

Based on the onboarding action code, this will:

  • Block onboarding for not_found status
  • Block onboarding when errors are thrown (server errors >= 500)

This seems correct, but consider if you want to distinguish between "invalid requestId" (should block) vs "server unavailable" (might want to allow onboarding with degraded fraud detection).

The current behavior blocks onboarding for both fingerprint validation failures and service outages. Is this the intended UX, or should service outages allow onboarding to proceed without fingerprint verification?

apps/web/app/(ee)/partners.dub.co/(auth)/login/partner-login-form.tsx (1)

5-10: Remove the stale TODO comment—FingerprintProvider is already configured at the parent level but not actually used.

The parent page.tsx wraps PartnerLoginForm with FingerprintProvider, which provides access to fingerprint context for any child components. However, neither PartnerLoginForm nor the LoginForm component it renders actually use fingerprint functionality (no useFingerprintJS hook calls). The TODO appears incomplete or abandoned. Either remove the comment if fingerprint integration is no longer planned, or clarify what specific fingerprint functionality is needed and implement it using the already-available context.

import { getSession } from "@/lib/auth";
import { prisma } from "@dub/prisma";
import { Suspense } from "react";
import { FingerprintProvider } from "../../fingerprint-provider";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the target file
fd -t f "page.tsx" --path "*onboarding*" | head -20

Repository: dubinc/dub

Length of output: 287


🏁 Script executed:

# Correct fd syntax to find the onboarding page.tsx
fd -t f "page.tsx" | grep -i onboarding

Repository: dubinc/dub

Length of output: 1061


🏁 Script executed:

# Read the target file to examine its current structure
cat -n apps/web/app/\(ee\)/partners.dub.co/\(onboarding\)/onboarding/page.tsx

Repository: dubinc/dub

Length of output: 1470


🏁 Script executed:

# Find the OnboardingForm component
fd -t f "onboarding-form" | grep -v node_modules

Repository: dubinc/dub

Length of output: 131


🏁 Script executed:

# Read the OnboardingForm component to check for hook usage
cat -n "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

Repository: dubinc/dub

Length of output: 13193


🏁 Script executed:

# Find and read the FingerprintProvider component
fd -t f "fingerprint-provider" | grep -v node_modules

Repository: dubinc/dub

Length of output: 112


🏁 Script executed:

# Read the FingerprintProvider to confirm it's a context provider
cat -n "apps/web/app/(ee)/partners.dub.co/fingerprint-provider.tsx"

Repository: dubinc/dub

Length of output: 724


Based on my verification, the review comment is accurate and identifies a genuine issue:

Wrap the Suspense fallback in FingerprintProvider

OnboardingForm uses the useVisitorData hook from @fingerprintjs/fingerprintjs-pro-react (line 54 in onboarding-form.tsx), which requires the FpjsProvider context (wrapped by FingerprintProvider). Currently, the normal render path (lines 31-35) wraps OnboardingForm with FingerprintProvider, but the Suspense fallback (line 14) renders it without the provider. If the fallback triggers, useVisitorData will fail since it won't have the required context.

-      <div className="animate-slide-up-fade w-full rounded-xl p-8 [--offset:10px] [animation-delay:500ms] [animation-duration:1s] [animation-fill-mode:both]">
-        <Suspense fallback={<OnboardingForm />}>
-          <OnboardingFormRSC />
-        </Suspense>
-      </div>
+      <div className="animate-slide-up-fade w-full rounded-xl p-8 [--offset:10px] [animation-delay:500ms] [animation-duration:1s] [animation-fill-mode:both]">
+        <Suspense
+          fallback={
+            <FingerprintProvider>
+              <OnboardingForm />
+            </FingerprintProvider>
+          }
+        >
+          <OnboardingFormRSC />
+        </Suspense>
+      </div>

This ensures both the fallback and resolved tree render under the same provider context.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/page.tsx around
line 4 (and affecting the Suspense at ~line 14 and the normal render at ~lines
31-35), the Suspense fallback renders OnboardingForm without the
FingerprintProvider which breaks useVisitorData; wrap the Suspense fallback (or
move FingerprintProvider to be above the Suspense) so that both the fallback and
the resolved tree are rendered inside FingerprintProvider (i.e., ensure
FingerprintProvider is an ancestor of the Suspense and/or its fallback
component).

Comment on lines +602 to +604
requestId: z
.string({ required_error: "Request ID is required" })
.describe("The Fingerprint request ID of the partner."),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for requestId usage in onboarding-related files
echo "=== Searching for requestId in onboarding files ==="
rg -n -A5 -B5 'requestId' --type=ts --type=tsx -g '*onboard*'

echo ""
echo "=== Searching for Fingerprint integration in onboarding files ==="
rg -n -A5 -B5 'fingerprint|Fingerprint' --type=ts --type=tsx -g '*onboard*'

Repository: dubinc/dub

Length of output: 245


🏁 Script executed:

#!/bin/bash
# Find onboarding-related files
echo "=== Finding onboarding-related files ==="
fd -e ts -e tsx 'onboard' | head -20

echo ""
echo "=== Searching for requestId in TypeScript files ==="
rg -n -A5 -B5 'requestId' --type=ts -g '*onboard*' | head -100

echo ""
echo "=== Searching for Fingerprint in TypeScript files ==="
rg -n -A5 -B5 'Fingerprint|fingerprint' --type=ts -g '*onboard*' | head -100

echo ""
echo "=== Broad search for requestId in all TypeScript files ==="
rg -n 'requestId' --type=ts | head -50

Repository: dubinc/dub

Length of output: 8316


🏁 Script executed:

#!/bin/bash
# Read the onboarding form to see visitorData initialization
echo "=== Reading onboarding-form.tsx (first 150 lines) ==="
head -150 "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

echo ""
echo "=== Reading lines 90-110 of onboarding-form.tsx ==="
sed -n '90,110p' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

echo ""
echo "=== Reading fingerprint.ts ==="
cat "apps/web/lib/api/fraud/fingerprint.ts"

echo ""
echo "=== Reading onboard-partner.ts ==="
cat "apps/web/lib/actions/partners/onboard-partner.ts"

Repository: dubinc/dub

Length of output: 10960


🏁 Script executed:

#!/bin/bash
# Read the partners schema around line 602
echo "=== Reading partners.ts schema (lines 590-620) ==="
sed -n '590,620p' "apps/web/lib/zod/schemas/partners.ts"

echo ""
echo "=== Search for onboardPartnerSchema definition ==="
rg -n 'onboardPartnerSchema' --type=ts -A20 -B5 | head -80

echo ""
echo "=== Search for submit button in onboarding-form.tsx ==="
rg -n 'submit\|Submit\|disable' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" | head -30

Repository: dubinc/dub

Length of output: 7724


🏁 Script executed:

#!/bin/bash
# Search for submit button in the form
echo "=== Finding submit button in onboarding-form.tsx ==="
rg -n 'Button\|button' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" -A2 -B2

echo ""
echo "=== Check if there's validation before submission ==="
rg -n 'isSubmitting\|isPending\|disabled' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" -A2 -B2

echo ""
echo "=== Get line count of onboarding-form.tsx ==="
wc -l "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

echo ""
echo "=== Read the end of onboarding-form.tsx to see button implementation ==="
tail -80 "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

Repository: dubinc/dub

Length of output: 3327


🏁 Script executed:

#!/bin/bash
# Search for requestId field rendering
echo "=== Search for requestId field in form ==="
rg -n 'requestId' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" -B5 -A5

echo ""
echo "=== Check for error display and validation messages ==="
rg -n 'errors\.' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" | head -20

echo ""
echo "=== Check useVisitorData hook loading state ==="
rg -n 'visitorData\|useVisitorData' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" -A3 -B3

Repository: dubinc/dub

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Check if useVisitorData is used elsewhere or if there's loading state
echo "=== Search for useVisitorData usage and loading state ==="
rg -n 'isLoading\|isPending\|loading' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx" -B2 -A2

echo ""
echo "=== Check full useVisitorData initialization ==="
sed -n '50,56p' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

echo ""
echo "=== Check default values and form initialization ==="
sed -n '60,75p' "apps/web/app/(ee)/partners.dub.co/(onboarding)/onboarding/onboarding-form.tsx"

Repository: dubinc/dub

Length of output: 820


Based on my verification, I've found that the requestId field is properly validated and used in the onboarding flow, but there's a notable UX consideration. Let me provide the rewritten review comment:


Add loading state indicator while Fingerprint request ID is being retrieved.

The requestId field is correctly validated and properly integrated: it's populated from the Fingerprint SDK (via useVisitorData hook), set into the form via setValue in onboarding-form.tsx:99, validated by the Zod schema as required, and verified server-side in onboard-partner.ts:41 using fetchVisitorFingerprint(). However, the form lacks a loading state while the Fingerprint data is being retrieved. Since requestId is a hidden field, if a user submits the form before visitorData?.requestId is populated, they'll encounter a "Request ID is required" validation error without understanding why. Consider:

  • Adding a loading indicator while useVisitorData retrieves the Fingerprint data
  • Disabling the submit button until visitorData?.requestId is available
  • Or set requestId with a fallback value in defaultValues to clarify its status

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (1)

26-34: Re‑evaluate who is allowed to see detailed fraud signals and severity

This handler currently returns riskSignals and severity for any caller that passes withWorkspace checks and is on an advanced/enterprise plan; there is no requiredPermissions gating or in‑handler permission check, and nothing redacts the response for lower‑privilege workspace members.

If these fraud signals and aggregate severity are considered sensitive (as implied by earlier discussion about hiding fraud details), consider:

  • Requiring an explicit permission in the withWorkspace options (e.g., whatever you use for fraud/program management), and/or
  • Conditioning the response on the caller’s permissions, returning a redacted structure (empty risksDetected, null/sentinel riskSeverity) for users who shouldn’t see fraud outcomes, and optionally skipping getApplicationRiskSignals entirely for those callers.

This is conceptually similar to the prior suggestion about not leaking fraud details via riskSeverity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00ae03c and cb1357e.

📒 Files selected for processing (3)
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (3 hunks)
  • apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (1 hunks)
  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
  • apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts
  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
📚 Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts
🧬 Code graph analysis (3)
apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (3)
apps/web/ui/partners/partners-upgrade-modal.tsx (1)
  • usePartnersUpgradeModal (285-304)
apps/web/lib/types.ts (1)
  • FraudSeverity (687-687)
apps/web/ui/partners/fraud-risks/partner-application-fraud-severity-indicator.tsx (1)
  • PartnerApplicationFraudSeverityIndicator (7-42)
apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (2)
apps/web/lib/auth/workspace.ts (1)
  • withWorkspace (55-488)
apps/web/lib/api/fraud/get-application-risk-signals.ts (1)
  • getApplicationRiskSignals (12-85)
apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (2)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-22)
apps/web/lib/api/fraud/get-application-risk-signals.ts (1)
  • getApplicationRiskSignals (12-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
apps/web/app/(ee)/api/partners/[partnerId]/application-risks/route.ts (1)

1-24: Centralizing application risk computation via getApplicationRiskSignals looks good

Fetching the enrollment with getProgramEnrollmentOrThrow (including full partner) and then delegating to getApplicationRiskSignals({ program: { id: programId }, partner }) is a clean consolidation of partner-scoped rules (cross‑program bans, duplicate payout/account, country/social/email checks). The call site matches the helper’s expected shape and the “application/onboarding” responsibility split for partner rules.

Based on learnings, this aligns with partner-specific rules being enforced in application flows rather than via the generic FRAUD_RULES registry.

apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (4)

40-40: LGTM! Consistent with signature change.

The call site correctly updated to not pass the severity prop, matching the refactored PartnerApplicationRiskSummaryUpsell component signature.


110-122: LGTM! Appropriate hardcoding for upsell component.

The removal of the severity parameter and hardcoding to "medium" makes sense since this upsell component is shown when users lack access to fraud management features. Using dummy data with a fixed severity is appropriate for demo/preview purposes.


135-135: LGTM! Consistent hardcoded severity for demo UI.

The hardcoded "medium" severity is consistent with the severityConfig at line 121 and appropriate for the blurred demo content shown in the upsell overlay.


155-160: LGTM! UI structure improved for better visual hierarchy.

The new overlay structure with a dedicated header section creates better visual prominence for the upsell message. The negative margins and layering (isolate z-10) create a "cut-through" effect where the header appears above the blurred content.

Verify the visual rendering to ensure the header aligns properly with the parent container and the negative margins (-mx-1 -mt-1) work as intended with the parent's padding.

apps/web/app/(ee)/api/cron/auto-approve-partner/route.ts (1)

2-5: Fraud capability gating + application risk signals integration looks solid; confirm severity threshold.

The changes to:

  • Select the full partner object from Prisma (Lines 54–55),
  • Gate fraud checks on canManageFraudEvents derived from getPlanCapabilities (Lines 94–98), and
  • Use getApplicationRiskSignals to block auto-approval only when severity === "high" (Lines 100–110),

are consistent with the getPlanCapabilities and getApplicationRiskSignals contracts and keep risk evaluation scoped to plans that can manage fraud events.

Please double‑check that the product requirement is to only block auto‑approval on "high" severity; as written, "medium" and "low" severities will still be auto‑approved.

Also applies to: 54-55, 94-110

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb1357e and b7acff8.

📒 Files selected for processing (2)
  • apps/web/lib/swr/use-partner-application-risks.ts (1 hunks)
  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx
🧬 Code graph analysis (1)
apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (3)
apps/web/ui/partners/partners-upgrade-modal.tsx (1)
  • usePartnersUpgradeModal (285-304)
apps/web/lib/types.ts (1)
  • FraudSeverity (687-687)
apps/web/ui/partners/fraud-risks/partner-application-fraud-severity-indicator.tsx (1)
  • PartnerApplicationFraudSeverityIndicator (7-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
apps/web/ui/partners/fraud-risks/partner-application-risk-summary.tsx (2)

40-40: LGTM - Consistent with function signature change.

The call to PartnerApplicationRiskSummaryUpsell without props is consistent with the updated function signature that no longer accepts a severity parameter.


155-191: LGTM - Clear upsell UI with improved messaging.

The updated overlay structure with a dedicated header (lines 156-160) and revised copy mentioning the "Advanced plan" (line 174) creates a clear and actionable upgrade prompt. The visual hierarchy and messaging effectively communicate the value proposition.

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​fingerprintjs/​fingerprintjs-pro-react@​2.7.19210010083100

View full report

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.

3 participants