Authorize with domain data
Use a function access rule when authentication alone is not enough. The rule can
check the trusted principal against validated domain input before run loads
data or calls a provider.
This recipe grants ticket:classify only when the principal has the permission
and, when present, the requested ticket is in allowedTicketIds.
Build the rule
Section titled “Build the rule”-
Define the permission port
export interface PermissionChecker {can(principal: Principal,permission: "ticket:classify",ticketId: string,): boolean | Promise<boolean>;}The capability depends on a domain decision. Your engine can implement it from principal attributes or delegate it to an existing policy service.
-
Implement a fail-closed attribute checker
function stringArray(value: unknown): ReadonlyArray<string> | null {return Array.isArray(value) &&value.every((item) => typeof item === "string")? value: null;}export function createAttributePermissionChecker(): PermissionChecker {return {can(principal, permission, ticketId) {const permissions = stringArray(principal.attributes?.permissions);if (permissions?.includes(permission) !== true) return false;const constraint = principal.attributes?.allowedTicketIds;if (constraint === undefined) return true;const allowedTicketIds = stringArray(constraint);return allowedTicketIds?.includes(ticketId) === true;},};}A malformed permission list or resource constraint denies access. The checker does not guess how to interpret caller-controlled values.
-
Call the port from
accessaccess: async ({ principal, input }) => {if (principal === null) return false;return permissions.can(principal,"ticket:classify",input.ticketId,);},Invokta has already validated and transformed
inputat this point. A denial stops the pipeline beforerunreceives the request. -
Supply trusted identity at the composition root
export const localPrincipal: Principal = Object.freeze({id: "local:support-operator",attributes: Object.freeze({permissions: Object.freeze(["ticket:classify"]),allowedTicketIds: Object.freeze(["T-123", "T-456"]),}),});process.exitCode = await runCli(engine, {principal: localPrincipal,});The CLI user cannot select another identity with an argument. HTTP identity should come from the adapter’s authentication hook.
Run it
Section titled “Run it”Build the repository, then invoke an allowed ticket:
yarn buildnode examples/support-engine/dist/cli.js run support.classify-ticket \ --input '{"ticketId":"T-123"}'Now request a ticket outside the principal’s resource constraint:
node examples/support-engine/dist/cli.js run support.classify-ticket \ --input '{"ticketId":"T-999"}'The second command returns FORBIDDEN. The repository and classifier are never
called for that request.
Verify it
Section titled “Verify it”The support-engine tests assert the call order and confirm that denied access does not touch either outbound dependency:
yarn workspace @invokta/example-support testRead the complete
support-engine
example for the permission port, local principal, HTTP authentication hook, and
denial tests.