Skip to content

Commit f3f2adc

Browse files
N2D4fomalhautbbazumo
authored
Remove SmartRequest.auth.project.config (stack-auth#658)
Co-authored-by: Zai Shi <zaishi00@outlook.com> Co-authored-by: moritz <moritsch@student.ethz.ch>
1 parent 47a7a43 commit f3f2adc

File tree

16 files changed

+97
-79
lines changed

16 files changed

+97
-79
lines changed

apps/backend/prisma/seed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async function seed() {
6969
allow_localhost: allowLocalhost,
7070
domains: [
7171
...(dashboardDomain && new URL(dashboardDomain).hostname !== 'localhost' ? [{ domain: dashboardDomain, handler_path: '/handler' }] : []),
72-
...internalProject.config.domains.filter((d) => d.domain !== dashboardDomain),
72+
...internalTenancy.config.domains.filter((d) => d.domain !== dashboardDomain),
7373
]
7474
},
7575
},

apps/backend/src/app/api/latest/(api-keys)/handlers.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
169169
body: type === 'user' ? userApiKeysCreateOutputSchema.defined() : teamApiKeysCreateOutputSchema.defined(),
170170
}),
171171
handler: async ({ url, auth, body }) => {
172-
await throwIfFeatureDisabled(auth.project.config, type);
172+
await throwIfFeatureDisabled(auth.tenancy.config, type);
173173
const { userId, teamId } = await parseTypeAndParams({ type, params: body });
174174
await ensureUserCanManageApiKeys(auth, {
175175
userId,
@@ -222,6 +222,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
222222
auth: yupObject({
223223
type: serverOrHigherAuthTypeSchema,
224224
project: adaptSchema.defined(),
225+
tenancy: adaptSchema.defined(),
225226
}).defined(),
226227
body: yupObject({
227228
api_key: yupString().defined(),
@@ -233,7 +234,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
233234
body: (type === 'user' ? userApiKeysCrud : teamApiKeysCrud).server.readSchema.defined(),
234235
}),
235236
handler: async ({ auth, body }) => {
236-
await throwIfFeatureDisabled(auth.project.config, type);
237+
await throwIfFeatureDisabled(auth.tenancy.config, type);
237238

238239
const apiKey = await prismaClient.projectApiKey.findUnique({
239240
where: {
@@ -282,7 +283,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
282283
}),
283284

284285
onList: async ({ auth, query }) => {
285-
await throwIfFeatureDisabled(auth.project.config, type);
286+
await throwIfFeatureDisabled(auth.tenancy.config, type);
286287
const { userId, teamId } = await parseTypeAndParams({ type, params: query });
287288
await ensureUserCanManageApiKeys(auth, {
288289
userId,
@@ -307,7 +308,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
307308
},
308309

309310
onRead: async ({ auth, query, params }) => {
310-
await throwIfFeatureDisabled(auth.project.config, type);
311+
await throwIfFeatureDisabled(auth.tenancy.config, type);
311312

312313
const apiKey = await prismaClient.projectApiKey.findUnique({
313314
where: {
@@ -330,7 +331,7 @@ function createApiKeyHandlers<Type extends "user" | "team">(type: Type) {
330331
},
331332

332333
onUpdate: async ({ auth, data, params, query }) => {
333-
await throwIfFeatureDisabled(auth.project.config, type);
334+
await throwIfFeatureDisabled(auth.tenancy.config, type);
334335

335336
const existingApiKey = await prismaClient.projectApiKey.findUnique({
336337
where: {

apps/backend/src/app/api/latest/auth/mfa/sign-in/verification-code-handler.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const mfaVerificationCodeHandler = createVerificationCodeHandler({
7373
},
7474
});
7575

76-
export async function createMfaRequiredError(options: { project: ProjectsCrud["Admin"]["Read"], branchId: string, isNewUser: boolean, userId: string }) {
76+
export async function createMfaRequiredError(options: { project: Omit<ProjectsCrud["Admin"]["Read"], "config">, branchId: string, isNewUser: boolean, userId: string }) {
7777
const attemptCode = await mfaVerificationCodeHandler.createCode({
7878
expiresInMs: 1000 * 60 * 5,
7979
project: options.project,

apps/backend/src/app/api/latest/integrations/neon/domains/crud.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const domainCrudHandlers = createLazyProxy(() => createCrudHandlers(domai
5050
domain: domainSchema.optional(),
5151
}),
5252
onCreate: async ({ auth, data, params }) => {
53-
const oldDomains = auth.project.config.domains;
53+
const oldDomains = auth.tenancy.config.domains;
5454
await projectsCrudHandlers.adminUpdate({
5555
data: {
5656
config: {
@@ -64,7 +64,7 @@ export const domainCrudHandlers = createLazyProxy(() => createCrudHandlers(domai
6464
return { domain: data.domain };
6565
},
6666
onDelete: async ({ auth, params }) => {
67-
const oldDomains = auth.project.config.domains;
67+
const oldDomains = auth.tenancy.config.domains;
6868
await projectsCrudHandlers.adminUpdate({
6969
data: {
7070
config: { domains: oldDomains.filter((domain) => domain.domain !== params.domain) },
@@ -75,7 +75,7 @@ export const domainCrudHandlers = createLazyProxy(() => createCrudHandlers(domai
7575
},
7676
onList: async ({ auth }) => {
7777
return {
78-
items: auth.project.config.domains.map((domain) => ({ domain: domain.domain })),
78+
items: auth.tenancy.config.domains.map((domain) => ({ domain: domain.domain })),
7979
is_paginated: false,
8080
};
8181
},

apps/backend/src/app/api/latest/integrations/neon/oauth-providers/crud.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createOrUpdateProject } from "@/lib/projects";
2+
import { getTenancy } from "@/lib/tenancies";
23
import { createCrudHandlers } from "@/route-handlers/crud-handler";
34
import { createCrud } from "@stackframe/stack-shared/dist/crud";
45
import * as schemaFields from "@stackframe/stack-shared/dist/schema-fields";
@@ -94,8 +95,9 @@ export const oauthProvidersCrudHandlers = createLazyProxy(() => createCrudHandle
9495
}
9596
}
9697
});
98+
const updatedTenancy = await getTenancy(auth.tenancy.id) ?? throwErr('Tenancy not found after update?'); // since we updated the config, we need to re-fetch the tenancy
9799

98-
return updated.config.oauth_providers.find(provider => provider.id === data.id) ?? throwErr('Provider not found');
100+
return updatedTenancy.config.oauth_providers.find(provider => provider.id === data.id) ?? throwErr('Provider not found');
99101
},
100102
onUpdate: async ({ auth, data, params }) => {
101103
if (!auth.tenancy.config.oauth_providers.find(provider => provider.id === params.oauth_provider_id)) {
@@ -107,16 +109,17 @@ export const oauthProvidersCrudHandlers = createLazyProxy(() => createCrudHandle
107109
projectId: auth.project.id,
108110
data: {
109111
config: {
110-
oauth_providers: auth.project.config.oauth_providers
112+
oauth_providers: auth.tenancy.config.oauth_providers
111113
.map(provider => provider.id === params.oauth_provider_id ? {
112114
...provider,
113115
...data,
114116
} : provider),
115117
}
116118
}
117119
});
120+
const updatedTenancy = await getTenancy(auth.tenancy.id) ?? throwErr('Tenancy not found after update?'); // since we updated the config, we need to re-fetch the tenancy
118121

119-
return updated.config.oauth_providers.find(provider => provider.id === params.oauth_provider_id) ?? throwErr('Provider not found');
122+
return updatedTenancy.config.oauth_providers.find(provider => provider.id === params.oauth_provider_id) ?? throwErr('Provider not found');
120123
},
121124
onList: async ({ auth }) => {
122125
return {
@@ -134,7 +137,7 @@ export const oauthProvidersCrudHandlers = createLazyProxy(() => createCrudHandle
134137
projectId: auth.project.id,
135138
data: {
136139
config: {
137-
oauth_providers: auth.project.config.oauth_providers.filter(provider =>
140+
oauth_providers: auth.tenancy.config.oauth_providers.filter(provider =>
138141
provider.id !== params.oauth_provider_id
139142
)
140143
}

apps/backend/src/app/api/latest/internal/projects/crud.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { createOrUpdateProject, getProjectQuery, listManagedProjectIds } from "@/lib/projects";
2+
import { getSoleTenancyFromProject } from "@/lib/tenancies";
23
import { prismaClient, rawQueryAll } from "@/prisma-client";
34
import { createCrudHandlers } from "@/route-handlers/crud-handler";
45
import { KnownErrors } from "@stackframe/stack-shared";
56
import { adminUserProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
67
import { projectIdSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
78
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
8-
import { typedEntries, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
9+
import { isNotNull, typedEntries, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
910
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
1011

1112
// if one of these users creates a project, the others will be added as owners
@@ -28,24 +29,36 @@ export const adminUserProjectsCrudHandlers = createLazyProxy(() => createCrudHan
2829
const ownerPack = ownerPacks.find(p => p.has(user.id));
2930
const userIds = ownerPack ? [...ownerPack] : [user.id];
3031

31-
return await createOrUpdateProject({
32+
const project = await createOrUpdateProject({
3233
ownerIds: userIds,
3334
initialBranchId: 'main',
3435
type: 'create',
3536
data,
3637
});
38+
const tenancy = await getSoleTenancyFromProject(project);
39+
return {
40+
...project,
41+
config: tenancy.config,
42+
};
3743
},
3844
onList: async ({ auth }) => {
3945
const projectIds = listManagedProjectIds(auth.user ?? throwErr('auth.user is required'));
4046
const projectsRecord = await rawQueryAll(prismaClient, typedFromEntries(projectIds.map((id, index) => [index, getProjectQuery(id)])));
41-
const projects = await Promise.all(typedEntries(projectsRecord).map(async ([_, project]) => await project));
47+
const projects = (await Promise.all(typedEntries(projectsRecord).map(async ([_, project]) => await project))).filter(isNotNull);
4248

43-
if (projects.filter(x => x !== null).length !== projectIds.length) {
49+
if (projects.length !== projectIds.length) {
4450
throw new StackAssertionError('Failed to fetch all projects of a user');
4551
}
4652

53+
const projectsWithConfig = await Promise.all(projects.map(async (project) => {
54+
return {
55+
...project,
56+
config: (await getSoleTenancyFromProject(project)).config,
57+
};
58+
}));
59+
4760
return {
48-
items: projects as NonNullable<typeof projects[number]>[],
61+
items: projectsWithConfig,
4962
is_paginated: false,
5063
} as const;
5164
}

apps/backend/src/app/api/latest/internal/projects/current/crud.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createOrUpdateProject } from "@/lib/projects";
2+
import { getSoleTenancyFromProject } from "@/lib/tenancies";
23
import { retryTransaction } from "@/prisma-client";
34
import { createCrudHandlers } from "@/route-handlers/crud-handler";
45
import { projectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
@@ -8,14 +9,21 @@ import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
89
export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(projectsCrud, {
910
paramsSchema: yupObject({}),
1011
onUpdate: async ({ auth, data }) => {
11-
return await createOrUpdateProject({
12+
const project = await createOrUpdateProject({
1213
type: "update",
1314
projectId: auth.project.id,
1415
data: data,
1516
});
17+
return {
18+
...project,
19+
config: (await getSoleTenancyFromProject(project)).config, // since we updated the project, we need tore-fetch the new config
20+
};
1621
},
1722
onRead: async ({ auth }) => {
18-
return auth.project;
23+
return {
24+
...auth.project,
25+
config: auth.tenancy.config,
26+
};
1927
},
2028
onDelete: async ({ auth }) => {
2129
await retryTransaction(async (tx) => {

apps/backend/src/app/api/latest/projects/current/crud.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
66
export const clientProjectsCrudHandlers = createLazyProxy(() => createCrudHandlers(clientProjectsCrud, {
77
paramsSchema: yupObject({}),
88
onRead: async ({ auth }) => {
9-
return auth.project;
9+
return {
10+
...auth.project,
11+
config: auth.tenancy.config,
12+
};
1013
},
1114
}));

apps/backend/src/lib/projects.tsx

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { StackAssertionError, captureError } from "@stackframe/stack-shared/dist
77
import { filterUndefined, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
88
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
99
import { RawQuery, prismaClient, rawQuery, retryTransaction } from "../prisma-client";
10-
import { getRenderedOrganizationConfigQuery, overrideEnvironmentConfigOverride, renderedOrganizationConfigToProjectCrud } from "./config";
10+
import { overrideEnvironmentConfigOverride } from "./config";
1111
import { getSoleTenancyFromProject } from "./tenancies";
1212

1313
function isStringArray(value: any): value is string[] {
@@ -27,51 +27,34 @@ export function listManagedProjectIds(projectUser: UsersCrud["Admin"]["Read"]) {
2727
return managedProjectIds;
2828
}
2929

30-
export function getProjectQuery(projectId: string): RawQuery<Promise<ProjectsCrud["Admin"]["Read"] | null>> {
31-
return RawQuery.then(
32-
RawQuery.all([
33-
{
34-
sql: Prisma.sql`
30+
export function getProjectQuery(projectId: string): RawQuery<Promise<Omit<ProjectsCrud["Admin"]["Read"], "config"> | null>> {
31+
return {
32+
sql: Prisma.sql`
3533
SELECT "Project".*
3634
FROM "Project"
3735
WHERE "Project"."id" = ${projectId}
3836
`,
39-
postProcess: (queryResult) => {
40-
if (queryResult.length > 1) {
41-
throw new StackAssertionError(`Expected 0 or 1 projects with id ${projectId}, got ${queryResult.length}`, { queryResult });
42-
}
43-
if (queryResult.length === 0) {
44-
return null;
45-
}
46-
const row = queryResult[0];
47-
return {
48-
id: row.id,
49-
display_name: row.displayName,
50-
description: row.description,
51-
created_at_millis: new Date(row.createdAt + "Z").getTime(),
52-
user_count: row.userCount,
53-
is_production_mode: row.isProductionMode,
54-
};
55-
},
56-
} as const,
57-
getRenderedOrganizationConfigQuery({ projectId, branchId: "main", organizationId: null }),
58-
] as const),
59-
async (result) => {
60-
const projectPart = result[0];
61-
if (!projectPart) {
37+
postProcess: async (queryResult) => {
38+
if (queryResult.length > 1) {
39+
throw new StackAssertionError(`Expected 0 or 1 projects with id ${projectId}, got ${queryResult.length}`, { queryResult });
40+
}
41+
if (queryResult.length === 0) {
6242
return null;
6343
}
64-
const renderedConfig = await result[1];
65-
44+
const row = queryResult[0];
6645
return {
67-
...projectPart,
68-
config: renderedOrganizationConfigToProjectCrud(renderedConfig),
46+
id: row.id,
47+
display_name: row.displayName,
48+
description: row.description,
49+
created_at_millis: new Date(row.createdAt + "Z").getTime(),
50+
user_count: row.userCount,
51+
is_production_mode: row.isProductionMode,
6952
};
70-
}
71-
);
53+
},
54+
};
7255
}
7356

74-
export async function getProject(projectId: string): Promise<ProjectsCrud["Admin"]["Read"] | null> {
57+
export async function getProject(projectId: string): Promise<Omit<ProjectsCrud["Admin"]["Read"], "config"> | null> {
7558
const result = await rawQuery(prismaClient, getProjectQuery(projectId));
7659
return result;
7760
}

apps/backend/src/lib/tenancies.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
33
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
44
import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
55
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
6-
import { getRenderedOrganizationConfigQuery } from "./config";
6+
import { getRenderedOrganizationConfigQuery, renderedOrganizationConfigToProjectCrud } from "./config";
77
import { getProject } from "./projects";
88

99
export async function tenancyPrismaToCrud(prisma: Prisma.TenancyGetPayload<{}>) {
@@ -21,11 +21,12 @@ export async function tenancyPrismaToCrud(prisma: Prisma.TenancyGetPayload<{}>)
2121
branchId: prisma.branchId,
2222
organizationId: prisma.organizationId,
2323
}));
24+
const oldProjectConfig = renderedOrganizationConfigToProjectCrud(completeConfig);
2425

2526
return {
2627
id: prisma.id,
2728
/** @deprecated */
28-
config: projectCrud.config,
29+
config: oldProjectConfig,
2930
completeConfig,
3031
branchId: prisma.branchId,
3132
organization: prisma.organizationId === null ? null : {
@@ -50,13 +51,13 @@ const soleTenancyIdsCache = new Map<string, string>();
5051
* @deprecated This is a temporary function for the situation where every project has exactly one tenancy. Later,
5152
* we will support multiple tenancies per project, and all uses of this function will be refactored.
5253
*/
53-
export function getSoleTenancyFromProject(project: ProjectsCrud["Admin"]["Read"] | string): Promise<Tenancy>;
54+
export function getSoleTenancyFromProject(project: Omit<ProjectsCrud["Admin"]["Read"], "config"> | string): Promise<Tenancy>;
5455
/**
5556
* @deprecated This is a temporary function for the situation where every project has exactly one tenancy. Later,
5657
* we will support multiple tenancies per project, and all uses of this function will be refactored.
5758
*/
58-
export function getSoleTenancyFromProject(project: ProjectsCrud["Admin"]["Read"] | string, returnNullIfNotFound: boolean): Promise<Tenancy | null>;
59-
export async function getSoleTenancyFromProject(projectOrId: ProjectsCrud["Admin"]["Read"] | string, returnNullIfNotFound: boolean = false): Promise<Tenancy | null> {
59+
export function getSoleTenancyFromProject(project: Omit<ProjectsCrud["Admin"]["Read"], "config"> | string, returnNullIfNotFound: boolean): Promise<Tenancy | null>;
60+
export async function getSoleTenancyFromProject(projectOrId: Omit<ProjectsCrud["Admin"]["Read"], "config"> | string, returnNullIfNotFound: boolean = false): Promise<Tenancy | null> {
6061
let project;
6162
if (!projectOrId) {
6263
throw new StackAssertionError("Project is required", { projectOrId });
@@ -82,10 +83,11 @@ export async function getSoleTenancyFromProject(projectOrId: ProjectsCrud["Admin
8283
branchId: "main",
8384
organizationId: null,
8485
}));
86+
const oldProjectConfig = renderedOrganizationConfigToProjectCrud(completeConfig);
8587

8688
return {
8789
id: tenancyId,
88-
config: project.config,
90+
config: oldProjectConfig,
8991
completeConfig,
9092
branchId: "main",
9193
organization: null,

0 commit comments

Comments
 (0)