Skip to content

Latest commit

 

History

History

nestjs-demo

opa-typescript NestJS example

Description

Based on the Nest framework TypeScript starter repository.

Input Payloads from Authz Decorators

Static key/val pairs

Use the Authz decorator to add static information to the request payload sent to OPA:

@Authz(() => ({ resource: 'cat' }))

on either the class or the handler method will add

{ "resource": "cat" }

to each request payload.

There's a AuthzStatic() variant that lets you condense this to

@Authz({ resource: 'cat' })

Feel free to

import { AuthzStatic as Authz } from '../authz/decorators/action';

if the dynamic functionality isn't required.

Forward request body

POST /cats
{"name": "garfield", "age": 5, "breed": "unknown"}

The entire request body can be forwarded:

@Authz(({ body }) => body)
{
  "name": "garfield",
  "age": 5,
  "breed": "unknown"
}

Note that we can also add a static part to this:

@Authz(({ body }) => ({ ...body, action: 'create' }))
{
  "name": "garfield",
  "age": 5,
  "breed": "unknown",
  "action": "create"
}

It's also possible to select just parts of the body:

@Authz(({ body: { name } }) => ({ name, action: 'create' }))
{
  "name": "garfield",
  "action": "create"
}

If you want to put the body into some key on the payload, use

@Authz(({ body }) => ({ payload: body }))
{
  "payload": {
    "name": "garfield",
    "age": 5,
    "breed": "unknown"
  }
}

Route parameters can also be injected:

@Get(':name')
@Authz(({ params }) => ({ ...params, action: 'get' }))
async findByName(@Param('name') name: string): Promise<Cat> {
  return this.catsService.findByName(name);
}
{
  "name": "garfield",
  "action": "get"
}

More Request Details

Under the hood, the @Authz decorator registers functions that (optionally) take a Request parameter. See the NestJS docs for all the fields that you can use.

Pass along the client IP via

@Authz(({ ip }) => ({ ip }))

Or one specific header,

@Authz(({ headers: { 'x-user': u } }) => ({
  ['x-user']: u,
}))

Response payload mapping

Use the @Decision and @DecisionStatic decorators to instruct the AuthzGuard on how to get a boolean decision from the OPA response:

@DecisionStatic('allowed')

will use the allow field of an object response. So if POST /v1/data/cats/result returns

{
  "allowed": true,
  "details": []
}

the AuthzGuard will use the allowed field to make the decision.

The same be achieved using a non-static @Decision decorator:

@Decision((r) => r['allowed'])

Note that you can also pass a function, like

function pluckAllow(r: any): boolean {
  return r['allow'];
}

via

@Decision(pluckAllow)

You can use all of the result for the decision, for example

@Decision((r) => r.violations.length == 0)

Installation

$ npm install

Running the app

# default env variables, corresponding to http://127.0.0.1:8181/v1/data/cats/allow
cp example.env .env

# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod

Start OPA with the example policies

opa run --server policies

Run some HTTP calls

hurl basic.hurl

(hurl is https://hurl.dev)