diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 528d84a299..acaf18a8d4 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -72,7 +72,8 @@ "winston": "^3.2.1", "yaml": "^1.10.0", "vm2": "^3.9.6", - "zen-observable": "^0.8.15" + "zen-observable": "^0.8.15", + "zod": "^3.11.6" }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.25-next.0", diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index da5e2c3030..01a6bbb235 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -22,7 +22,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import { Config, JsonObject } from '@backstage/config'; import { InputError, NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { @@ -35,6 +35,7 @@ import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Logger } from 'winston'; import { TemplateFilter } from '../lib'; +import { z } from 'zod'; import { createBuiltinActions, DatabaseTaskStore, @@ -336,20 +337,27 @@ export async function createRouter( }); }) .post('/v2/dry-run', async (req, res) => { - const template = req.body.template as TemplateEntityV1beta3; + const bodySchema = z.object({ + template: z.unknown(), + values: z.record(z.unknown()), + secrets: z.record(z.string()), + content: z.array( + z.object({ path: z.string(), base64Content: z.string() }), + ), + }); + const body = await bodySchema.parseAsync(req.body).catch(e => { + throw new InputError(`Malformed request: ${e}`); + }); + + const template = body.template as TemplateEntityV1beta3; if (!(await templateEntityV1beta3Validator.check(template))) { throw new InputError('Input template is not a template'); } - const values = req.body.values; const token = getBearerToken(req.headers.authorization); - const content = req.body.content as { - path: string; - base64Content: string; - }[]; for (const parameters of [template.spec.parameters ?? []].flat()) { - const result = validate(values, parameters); + const result = validate(body.values, parameters); if (!result.valid) { res.status(400).json({ errors: result.errors }); return; @@ -367,15 +375,15 @@ export async function createRouter( apiVersion: template.apiVersion, steps, output: template.spec.output ?? {}, - parameters: values, + parameters: body.values as JsonObject, }, - content: (content ?? []).map(file => ({ + content: (body.content ?? []).map(file => ({ path: file.path, content: Buffer.from(file.base64Content, 'base64'), })), secrets: { - ...req.body.secrets, - backstageToken: token, + ...body.secrets, + ...(token && { backstageToken: token }), }, });