Merge branch 'stack-auth:dev' into feat/python-sdk

This commit is contained in:
Ejiro Asiuwhu 2026-03-26 08:21:34 +01:00 committed by GitHub
commit 5c09ea70c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 77 additions and 81 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/backend",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"type": "module",

View File

@ -10,6 +10,10 @@ ALTER TABLE "ProjectUser"
ADD COLUMN "signUpEmailNormalized" TEXT,
ADD COLUMN "signUpEmailBase" TEXT;
-- Backward compatibility during rollout: old backends omit `signedUpAt` on
-- INSERT, so set a default immediately in the first migration.
ALTER TABLE "ProjectUser" ALTER COLUMN "signedUpAt" SET DEFAULT CURRENT_TIMESTAMP;
-- Add the risk score bounds without validating existing rows yet.
ALTER TABLE "ProjectUser"
ADD CONSTRAINT "ProjectUser_risk_score_bot_range"

View File

@ -28,13 +28,6 @@ ALTER TABLE "ProjectUser" VALIDATE CONSTRAINT "ProjectUser_risk_score_bot_range"
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" VALIDATE CONSTRAINT "ProjectUser_risk_score_free_trial_abuse_range";
-- Enforce `signedUpAt` after the backfill is complete. Set a DEFAULT first so
-- that inserts omitting the column still get a sensible value (CURRENT_TIMESTAMP).
-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
ALTER TABLE "ProjectUser" ALTER COLUMN "signedUpAt" SET DEFAULT CURRENT_TIMESTAMP;
-- SPLIT_STATEMENT_SENTINEL
-- SINGLE_STATEMENT_SENTINEL
-- RUN_OUTSIDE_TRANSACTION_SENTINEL

View File

@ -1,5 +1,6 @@
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
import { captureError, StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { wait } from "@stackframe/stack-shared/dist/utils/promises";
import { traceSpan } from "@stackframe/stack-shared/dist/utils/telemetry";
import createEmailableClient from "emailable";
@ -41,17 +42,15 @@ function validateVerifyResponse(value: unknown) {
async function verifyWithRetries(verifyFn: () => Promise<unknown>, maxAttempts: number, delayBaseMs: number) {
for (let i = 0; i < maxAttempts; i++) {
try {
return await verifyFn();
} catch (error) {
const code = (error != null && typeof error === "object" && !Array.isArray(error))
? Reflect.get(error, "code")
: null;
if (code !== 249) throw error; // only retry rate-limit errors
if (i < maxAttempts - 1) {
await new Promise(r => setTimeout(r, (Math.random() + 0.5) * delayBaseMs * (2 ** i)));
const res: any = await verifyFn();
if (!("state" in res)) {
if ("message" in res && res.message.includes("Your request is taking longer than normal")) {
await wait((Math.random() + 0.5) * delayBaseMs * (2 ** i));
continue;
}
throw new StackAssertionError("Emailable returned an unexpected response body", { response: res });
}
return res;
}
throw new StackAssertionError("Timed out while verifying email address with Emailable");
}
@ -81,46 +80,46 @@ export async function checkEmailWithEmailable(
_clientFactory?: (apiKey: string) => { verify: (email: string) => Promise<unknown> },
},
): Promise<EmailableCheckResult> {
const rawApiKey = getEnvVariable("STACK_EMAILABLE_API_KEY", "");
const emailDomain = email.split("@")[1]?.toLowerCase() ?? "";
try {
const rawApiKey = getEnvVariable("STACK_EMAILABLE_API_KEY", "");
const emailDomain = email.split("@")[1]?.toLowerCase() ?? "";
// Always reject the explicit test domain, regardless of API key
if (emailDomain === EMAILABLE_NOT_DELIVERABLE_TEST_DOMAIN) {
const testResponse = buildTestUndeliverableResponse(email);
return { status: "not-deliverable", emailableResponse: testResponse, emailableScore: testResponse.score };
}
// Always reject the explicit test domain, regardless of API key
if (emailDomain === EMAILABLE_NOT_DELIVERABLE_TEST_DOMAIN) {
const testResponse = buildTestUndeliverableResponse(email);
return { status: "not-deliverable", emailableResponse: testResponse, emailableScore: testResponse.score };
}
if (!rawApiKey) {
if (["development", "test"].includes(getNodeEnvironment())) {
if (!rawApiKey) {
if (["development", "test"].includes(getNodeEnvironment())) {
return { status: "ok", emailableScore: null };
}
throw new StackAssertionError("STACK_EMAILABLE_API_KEY must not be empty; set it to 'disable_email_validation' to disable email validation");
}
const apiKey = rawApiKey === "disable_email_validation" ? "" : rawApiKey;
if (!apiKey || isReservedTestDomain(emailDomain)) {
return { status: "ok", emailableScore: null };
}
throw new StackAssertionError("STACK_EMAILABLE_API_KEY must not be empty; set it to 'disable_email_validation' to disable email validation");
const clientFactory = options?._clientFactory ?? createEmailableClient;
const retryDelayBase = options?.retryExponentialDelayBaseMs ?? RETRY_BACKOFF_BASE_MS;
return await traceSpan("checking email address with Emailable", async () => {
const client = clientFactory(apiKey);
const raw = await verifyWithRetries(() => client.verify(email), 4, retryDelayBase);
console.log("Received emailable response", { email, raw });
const response = validateVerifyResponse(raw);
if (response.state === "undeliverable") {
return { status: "not-deliverable", emailableResponse: response, emailableScore: response.score };
}
return { status: "ok", emailableScore: response.score };
});
} catch (error) {
captureError("emailable-api-error", new StackAssertionError("Error while checking email address with Emailable", { cause: error, email, options }));
return { status: "error", error, emailableScore: null };
}
const apiKey = rawApiKey === "disable_email_validation" ? "" : rawApiKey;
if (!apiKey || isReservedTestDomain(emailDomain)) {
return { status: "ok", emailableScore: null };
}
const clientFactory = options?._clientFactory ?? createEmailableClient;
const retryDelayBase = options?.retryExponentialDelayBaseMs ?? RETRY_BACKOFF_BASE_MS;
return await traceSpan("checking email address with Emailable", async () => {
const client = clientFactory(apiKey);
let raw: unknown;
try {
raw = await verifyWithRetries(() => client.verify(email), 4, retryDelayBase);
} catch (error) {
captureError("emailable-api-error", error);
return { status: "error", error, emailableScore: null };
}
const response = validateVerifyResponse(raw);
if (response.state === "undeliverable" || response.disposable) {
return { status: "not-deliverable", emailableResponse: response, emailableScore: response.score };
}
return { status: "ok", emailableScore: response.score };
});
}

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dashboard",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dev-launchpad",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/e2e-tests",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"type": "module",

View File

@ -1,7 +1,7 @@
{
"name": "@stackframe/hosted-components",
"private": true,
"version": "2.8.77",
"version": "2.8.78",
"type": "module",
"scripts": {
"dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/mock-oauth-server",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"main": "index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-docs",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"description": "",
"main": "index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-cjs-test",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/convex-example",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-demo-app",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"description": "",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/docs-examples",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"description": "",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/e-commerce-demo",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/js-example",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"description": "",

View File

@ -1,7 +1,7 @@
{
"name": "@stackframe/lovable-react-18-example",
"private": true,
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"type": "module",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-middleware-demo",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,7 +1,7 @@
{
"name": "react-example",
"private": true,
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"type": "module",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-supabase",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dashboard-ui-components",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/init-stack",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"description": "The setup wizard for Stack. https://stack-auth.com",
"main": "dist/index.mjs",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/js",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/react",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-cli",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"description": "The CLI for Stack Auth. https://stack-auth.com",
"main": "dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-sc",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"exports": {
"./force-react-server": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-shared",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"scripts": {
"build": "rimraf dist && tsdown",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-ui",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/stack",
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -11,7 +11,7 @@
"//": "NEXT_LINE_PLATFORM template",
"private": true,
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -2,7 +2,7 @@
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/template",
"private": true,
"version": "2.8.77",
"version": "2.8.78",
"repository": "https://github.com/stack-auth/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/swift-sdk",
"version": "2.8.77",
"version": "2.8.78",
"private": true,
"description": "Stack Auth Swift SDK",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/sdk-spec",
"version": "2.8.77",
"version": "2.8.78",
"private": true,
"description": "Stack Auth SDK specification files",
"scripts": {}