mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
rework weights for same name signups (#1298)
- **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.** <!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Risk scoring now tracks and reports counts of recent signups that share a normalized email (with configurable limit), exposing this as part of signup-risk statistics. * **Performance** * Added a database index and migration to speed up recent-signup queries, improving risk assessment responsiveness. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
fd158bb54a
commit
328fd0252f
@ -0,0 +1,5 @@
|
||||
-- SPLIT_STATEMENT_SENTINEL
|
||||
-- SINGLE_STATEMENT_SENTINEL
|
||||
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signUpEmailNormalized_recent_idx"
|
||||
ON "ProjectUser"("tenancyId", "isAnonymous", "signUpEmailNormalized", "signedUpAt");
|
||||
@ -332,6 +332,7 @@ model ProjectUser {
|
||||
@@index([tenancyId, createdAt(sort: Desc)], name: "ProjectUser_createdAt_desc")
|
||||
@@index([tenancyId, isAnonymous, signedUpAt(sort: Asc)], name: "ProjectUser_signedUpAt_asc")
|
||||
@@index([tenancyId, isAnonymous, signUpIp, signedUpAt], name: "ProjectUser_signUpIp_recent_idx")
|
||||
@@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt], name: "ProjectUser_signUpEmailNormalized_recent_idx")
|
||||
@@index([tenancyId, isAnonymous, signUpEmailBase, signedUpAt], name: "ProjectUser_signUpEmailBase_recent_idx")
|
||||
@@index([tenancyId, sequenceId], name: "ProjectUser_tenancyId_sequenceId_idx")
|
||||
@@index([shouldUpdateSequenceId, tenancyId], name: "ProjectUser_shouldUpdateSequenceId_idx")
|
||||
|
||||
@ -30,14 +30,17 @@ export type SignUpRiskAssessment = {
|
||||
export type SignUpRiskRecentStatsRequest = {
|
||||
signedUpAt: Date,
|
||||
signUpIp: string | null,
|
||||
signUpEmailNormalized: string | null,
|
||||
signUpEmailBase: string | null,
|
||||
recentWindowHours: number,
|
||||
sameIpLimit: number,
|
||||
sameEmailLimit: number,
|
||||
similarEmailLimit: number,
|
||||
};
|
||||
|
||||
export type SignUpRiskRecentStats = {
|
||||
sameIpCount: number,
|
||||
sameEmailCount: number,
|
||||
similarEmailCount: number,
|
||||
};
|
||||
|
||||
@ -64,7 +67,7 @@ async function loadRecentSignUpStats(
|
||||
const schema = await getPrismaSchemaForTenancy(tenancy);
|
||||
const windowStart = new Date(request.signedUpAt.getTime() - request.recentWindowHours * 60 * 60 * 1000);
|
||||
|
||||
const [sameIpRows, similarEmailRows] = await Promise.all([
|
||||
const [sameIpRows, sameEmailRows, similarEmailRows] = await Promise.all([
|
||||
request.signUpIp == null || request.sameIpLimit === 0
|
||||
? []
|
||||
: prisma.$replica().$queryRaw<{ matched: number }[]>`
|
||||
@ -77,6 +80,18 @@ async function loadRecentSignUpStats(
|
||||
LIMIT ${request.sameIpLimit}
|
||||
`,
|
||||
|
||||
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}
|
||||
`,
|
||||
|
||||
request.signUpEmailBase == null || request.similarEmailLimit === 0
|
||||
? []
|
||||
: prisma.$replica().$queryRaw<{ matched: number }[]>`
|
||||
@ -92,6 +107,7 @@ async function loadRecentSignUpStats(
|
||||
|
||||
return {
|
||||
sameIpCount: sameIpRows.length,
|
||||
sameEmailCount: sameEmailRows.length,
|
||||
similarEmailCount: similarEmailRows.length,
|
||||
};
|
||||
}
|
||||
@ -144,7 +160,7 @@ import.meta.vitest?.test("loaded private sign-up risk engine can calculate score
|
||||
turnstileAssessment: { status: "ok" },
|
||||
}, {
|
||||
checkPrimaryEmailRisk: async () => ({ emailableScore: null }),
|
||||
loadRecentSignUpStats: async () => ({ sameIpCount: 0, similarEmailCount: 0 }),
|
||||
loadRecentSignUpStats: async () => ({ sameIpCount: 0, sameEmailCount: 0, similarEmailCount: 0 }),
|
||||
});
|
||||
|
||||
expect(assessment).toMatchInlineSnapshot(`
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit e4f32c675a640f40ffc19b1013ff46fc633d2438
|
||||
Subproject commit 576f383b69a9593a9cff8d755c64c810aeeae239
|
||||
Loading…
Reference in New Issue
Block a user