Skip to content

rework weights for same name signups#1298

Open
mantrakp04 wants to merge 2 commits intodevfrom
risk-score-rework
Open

rework weights for same name signups#1298
mantrakp04 wants to merge 2 commits intodevfrom
risk-score-rework

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented Mar 28, 2026

  • update submodule
  • Enhance sign-up risk assessment by adding sameEmailCount and sameEmailLimit to recent stats request. Update loadRecentSignUpStats function to include email normalization checks. Adjust tests to reflect new return structure.

Summary by CodeRabbit

  • New Features
    • Enhanced signup risk assessment with improved detection of duplicate email registrations within configurable time windows.

…lLimit to recent stats request. Update loadRecentSignUpStats function to include email normalization checks. Adjust tests to reflect new return structure.
Copilot AI review requested due to automatic review settings March 28, 2026 01:03
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Mar 28, 2026 1:09am
stack-backend Ready Ready Preview, Comment Mar 28, 2026 1:09am
stack-dashboard Ready Ready Preview, Comment Mar 28, 2026 1:09am
stack-demo Ready Ready Preview, Comment Mar 28, 2026 1:09am
stack-docs Ready Ready Preview, Comment Mar 28, 2026 1:09am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

This PR enhances the risk-scoring system by adding normalized email tracking to sign-up risk assessment. It introduces fields to capture and count sign-ups with the same normalized email address within a configurable time window, supported by a new parallel database query.

Changes

Cohort / File(s) Summary
Risk Scoring Enhancement
apps/backend/src/lib/risk-scores.tsx
Extended SignUpRiskRecentStatsRequest and SignUpRiskRecentStats types with normalized email tracking. Implemented parallel $queryRaw query to count non-anonymous project users with matching normalized email within a configurable window. Updated test mocks to reflect new sameEmailCount field in return shape.
Submodule Update
apps/backend/src/private/implementation
Updated git submodule reference to latest commit (576f383b...).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • N2D4

Poem

🐰 A rabbit hops through email trails,
Counting duplicates without fails,
Normalized addresses, oh what a sight,
Risk scores now glow ever so bright!
✨📧

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'same name signups' but the actual changes focus on email normalization and sameEmailCount logic, not name-based logic. Update the title to more accurately reflect the core change, such as 'Add sameEmailCount to sign-up risk assessment' or 'Enhance sign-up risk assessment with email normalization checks'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the main changes and objectives, including submodule update, type extensions, and test adjustments, aligning well with the actual modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch risk-score-rework

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR extends the sign-up risk assessment system by adding a new sameEmailCount signal that tracks how many recent sign-ups shared the same normalized email address (complementing the existing similarEmailCount which uses the email base/alias). The changes are well-scoped: the new field is added to the request/response types, the DB query is correctly parameterized, and the test mock is updated accordingly.

Key changes:

  • Added signUpEmailNormalized and sameEmailLimit to SignUpRiskRecentStatsRequest
  • Added sameEmailCount to SignUpRiskRecentStats
  • New raw SQL query against ProjectUser.\"signUpEmailNormalized\" using the existing column in schema.prisma
  • Updated inline test mock to include sameEmailCount: 0

Issues found:

  • The new signUpEmailNormalized query has no corresponding DB index in schema.prisma, unlike the existing signUpIp and signUpEmailBase queries which both have dedicated composite indexes. This query runs on every sign-up and will cause table scans on large tenancies. A migration adding @@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt]) should be included.

Confidence Score: 4/5

Safe to merge functionally, but missing a DB index will cause query performance regressions at scale on every sign-up.

The logic change is correct and well-tested. The single blocking concern is a missing composite index for the new signUpEmailNormalized query — without it, the query falls back to a table scan on each sign-up, matching the P1 severity pattern of the existing sibling indexes.

apps/backend/prisma/schema.prisma — needs a new index for signUpEmailNormalized to match the pattern of existing signUpIp and signUpEmailBase indexes.

Important Files Changed

Filename Overview
apps/backend/src/lib/risk-scores.tsx Adds sameEmailCount/sameEmailLimit to the risk stats request and response types, and introduces a new parameterized DB query for exact normalized email match — logic is correct but lacks a corresponding DB index in schema.prisma.
apps/backend/prisma/schema.prisma No schema changes in this PR, but the new signUpEmailNormalized query added in risk-scores.tsx requires a missing index on [tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt].

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Sign-up request] --> B[calculateSignUpRiskAssessment]
    B --> C[signUpRiskEngine.calculateRiskAssessment]
    C --> D[loadRecentSignUpStats]

    D --> E{signUpIp != null & sameIpLimit > 0?}
    D --> F{signUpEmailNormalized != null & sameEmailLimit > 0?}
    D --> G{signUpEmailBase != null & similarEmailLimit > 0?}

    E -->|Yes| H[Query: sameIpRows - index: signUpIp_recent_idx]
    E -->|No| I[return empty]

    F -->|Yes| J[Query: sameEmailRows - index: MISSING]
    F -->|No| K[return empty]

    G -->|Yes| L[Query: similarEmailRows - index: signUpEmailBase_recent_idx]
    G -->|No| M[return empty]

    H & J & L --> N[Promise.all]
    I & K & M --> N

    N --> O[Return: sameIpCount, sameEmailCount, similarEmailCount]
    O --> C
Loading

Comments Outside Diff (1)

  1. apps/backend/prisma/schema.prisma, line 315 (link)

    P1 Missing index for signUpEmailNormalized query

    The new sameEmailCount query in risk-scores.tsx filters on [tenancyId, isAnonymous, signedUpAt, signUpEmailNormalized], but no corresponding index exists for signUpEmailNormalized. The two existing parallel queries have dedicated indexes:

    • signUpIpProjectUser_signUpIp_recent_idx ([tenancyId, isAnonymous, signUpIp, signedUpAt])
    • signUpEmailBaseProjectUser_signUpEmailBase_recent_idx ([tenancyId, isAnonymous, signUpEmailBase, signedUpAt])

    Without a matching index, this new query will fall back to a table scan on ProjectUser per sign-up event, which will degrade performance at scale. A migration adding the index should accompany this change:

    @@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt], name: "ProjectUser_signUpEmailNormalized_recent_idx")

    Fix in Claude Code Fix in Cursor Fix in Codex

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Reviews (1): Last reviewed commit: "Enhance sign-up risk assessment by addin..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enhances the backend sign-up risk assessment plumbing by extending “recent sign-up stats” to include same-normalized-email counts/limits, and updates dependency lockfile entries likely due to a submodule/dependency refresh.

Changes:

  • Extend recent stats request/response types to include signUpEmailNormalized, sameEmailLimit, and sameEmailCount.
  • Add a DB query to count recent sign-ups matching the same normalized email (bounded by sameEmailLimit).
  • Update the in-source Vitest stubbed dependency return shape to match the new stats structure.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
pnpm-lock.yaml Updates dependency resolution (notably Nitro now pulling in rolldown) and removes an importer entry.
apps/backend/src/lib/risk-scores.tsx Adds same-normalized-email recent stats fields and query; updates test stub return shape.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +92
SELECT 1 AS "matched"
FROM ${sqlQuoteIdent(schema)}."ProjectUser"
WHERE "tenancyId" = ${tenancy.id}::UUID
AND "isAnonymous" = false
AND "signedUpAt" >= ${windowStart}
AND "signUpEmailNormalized" = ${request.signUpEmailNormalized}
LIMIT ${request.sameEmailLimit}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The new recent-stats query filters on "signUpEmailNormalized" and "signedUpAt" (plus tenancyId/isAnonymous), but the DB migration set currently only adds a recent index for signUpIp and signUpEmailBase (no signUpEmailNormalized). This will likely result in sequential scans on ProjectUser for tenants with many users. Consider adding a concurrent index similar to ProjectUser_signUpEmailBase_recent_idx, e.g. (tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt), or otherwise ensuring this query is backed by an index.

Copilot uses AI. Check for mistakes.
@mantrakp04 mantrakp04 requested a review from N2D4 March 28, 2026 01:08
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/src/lib/risk-scores.tsx`:
- Around line 83-93: The query in apps/backend/src/lib/risk-scores.tsx that uses
request.signUpEmailNormalized (inside the prisma.$replica().$queryRaw block that
checks sameEmailLimit) needs a corresponding DB index to avoid full table scans;
add the index to migration 20260308000002_finalize_signup_fraud_protection.sql
(CREATE INDEX CONCURRENTLY IF NOT EXISTS
"ProjectUser_signUpEmailNormalized_recent_idx" ON "ProjectUser"("tenancyId",
"isAnonymous", "signUpEmailNormalized", "signedUpAt")) and add the matching
Prisma index annotation to the ProjectUser model (@@index([tenancyId,
isAnonymous, signUpEmailNormalized, signedUpAt], name:
"ProjectUser_signUpEmailNormalized_recent_idx")) so the signUpEmailNormalized
filter in the sameEmailLimit query can use the index.

In `@apps/backend/src/private/implementation`:
- Line 1: Verify the private submodule update by checking the commit range
between a93d7ea0c0a91d7a4dfbc97c0032c9c9c68ec4d6 and
576f383b69a9593a9cff8d755c64c810aeeae239: run git fetch in the private repo and
git log --oneline a93d7ea..576f383b6 to enumerate commits, then run git diff
--name-status a93d7ea0c..576f383b6 to ensure only the intended risk-score and
signup-stat files were changed (no other files or code paths), inspect each
commit message and diff for breaking/security-impacting edits, run the private
repo’s test suite and any linters/security scanners on the new commits, and if
anything else appears, revert or request the correct commit range before
merging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1840880f-79f3-4e1f-95d1-fd9e84e37754

📥 Commits

Reviewing files that changed from the base of the PR and between 4ddf6a5 and dadc71f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/backend/src/lib/risk-scores.tsx
  • apps/backend/src/private/implementation

Comment on lines +83 to +93
request.signUpEmailNormalized == null || request.sameEmailLimit === 0
? []
: prisma.$replica().$queryRaw<{ matched: number }[]>`
SELECT 1 AS "matched"
FROM ${sqlQuoteIdent(schema)}."ProjectUser"
WHERE "tenancyId" = ${tenancy.id}::UUID
AND "isAnonymous" = false
AND "signedUpAt" >= ${windowStart}
AND "signUpEmailNormalized" = ${request.signUpEmailNormalized}
LIMIT ${request.sameEmailLimit}
`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that no index exists for signUpEmailNormalized

echo "=== Checking Prisma schema for signUpEmailNormalized index ==="
rg -n 'signUpEmailNormalized' apps/backend/prisma/schema.prisma

echo ""
echo "=== Checking migrations for signUpEmailNormalized index ==="
rg -rn 'signUpEmailNormalized' apps/backend/prisma/migrations/

Repository: stack-auth/stack-auth

Length of output: 345


🏁 Script executed:

#!/bin/bash
# Get the full ProjectUser model definition to see all indexes
echo "=== Full ProjectUser model definition ==="
sed -n '/^model ProjectUser/,/^}/p' apps/backend/prisma/schema.prisma | head -100

Repository: stack-auth/stack-auth

Length of output: 4513


🏁 Script executed:

#!/bin/bash
# Read the specific migration file to see what was actually created
echo "=== Checking migration file for signup fraud protection ==="
find apps/backend/prisma/migrations -name "*signup*fraud*" -o -name "*20260308*" | head -5

Repository: stack-auth/stack-auth

Length of output: 478


🏁 Script executed:

#!/bin/bash
# Check for all indexes on ProjectUser in schema
echo "=== All @@index definitions in ProjectUser model ==="
sed -n '/^model ProjectUser/,/^}/p' apps/backend/prisma/schema.prisma | grep -n "@@index"

Repository: stack-auth/stack-auth

Length of output: 1001


🏁 Script executed:

#!/bin/bash
# Read the main signup fraud protection migration
echo "=== Migration 20260308000000 ==="
cat apps/backend/prisma/migrations/20260308000000_add_signup_fraud_protection/migration.sql

Repository: stack-auth/stack-auth

Length of output: 1237


🏁 Script executed:

#!/bin/bash
# Read the finalize migration
echo "=== Migration 20260308000002 ==="
cat apps/backend/prisma/migrations/20260308000002_finalize_signup_fraud_protection/migration.sql

Repository: stack-auth/stack-auth

Length of output: 1879


🏁 Script executed:

#!/bin/bash
# Verify the actual code in risk-scores.tsx around lines 83-93
echo "=== Risk scores query code ==="
sed -n '75,100p' apps/backend/src/lib/risk-scores.tsx

Repository: stack-auth/stack-auth

Length of output: 1157


Add missing database index for signUpEmailNormalized to prevent full table scans.

The query on line 90 filters by signUpEmailNormalized, but no index exists for this column. The sibling queries on signUpIp and signUpEmailBase have corresponding indexes (ProjectUser_signUpIp_recent_idx and ProjectUser_signUpEmailBase_recent_idx), but signUpEmailNormalized was omitted. Without an index, this query will perform a sequential scan on the ProjectUser table, degrading sign-up latency as the table grows.

Add the index to migration 20260308000002_finalize_signup_fraud_protection.sql:

CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signUpEmailNormalized_recent_idx"
  ON "ProjectUser"("tenancyId", "isAnonymous", "signUpEmailNormalized", "signedUpAt");

And add it to the Prisma schema:

@@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt], name: "ProjectUser_signUpEmailNormalized_recent_idx")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/lib/risk-scores.tsx` around lines 83 - 93, The query in
apps/backend/src/lib/risk-scores.tsx that uses request.signUpEmailNormalized
(inside the prisma.$replica().$queryRaw block that checks sameEmailLimit) needs
a corresponding DB index to avoid full table scans; add the index to migration
20260308000002_finalize_signup_fraud_protection.sql (CREATE INDEX CONCURRENTLY
IF NOT EXISTS "ProjectUser_signUpEmailNormalized_recent_idx" ON
"ProjectUser"("tenancyId", "isAnonymous", "signUpEmailNormalized",
"signedUpAt")) and add the matching Prisma index annotation to the ProjectUser
model (@@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt],
name: "ProjectUser_signUpEmailNormalized_recent_idx")) so the
signUpEmailNormalized filter in the sameEmailLimit query can use the index.

@@ -1 +1 @@
Subproject commit a93d7ea0c0a91d7a4dfbc97c0032c9c9c68ec4d6
Subproject commit 576f383b69a9593a9cff8d755c64c810aeeae239
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify submodule pointer and inspect exact delta
git submodule status -- apps/backend/src/private/implementation || true

# Ensure submodule metadata is available
git submodule update --init --recursive -- apps/backend/src/private/implementation

OLD="a93d7ea0c0a91d7a4dfbc97c0032c9c9c68ec4d6"
NEW="576f383b69a9593a9cff8d755c64c810aeeae239"
SUB="apps/backend/src/private/implementation"

echo "== Commit range in submodule =="
git -C "$SUB" log --oneline --no-merges "${OLD}..${NEW}"

echo
echo "== Files changed in submodule range =="
git -C "$SUB" diff --name-status "${OLD}..${NEW}"

echo
echo "== High-signal grep for risk-score/signup/email changes =="
git -C "$SUB" diff --name-only "${OLD}..${NEW}" | rg -n "risk|signup|sign-up|email|stats|score" || true

Repository: stack-auth/stack-auth

Length of output: 1239


The private submodule cannot be verified from this environment—manual review required.

Line 1 updates the gitlink to commit 576f383b69a9593a9cff8d755c64c810aeeae239, but the private repository (https://github.com/stack-auth/private.git) is not accessible for automated verification. Before merging, confirm that the commit range from a93d7ea0c0a91d7a4dfbc97c0032c9c9c68ec4d6 to 576f383b69a9593a9cff8d755c64c810aeeae239 contains only the intended risk-score and signup-stat changes, with no unrelated breaking or security-impacting updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/private/implementation` at line 1, Verify the private
submodule update by checking the commit range between
a93d7ea0c0a91d7a4dfbc97c0032c9c9c68ec4d6 and
576f383b69a9593a9cff8d755c64c810aeeae239: run git fetch in the private repo and
git log --oneline a93d7ea..576f383b6 to enumerate commits, then run git diff
--name-status a93d7ea0c..576f383b6 to ensure only the intended risk-score and
signup-stat files were changed (no other files or code paths), inspect each
commit message and diff for breaking/security-impacting edits, run the private
repo’s test suite and any linters/security scanners on the new commits, and if
anything else appears, revert or request the correct commit range before
merging.

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.

2 participants