Skip to main content

@styra/opa@1.7.2
Built and signed on GitHub Actions

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

This package works with Node.js, Deno, Bun, BrowsersIt is unknown whether this package works with Cloudflare Workers
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
88%
Published
6 hours ago (1.7.2)

OPA Typescript SDK

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

License NPM Version JSR

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

PNPM

pnpm add @styra/opa

Bun

bun add @styra/opa

Yarn

yarn add @styra/opa zod

# Note that Yarn does not install peer dependencies automatically. You will need
# to install zod as shown above.

Summary

For more information about the API: Enterprise OPA documentation

Table of Contents

Requirements

For supported JavaScript runtimes, please consult RUNTIMES.md.

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, {
  fromResult: (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.

Note: For low-level SDK usage, see the sections below.


OPA OpenAPI SDK (low-level)

Available Resources and Operations

Available methods

OpaApiClient SDK

Retries

Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK.

To change the default retry strategy for a single API call, simply provide a retryConfig object to the call:

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

const opaApiClient = new OpaApiClient();

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69, {
    retries: {
      strategy: "backoff",
      backoff: {
        initialInterval: 1,
        maxInterval: 50,
        exponent: 1.1,
        maxElapsedTime: 100,
      },
      retryConnectionErrors: false,
    },
  });

  // Handle the result
  console.log(result);
}

run();

If you'd like to override the default retry strategy for all operations that support retries, you can provide a retryConfig at SDK initialization:

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

const opaApiClient = new OpaApiClient({
  retryConfig: {
    strategy: "backoff",
    backoff: {
      initialInterval: 1,
      maxInterval: 50,
      exponent: 1.1,
      maxElapsedTime: 100,
    },
    retryConnectionErrors: false,
  },
});

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69);

  // Handle the result
  console.log(result);
}

run();

Authentication

Per-Client Security Schemes

This SDK supports the following security scheme globally:

Name Type Scheme
bearerAuth http HTTP Bearer

To authenticate with the API the bearerAuth parameter must be set when initializing the SDK client instance. For example:

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

const opaApiClient = new OpaApiClient({
  bearerAuth: "<YOUR_BEARER_TOKEN_HERE>",
});

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69);

  // Handle the result
  console.log(result);
}

run();

Debugging

You can setup your SDK to emit debug logs for SDK requests and responses.

You can pass a logger that matches console's interface as an SDK option.

Warning

Beware that debug logging will reveal secrets, like API tokens in headers, in log messages printed to a console or files. It's recommended to use this feature only during local development and not in production.

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

const sdk = new OpaApiClient({ debugLogger: console });

Standalone functions

All the methods listed above are available as standalone functions. These functions are ideal for use in applications running in the browser, serverless runtimes or other environments where application bundle size is a primary concern. When using a bundler to build your application, all unused functionality will be either excluded from the final bundle or tree-shaken away.

To read more about standalone functions, check FUNCTIONS.md.

Available standalone functions

Community

For questions, discussions and announcements related to Styra products, services and open source projects, please join the Styra community on Slack!

Built and signed on
GitHub Actions
View transparency log

Add Package

deno add jsr:@styra/opa

Import symbol

import * as opa from "@styra/opa";

---- OR ----

Import directly with a jsr specifier

import * as opa from "jsr:@styra/opa";

Add Package

npx jsr add @styra/opa

Import symbol

import * as opa from "@styra/opa";

Add Package

yarn dlx jsr add @styra/opa

Import symbol

import * as opa from "@styra/opa";

Add Package

pnpm dlx jsr add @styra/opa

Import symbol

import * as opa from "@styra/opa";

Add Package

bunx jsr add @styra/opa

Import symbol

import * as opa from "@styra/opa";