remove old config

This commit is contained in:
Zai Shi 2025-07-24 13:00:50 -07:00
parent 01b0c84a0d
commit a9ceab2fb4
8 changed files with 107 additions and 32 deletions

View File

@ -44,7 +44,7 @@ async function createProjectUserOAuthAccount(prisma: PrismaClient, params: {
}
const redirectOrThrowError = (error: KnownError, tenancy: Tenancy, errorRedirectUrl?: string) => {
if (!errorRedirectUrl || !validateRedirectUrl(errorRedirectUrl, tenancy.config.domains, tenancy.config.allow_localhost)) {
if (!errorRedirectUrl || !validateRedirectUrl(errorRedirectUrl, Object.values(tenancy.config.domains), tenancy.config.domains.allowLocalhost)) {
throw error;
}
@ -119,12 +119,17 @@ const handler = createSmartRouteHandler({
throw new KnownErrors.OuterOAuthTimeout();
}
const provider = tenancy.config.oauth_providers.find((p) => p.id === params.provider_id);
if (!provider) {
const providerRaw = Object.entries(tenancy.config.auth.oauth.providers).find(([providerId, _]) => providerId === params.provider_id);
if (!providerRaw) {
throw new KnownErrors.OAuthProviderNotFoundOrNotEnabled();
}
const providerObj = await getProvider(provider);
const provider = {
id: providerRaw[0],
...providerRaw[1],
};
const providerObj = await getProvider(provider as any);
let callbackResult: Awaited<ReturnType<typeof providerObj.getCallback>>;
try {
callbackResult = await providerObj.getCallback({
@ -279,7 +284,7 @@ const handler = createSmartRouteHandler({
// ========================== sign up user ==========================
if (!tenancy.config.sign_up_enabled) {
if (!tenancy.config.auth.allowSignUp) {
throw new KnownErrors.SignUpNotEnabled();
}
@ -298,7 +303,7 @@ const handler = createSmartRouteHandler({
// Check if we should link this OAuth account to an existing user based on email
if (oldContactChannel && oldContactChannel.usedForAuth) {
const oauthAccountMergeStrategy = tenancy.config.oauth_account_merge_strategy;
const oauthAccountMergeStrategy = tenancy.config.auth.oauth.accountMergeStrategy;
switch (oauthAccountMergeStrategy) {
case 'link_method': {
if (!oldContactChannel.isVerified) {

View File

@ -2,7 +2,7 @@ import { renderEmailWithTemplate } from "@/lib/email-rendering";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors";
import { adaptSchema, yupMixed, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { captureError, StackAssertionError, StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { StackAssertionError, StatusError, captureError } from "@stackframe/stack-shared/dist/utils/errors";
export const POST = createSmartRouteHandler({
@ -40,8 +40,8 @@ export const POST = createSmartRouteHandler({
if ((!body.template_id && !body.template_tsx_source) || (body.template_id && body.template_tsx_source)) {
throw new StatusError(400, "Exactly one of template_id or template_tsx_source must be provided");
}
const themeList = new Map(Object.entries(tenancy.completeConfig.emails.themeList));
const templateList = new Map(Object.entries(tenancy.completeConfig.emails.templateList));
const themeList = new Map(Object.entries(tenancy.config.emails.themeList));
const templateList = new Map(Object.entries(tenancy.config.emails.templateList));
const themeSource = body.theme_id ? themeList.get(body.theme_id)?.tsxSource : body.theme_tsx_source;
const templateSource = body.template_id ? templateList.get(body.template_id)?.tsxSource : body.template_tsx_source;
if (!themeSource) {

View File

@ -0,0 +1,39 @@
import { overrideEnvironmentConfigOverride } from "@/lib/config";
import { getPrismaClientForTenancy } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { environmentConfigCrud } from "@stackframe/stack-shared/dist/interface/crud/environment-config";
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
export const environmentConfigCrudHandlers = createLazyProxy(() => createCrudHandlers(environmentConfigCrud, {
paramsSchema: yupObject({}),
onRead: async ({ auth }) => {
return {
id: auth.tenancy.id,
project_id: auth.project.id,
branch_id: auth.tenancy.branchId,
organization_id: auth.tenancy.organization?.id,
config: auth.tenancy.completeConfig,
};
},
onUpdate: async ({ auth, data }) => {
const prisma = await getPrismaClientForTenancy(auth.tenancy);
if (data.config) {
await overrideEnvironmentConfigOverride({
tx: prisma,
projectId: auth.project.id,
branchId: auth.tenancy.branchId,
environmentConfigOverrideOverride: data.config,
});
}
return {
id: auth.tenancy.id,
project_id: auth.project.id,
branch_id: auth.tenancy.branchId,
organization_id: auth.tenancy.organization?.id,
config: auth.tenancy.completeConfig,
};
},
}));

View File

@ -1,3 +1,4 @@
import { renderedOrganizationConfigToProjectCrud } from "@/lib/config";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { clientProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
@ -8,7 +9,7 @@ export const clientProjectsCrudHandlers = createLazyProxy(() => createCrudHandle
onRead: async ({ auth }) => {
return {
...auth.project,
config: auth.tenancy.config,
config: renderedOrganizationConfigToProjectCrud(auth.tenancy.config),
};
},
}));

View File

@ -1,7 +1,7 @@
import { StackAssertionError, captureError } from "@stackframe/stack-shared/dist/utils/errors";
import { createUrlIfValid, isLocalhost } from "@stackframe/stack-shared/dist/utils/urls";
export function validateRedirectUrl(urlOrString: string | URL, domains: { domain: string, handler_path: string }[], allowLocalhost: boolean): boolean {
export function validateRedirectUrl(urlOrString: string | URL, domains: { baseUrl: string }[], allowLocalhost: boolean): boolean {
const url = createUrlIfValid(urlOrString);
if (!url) return false;
if (allowLocalhost && isLocalhost(url)) {
@ -9,10 +9,10 @@ export function validateRedirectUrl(urlOrString: string | URL, domains: { domain
}
return domains.some((domain) => {
const testUrl = url;
const baseUrl = createUrlIfValid(domain.domain);
const baseUrl = createUrlIfValid(domain.baseUrl);
if (!baseUrl) {
captureError("invalid-redirect-domain", new StackAssertionError("Invalid redirect domain; maybe this should be fixed in the database", {
domain: domain.domain,
domain: domain.baseUrl,
}));
return false;
}

View File

@ -2,7 +2,7 @@ import { globalPrismaClient, rawQuery } from "@/prisma-client";
import { Prisma } from "@prisma/client";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { getRenderedOrganizationConfigQuery, renderedOrganizationConfigToProjectCrud } from "./config";
import { getRenderedOrganizationConfigQuery } from "./config";
import { getProject } from "./projects";
/**
@ -31,13 +31,10 @@ export async function tenancyPrismaToCrud(prisma: Prisma.TenancyGetPayload<{}>)
branchId: prisma.branchId,
organizationId: prisma.organizationId,
}));
const oldProjectConfig = renderedOrganizationConfigToProjectCrud(completeConfig);
return {
id: prisma.id,
/** @deprecated */
config: oldProjectConfig,
completeConfig,
config: completeConfig,
branchId: prisma.branchId,
organization: prisma.organizationId === null ? null : {
// TODO actual organization type

View File

@ -1,7 +1,6 @@
import { DEFAULT_BRANCH_ID } from "@/lib/tenancies";
import { DEFAULT_BRANCH_ID, Tenancy } from "@/lib/tenancies";
import { DiscordProvider } from "@/oauth/providers/discord";
import OAuth2Server from "@node-oauth/oauth2-server";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { OAuthModel } from "./model";
@ -16,8 +15,8 @@ import { LinkedInProvider } from "./providers/linkedin";
import { MicrosoftProvider } from "./providers/microsoft";
import { MockProvider } from "./providers/mock";
import { SpotifyProvider } from "./providers/spotify";
import { XProvider } from "./providers/x";
import { TwitchProvider } from "./providers/twitch";
import { XProvider } from "./providers/x";
const _providers = {
github: GithubProvider,
@ -57,27 +56,28 @@ export function getProjectBranchFromClientId(clientId: string): [projectId: stri
return [projectId, branchId];
}
export async function getProvider(provider: ProjectsCrud['Admin']['Read']['config']['oauth_providers'][number]): Promise<OAuthBaseProvider> {
if (provider.type === 'shared') {
const clientId = _getEnvForProvider(provider.id).clientId;
const clientSecret = _getEnvForProvider(provider.id).clientSecret;
export async function getProvider(provider: Tenancy['config']['auth']['oauth']['providers'][string]): Promise<OAuthBaseProvider> {
const providerType = provider.type || throwErr("Provider type is required for shared providers");
if (provider.isShared) {
const clientId = _getEnvForProvider(providerType).clientId;
const clientSecret = _getEnvForProvider(providerType).clientSecret;
if (clientId === "MOCK") {
if (clientSecret !== "MOCK") {
throw new StackAssertionError("If OAuth provider client ID is set to MOCK, then client secret must also be set to MOCK");
}
return await mockProvider.create(provider.id);
return await mockProvider.create(providerType);
} else {
return await _providers[provider.id].create({
return await _providers[providerType].create({
clientId,
clientSecret,
});
}
} else {
return await _providers[provider.id].create({
clientId: provider.client_id || throwErr("Client ID is required for standard providers"),
clientSecret: provider.client_secret || throwErr("Client secret is required for standard providers"),
facebookConfigId: provider.facebook_config_id,
microsoftTenantId: provider.microsoft_tenant_id,
return await _providers[providerType].create({
clientId: provider.clientId || throwErr("Client ID is required for standard providers"),
clientSecret: provider.clientSecret || throwErr("Client secret is required for standard providers"),
facebookConfigId: provider.facebookConfigId,
microsoftTenantId: provider.microsoftTenantId,
});
}
}

View File

@ -0,0 +1,33 @@
import { CrudTypeOf, createCrud } from "../../crud";
import * as schemaFields from "../../schema-fields";
import { yupObject } from "../../schema-fields";
export const environmentConfigCrudAdminReadSchema = yupObject({
project_id: schemaFields.yupString().defined(),
branch_id: schemaFields.yupString().defined(),
organization_id: schemaFields.yupString().optional(),
id: schemaFields.yupString().defined(),
config: schemaFields.yupMixed().defined(),
}).defined();
export const environmentConfigCrudAdminUpdateSchema = yupObject({
config: schemaFields.yupMixed().optional(),
}).defined();
export const environmentConfigCrud = createCrud({
adminReadSchema: environmentConfigCrudAdminReadSchema,
adminUpdateSchema: environmentConfigCrudAdminUpdateSchema,
docs: {
adminRead: {
summary: 'Get the current environment config',
description: 'Get the current environment config',
tags: ['Environment Config'],
},
adminUpdate: {
summary: 'Update the current environment config',
description: 'Update the current environment config',
tags: ['Environment Config'],
},
},
});
export type EnvironmentConfigCrud = CrudTypeOf<typeof environmentConfigCrud>;