Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions src/api/routers/_shared/orgAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TRPCError } from '@trpc/server';
import { eq } from 'drizzle-orm';
import { getDb } from '../../../db/client.js';
import { getRunById } from '../../../db/repositories/runsRepository.js';
import { projects } from '../../../db/schema/index.js';

/**
* Verify that a project belongs to the given org.
* Throws `NOT_FOUND` if the project does not exist or belongs to a different org.
*/
export async function verifyProjectOrgAccess(projectId: string, orgId: string): Promise<void> {
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, projectId));
if (!project || project.orgId !== orgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
}

/**
* Verify that a run belongs to the given org (via its associated project).
* Throws `NOT_FOUND` if the run does not exist or belongs to a different org.
* Runs without a projectId are allowed through (no org scoping needed).
*/
export async function verifyRunOrgAccess(runId: string, orgId: string): Promise<void> {
const run = await getRunById(runId);
if (!run) throw new TRPCError({ code: 'NOT_FOUND' });
if (run.projectId) {
await verifyProjectOrgAccess(run.projectId, orgId);
}
}
37 changes: 6 additions & 31 deletions src/api/routers/agentConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
listAgentConfigs,
updateAgentConfig,
} from '../../db/repositories/settingsRepository.js';
import { agentConfigs, projects } from '../../db/schema/index.js';
import { agentConfigs } from '../../db/schema/index.js';
import { protectedProcedure, publicProcedure, router } from '../trpc.js';
import { verifyProjectOrgAccess } from './_shared/orgAccess.js';

async function validatePromptIfPresent(prompt: string | null | undefined) {
if (!prompt) return;
Expand All @@ -36,14 +37,7 @@ export const agentConfigsRouter = router({
.query(async ({ ctx, input }) => {
if (input?.projectId) {
// Verify project belongs to org
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, input.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(input.projectId, ctx.effectiveOrgId);
return listAgentConfigs({ projectId: input.projectId });
}
return listAgentConfigs({ orgId: ctx.effectiveOrgId });
Expand All @@ -64,14 +58,7 @@ export const agentConfigsRouter = router({
.mutation(async ({ ctx, input }) => {
// If projectId given, verify ownership
if (input.projectId) {
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, input.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(input.projectId, ctx.effectiveOrgId);
}
await validatePromptIfPresent(input.prompt);
return createAgentConfig({
Expand Down Expand Up @@ -112,13 +99,7 @@ export const agentConfigsRouter = router({
}
// Check project-scoped configs belong to user's org
if (config.projectId) {
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, config.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(config.projectId, ctx.effectiveOrgId);
}

const { id, ...updates } = input;
Expand All @@ -141,13 +122,7 @@ export const agentConfigsRouter = router({
throw new TRPCError({ code: 'NOT_FOUND' });
}
if (config.projectId) {
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, config.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(config.projectId, ctx.effectiveOrgId);
}

await deleteAgentConfig(input.id);
Expand Down
44 changes: 5 additions & 39 deletions src/api/routers/runs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { TRPCError } from '@trpc/server';
import { eq } from 'drizzle-orm';
import { z } from 'zod';
import { loadProjectConfigById } from '../../config/provider.js';
import { getDb } from '../../db/client.js';
import {
deleteDebugAnalysisByRunId,
getDebugAnalysisByRunId,
Expand All @@ -12,10 +10,10 @@ import {
listLlmCallsMeta,
listRuns,
} from '../../db/repositories/runsRepository.js';
import { projects } from '../../db/schema/index.js';
import { isAnalysisRunning } from '../../triggers/shared/debug-status.js';
import { logger } from '../../utils/logging.js';
import { protectedProcedure, router } from '../trpc.js';
import { verifyProjectOrgAccess } from './_shared/orgAccess.js';

const useQueue = !!process.env.REDIS_URL;

Expand Down Expand Up @@ -57,14 +55,7 @@ export const runsRouter = router({

// Verify org access
if (run.projectId) {
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, run.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(run.projectId, ctx.effectiveOrgId);
}

return run;
Expand Down Expand Up @@ -119,14 +110,7 @@ export const runsRouter = router({

// Verify org access
if (run.projectId) {
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, run.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(run.projectId, ctx.effectiveOrgId);
}

if (run.agentType === 'debug') {
Expand Down Expand Up @@ -199,18 +183,7 @@ export const runsRouter = router({
)
.mutation(async ({ ctx, input }) => {
// Verify org ownership of project
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, input.projectId));

if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Project not found',
});
}
await verifyProjectOrgAccess(input.projectId, ctx.effectiveOrgId);

const pc = await loadProjectConfigById(input.projectId);
if (!pc) {
Expand Down Expand Up @@ -273,14 +246,7 @@ export const runsRouter = router({

// Verify org access
if (run.projectId) {
const db = getDb();
const [project] = await db
.select({ orgId: projects.orgId })
.from(projects)
.where(eq(projects.id, run.projectId));
if (!project || project.orgId !== ctx.effectiveOrgId) {
throw new TRPCError({ code: 'NOT_FOUND' });
}
await verifyProjectOrgAccess(run.projectId, ctx.effectiveOrgId);
}

if (!run.projectId) {
Expand Down
Loading