Skip to content

Add extra validations for endpoints that do not use principal to get the resources#2112

Merged
MarceloRGonc merged 10 commits intomainfrom
mg/add-validations
Mar 11, 2026
Merged

Add extra validations for endpoints that do not use principal to get the resources#2112
MarceloRGonc merged 10 commits intomainfrom
mg/add-validations

Conversation

@MarceloRGonc
Copy link
Contributor

@MarceloRGonc MarceloRGonc commented Mar 11, 2026

Fixes OPS-3895.

MarceloRGonc and others added 3 commits March 11, 2026 16:57
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
@MarceloRGonc MarceloRGonc changed the title Mg/add validations Add extra validations for endpoints that do not use principal to get the resources Mar 11, 2026
@linear
Copy link

linear bot commented Mar 11, 2026

flowVersion: FlowVersion,
projectId: string,
): Promise<void> {
const flow = await flowService.getOne({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm so, if the flowservice does not return a flow for the version it explicitly means that the flow version does not belong to the project?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add logic; I just moved the method and renamed it. But I think it makes sense that we have a FlowVersion object with a flowId if we search the DB for the flowId, and a projectId. If we don't have any results, we can assume it's because of the project. It's debatable, but it's not new code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh gotcha, i missed the collapsed deleted part

@sonarqubecloud
Copy link

@MarceloRGonc MarceloRGonc marked this pull request as ready for review March 11, 2026 17:35
Copilot AI review requested due to automatic review settings March 11, 2026 17:35
@MarceloRGonc MarceloRGonc merged commit f2963e0 into main Mar 11, 2026
23 checks passed
@MarceloRGonc MarceloRGonc deleted the mg/add-validations branch March 11, 2026 17:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens project-scoped authorization for flow-related endpoints that previously fetched resources without constraining them to the principal’s project, aligning error handling with the global ApplicationError -> { code, params } response shape.

Changes:

  • Introduces shared flow/flow-version project ownership assertions (assertFlowBelongsToProject, assertFlowVersionBelongsToProject) and replaces ad-hoc validation/reply logic.
  • Adds project scoping to endpoints that were missing it (form retrieval by flowId, flow version test-output routes, flow-run retry).
  • Updates/introduces unit + integration tests to validate the new authorization/error response behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/server/api/src/app/flows/common/flow-validations.ts Adds shared ownership assertion helpers that throw ApplicationError for consistent error handling.
packages/server/api/src/app/flows/common/flow-version-validation.ts Removes old reply-based validation helper.
packages/server/api/src/app/flows/test/test.controller.ts Switches to assertion-based validation for test endpoints.
packages/server/api/src/app/flows/flow/form/form.service.ts Adds projectId parameter and asserts flow ownership before returning form config.
packages/server/api/src/app/flows/flow/form/form.controller.ts Passes principal.projectId into form service to enable ownership validation.
packages/server/api/src/app/flows/flow/flow-version.controller.ts Adds ownership assertions for additional routes and relies on global ApplicationError handler.
packages/server/api/src/app/flows/flow-run/flow-run-service.ts Requires projectId for retry and scopes flow-run lookup accordingly.
packages/server/api/src/app/flows/flow-run/flow-run-controller.ts Passes principal.projectId into retry call.
packages/server/api/src/app/flows/flow/flow.controller.ts Updates delete-flow permission to Permission.DELETE_FLOW.
packages/server/api/test/unit/flow/flow-validations.test.ts Adds unit tests for the new assertion helpers.
packages/server/api/test/integration/ce/flows/flow-version.test.ts Updates integration assertions to match { code, params } error responses.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +90 to +93
expect(() => assertFlowBelongsToProject(flow, projectId)).toThrow(
expect.objectContaining({
error: expect.objectContaining({
code: ErrorCode.AUTHORIZATION,
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, toThrow(expect.objectContaining(...)) is unlikely to validate the thrown ApplicationError.error contents (it mostly matches the error message/type). Use toThrow(new ApplicationError(...)) or wrap the call and assert with toMatchObject on the caught error to ensure the code/params are actually checked.

Copilot uses AI. Check for mistakes.
}

assertFlowBelongsToProject(flow, projectId);
logger.info(flow.version.trigger.settings);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger.info(flow.version.trigger.settings) logs the full trigger settings on every form fetch. This can be noisy in production and may log user-supplied form configuration. Consider removing this log or downgrading it to debug/trace with a minimal, non-sensitive summary (e.g., flowId/triggerName).

Suggested change
logger.info(flow.version.trigger.settings);
logger.debug(
{ flowId: flow.id, triggerName: flow.version.trigger.settings.triggerName },
'Fetched form trigger settings',
);

Copilot uses AI. Check for mistakes.
Comment on lines 69 to 72
const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId);

const isValid = await validateFlowVersionBelongsToProject(
flowVersion,
projectId,
reply,
);
await assertFlowVersionBelongsToProject(flowVersion, projectId);

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertFlowVersionBelongsToProject throws an ApplicationError (mapped to 403 by the global error handler). Because this call is inside the surrounding try/catch, that ApplicationError will be caught and turned into a 400 later in the handler. Update the catch block to re-throw ApplicationError (or move this call outside the try) so auth failures keep the correct status code/shape.

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 61
if (flowVersion.flowId !== updateData.flowId) {
await reply.status(StatusCodes.BAD_REQUEST).send({
success: false,
message:
'It is not possible to update the flowId of a flow version',
});
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After sending an error response (e.g., when flowVersion.flowId !== updateData.flowId), the handler doesn't return, so execution continues and may attempt to send additional responses (locked/conflict/success), causing "Reply was already sent" or unintended updates. Add return after each reply.status(...).send(...) in these validation branches (or restructure with else).

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +61
).rejects.toThrow(
expect.objectContaining({
error: expect.objectContaining({
code: ErrorCode.AUTHORIZATION,
params: {
message:
'The flow and version are not associated with the project',
},
}),
}),
);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions use rejects.toThrow(expect.objectContaining(...)). toThrow primarily matches against the thrown error’s message/class, so this pattern is brittle and may not assert the ApplicationError.error payload as intended. Prefer rejects.toMatchObject({ error: { code: ..., params: ... } }) or rejects.toThrow(new ApplicationError(...)) (as used in other tests).

Suggested change
).rejects.toThrow(
expect.objectContaining({
error: expect.objectContaining({
code: ErrorCode.AUTHORIZATION,
params: {
message:
'The flow and version are not associated with the project',
},
}),
}),
);
).rejects.toMatchObject({
error: {
code: ErrorCode.AUTHORIZATION,
params: {
message:
'The flow and version are not associated with the project',
},
},
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants