Styra OPA TypeScript SDK

The Styra-supported driver to connect to Open Policy Agent (OPA) and Enterprise OPA deployments.

License NPM Version

The documentation for this SDK lives at https://docs.styra.com/sdk, with reference documentation available at https://styrainc.github.io/opa-typescript

You can use the Styra OPA SDK to connect to Open Policy Agent and Enterprise OPA deployments.

SDK Installation

NPM

npm add @styra/opa

Yarn

yarn add @styra/opa

Requirements

See the repository docs for supported JavaScript runtimes.

SDK Example Usage (high-level)

All the code examples that follow assume that the high-level SDK module has been imported, and that an OPA instance was created:

import { OPAClient } from "@styra/opa";

const serverURL = "http://opa-host:8181";
const path = "authz/allow";
const opa = new OPAClient(serverURL);

Simple query

For a simple boolean response without input, use the SDK as follows:

const allowed = await opa.evaluate(path);
console.log(allowed ? "allowed!" : "denied!");

Note that allowed will be of type any. You can change that by providing type parameters to evaluate:

const allowed = await opa.evaluate<never, boolean>(path);

The first parameter is the type of input passed into evaluate; we don't have any in this example, so you can use anything for it (any, unknown, or never).

HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json

{}

Input

Input is provided as a second (optional) argument to evaluate:

const input = { user: "alice" };
const allowed = await opa.evaluate(path, input);
console.log(allowed ? "allowed!" : "denied!");

For providing types, use

interface myInput {
user: string;
}
const input: myInput = { user: "alice" };
const allowed = await opa.evaluate<myInput, boolean>(path, input);
console.log(allowed ? "allowed!" : "denied!");
HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json

{ "input": { "user": "alice" } }

Result Types

When the result of the policy evaluation is more complex, you can pass its type to evaluate and get a typed result:

interface myInput {
user: string;
}
interface myResult {
authorized: boolean;
details: string[];
}
const input: myInput = { user: "alice" };
const result = await opa.evaluate<myInput, myResult>(path, input);
console.log(result.evaluated ? "allowed!" : "denied!");

Input Transformations

If you pass in an arbitrary object as input, it'll be stringified (JSON.stringify):

class A {
// With these names, JSON.stringify() returns the right thing.
name: string;
list: any[];

constructor(name: string, list: any[]) {
this.name = name;
this.list = list;
}
}
const inp = new A("alice", [1, 2, true]);
const allowed = await opa.evaluate<myInput, boolean>(path, inp);
console.log(allowed ? "allowed!" : "denied!");

You can control the input that's constructed from an object by implementing ToInput:

class A implements ToInput {
// With these names, JSON.stringify() doesn't return the right thing.
private n: string;
private l: any[];

constructor(name: string, list: any[]) {
this.n = name;
this.l = list;
}

toInput(): Input {
return { name: this.n, list: this.l };
}
}
const inp = new A("alice", [1, 2, true]);
const allowed = await opa.evaluate<myInput, boolean>(path, inp);
console.log(allowed ? "allowed!" : "denied!");
HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json

{ "input": { "name": "alice", "list": [ 1, 2, true ] } }

Result Transformations

If the result format of the policy evaluation does not match what you want it to be, you can provide a third argument, a function that transforms the API result.

Assuming that the policy evaluates to

{
"allowed": true,
"details": ["property-a is OK", "property-B is OK"]
}

you can turn it into a boolean result like this:

const allowed = await opa.evaluate<any, boolean>(
path,
undefined,
(r?: Result) => (r as Record<string, any>)["allowed"] ?? false,
);
console.log(allowed ? "allowed!" : "denied!");

Example Projects

Express

In the StyraInc/styra-demo-tickethub repository, you'll find a NodeJS backend service that is using @styra/opa:

router.get("/tickets/:id", [param("id").isInt().toInt()], async (req, res) => {
const {
params: { id },
} = req;
await authz.evaluated(path, { action: "get", id }, req);

const ticket = await prisma.tickets.findUniqueOrThrow({
where: { id },
...includeCustomers,
});
return res.status(OK).json(toTicket(ticket));
});

NestJS

In StyraInc/opa-typescript-example-nestjs, we have an decorator-based API authorization example using @styra/opa:

@Controller('cats')
@AuthzQuery('cats/allow')
@AuthzStatic({ resource: 'cat' })
export class CatsController {
constructor(private catsService: CatsService) {}

@Post()
@Authz(({ body: { name } }) => ({ name, action: 'create' }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}

@Get(':name')
@AuthzQuery('cats') // For illustration, we're querying the package extent
@Decision((r) => r.allow)
@Authz(({ params: { name } }) => ({
name,
action: 'get',
}))
async findByName(@Param('name') name: string): Promise<Cat> {
return this.catsService.findByName(name);
}
}

Please refer to the repository's README.md for more details.


OPA OpenAPI SDK (low-level)

Available Resources and Operations

OpaApiClient SDK

Development

Maturity

This SDK is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage to a specific package version. This way, you can install the same version each time without breaking changes unless you are intentionally looking for the latest version.