post api stacks honors the target environment in the body

This commit is contained in:
Jarek Krochmalski committed 2026-09-02 15:52:45 +02:00
1 parent 52d99fa295
commit 585d923e3c
1 file changed
+16 -6
+16 -6
View File
@@ -84,9 +84,9 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
/** /**
* @openapi * @openapi
* summary: Create and (optionally) deploy a compose stack * summary: Create and (optionally) deploy a compose stack
* description: Writes the compose + .env to the stack dir, stores secrets in the DB, and with start deploys it. Can bind a secret provider. * description: Writes the compose + .env to the stack dir, stores secrets in the DB, and with start deploys it. Can bind a secret provider. Target environment comes from the env query param, or from envId/environmentId in the body when the query is absent.
* query: env:integer Target environment id * query: env:integer Target environment id (takes precedence over envId/environmentId in the body)
* body: {name:string!, compose:string!, composePath:string, envPath:string, envVars:array<object>, rawEnvContent:string, secretProviderId:integer, start:boolean} * body: {name:string!, compose:string!, composePath:string, envPath:string, envVars:array<object>, rawEnvContent:string, secretProviderId:integer, start:boolean, envId:integer, environmentId:integer}
* resp-400: Invalid request (e.g. missing name/compose, or secretProviderId wrong type) * resp-400: Invalid request (e.g. missing name/compose, or secretProviderId wrong type)
* resp-403: Permission denied (needs stacks:create; binding a secret provider also needs secrets:view) * resp-403: Permission denied (needs stacks:create; binding a secret provider also needs secrets:view)
* resp-500: Failed to create or deploy the stack * resp-500: Failed to create or deploy the stack
@@ -95,8 +95,19 @@ export const POST: RequestHandler = async (event) => {
const { request, url, cookies } = event; const { request, url, cookies } = event;
const auth = await authorize(cookies); const auth = await authorize(cookies);
const envId = url.searchParams.get('env'); let body: any;
const envIdNum = envId ? parseInt(envId) : undefined; try {
body = await request.json();
} catch {
return json({ error: 'Invalid JSON body' }, { status: 400 });
}
// Target env: the ?env= query wins (the UI uses it), else fall back to envId /
// environmentId in the body so an API caller can target a remote env that way too
// (#1491). Resolve BEFORE the permission checks so they scope to the real target.
const queryEnv = url.searchParams.get('env');
const bodyEnv = body?.envId ?? body?.environmentId;
const envIdNum = queryEnv ? parseInt(queryEnv) : (typeof bodyEnv === 'number' ? bodyEnv : undefined);
// Permission check with environment context // Permission check with environment context
if (auth.authEnabled && !(await auth.can('stacks', 'create', envIdNum))) { if (auth.authEnabled && !(await auth.can('stacks', 'create', envIdNum))) {
@@ -109,7 +120,6 @@ export const POST: RequestHandler = async (event) => {
} }
try { try {
const body = await request.json();
const { name, compose, start, envVars, rawEnvContent, composePath, envPath, secretProviderId } = body; const { name, compose, start, envVars, rawEnvContent, composePath, envPath, secretProviderId } = body;
if (!name || typeof name !== 'string') { if (!name || typeof name !== 'string') {