Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
4c8ed5e
Risk scores
N2D4 Mar 8, 2026
0b9a688
Merge branch 'dev' into fraud-protection
mantrakp04 Mar 9, 2026
14f5b22
COUNTRY CODE (1)
mantrakp04 Mar 9, 2026
fb315a9
Enhance sign-up rules with derived country code and risk score handli…
mantrakp04 Mar 9, 2026
26b3506
Refactor country code handling across the application. Introduced a c…
mantrakp04 Mar 9, 2026
7e258d7
Refactor sign-up country code handling to derive only from request ge…
mantrakp04 Mar 9, 2026
5250595
Refactor country code validation by introducing a centralized `validC…
mantrakp04 Mar 9, 2026
ebbfa47
Enhance `getDerivedSignUpCountryCode` function to support email-based…
mantrakp04 Mar 9, 2026
cabefdd
Refactor user schema to centralize metadata for admin restrictions an…
mantrakp04 Mar 9, 2026
33dd37a
Add KMS script to package.json for managing port processes
mantrakp04 Mar 9, 2026
23bd2aa
Implement country code selection component in sign-up rules and updat…
mantrakp04 Mar 9, 2026
6db97e7
Update user test cases to include country_code and risk_scores fields…
mantrakp04 Mar 9, 2026
7de1a49
Update user and OAuth test cases to include country_code and risk_sco…
mantrakp04 Mar 9, 2026
180c1a7
Update team membership and user tests to reflect changes in response …
mantrakp04 Mar 9, 2026
0b962fe
Enhance CEL expression evaluation tests for country code handling. Ad…
mantrakp04 Mar 10, 2026
bd51d88
Refactor user creation logic to improve handling of restricted user a…
mantrakp04 Mar 10, 2026
6539bdb
Implement disposable email domain heuristics for sign-up risk scoring…
mantrakp04 Mar 10, 2026
4c62869
Make heuristic pipeline weights configurable via env vars
mantrakp04 Mar 10, 2026
3e1e509
Integrate Emailable API for email validation and enhance sign-up heur…
mantrakp04 Mar 10, 2026
bbb7983
Merge branch 'dev' into fraud-protection
mantrakp04 Mar 10, 2026
1257021
Merge branch 'fraud-protection' into fraud-protection-country-code
mantrakp04 Mar 10, 2026
5f7d7f4
Merge branch 'dev' into fraud-protection
mantrakp04 Mar 10, 2026
9c5eda1
Merge branch 'fraud-protection' into fraud-protection-country-code
mantrakp04 Mar 10, 2026
7a492f1
Enhance Vitest configuration and TypeScript paths for stack-shared ut…
mantrakp04 Mar 10, 2026
ba30281
Clean up shared import resolution on FML
mantrakp04 Mar 10, 2026
41e6cd2
Merge fraud-protection-country-code
mantrakp04 Mar 10, 2026
f576fd7
Fix Docker build
N2D4 Mar 9, 2026
c8b7f27
Stack CLI (#1227)
BilalG1 Mar 9, 2026
169984e
Managed email provider (#1222)
BilalG1 Mar 10, 2026
e643feb
chore: update package versions
N2D4 Mar 10, 2026
3fb9e83
Hosted components (#1229)
BilalG1 Mar 10, 2026
3f4aefc
Update dashboard components (Except for major 4) (#1205)
Developing-Gamer Mar 10, 2026
d9d4512
Use package exports for stack-shared imports
mantrakp04 Mar 10, 2026
bad853b
Enhance sign-up process with Turnstile integration and refactor relat…
mantrakp04 Mar 10, 2026
58aeb3c
Merge branch 'dev' into fraud-protection
mantrakp04 Mar 10, 2026
7d42522
Merge branch 'fraud-protection' into fraud-protection-country-code
mantrakp04 Mar 10, 2026
0e6f99c
Merge branch 'fraud-protection-country-code' into fraud-protection-te…
mantrakp04 Mar 10, 2026
28e0af9
Enhance package exports for stack-shared utilities
mantrakp04 Mar 10, 2026
af11525
Merge branch 'fraud-protection-temp-emails' into fraud-protection-tru…
mantrakp04 Mar 10, 2026
04d4a6b
Refactor imports in backend and dashboard components to use stack-sha…
mantrakp04 Mar 10, 2026
e5fafda
Update codegen-docs:watch script to exclude node_modules from watch p…
mantrakp04 Mar 10, 2026
2a84dd0
Enhance Turnstile integration and refactor related components
mantrakp04 Mar 11, 2026
5d029e0
Refactor Turnstile integration across authentication routes
mantrakp04 Mar 11, 2026
142d548
Add risk score weights and thresholds for sign-up assessments
mantrakp04 Mar 11, 2026
da8b06e
Refactor sign-up context and enhance risk score handling
mantrakp04 Mar 11, 2026
c64ad75
Refactor risk score handling and enhance Turnstile integration
mantrakp04 Mar 11, 2026
6cc06d3
Enhance development scripts and update dependencies
mantrakp04 Mar 11, 2026
040e1e7
Remove deprecated react-dom-client type and implementation files
mantrakp04 Mar 11, 2026
94626ee
Update risk score weights and enhance development scripts
mantrakp04 Mar 12, 2026
eec2387
Add submodule for private risk engine and refactor risk score calcula…
mantrakp04 Mar 12, 2026
6f97329
Refactor ProjectUser schema and enhance sign-up data handling
mantrakp04 Mar 12, 2026
cba1f98
Enhance risk score validation and refactor related components
mantrakp04 Mar 12, 2026
762b872
Remove terminal word separators from VSCode settings for cleaner term…
mantrakp04 Mar 12, 2026
b9290d5
Merge branch 'dev' into fraud-protection
mantrakp04 Mar 12, 2026
5e4da43
Merge branch 'fraud-protection' into fraud-protection-country-code
mantrakp04 Mar 12, 2026
441e35e
Merge branch 'fraud-protection-country-code' into fraud-protection-te…
mantrakp04 Mar 12, 2026
a02dbf9
Merge branch 'fraud-protection-temp-emails' into fraud-protection-tru…
mantrakp04 Mar 12, 2026
52fe7ed
Improve error handling in Turnstile script loading
mantrakp04 Mar 12, 2026
8010c64
Merge branch 'dev' into fraud-protection-trunstile
mantrakp04 Mar 13, 2026
a24fcdd
Refactor sign-up timestamp handling in ProjectUser model
mantrakp04 Mar 13, 2026
d2f2d17
Merge branch 'dev' into fraud-protection-trunstile
mantrakp04 Mar 13, 2026
09a039a
Enhance email validation handling and update dependencies
mantrakp04 Mar 14, 2026
8d3cdc0
Refactor ProjectUser model and enhance sign-up fraud protection
mantrakp04 Mar 16, 2026
e436762
Clear STACK_EMAILABLE_API_KEY in development environment for security…
mantrakp04 Mar 16, 2026
a9d3650
Enhance email validation logic to handle reserved test domains
mantrakp04 Mar 16, 2026
ec5a8d3
Refactor OAuth response handling and update CORS configuration
mantrakp04 Mar 16, 2026
fce866d
Refactor ProjectUser model and enhance sign-up risk score initialization
mantrakp04 Mar 16, 2026
4bbcdf1
Update submodule branch and enhance ProjectUser migration scripts for…
mantrakp04 Mar 16, 2026
4160fb0
Enhance Turnstile integration and refactor user handling logic
mantrakp04 Mar 16, 2026
443dc99
Refactor ProjectUser model and enhance sign-up handling
mantrakp04 Mar 16, 2026
141fd4b
Enhance ProjectUser model and implement sign-up fraud protection feat…
mantrakp04 Mar 16, 2026
ba0648c
Enhance Turnstile integration and improve error handling
mantrakp04 Mar 17, 2026
930b10b
Refactor Turnstile integration and improve error handling
mantrakp04 Mar 17, 2026
629ced0
Refactor user signup handling and improve error management
mantrakp04 Mar 17, 2026
c7c3fdf
Refactor CEL evaluator and email validation logic
mantrakp04 Mar 17, 2026
a95f97f
Enhance Turnstile integration and update environment configuration
mantrakp04 Mar 17, 2026
e753510
Update Turnstile assessment handling and improve hostname validation
mantrakp04 Mar 17, 2026
9ecb43c
Merge branch 'dev' into fraud-protection-trunstile
mantrakp04 Mar 17, 2026
8098582
Refactor Turnstile integration to Bot Challenge and enhance fraud pro…
mantrakp04 Mar 17, 2026
6c4a811
Update ProjectUser model and migrations for signedUpAt field
mantrakp04 Mar 17, 2026
fa208cf
Update ProjectUser model to reflect partial index management
mantrakp04 Mar 17, 2026
fe3a061
Finalize sign-up fraud protection implementation
mantrakp04 Mar 17, 2026
3cac0e5
Update user sign-up handling and enforce non-nullable signedUpAt field
mantrakp04 Mar 17, 2026
42c913b
Remove non-null constraint and validation for signedUpAt field in Pro…
mantrakp04 Mar 17, 2026
cc6dac4
Implement non-null constraint and trigger for signedUpAt field in Pro…
mantrakp04 Mar 17, 2026
e7d8865
Add RUN_OUTSIDE_TRANSACTION_SENTINEL to ProjectUser migration triggers
mantrakp04 Mar 18, 2026
6f5d02e
Enhance external DB sync test with timeout and interval settings
mantrakp04 Mar 18, 2026
39f4381
Merge branch 'dev' into fraud-protection-trunstile
mantrakp04 Mar 19, 2026
05a6d87
Enhance OAuth and OTP handling with request context serialization
mantrakp04 Mar 19, 2026
785efe0
Refactor OTP verification code handling and sign-up context schema
mantrakp04 Mar 19, 2026
dc69508
Implement Turnstile bot challenge handling and sign-up policy adjustm…
mantrakp04 Mar 19, 2026
b1be033
Merge branch 'dev' into fraud-protection-trunstile
mantrakp04 Mar 19, 2026
fd68a88
Enhance bot challenge error handling in client interface
mantrakp04 Mar 19, 2026
8b1c6cb
Refactor OpenAPI response handling to support multiple response variants
mantrakp04 Mar 19, 2026
94c8c2c
Enhance bot challenge handling in client interface
mantrakp04 Mar 20, 2026
9a55542
Implement bot challenge disabling feature in local development
mantrakp04 Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "packages/private"]
path = packages/private
url = https://github.com/stack-auth/private.git
Comment thread
mantrakp04 marked this conversation as resolved.
branch = main
2 changes: 1 addition & 1 deletion apps/backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ STACK_EMAIL_PORT=# for local inbucket: 8129
STACK_EMAIL_USERNAME=# for local inbucket: test
STACK_EMAIL_PASSWORD=# for local inbucket: none
STACK_EMAIL_SENDER=# for local inbucket: noreply@test.com
STACK_EMAILABLE_API_KEY=# for Emailable email validation, see https://emailable.com
STACK_EMAILABLE_API_KEY=# Emailable API key for email validation, see https://emailable.com. Use a test key (starting with "test_") for local dev — it does not consume credits. Set to "disable_email_validation" to disable.

STACK_DEFAULT_EMAIL_CAPACITY_PER_HOUR=# the number of emails a new project can send. Defaults to 200

Expand Down
19 changes: 19 additions & 0 deletions apps/backend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=this-secret-server-key-is-for-loca
STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=this-super-secret-admin-key-is-for-local-development-only

STACK_OAUTH_MOCK_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}14
STACK_TURNSTILE_SITEVERIFY_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}14/turnstile/siteverify

# Cloudflare Turnstile test keys — always-pass widgets, no real challenges
# See https://developers.cloudflare.com/turnstile/troubleshooting/testing/
NEXT_PUBLIC_STACK_BOT_CHALLENGE_SITE_KEY=1x00000000000000000000AA
NEXT_PUBLIC_STACK_BOT_CHALLENGE_INVISIBLE_SITE_KEY=1x00000000000000000000BB
STACK_TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
# Set to true to disable Turnstile entirely in local development.
# This skips invisible/visible bot challenge flow and removes the Turnstile risk penalty.
STACK_DISABLE_BOT_CHALLENGE=false
# Default behavior is to block sign-up if the visible challenge cannot be completed.
# Flip this only when you intentionally want local sign-up to continue during Turnstile outages.
STACK_ALLOW_SIGN_UP_ON_VISIBLE_BOT_CHALLENGE_FAILURE=false

STACK_GITHUB_CLIENT_ID=MOCK
STACK_GITHUB_CLIENT_SECRET=MOCK
Expand Down Expand Up @@ -47,6 +60,10 @@ STACK_DEFAULT_EMAIL_CAPACITY_PER_HOUR=10000
STACK_SVIX_SERVER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}13
STACK_SVIX_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTUxNDA2MzksImV4cCI6MTk3MDUwMDYzOSwibmJmIjoxNjU1MTQwNjM5LCJpc3MiOiJzdml4LXNlcnZlciIsInN1YiI6Im9yZ18yM3JiOFlkR3FNVDBxSXpwZ0d3ZFhmSGlyTXUifQ.En8w77ZJWbd0qrMlHHupHUB-4cx17RfzFykseg95SUk

# Trusted reverse proxy for reading real client IP addresses.
# Set to "vercel", "cloudflare", or leave empty/unset for no proxy trust.
STACK_TRUSTED_PROXY=

STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS=500

STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING=yes
Expand All @@ -69,6 +86,8 @@ STACK_EMAIL_MONITOR_INBUCKET_API_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_P
STACK_EMAIL_MONITOR_USE_INBUCKET=true
STACK_EMAIL_MONITOR_SECRET_TOKEN=this-secret-token-is-for-local-development-only

STACK_EMAILABLE_API_KEY=

# S3 Configuration for local development using s3mock
STACK_S3_ENDPOINT=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}21
STACK_S3_REGION=us-east-1
Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"chokidar-cli": "^3.0.0",
"dotenv": "^16.4.5",
"dotenv-cli": "^7.3.0",
"emailable": "^3.1.1",
"freestyle-sandboxes": "^0.1.6",
"jiti": "^2.6.1",
"jose": "^6.1.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
ALTER TABLE "ProjectUser" ADD COLUMN "signUpRiskScoreBot" SMALLINT NOT NULL DEFAULT 0;
Comment thread
mantrakp04 marked this conversation as resolved.
ALTER TABLE "ProjectUser" ADD COLUMN "signUpRiskScoreFreeTrialAbuse" SMALLINT NOT NULL DEFAULT 0;

ALTER TABLE "ProjectUser"
ADD CONSTRAINT "ProjectUser_risk_score_bot_range"
CHECK ("signUpRiskScoreBot" >= 0 AND "signUpRiskScoreBot" <= 100) NOT VALID;

ALTER TABLE "ProjectUser"
ADD CONSTRAINT "ProjectUser_risk_score_free_trial_abuse_range"
CHECK ("signUpRiskScoreFreeTrialAbuse" >= 0 AND "signUpRiskScoreFreeTrialAbuse" <= 100) NOT VALID;

ALTER TABLE "ProjectUser" ADD COLUMN "signUpCountryCode" TEXT;

ALTER TABLE "ProjectUser"
ADD COLUMN "signedUpAt" TIMESTAMP(3),
ADD COLUMN "signUpIp" TEXT,
ADD COLUMN "signUpIpTrusted" BOOLEAN,
ADD COLUMN "signUpEmailNormalized" TEXT,
ADD COLUMN "signUpEmailBase" TEXT;
Comment thread
mantrakp04 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- SINGLE_STATEMENT_SENTINEL
-- CONDITIONALLY_REPEAT_MIGRATION_SENTINEL
WITH to_update AS (
SELECT "projectUserId", "tenancyId"
FROM "ProjectUser"
WHERE "signedUpAt" IS NULL
LIMIT 10000
),
updated AS (
UPDATE "ProjectUser" pu
SET "signedUpAt" = pu."createdAt"
FROM to_update tu
WHERE pu."tenancyId" = tu."tenancyId" AND pu."projectUserId" = tu."projectUserId"
RETURNING 1
)
SELECT COUNT(*) > 0 AS should_repeat_migration FROM updated;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { randomUUID } from 'crypto';
import type { Sql } from 'postgres';
import { expect } from 'vitest';

export const preMigration = async (sql: Sql) => {
const projectId = `test-${randomUUID()}`;
const tenancyId = randomUUID();
const regularUserId = randomUUID();
const anonUserId = randomUUID();

await sql`INSERT INTO "Project" ("id", "createdAt", "updatedAt", "displayName", "description", "isProductionMode") VALUES (${projectId}, NOW(), NOW(), 'Test', '', false)`;
await sql`INSERT INTO "Tenancy" ("id", "createdAt", "updatedAt", "projectId", "branchId", "hasNoOrganization") VALUES (${tenancyId}::uuid, NOW(), NOW(), ${projectId}, 'main', 'TRUE'::"BooleanTrue")`;
await sql`INSERT INTO "ProjectUser" ("projectUserId", "tenancyId", "mirroredProjectId", "mirroredBranchId", "createdAt", "updatedAt", "lastActiveAt") VALUES (${regularUserId}::uuid, ${tenancyId}::uuid, ${projectId}, 'main', NOW(), NOW(), NOW())`;
await sql`INSERT INTO "ProjectUser" ("projectUserId", "tenancyId", "mirroredProjectId", "mirroredBranchId", "createdAt", "updatedAt", "lastActiveAt", "isAnonymous") VALUES (${anonUserId}::uuid, ${tenancyId}::uuid, ${projectId}, 'main', NOW(), NOW(), NOW(), true)`;

return { regularUserId, anonUserId };
};

export const postMigration = async (sql: Sql, ctx: Awaited<ReturnType<typeof preMigration>>) => {
for (const userId of [ctx.regularUserId, ctx.anonUserId]) {
const rows = await sql`
SELECT "signedUpAt", "createdAt", "signUpRiskScoreBot", "signUpRiskScoreFreeTrialAbuse"
FROM "ProjectUser"
WHERE "projectUserId" = ${userId}::uuid
`;

expect(rows).toHaveLength(1);
expect(rows[0].signedUpAt.toISOString()).toBe(rows[0].createdAt.toISOString());
expect(rows[0].signUpRiskScoreBot).toBe(0);
expect(rows[0].signUpRiskScoreFreeTrialAbuse).toBe(0);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signedUpAt_asc"
ON "ProjectUser"("tenancyId", "isAnonymous", "signedUpAt" ASC);

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signUpIp_recent_idx"
ON "ProjectUser"("tenancyId", "isAnonymous", "signUpIp", "signedUpAt");

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signUpEmailBase_recent_idx"
ON "ProjectUser"("tenancyId", "isAnonymous", "signUpEmailBase", "signedUpAt");

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" VALIDATE CONSTRAINT "ProjectUser_risk_score_bot_range";

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" VALIDATE CONSTRAINT "ProjectUser_risk_score_free_trial_abuse_range";

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
CREATE OR REPLACE FUNCTION "set_project_user_signed_up_at_from_created_at"()
RETURNS TRIGGER AS $$
BEGIN
IF NEW."signedUpAt" IS NULL THEN
NEW."signedUpAt" := NEW."createdAt";
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
CREATE TRIGGER "ProjectUser_set_signedUpAt_from_createdAt"
BEFORE INSERT ON "ProjectUser"
FOR EACH ROW
EXECUTE FUNCTION "set_project_user_signed_up_at_from_created_at"();

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser"
ADD CONSTRAINT "ProjectUser_signedUpAt_not_null"
CHECK ("signedUpAt" IS NOT NULL) NOT VALID;

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" VALIDATE CONSTRAINT "ProjectUser_signedUpAt_not_null";

-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" ALTER COLUMN "signedUpAt" SET NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Sql } from 'postgres';
import { expect } from 'vitest';

export const postMigration = async (sql: Sql) => {
const triggers = await sql`
SELECT tgname
FROM pg_trigger
WHERE tgrelid = '"ProjectUser"'::regclass
AND tgname = 'ProjectUser_set_signedUpAt_from_createdAt'
AND NOT tgisinternal
`;
expect(triggers).toHaveLength(1);

const constraints = await sql`
SELECT conname, convalidated
FROM pg_constraint
WHERE conrelid = '"ProjectUser"'::regclass
AND conname IN (
'ProjectUser_risk_score_bot_range',
'ProjectUser_risk_score_free_trial_abuse_range',
'ProjectUser_signedUpAt_not_null'
)
ORDER BY conname
`;

expect(constraints).toHaveLength(3);
for (const c of constraints) {
expect(c.convalidated, `${c.conname} should be validated`).toBe(true);
}

const colInfo = await sql`
SELECT is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'ProjectUser' AND column_name = 'signedUpAt'
`;
expect(colInfo).toHaveLength(1);
expect(colInfo[0].is_nullable).toBe('NO');
expect(colInfo[0].column_default).toBe(null);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { randomUUID } from 'crypto';
import type { Sql } from 'postgres';
import { expect } from 'vitest';

export const postMigration = async (sql: Sql) => {
const projectId = `test-${randomUUID()}`;
const tenancyId = randomUUID();
const userId = randomUUID();

await sql`INSERT INTO "Project" ("id", "createdAt", "updatedAt", "displayName", "description", "isProductionMode") VALUES (${projectId}, NOW(), NOW(), 'Test', '', false)`;
await sql`INSERT INTO "Tenancy" ("id", "createdAt", "updatedAt", "projectId", "branchId", "hasNoOrganization") VALUES (${tenancyId}::uuid, NOW(), NOW(), ${projectId}, 'main', 'TRUE'::"BooleanTrue")`;
await sql`INSERT INTO "ProjectUser" ("projectUserId", "tenancyId", "mirroredProjectId", "mirroredBranchId", "createdAt", "updatedAt", "lastActiveAt") VALUES (${userId}::uuid, ${tenancyId}::uuid, ${projectId}, 'main', NOW(), NOW(), NOW())`;

const rows = await sql`
SELECT "signedUpAt", "createdAt"
FROM "ProjectUser"
WHERE "projectUserId" = ${userId}::uuid
`;

expect(rows).toHaveLength(1);
expect(rows[0].signedUpAt).not.toBeNull();
expect(rows[0].signedUpAt.toISOString()).toBe(rows[0].createdAt.toISOString());
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Sql } from 'postgres';
import { expect } from 'vitest';

export const postMigration = async (sql: Sql) => {
const indexes = await sql`
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = current_schema()
AND tablename = 'ProjectUser'
AND indexname IN (
'ProjectUser_signedUpAt_asc',
'ProjectUser_signUpIp_recent_idx',
'ProjectUser_signUpEmailBase_recent_idx'
)
ORDER BY indexname
`;

expect(indexes.map((row) => row.indexname)).toEqual([
'ProjectUser_signUpEmailBase_recent_idx',
'ProjectUser_signUpIp_recent_idx',
'ProjectUser_signedUpAt_asc',
]);

const indexDefByName = Object.fromEntries(indexes.map((row) => [row.indexname, row.indexdef]));

expect(indexDefByName['ProjectUser_signedUpAt_asc']).toContain('"tenancyId", "isAnonymous", "signedUpAt"');
expect(indexDefByName['ProjectUser_signUpIp_recent_idx']).toContain('"tenancyId", "isAnonymous", "signUpIp", "signedUpAt"');
expect(indexDefByName['ProjectUser_signUpEmailBase_recent_idx']).toContain('"tenancyId", "isAnonymous", "signUpEmailBase", "signedUpAt"');
};
15 changes: 15 additions & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -268,12 +268,24 @@ model ProjectUser {
requiresTotpMfa Boolean @default(false)
totpSecret Bytes?
isAnonymous Boolean @default(false)
signUpCountryCode String?

// Admin restriction fields - can be set by signup rules or manually by admins
restrictedByAdmin Boolean @default(false)
restrictedByAdminReason String? // Publicly viewable reason (shown to user)
restrictedByAdminPrivateDetails String? // Private details (server access only)

// Sign-up metadata
signedUpAt DateTime
signUpIp String?
signUpIpTrusted Boolean?
signUpEmailNormalized String?
signUpEmailBase String?

// Sign-up risk scores (0-100, set at sign-up time)
signUpRiskScoreBot Int @default(0) @db.SmallInt
signUpRiskScoreFreeTrialAbuse Int @default(0) @db.SmallInt
Comment thread
mantrakp04 marked this conversation as resolved.

projectUserOAuthAccounts ProjectUserOAuthAccount[]
teamMembers TeamMember[]
contactChannels ContactChannel[]
Expand All @@ -298,6 +310,9 @@ model ProjectUser {
@@index([tenancyId, displayName(sort: Desc)], name: "ProjectUser_displayName_desc")
@@index([tenancyId, createdAt(sort: Asc)], name: "ProjectUser_createdAt_asc")
@@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, signUpEmailBase, signedUpAt], name: "ProjectUser_signUpEmailBase_recent_idx")
@@index([tenancyId, sequenceId], name: "ProjectUser_tenancyId_sequenceId_idx")
@@index([shouldUpdateSequenceId, tenancyId], name: "ProjectUser_shouldUpdateSequenceId_idx")
}
Expand Down
8 changes: 7 additions & 1 deletion apps/backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { ensurePermissionDefinition, grantTeamPermission } from '@/lib/permissions';
import { createOrUpdateProjectWithLegacyConfig, getProject } from '@/lib/projects';
import { DEFAULT_BRANCH_ID, getSoleTenancyFromProjectBranch, type Tenancy } from '@/lib/tenancies';
import { getPrismaClientForTenancy, globalPrismaClient, PrismaClientTransaction } from '@/prisma-client';
import { PrismaClientTransaction, getPrismaClientForTenancy, globalPrismaClient } from '@/prisma-client';
import { ALL_APPS } from '@stackframe/stack-shared/dist/apps/apps-config';
import { DEFAULT_EMAIL_THEME_ID } from '@stackframe/stack-shared/dist/helpers/emails';
import { AdminUserProjectsCrud, ProjectsCrud } from '@stackframe/stack-shared/dist/interface/crud/projects';
Expand Down Expand Up @@ -318,6 +318,9 @@ export async function seed() {
tenancyId: internalTenancy.id,
mirroredProjectId: 'internal',
mirroredBranchId: DEFAULT_BRANCH_ID,
signedUpAt: new Date(),
signUpRiskScoreBot: 0,
signUpRiskScoreFreeTrialAbuse: 0,
}
});

Expand Down Expand Up @@ -447,6 +450,9 @@ export async function seed() {
tenancyId: internalTenancy.id,
mirroredProjectId: 'internal',
mirroredBranchId: DEFAULT_BRANCH_ID,
signedUpAt: new Date(),
signUpRiskScoreBot: 0,
signUpRiskScoreFreeTrialAbuse: 0,
}
});

Expand Down
Loading
Loading