{"id":21938880,"url":"https://github.com/aserto-dev/aserto-node","last_synced_at":"2025-04-22T14:49:09.778Z","repository":{"id":61884227,"uuid":"549921726","full_name":"aserto-dev/aserto-node","owner":"aserto-dev","description":"Aserto Node.JS SDK (Authorizer v2)","archived":false,"fork":false,"pushed_at":"2025-03-21T14:16:13.000Z","size":3757,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T16:11:08.500Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aserto-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-12T00:09:32.000Z","updated_at":"2025-03-21T14:16:17.000Z","dependencies_parsed_at":"2023-11-21T10:26:43.884Z","dependency_job_id":"62b81573-2f45-4e66-b245-88606f95fa34","html_url":"https://github.com/aserto-dev/aserto-node","commit_stats":{"total_commits":38,"total_committers":3,"mean_commits":"12.666666666666666","dds":0.5526315789473684,"last_synced_commit":"24899b8239aca5840a48ec7f2bb9bae803e4ac5f"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aserto-dev%2Faserto-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aserto-dev%2Faserto-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aserto-dev%2Faserto-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aserto-dev%2Faserto-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aserto-dev","download_url":"https://codeload.github.com/aserto-dev/aserto-node/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250263079,"owners_count":21401798,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-29T02:15:20.664Z","updated_at":"2025-04-22T14:49:09.771Z","avatar_url":"https://github.com/aserto-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aserto-node\n[![npm version](https://badge.fury.io/js/@aserto%2Faserto-node.svg)](https://badge.fury.io/js/@aserto%2Faserto-node)\n[![codecov](https://codecov.io/github/aserto-dev/aserto-node/graph/badge.svg?token=xMt1L6Hd2D)](https://codecov.io/github/aserto-dev/aserto-node)\n[![Tests](https://github.com/aserto-dev/aserto-node/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/aserto-dev/aserto-node/actions/workflows/tests.yaml)\n[\u003cimg src=\"https://img.shields.io/badge/slack-@asertocommunity-yellow.svg?logo=slack\"\u003e](https://www.aserto.com/slack)\n\nAserto authorization middleware for the node Express server, based on\nAuth0's [express-jwt-authz](https://github.com/auth0/express-jwt-authz)\npackage.\n\nThis package provides multiple capabilities:\n\n1. `Middleware` - Provides 2 implementations: `Authz` and `Check` middlewares that sits on a route, and validates a request to authorize access to that route.\n2. `Authorizer` - Authorizer Client that provides functions to facilitate communication with an Authorizer v2 service.\n3. `DirectoryServiceV3` - Directory Client that provides functions to facilitate communication with an Directory v3 service.\n4. `DirectoryServiceV2` - Directory Client that provides functions to facilitate communication with an Directory v2 service.\n5. `jwtAuthz`(deprecated): middleware that sits on a route, and validates a request to authorize access to that route.\n6. `displayStateMap`: middleware that adds an endpoint for returning the display state map for a service, based on its authorization policy.\n7. `is`: a function that can be called to make a decision about a user's access to a resource based on a policy.\n8. `ds`(deprecated): an object containing the `object` and `relation` functions, which can be called to retrieve an object or relation, respectively, from the directory.\n\n## Installation\n\nUsing [npm](https://npmjs.org):\n\n```sh\nnpm install @aserto/aserto-node\n```\n\nUsing [yarn](https://yarnpkg.com):\n\n```sh\nyarn add @aserto/aserto-node\n```\n\n\u003e `express@^4.0.0` is a peer dependency. Make sure it is installed in your project.\n\n## Migration from prior versions guides\nIf you are migrating from older versions, check out our [migration guide](https://github.com/aserto-dev/aserto-node/blob/main/MIGRATING.md);\n\n## Authorizer\n\n### Authorizer Client\n```ts\ninterface Authorizer {\n  config: AuthorizerConfig,\n};\n\ntype AuthorizerConfig = {\n  authorizerServiceUrl?: string;\n  tenantId?: string;\n  authorizerApiKey?: string;\n  token?: string;\n  caFile?: string;\n  insecure?: boolean;\n  customHeaders?: { [key: string]: unknown };\n\n};\n```\n```ts\nconst authClient = new Authorizer({\n  authorizerServiceUrl: \"authorizer.prod.aserto.com:8443\",\n  authorizerApiKey: \"my-authorizer-api-key\",\n  tenantId: \"my-tenant-id\",\n});\n```\n\n- `authorizerServiceUrl`: hostname:port of authorizer service (_required_)\n- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)\n- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)\n- `caFile`: Path to the authorizer CA file. (optional)\n- `insecure`: Skip server certificate and domain verification. (NOT SECURE!). Defaults to `false`.\n\n### Topaz\n```ts\nconst authClient = new Authorizer({\n  authorizerServiceUrl: \"localhost:8282\",\n  caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`\n});\n```\n\n#### Example:\n```ts\nimport {\n  Authorizer,\n  identityContext,\n  policyContext,\n  policyInstance,\n} from \"@aserto/aserto-node\";\n\nconst authClient = new Authorizer(\n  {\n    authorizerServiceUrl: \"localhost:8282\",\n    caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`\n  },\n);\n\nauthClient\n  .Is({\n    identityContext: identityContext(\n      \"rick@the-citadel.com\",\n      \"SUB\"\n    ),\n    policyInstance: policyInstance(\"rebac\", \"rebac\"),\n    policyContext: policyContext(\"rebac.check\", [\"allowed\"]),\n    resourceContext: {\n      object_type: \"group\",\n      object_id: \"evil_genius\",\n      relation: \"member\",\n    },\n  })\n```\n\n### Methods\n```ts\n// Is\n// (method) Authorizer.Is(params: IsRequest, options?: CallOptions): Promise\u003cboolean\u003e\nawait authClient\n  .Is({\n    identityContext: identityContext(\n      \"morty@the-citadel.com\",\n      \"SUB\"\n    ),\n    policyInstance: policyInstance(\"todo\", \"todo\"),\n    policyContext: policyContext(\"todoApp.POST.todos\", [\"allowed\"]),\n    resourceContext: {\n      ownerID: \"fd1614d3-c39a-4781-b7bd-8b96f5a5100d\",\n    },\n  })\n\n// Query\n// (method) Authorizer.Query(params: QueryRequest, options?: CallOptions): Promise\u003cJsonObject\u003e\nawait authClient\n  .Query({\n    identityContext: identityContext(\n      \"morty@the-citadel.com\",\n      \"SUB\"\n    ),\n    policyInstance: policyInstance(\"todo\", \"todo\"),\n    policyContext: policyContext(\"todoApp.POST.todos\", [\"allowed\"]),\n    resourceContext: {\n      ownerID: \"fd1614d3-c39a-4781-b7bd-8b96f5a5100d\",\n    },\n    query: \"x = data\",\n  })\n\n\n// DecisionTree\n// (method) Authorizer.DecisionTree(params: DecisionTreeRequest, options?: CallOptions): Promise\u003c{\n//     path: Path;\n//     pathRoot: string;\n// }\u003e\nawait authClient\n  .DecisionTree({\n    identityContext: identityContext(\n      \"morty@the-citadel.com\",\n      \"SUB\"\n    ),\n    policyInstance: policyInstance(\"todo\", \"todo\"),\n    policyContext: policyContext(\"todoApp.POST.todos\", [\"allowed\"]),\n    resourceContext: {\n      ownerID: \"fd1614d3-c39a-4781-b7bd-8b96f5a5100d\",\n    },\n  })\n\n\n// ListPolicies\n// (method) Authorizer.ListPolicies(params: PlainMessage\u003cListPoliciesRequest\u003e, options?: CallOptions): Promise\u003cModule[]\u003e\nawait authClient\n  .ListPolicies({ policyInstance: policyInstance(\"todo\", \"todo\") })\n```\n\n#### Custom Headers\n```ts\nawait authClient.ListPolicies(\n  { policyInstance: policyInstance(\"todo\", \"todo\") },\n  { headers: { customKey: \"customValue\" } }\n);\n```\n\n### Middleware\n\nWhen authorization middleware is configured and attached to a server, it examines incoming requests, extracts authorization parameters like the caller's identity, calls the Aserto authorizers, and rejects messages if their access is denied.\n\n`failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly.\n`callOptions`: Options for a call.(see: https://github.com/connectrpc/connect-es/blob/v1.5.0/packages/connect/src/call-options.ts#L21-L54)\n\n```ts\ninterface Middleware {\n  client: Authorizer;\n  policy: Policy;\n  resourceMapper?: ResourceMapper;\n  identityMapper?: IdentityMapper;\n  policyMapper?: PolicyMapper;\n  failWithError?: boolean;\n  callOptions?: CallOptions;\n}\n\ntype Policy = {\n  root: string;\n  name?: string;\n  decision?: string;\n  path?: string;\n};\n\ntype CheckOptions = {\n  object?: ObjectMapper;\n  objectId?: string | StringMapper;\n  objectType?: string | StringMapper;\n  relation?: string | StringMapper;\n  subjectType?: string;\n};\n\ntype ResourceMapper =\n  | ResourceContext\n  | ((req?: Request) =\u003e Promise\u003cResourceContext\u003e);\n\ntype IdentityMapper = (req?: Request) =\u003e Promise\u003cIdentityContext\u003e;\ntype PolicyMapper = (req?: Request) =\u003e Promise\u003cPolicyContext\u003e;\n```\n\n#### Methods\n\n```ts\nfunction Authz()\nfunction Check(options: CheckOptions)\n```\n\n#### Examples\n```ts\nconst app: express.Application = express();\n\n\n// Standard REST\nconst restMw = new Middleware({\n  client: client,\n  policy: {\n    name: 'todo',\n    root: 'todoApp',\n  },\n  resourceMapper: async (req: express.Request) =\u003e {\n    if (!req.params?.id) {\n      return {};\n    }\n\n    const todo = await store.get(req.params.id);\n    return { ownerID: todo.OwnerID };\n  },\n})\n\napp.get(\"/todos\", checkJwt, restMw.Authz(), server.list.bind(server));\napp.post(\"/todos\", checkJwt, restMw.Authz(), server.create.bind(server));\napp.put(\"/todos/:id\", checkJwt, restMw.Authz(), server.update.bind(server));\n\n\n// Check\nconst rebacMw = new Middleware({\n  client: authClient,\n  policy: {\n    name: 'policy-rebac',\n    root: 'rebac',\n  }\n})\n\n// Only users that are in the `evil_genius` group are allowed to delete todos.\napp.delete(\"/todos/:id\", checkJwt, rebacMw.Check({\n  objectType: \"group\",\n  objectId: \"evil_genius\"\n  relation:  \"member\",\n}))\n```\n\n#### Mappers\n\n##### Identity\nTo determine the identity of the user, the middleware can be configured to use a JWT token or a claim using the `IdentityMapper`.\n\n```ts\n// use the identity type sub\nimport { SubIdentityMapper } from \"@aserto/aserto-node\";\n\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  identityMapper: SubIdentityMapper,\n})\n\n// use the jwt type sub from a custom header\nimport { JWTIdentityMapper } from \"@aserto/aserto-node\";\n\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  identityMapper: JWTIdentityMapper(\"my-header\");,\n})\n```\n\n```ts\n// use the manual identity type\nimport { ManualIdentityMapper } from \"@aserto/aserto-node\";\n\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  identityMapper: ManualIdentityMapper(\"my-identity\");,\n})\n```\n\nThe whole identity resolution can be overwritten by providing a custom function.\n```ts\n// needs to return an IdentityContext\nimport { identityContext } from \"@aserto/aserto-node\";\n\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  identityMapper: async () =\u003e {\n    return identityContext('test', 'SUB')\n  },\n})\n```\n\n### Policy\n\nThe authorization policy's ID and the decision to be evaluated are specified when creating authorization Middleware, but the policy path is often derived from the URL or method being called.\n\nBy default, the policy path is derived from the URL path.\n\nTo provide custom logic, use a PolicyMapper. For example:\n\n```ts\n// needs to return an IdentityContext\nimport { identityContext } from \"@aserto/aserto-node\";\n\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  policyMapper: async () =\u003e {\n    return policyContext('path', ['decision'])\n  }\n})\n```\n\n#### Resource\nA resource can be any structured data that the authorization policy uses to evaluate decisions. By default, the request params are included in the ResourceContext.\n\nThis behavior can be overwritten by providing a custom function:\n\n```ts\nconst restMw = new Middleware({\n  client: authClient,\n  policy: policy,\n  resourceMapper: async () =\u003e {\n    return { customKey: \"customValue\" };\n  },\n})\n```\n\n#### Mappers\n\n##### Resource\n\n```ts\n// provides a custom resource context,\ntype ResourceMapper =\n  | ResourceContext\n  | ((req?: Request) =\u003e Promise\u003cResourceContext\u003e);\n\n// examples\nasync (req: Request) =\u003e { return { customKey: req.params.id } };\n// or just a plain resource context\n{ customKey: \"customValue\" }\n```\n\n##### Identity\n\n```ts\ntype IdentityMapper = (req?: Request) =\u003e Promise\u003cIdentityContext\u003e;\n\n// You can also use the built-in policyContext function to create a identity context and pass it as the mapper response\nconst identityContext = (value: string, type: keyof typeof IdentityType) =\u003e {\n\nIdentityType {\n    /**\n     * Unknown, value not set, requests will fail with identity type not set error.\n     *\n     * @generated from enum value: IDENTITY_TYPE_UNKNOWN = 0;\n     */\n    UNKNOWN = 0,\n    /**\n     * None, no explicit identity context set, equals anonymous.\n     *\n     * @generated from enum value: IDENTITY_TYPE_NONE = 1;\n     */\n    NONE = 1,\n    /**\n     * Sub(ject), identity field contains an oAUTH subject.\n     *\n     * @generated from enum value: IDENTITY_TYPE_SUB = 2;\n     */\n    SUB = 2,\n    /**\n     * JWT, identity field contains a JWT access token.\n     *\n     * @generated from enum value: IDENTITY_TYPE_JWT = 3;\n     */\n    JWT = 3,\n    /**\n     * Manual, propagates thw identity field as-is, without validation, into the input object.\n     *\n     * @generated from enum value: IDENTITY_TYPE_MANUAL = 4;\n     */\n    MANUAL = 4\n}\n\n// example\nidentityContext(\"morty@the-citadel.com\", \"SUB\")\n```\n\n##### Policy\n\n```ts\ntype PolicyMapper = (req?: Request) =\u003e Promise\u003cPolicyContext\u003e;\n\n\n// You can also use the built-in policyContext function to create a policy context and pass it as the mapper response\npolicyContext = (policyPath: string, decisionsList: Array\u003cstring\u003e = [\"allowed\"])\n\n// Example\npolicyContext(\"todoApp.POST.todos\", [\"allowed\"])\n```\n\n## Directory\n\nThe Directory APIs can be used to get, set or delete object instances, relation instances and manifests. They can also be used to check whether a user has a permission or relation on an object instance.\n\n### Directory Client\n\n```ts\ntype ServiceConfig = {\n  url?: string;\n  tenantId?: string;\n  apiKey?: string;\n  caFile?: string;\n  rejectUnauthorized?: boolean;\n  insecure?: boolean;\n  customHeaders?: { [key: string]: unknown };\n\n};\n\nexport type DirectoryV3Config = ServiceConfig \u0026 {\n  reader?: ServiceConfig;\n  writer?: ServiceConfig;\n  importer?: ServiceConfig;\n  exporter?: ServiceConfig;\n  model?: ServiceConfig;\n};\n```\n\nYou can initialize a directory client as follows:\n\n```typescript\nimport { DirectoryServiceV3 } from \"@aserto/aserto-node\";\n\nconst directoryClient = DirectoryServiceV3({\n  url: 'localhost:9292',\n  caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`\n});\n\n- `url`: hostname:port of directory service (_required_)\n- `apiKey`: API key for directory service (_required_ if using hosted directory)\n- `tenantId`: Aserto tenant ID (_required_ if using hosted directory)\n- `caFile`: Path to the directory CA file. (optional)\n- `rejectUnauthorized`: reject clients with invalid certificates. Defaults to `true`.\n- `insecure`: Skip server certificate and domain verification. (NOT SECURE!). Defaults to `false`.\n- `reader`: ServiceConfig for the reader client(optional)\n- `writer`: ServiceConfig for the writer client(option)\n- `importer`: ServiceConfig for the importer client(option)\n- `exporter`: ServiceConfig for the exporter client(option)\n- `model`: ServiceConfig for the model client(option)\n```\n\n#### Example\nDefine a writer client that uses the same credentials but connects to localhost:9393. All other services will have the default configuration\n```ts\nimport { DirectoryServiceV3 } from \"@aserto/aserto-node\";\n\nconst directoryClient = DirectoryServiceV3({\n  url: 'localhost:9292',\n  tenantId: '1234',\n  apiKey: 'my-api-key',\n  writer: {\n    url: 'localhost:9393'\n  }\n});\n```\n\n### Getting objects and relations\n\n#### 'object' function\n\n`object({ objectType: \"type-name\", objectId: \"object-id\" }, options?: CallOptions)`:\n\nGet an object instance with the type `type-name` and the id `object-id`. For example:\n\n```typescript\nconst user = await directoryClient.object({ objectType: 'user', objectId: 'euang@acmecorp.com' });\n\n// Handle a specific Directory Error\nimport { NotFoundError } from  \"@aserto/aserto-node\"\n\ntry {\n  directoryClient.object({\n    objectType: \"user\",\n    objectId: \"euang@acmecorp.com\",\n  });\n} catch (error) {\n  if (error instanceof NotFoundError) {\n    // handle the case where the object was not found\n  }\n  throw error;\n}\n```\n\n#### 'relation' function\n\n```typescript\n  relation({\n    subjectType:  'subject-type',\n    subjectId: 'subject-id',\n    relation: 'relation-name',\n    objectType: 'object-type',\n    objectId: 'object-id',\n  })\n```\n\nGet an relation of a certain type between as subject and an object. For example:\n\n```typescript\nconst identity = 'euang@acmecorp.com';\nconst relation = await directoryClient.relation({\n  subjectType: 'user',\n  subjectId: 'euang@acmecorp.com',\n  relation: 'identifier',\n  objectType: 'identity'\n  objectId: identity\n});\n```\n\n#### 'relations' function\n\n```typescript\n  relations({\n    subjectType:  'subject-type',\n    relation: 'relation-name',\n    objectType: 'object-type',\n    objectId: 'object-id',\n  })\n```\n\n### Setting objects and relations\n\n#### 'setObject' function\n\n`setObject({ object: $Object }, options?: CallOptions)`:\n\nCreate an object instance with the specified fields. For example:\n\n```typescript\nconst user = await directoryClient.setObject(\n  {\n    object: {\n      type: \"user\",\n      id: \"test-object\",\n      properties: {\n        displayName: \"test object\"\n      }\n    }\n  }\n);\n```\n\n#### 'setRelation' function\n\n`setRelation({ relation: Relation }, options?: CallOptions)`:\n\nCreate a relation with a specified name between two objects. For example:\n\n```typescript\nconst relation = await directoryClient.setRelation({\n  subjectId: 'subjectId',\n  subjectType: 'subjectType',\n  relation: 'relationName',\n  objectType: 'objectType',\n  objectId: 'objectId',\n});\n```\n\n#### 'deleteObject' function\n\n`deleteObject({ objectType: \"type-name\", objectId: \"object-id\", withRelations: false }, options?: CallOptions)`:\n\nDeletes an object instance with the specified type and key. For example:\n\n```typescript\nawait directoryClient.deleteObject({ objectType: 'user', objectId: 'euang@acmecorp.com' });\n```\n\n\n#### 'deleteRelation' function\n\n`deleteRelation({ objectType: string, objectId: string, relation: string, subjectType: string, subjectId: string, subjectRelation: string })`:\n\nDelete a relation:\n\n```typescript\nawait directoryClient.deleteRelation({\n  subjectType: 'subjectType',\n  subjectId: 'subjectId',\n  relation: 'relationName',\n  objectType: 'objectType',\n  objectId: 'objectId',\n});\n```\n\n### Checking permissions and relations\n\nYou can evaluate graph queries over the directory, to determine whether a subject (e.g. user) has a permission or a relation to an object instance.\n\n#### 'check' function\n\n`check({ objectType: string, objectId: string, relation: string, subjectType: string, subjectId: string, trace: boolean }, options?: CallOptions)`:\n\nCheck that an `user` object with the key `euang@acmecorp.com` has the `read` permission in the `admin` group:\n\n```typescript\nconst check = await directoryClient.check({\n  subjectId: 'euang@acmecorp.com',\n  subjectType: 'user',\n  relation: 'read',\n  objectType: 'group',\n  objectId: 'admin',\n});\n```\n\nCheck that `euang@acmecorp.com` has an `identifier` relation to an object with key `euang@acmecorp.com` and type `identity`:\n\n```typescript\nconst check = directoryClient.check({\n  subjectId: 'euang@acmecorp.com',\n  subjectType: 'user',\n  relation: 'identifier',\n  objectType: 'identity',\n  objectId: 'euang@acmecorp.com',\n});\n```\n\n### Example\n\n```typescript\nconst identity = 'euang@acmecorp.com';\nconst relation = await directoryClient.relation(\n  {\n    subjectType: 'user',\n    objectType: 'identity',\n    objectId: identity,\n    relation: 'identifier',\n    subjectId: 'euang@acmecorp.com'\n  }\n);\n\nif (!relation) {\n  throw new Error(`No relations found for identity ${identity}`)\n};\n\nconst user = await directoryClient.object(\n  { objectId: relation.subjectId, objectType: relation.subjectType }\n);\n```\n\n### Manifest\n\nYou can get, set, or delete the manifest\n\n#### 'getManifest' function\n\n```ts\nawait directoryClient.getManifest();\n```\n\n#### 'setManifest' function\n\n```ts\nawait directoryClient.setManifest(`\n# yaml-language-server: $schema=https://www.topaz.sh/schema/manifest.json\n---\n### model ###\nmodel:\n  version: 3\n\n### object type definitions ###\ntypes:\n  ### display_name: User ###\n  user:\n    relations:\n      ### display_name: user#manager ###\n      manager: user\n\n  ### display_name: Identity ###\n  identity:\n    relations:\n      ### display_name: identity#identifier ###\n      identifier: user\n\n  ### display_name: Group ###\n  group:\n    relations:\n      ### display_name: group#member ###\n      member: user\n    permissions:\n      read: member\n`);\n```\n\n#### 'deleteManifest' function\n\n```ts\nawait directoryClient.deleteManifest();\n```\n\n### Import\n\n`createAsyncIterable` has been deprecated, please use `createImportRequest`\n\n```ts\nimport { ImportMsgCase, ImportOpCode, createImportRequest } from \"@aserto/aserto-node\"\nconst importRequest = createImportRequest([\n  {\n    opCode: ImportOpCode.SET,\n    msg: {\n      case: ImportMsgCase.OBJECT,\n      value: {\n        id: \"import-user\",\n        type: \"user\",\n        properties: { foo: \"bar\" },\n        displayName: \"name1\",\n      },\n    },\n  },\n  {\n    opCode: ImportOpCode.SET,\n    msg: {\n      case: ImportMsgCase.OBJECT,\n      value: {\n        id: \"import-group\",\n        type: \"group\",\n        properties: {},\n        displayName: \"name2\",\n      },\n    },\n  },\n  {\n    opCode: ImportOpCode.SET,\n    msg: {\n      case: ImportMsgCase.RELATION,\n      value: {\n        subjectId: \"import-user\",\n        subjectType: \"user\",\n        objectId: \"import-group\",\n        objectType: \"group\",\n        relation: \"member\",\n      },\n    },\n  },\n]);\n\nconst resp = await directoryClient.import(importRequest);\nawait (readAsyncIterable(resp))\n```\n\n### Export\n\n```ts\nconst response = await readAsyncIterable(\n  await directoryClient.export({ options: \"DATA\" })\n)\n\n```\n\n\n### Custom Headers\n\n```ts\n// passing custom headers to a request\nconst user = await directoryClient.object(\n  {\n    objectType: \"user\",\n    objectId: \"euang@acmecorp.com\",\n  },\n  {\n    headers: {\n      customKey: \"customValue\",\n    },\n  }\n);\n```\n\n### Serializing data\n\nUse [Protocol Buffers](https://github.com/bufbuild/protobuf-es) to serialize data.\n\n\n```ts\nimport { GetObjectsResponseSchema, toJson } from \"@aserto/aserto-node\";\n\nconst objects = await directoryClient.objects({objectType: \"user\"});\nconst json = toJson(GetObjectsResponseSchema, objects)\n```\n\n\n## Custom Logging\naserto-node publishes log events using the Node.js [Event emitter](https://nodejs.org/en/learn/asynchronous-work/the-nodejs-event-emitter#the-nodejs-event-emitter).\nThe events for each log level are defined as:\n```ts\nexport enum LOG_EVENT {\n  DEBUG = \"aserto-node-debug\",\n  ERROR = \"aserto-node-error\",\n  INFO  = \"aserto-node-info\",\n  TRACE = \"aserto-node-trace\",\n  WARN  = \"aserto-node-warn\",\n}\n```\nConsumers can register a function when any of these events are triggered and handle the logging.\n```ts\nimport { LOG_EVENT, setLogEventEmitter } from '@aserto/aserto-node'\n\n// create a new Event emitter\nconst emitter = new EventEmitter()\n\n// configure aserto-node to use the emitter\nsetLogEventEmitter(emitter)\n\n// handle aserto-node log events\nemitter.on(LOG_EVENT.TRACE, (message) =\u003e {\n  log.trace(message)\n})\nemitter.on(LOG_EVENT.DEBUG, (message) =\u003e {\n  log.debug(message)\n})\n\nemitter.on(LOG_EVENT.INFO, (message) =\u003e {\n  log.info(message)\n})\nemitter.on(LOG_EVENT.WARN, (message) =\u003e {\n  log.warn(message)\n})\nemitter.on(LOG_EVENT.ERROR, (message) =\u003e {\n  log.error(message)\n})\n```\n\n\n## Deprecated Methods\n\n\u003e Note: the `authorizerServiceUrl` option that is used throughout is no longer a URL, but the option name is retained for backward-compatibility. It is now expected to be a hostname that exposes a gRPC binding. Any \"https://\" prefix is stripped out of the value provided.\n\n### jwtAuthz middleware\n\n`jwtAuthz` is an Express-compatible middleware that you can place in the dispatch pipeline of a route.\n\nYou can use the jwtAuthz function together with [express-jwt](https://github.com/auth0/express-jwt) to both validate a JWT and make sure it has the correct permissions to call an endpoint.\n\n```javascript\nconst jwt = require('express-jwt');\nconst { jwtAuthz } = require('@aserto/aserto-node');\n\nconst options = {\n  authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port\n  policyRoot: 'mycars', // required - must be a string representing the policy root (the first component of the policy module name)\n  instanceName: 'instance-name', // optional (required only for a hosted authorizer)\n};\n\napp.get('/users/:id',\n  jwt({ secret: 'shared_secret' }),\n  jwtAuthz(options),\n  function(req, res) { ... });\n```\n\nBy default, `jwtAuthz` derives the policy file name and resource key from the Express route path. To override this behavior, two optional parameters are available.\n\n#### arguments\n\n`jwtAuthz(options[, packageName[, resourceMap]])`:\n\n- `options`: a javascript map containing at least `{ authorizerServiceUrl, policyName, policyRoot }` as well as `authorizerApiKey` and `tenantId` for the hosted authorizer\n- `packageName`: a string representing the policy package name (optional)\n- `resourceMap`: an optional resource context to send the authorizer. This can be either an object or a function that\n  takes an HTTP request and returns an object.\n\n#### options argument\n\n- `authorizerServiceUrl`: hostname:port of authorizer service (_required_)\n- `policyRoot`: Policy root (_required_)\n- `instanceName`: instance name (_required_ if using hosted authorizer)\n- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)\n- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)\n- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the \"Certificates\" section for more information.\n- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.\n- `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly.\n- `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`.\n- `identityHeader`: the name of the header from which to extract the `identity` field to pass into the authorize call. This only happens if `useAuthorizationHeader` is false. Defaults to 'identity'.\n- `customUserKey`: The property name to check for the subject key. By default, permissions are checked against `req.user`, but you can change it to be `req.myCustomUserKey` with this option. Defaults to `user`.\n- `customSubjectKey`: The property name to check for the subject. By default, permissions are checked against `user.sub`, but you can change it to be `user.myCustomSubjectKey` with this option. Defaults to `sub`.\n\n#### packageName argument\n\nBy convention, Aserto policy package names are of the form `policyRoot.METHOD.path`. By default, the package name will be inferred from the policy name, HTTP method, and route path:\n\n- `GET /api/users` --\u003e `policyRoot.GET.api.users`\n- `POST /api/users/:id` --\u003e `policyRoot.POST.api.users.__id`\n\nPassing in the `packageName` parameter into the `jwtAuthz()` function will override this behavior.\n\n#### resourceMap argument\n\nBy default, the resource map will be req.params. For example, if the route path is `/api/users/:id`, the resource will be `{ 'id': 'value-of-id' }`.\n\nPassing in the `resourceMap` parameter into the `jwtAuthz()` function will override this behavior.\n\n### displayStateMap middleware\n\nUse the displayStateMap middleware to set up an endpoint that returns the display state map to a caller. The endpoint is named `__displaystatemap` by default, but can be overridden in `options`.\n\n```javascript\nconst { displayStateMap } = require('@aserto/aserto-node');\n\nconst options = {\n  authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port\n  policyRoot: 'policy' // required - must be a string representing the policy root (the first component of the policy module name)\n};\napp.use(displayStateMap(options));\n```\n\n#### arguments\n\n`displayStateMap(options)`\n\n#### options argument\n\n- `authorizerServiceUrl`: hostname:port of authorizer service (_required_)\n- `policyRoot`: Policy root (_required_)\n- `instanceName`: instance name (_required_ if using hosted authorizer)\n- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)\n- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)\n- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the \"Certificates\" section for more information.\n- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.\n- `endpointPath`: display state map endpoint path, defaults to `/__displaystatemap`.\n- `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly. Defaults to `false`.\n- `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`.\n- `identityHeader`: the name of the header from which to extract the `identity` field to pass into the displayStateMap call. This only happens if `useAuthorizationHeader` is false. Defaults to 'identity'.\n- `customUserKey`: The property name to check for the subject key. By default, permissions are checked against `req.user`, but you can change it to be `req.myCustomUserKey` with this option. Defaults to `user`.\n- `customSubjectKey`: The property name to check for the subject. By default, permissions are checked against `user.sub`, but you can change it to be `user.myCustomSubjectKey` with this option. Defaults to `sub`.\n\n### 'is' function\n\nWhile `jwtAuthz` is meant to be used as dispatch middleware for a route, `is` provides an explicit mechanism for calling the Aserto authorizer.\n\nUse the `is` function to call the authorizer with a `decision`, policy, and resource, and get a boolean `true` or `false` response. The `decision` is a named value in the policy: the string `allowed` is used by convention. Examples: `is('allowed')`, `is('enabled')`, `is('visible')`, etc.\n\n```javascript\nconst { is } = require('@aserto/aserto-node');\n\nconst options = {\n  authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port\n  policyRoot: 'policy' // required - must be a string representing the policy root (the first component of the policy module name)\n};\n\napp.get('/users/:id', async function(req, res) {\n  try {\n    const allowed = await is('allowed', req, options);\n    if (allowed) {\n      ...\n    } else {\n      res.status(403).send(\"Unauthorized\");\n    }\n  } catch (e) {\n    res.status(500).send(e.message);\n  }\n});\n```\n\n#### arguments\n\n`is(decision, req, options[, packageName[, resourceMap]])`:\n\n- `decision`: a string representing the name of the decision - typically `allowed` (_required_)\n- `req`: Express request object (_required_)\n- `options`: a javascript map containing at least `{ authorizerServiceUrl, policyRoot }` as well as `authorizerApiKey` and `tenantId` for the hosted authorizer (_required_)\n- `packageName`: a string representing the package name for the the policy (optional)\n- `resourceMap`: a map of key/value pairs to use as the resource context for evaluation (optional)\n\n#### decision argument\n\nThis is simply a string that is correlates to a decision referenced in the policy: for example, `allowed`, `enabled`, etc.\n\n#### req argument\n\nThe Express request object.\n\n#### options argument\n\n- `authorizerServiceUrl`: hostname:port of authorizer service (_required_)\n- `policyRoot`: Policy root (_required_)\n- `instanceName`: instance name (_required_ if using hosted authorizer)\n- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)\n- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)\n- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the \"Certificates\" section for more information.\n- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.\n- `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`.\n- `identityHeader`: the name of the header from which to extract the `identity` field to pass into the `authorize` call. This only happens if `useAuthorizationHeader` is false. Defaults to 'identity'.\n- `customUserKey`: The property name to check for the subject key. By default, permissions are checked against `req.user`, but you can change it to be `req.myCustomUserKey` with this option. Defaults to `user`.\n- `customSubjectKey`: The property name to check for the subject. By default, permissions are checked against `user.sub`, but you can change it to be `user.myCustomSubjectKey` with this option. Defaults to `sub`.\n\n#### packageName argument\n\nBy default, `is` will follow the same heuristic behavior as `jwtAuthz` - it will infer the package name from the policy name, HTTP method, and route path. If provided, the `packageName` argument will override this and specify a policy package to use.\n\nBy convention, Aserto Rego policies are named in the form `policyRoot.METHOD.path`. Following the node.js idiom, you can also pass it in as `policyRoot/METHOD/path`, and the path can contain the Express parameter syntax.\n\nFor example, passing in `policyRoot/GET/api/users/:id` will resolve to a policy called `policyRoot.GET.api.users.__id`.\n\n#### resourceMap argument\n\nBy default, `is` follows the same behavior as `jwtAuthz` in that resource map will be `req.params`. For example, if the route path is `/api/users/:id`, the resource will be `{ 'id': 'value-of-id' }`.\n\nPassing in the `resourceMap` parameter into the `Authz()` function will override this behavior.\n\nThe provided value can be either an object or a function that takes an http request and returns an object.\n\n## Certificates\n\nThe Topaz / Aserto [authorizers](github.com/aserto-dev/topaz) exposes SSL-only endpoints. In order for a node.js policy to properly communicate with the authorizer, TLS certificates must be verified.\n\nFor a hosted authorizer that has a TLS certificate that is signed by a trusted Certificate Authority, this section isn't relevant because that TLS certificate will be successfully validated.\n\nIn a development environment, [topaz](github.com/aserto-dev/topaz) automatically creates a set of self-signed certificates and certificates of the CA (certificate authority) that signed them. It places them in a well-known location on the filesystem, defaulting to `$HOME/.local/share/topaz/certs/` (or `$HOMEPATH\\AppData\\Local\\topaz\\certs\\` on Windows).\n\nIn order for the `aserto-node` package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in `$HOME/.local/share/topaz/certs/grpc-ca.crt`. Therefore, in order for this middleware to work successfully, either the `caFile` must be set to the correct path for the CA cert file, or the `disableTlsValidation` flag must be set to `true`. The same is true for the `caFile` argument of the `DirectoryClient`.\n\nFurthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses `aserto-node` to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the `caFile` option that is passed into any of the API calls defined above with the location of this cert file.\n\nAlternately, to ignore TLS certificate validation when creating a TLS connection to the authorizer, you can set the `disableTlsValidation` option to `true` and avoid TLS certificate validation. This option is **not recommended for production**.\n\n## Debugging\n\naserto-node provides a couple of environment variables that can be used to print debug information:\n\n`NODE_TRACE=true` - enables trace logging for the requests.\n\n`NODE_TRACE_MESSAGE=true` - logs the request payload for gRPC requests.\n\n## Issue Reporting\n\nIf you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker.\n\n## Author\n\n[Aserto](https://aserto.com) based on the original work by [Auth0](https://auth0.com).\n\n## License\n\nThis project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faserto-dev%2Faserto-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faserto-dev%2Faserto-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faserto-dev%2Faserto-node/lists"}