-
Notifications
You must be signed in to change notification settings - Fork 514
bot protection #1231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bot protection #1231
Changes from all commits
4c8ed5e
0b9a688
bbb7983
5f7d7f4
58aeb3c
b9290d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| -- AlterTable: add columns with a temporary default for existing rows, then drop the default | ||
| ALTER TABLE "ProjectUser" ADD COLUMN "signUpRiskScoreBot" SMALLINT NOT NULL DEFAULT 0; | ||
| ALTER TABLE "ProjectUser" ADD COLUMN "signUpRiskScoreFreeTrialAbuse" SMALLINT NOT NULL DEFAULT 0; | ||
| ALTER TABLE "ProjectUser" ALTER COLUMN "signUpRiskScoreBot" DROP DEFAULT; | ||
| ALTER TABLE "ProjectUser" ALTER COLUMN "signUpRiskScoreFreeTrialAbuse" DROP DEFAULT; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Tenancy } from "./tenancies"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type SignUpRiskScores = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bot: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| freeTrialAbuse: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type SignUpRiskScoreContext = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| primaryEmail: string | null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| primaryEmailVerified: boolean, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| authMethod: 'password' | 'otp' | 'oauth' | 'passkey', | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| oauthProvider?: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ipAddress: string | null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function calculateSignUpRiskScores(tenancy: Tenancy, context: SignUpRiskScoreContext): Promise<SignUpRiskScores> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (context.primaryEmail === "test@example.com") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bot: 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| freeTrialAbuse: 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bot: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| freeTrialAbuse: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+28
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO | |
| if (context.primaryEmail === "test@example.com") { | |
| return { | |
| bot: 100, | |
| freeTrialAbuse: 100, | |
| }; | |
| } else { | |
| return { | |
| bot: 0, | |
| freeTrialAbuse: 0, | |
| }; | |
| } | |
| // TODO: Implement real scoring logic. For now, only apply the stub behavior in tests | |
| if (process.env.NODE_ENV === "test" && context.primaryEmail === "test@example.com") { | |
| return { | |
| bot: 100, | |
| freeTrialAbuse: 100, | |
| }; | |
| } | |
| // Default to neutral risk scores in non-test environments | |
| return { | |
| bot: 0, | |
| freeTrialAbuse: 0, | |
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { KnownErrors } from "@stackframe/stack-shared"; | |||||||||
| import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users"; | ||||||||||
| import { KeyIntersect } from "@stackframe/stack-shared/dist/utils/types"; | ||||||||||
| import { createSignUpRuleContext } from "./cel-evaluator"; | ||||||||||
| import { calculateSignUpRiskScores } from "./risk-scores"; | ||||||||||
| import { evaluateSignUpRules } from "./sign-up-rules"; | ||||||||||
| import { Tenancy } from "./tenancies"; | ||||||||||
|
|
||||||||||
|
|
@@ -12,6 +13,7 @@ import { Tenancy } from "./tenancies"; | |||||||||
| export type SignUpRuleOptions = { | ||||||||||
| authMethod: 'password' | 'otp' | 'oauth' | 'passkey', | ||||||||||
| oauthProvider?: string, | ||||||||||
| ipAddress: string | null, | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Required
Either make
Suggested change
|
||||||||||
| ipAddress: string | null, | |
| ipAddress?: string | null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find all callers of createOrUpgradeAnonymousUserWithRules and check if they pass ipAddress
rg -n -A 10 'createOrUpgradeAnonymousUserWithRules\(' --type ts | head -100Repository: stack-auth/stack-auth
Length of output: 4091
🏁 Script executed:
# Get the complete call at password sign-up route
sed -n '57,75p' apps/backend/src/app/api/latest/auth/password/sign-up/route.tsxRepository: stack-auth/stack-auth
Length of output: 593
🏁 Script executed:
# Get the complete call at OTP handler
sed -n '110,130p' apps/backend/src/app/api/latest/auth/otp/sign-in/verification-code-handler.tsxRepository: stack-auth/stack-auth
Length of output: 635
🏁 Script executed:
# Get the complete call at OAuth
sed -n '207,225p' apps/backend/src/lib/oauth.tsxRepository: stack-auth/stack-auth
Length of output: 623
🏁 Script executed:
# View the SignUpRuleOptions type definition in users.tsx
sed -n '13,17p' apps/backend/src/lib/users.tsxRepository: stack-auth/stack-auth
Length of output: 211
🏁 Script executed:
# Check if there are any other callers we missed
rg -c 'createOrUpgradeAnonymousUserWithRules\(' --type tsRepository: stack-auth/stack-auth
Length of output: 277
Critical: ipAddress is now required in SignUpRuleOptions but three callers don't provide it.
The SignUpRuleOptions type defines ipAddress: string | null as a required field, but the following callers pass incomplete objects:
apps/backend/src/app/api/latest/auth/password/sign-up/route.tsx:67— passes only{ authMethod: 'password' }apps/backend/src/app/api/latest/auth/otp/sign-in/verification-code-handler.tsx:120— passes only{ authMethod: 'otp' }apps/backend/src/lib/oauth.tsx:217— passesparams.signUpRuleOptionswhich lacksipAddress
This will cause TypeScript compilation errors at all three sites.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/backend/src/lib/users.tsx` around lines 13 - 17, SignUpRuleOptions now
requires ipAddress but three callers pass objects without it; either make
ipAddress optional on SignUpRuleOptions or (preferred) update each caller to
supply a value (real IP from the request or null) when constructing the options.
Locate the type SignUpRuleOptions and add ipAddress: string | null where needed,
then update the password sign-up handler (password sign-up), the OTP
verification-code handler (otp sign-in verification), and the OAuth code path
that forwards params.signUpRuleOptions to include an ipAddress field (use
request IP extraction or null if unavailable). Ensure all call sites create a
complete SignUpRuleOptions object to satisfy the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe property access on optional nested object
data.risk_scores?.sign_up.botuses optional chaining onrisk_scoresbut not onsign_up. Ifrisk_scoresis present butsign_upis somehow absent at runtime (schema coercion edge case, or a future schema change), this would throwTypeError: Cannot read properties of undefined (reading 'bot').Use consistent optional chaining throughout: