fix: correct permission checks for compose loadServices and env editing

- Change compose.loadServices permission from service:create to service:read
  since loading services from a compose file is a read-only operation
- Add saveEnvironment endpoint to compose router with envVars:write permission
- Update show-environment.tsx to use saveEnvironment mutations instead of
  generic update mutations for all service types (compose, databases)

Closes #4052
This commit is contained in:
Mauricio Siu
2026-04-05 13:52:53 -06:00
parent f700017ccf
commit 91b44720ef
3 changed files with 42 additions and 9 deletions

View File

@@ -56,17 +56,17 @@ export const ShowEnvironment = ({ id, type }: Props) => {
const [isEnvVisible, setIsEnvVisible] = useState(true); const [isEnvVisible, setIsEnvVisible] = useState(true);
const mutationMap = { const mutationMap = {
compose: () => api.compose.update.useMutation(), compose: () => api.compose.saveEnvironment.useMutation(),
libsql: () => api.libsql.update.useMutation(), libsql: () => api.libsql.saveEnvironment.useMutation(),
mariadb: () => api.mariadb.update.useMutation(), mariadb: () => api.mariadb.saveEnvironment.useMutation(),
mongo: () => api.mongo.update.useMutation(), mongo: () => api.mongo.saveEnvironment.useMutation(),
mysql: () => api.mysql.update.useMutation(), mysql: () => api.mysql.saveEnvironment.useMutation(),
postgres: () => api.postgres.update.useMutation(), postgres: () => api.postgres.saveEnvironment.useMutation(),
redis: () => api.redis.update.useMutation(), redis: () => api.redis.saveEnvironment.useMutation(),
}; };
const { mutateAsync, isPending } = mutationMap[type] const { mutateAsync, isPending } = mutationMap[type]
? mutationMap[type]() ? mutationMap[type]()
: api.mongo.update.useMutation(); : api.mongo.saveEnvironment.useMutation();
const form = useForm<EnvironmentSchema>({ const form = useForm<EnvironmentSchema>({
defaultValues: { defaultValues: {

View File

@@ -61,6 +61,7 @@ import {
apiFindCompose, apiFindCompose,
apiRandomizeCompose, apiRandomizeCompose,
apiRedeployCompose, apiRedeployCompose,
apiSaveEnvironmentVariablesCompose,
apiUpdateCompose, apiUpdateCompose,
compose as composeTable, compose as composeTable,
environments, environments,
@@ -201,6 +202,31 @@ export const composeRouter = createTRPCRouter({
}); });
return updated; return updated;
}), }),
saveEnvironment: protectedProcedure
.input(apiSaveEnvironmentVariablesCompose)
.mutation(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.composeId, {
envVars: ["write"],
});
const updated = await updateCompose(input.composeId, {
env: input.env,
});
if (!updated) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error adding environment variables",
});
}
await audit(ctx, {
action: "update",
resourceType: "compose",
resourceId: input.composeId,
resourceName: updated?.name,
});
return true;
}),
delete: protectedProcedure delete: protectedProcedure
.input(apiDeleteCompose) .input(apiDeleteCompose)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -290,7 +316,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices) .input(apiFetchServices)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.composeId, { await checkServicePermissionAndAccess(ctx, input.composeId, {
service: ["create"], service: ["read"],
}); });
return await loadServices(input.composeId, input.type); return await loadServices(input.composeId, input.type);
}), }),

View File

@@ -225,6 +225,13 @@ export const apiUpdateCompose = createSchema
}) })
.omit({ serverId: true }); .omit({ serverId: true });
export const apiSaveEnvironmentVariablesCompose = createSchema
.pick({
composeId: true,
env: true,
})
.required();
export const apiRandomizeCompose = createSchema export const apiRandomizeCompose = createSchema
.pick({ .pick({
composeId: true, composeId: true,