Skip to content
Draft
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions apps/api/src/handlers/environments/createEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import {
users,
} from '@roomote/db/server';
import {
type EnvironmentManagementAction,
type TaskPayload,
canPerformEnvironmentManagementAction,
environmentConfigSchema,
getAmbiguousEnvironmentRepositoryError,
getDuplicateEnvironmentRepositoryConfigError,
getMissingEnvironmentRepositoryError,
getEnvironmentRepositoryInstallationError,
resolveEnvironmentManagementMode,
} from '@roomote/types';
import { captureActivationEnvironmentSaved } from '@roomote/telemetry/server';

Expand All @@ -35,6 +38,8 @@ export const EVAL_ENVIRONMENT_WRITE_ERROR =
'isEval is reserved for internal eval environments.';
export const ENVIRONMENT_ADMIN_REQUIRED_ERROR =
'Admin access is required to create or update environments.';
export const ENVIRONMENT_TASK_CAPABILITY_ERROR =
'This task is not allowed to perform that environment action.';

type PostgresErrorLike = {
code?: string;
Expand Down Expand Up @@ -140,6 +145,39 @@ export async function canAdministerEnvironments(
return user?.role === 'admin' && user.deletedAt == null;
}

export async function canTaskRunManageEnvironments(
auth: McpAuth,
action: EnvironmentManagementAction,
): Promise<boolean> {
if (auth.authContext.tokenType !== 'run') {
return true;
}

const runId = extractRunId(auth);
if (!runId) {
return false;
}

const taskRun = await db.query.taskRuns.findFirst({
where: eq(taskRuns.id, runId),
columns: { payload: true, payloadKind: true },
with: { task: { columns: { workflow: true } } },
});

if (!taskRun) {
return false;
}

return canPerformEnvironmentManagementAction(
resolveEnvironmentManagementMode({
payloadKind: taskRun.payloadKind,
payload: taskRun.payload,
workflow: taskRun.task.workflow,
}),
action,
);
}

/**
* When environment creation/update is triggered by a running task run, persist
* the resulting environment id on that job payload so the UI can resolve
Expand Down Expand Up @@ -234,6 +272,10 @@ export async function createEnvironment(
return c.json({ error: ENVIRONMENT_ADMIN_REQUIRED_ERROR }, 403);
}

if (!(await canTaskRunManageEnvironments(auth, 'create'))) {
return c.json({ error: ENVIRONMENT_TASK_CAPABILITY_ERROR }, 403);
}

let body: unknown;

try {
Expand Down
19 changes: 17 additions & 2 deletions apps/api/src/handlers/environments/recordVerification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
recordEnvironmentVerification,
taskRuns,
} from '@roomote/db/server';
import { getEnvironmentDefinitionIdFromPayload } from '@roomote/types';
import {
canPerformEnvironmentManagementAction,
getEnvironmentDefinitionIdFromPayload,
resolveEnvironmentManagementMode,
} from '@roomote/types';

import type { Variables } from '../../types';
import type { McpAuth } from '../mcp/middleware';
Expand Down Expand Up @@ -81,7 +85,8 @@ async function resolveVerificationCaller(

const taskRun = await db.query.taskRuns.findFirst({
where: eq(taskRuns.id, runId),
columns: { taskId: true, payload: true },
columns: { taskId: true, payload: true, payloadKind: true },
with: { task: { columns: { workflow: true } } },
});

if (!taskRun?.taskId) {
Expand All @@ -93,6 +98,16 @@ async function resolveVerificationCaller(
}

const payload = taskRun.payload as Record<string, unknown>;
const mode = resolveEnvironmentManagementMode({
payloadKind: taskRun.payloadKind,
payload,
workflow: taskRun.task.workflow,
});

if (!canPerformEnvironmentManagementAction(mode, 'record_verification')) {
return null;
}

const marker = payload.verifiesEnvironmentId;

const authorizedEnvironmentId =
Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/handlers/environments/updateEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import { logHandlerError } from '../utils';
import {
DUPLICATE_ENVIRONMENT_NAME_ERROR,
ENVIRONMENT_ADMIN_REQUIRED_ERROR,
ENVIRONMENT_TASK_CAPABILITY_ERROR,
EVAL_ENVIRONMENT_WRITE_ERROR,
attachEnvironmentIdToTaskRun,
canAdministerEnvironments,
canTaskRunManageEnvironments,
getEnvironmentRepositoryConfigError,
isEnvironmentNameUniqueViolation,
resolveCallingVerificationTaskId,
Expand All @@ -50,6 +52,10 @@ export async function updateEnvironment(
return c.json({ error: ENVIRONMENT_ADMIN_REQUIRED_ERROR }, 403);
}

if (!(await canTaskRunManageEnvironments(auth, 'update'))) {
return c.json({ error: ENVIRONMENT_TASK_CAPABILITY_ERROR }, 403);
}

const id = c.req.param('id');

if (!id) {
Expand Down
37 changes: 37 additions & 0 deletions apps/api/src/handlers/tasks/__tests__/launchTask.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions apps/api/src/handlers/tasks/launchTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,13 @@ export async function launchTask(

const basePayload = {
...workspacePayload,
...(requestedType === 'environment-definition'
? {
environmentManagementMode: body.environmentId
? ('update' as const)
: ('create' as const),
}
: {}),
...(sourceControlProvider ? { sourceControlProvider } : {}),
branch: body.branch,
sha: body.sha,
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/trpc/commands/environments/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/web/src/trpc/commands/environments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ export async function startEnvironmentDefinitionTaskCommand(
type: TaskPayloadKind.StandardTask,
payload: {
...workspacePayload,
environmentManagementMode: input.environmentId ? 'update' : 'create',
...(input.environmentId
? { environmentDefinitionId: input.environmentId }
: {}),
Expand Down Expand Up @@ -1107,6 +1108,7 @@ export async function retryEnvironmentVerificationCommand(
repo: ALL_REPOSITORIES,
environmentId: environment.id,
verifiesEnvironmentId: environment.id,
environmentManagementMode: 'verify',
description: prompt,
},
},
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/trpc/commands/preview-settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ export async function startPreviewSetupTaskCommand(
repo: ALL_REPOSITORIES,
environmentId: environment.id,
environmentDefinitionId: environment.id,
environmentManagementMode: 'update',
description: prompt,
},
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading