mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Add internal project check to listManagedProjectIds
This commit is contained in:
parent
5bfe1a79ce
commit
b8ea06f73d
@ -47,7 +47,10 @@ export const POST = createSmartRouteHandler({
|
||||
|
||||
// Verify user has access to the target project
|
||||
if (projectId != null) {
|
||||
const user = fullReq.auth?.user;
|
||||
if (fullReq.auth?.project.id !== "internal") {
|
||||
throw new StatusError(StatusError.Forbidden, "You do not have access to this project");
|
||||
}
|
||||
const user = fullReq.auth.user;
|
||||
if (user == null) {
|
||||
throw new StatusError(StatusError.Forbidden, "You do not have access to this project");
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { renderedOrganizationConfigToProjectCrud } from "@/lib/config";
|
||||
import { getPrismaClientForTenancy } from "@/prisma-client";
|
||||
import { createOrUpdateProjectWithLegacyConfig, getProjectQuery, listManagedProjectIds } from "@/lib/projects";
|
||||
import { ensureTeamMembershipExists } from "@/lib/request-checks";
|
||||
import { DEFAULT_BRANCH_ID, getSoleTenancyFromProjectBranch } from "@/lib/tenancies";
|
||||
import { globalPrismaClient, rawQueryAll } from "@/prisma-client";
|
||||
import { getPrismaClientForTenancy, globalPrismaClient, rawQueryAll } from "@/prisma-client";
|
||||
import { createCrudHandlers } from "@/route-handlers/crud-handler";
|
||||
import { KnownErrors } from "@stackframe/stack-shared";
|
||||
import { adminUserProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
|
||||
@ -17,14 +16,17 @@ export const adminUserProjectsCrudHandlers = createLazyProxy(() => createCrudHan
|
||||
projectId: projectIdSchema.defined(),
|
||||
}),
|
||||
onPrepare: async ({ auth }) => {
|
||||
if (!auth.user) {
|
||||
throw new KnownErrors.UserAuthenticationRequired;
|
||||
}
|
||||
if (auth.project.id !== "internal") {
|
||||
throw new KnownErrors.ExpectedInternalProject();
|
||||
}
|
||||
if (!auth.user) {
|
||||
throw new KnownErrors.UserAuthenticationRequired;
|
||||
}
|
||||
},
|
||||
onCreate: async ({ auth, data }) => {
|
||||
if (auth.project.id !== "internal") {
|
||||
throw new KnownErrors.ExpectedInternalProject();
|
||||
}
|
||||
const user = auth.user ?? throwErr('auth.user is required');
|
||||
const prisma = await getPrismaClientForTenancy(auth.tenancy);
|
||||
await ensureTeamMembershipExists(prisma, {
|
||||
@ -51,6 +53,12 @@ export const adminUserProjectsCrudHandlers = createLazyProxy(() => createCrudHan
|
||||
};
|
||||
},
|
||||
onList: async ({ auth }) => {
|
||||
if (auth.project.id !== "internal") {
|
||||
throw new KnownErrors.ExpectedInternalProject();
|
||||
}
|
||||
if (!auth.user) {
|
||||
throw new KnownErrors.UserAuthenticationRequired();
|
||||
}
|
||||
const projectIds = await listManagedProjectIds(auth.user ?? throwErr('auth.user is required'));
|
||||
const projectsRecord = await rawQueryAll(globalPrismaClient, typedFromEntries(projectIds.map((id, index) => [index, getProjectQuery(id)])));
|
||||
const projects = (await Promise.all(typedEntries(projectsRecord).map(async ([_, project]) => await project))).filter(isNotNull);
|
||||
|
||||
@ -132,6 +132,26 @@ describe("AI Query Endpoint - Validation", () => {
|
||||
expect(response.body).toEqual(expect.stringContaining("Invalid tool names"));
|
||||
});
|
||||
|
||||
it("rejects project-scoped AI requests outside internal project auth context", async ({ expect }) => {
|
||||
const { projectId } = await Project.createAndSwitch();
|
||||
|
||||
const response = await niceBackendFetch("/api/v1/ai/query/generate", {
|
||||
method: "POST",
|
||||
accessType: "admin",
|
||||
body: {
|
||||
quality: "smart",
|
||||
speed: "fast",
|
||||
tools: [],
|
||||
systemPrompt: "command-center-ask-ai",
|
||||
messages: [{ role: "user", content: "test" }],
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual(expect.stringContaining("You do not have access to this project"));
|
||||
});
|
||||
|
||||
it("rejects missing systemPrompt field", async ({ expect }) => {
|
||||
const response = await niceBackendFetch("/api/v1/ai/query/generate", {
|
||||
method: "POST",
|
||||
|
||||
@ -43,6 +43,17 @@ it("is not allowed to list all current projects without signing in", async ({ ex
|
||||
`);
|
||||
});
|
||||
|
||||
it("is not allowed to list internal projects from a non-internal project context", async ({ expect }) => {
|
||||
await Project.createAndSwitch();
|
||||
const response = await niceBackendFetch("/api/v1/internal/projects", { accessType: "admin" });
|
||||
expect(response.status).toBe(401);
|
||||
expect(response.headers.get("x-stack-known-error")).toBe("EXPECTED_INTERNAL_PROJECT");
|
||||
expect(response.body).toMatchObject({
|
||||
code: "EXPECTED_INTERNAL_PROJECT",
|
||||
error: "The project ID is expected to be internal.",
|
||||
});
|
||||
});
|
||||
|
||||
it("lists all current projects (empty list)", async ({ expect }) => {
|
||||
await Auth.fastSignUp();
|
||||
const response = await niceBackendFetch("/api/v1/internal/projects", { accessType: "client" });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user