Node.js v1 Client API
The Node.js v2 client is released! Bugfixes and security patches will be backported to v1, and there is currently no end-of-life date. However, we recommend migrating to the v2 client to take advantage of new features and improvements.
Instantiating an Oso Cloud client
The Oso Cloud client provides an Oso
class that takes your Oso Cloud URL and API key:
const { Oso } = require("oso-cloud");const oso = new Oso("https://cloud.osohq.com", YOUR_API_KEY);// Later, in an async function:await oso.tell("has_role", user, role, resource);// Wherever authorization needs to be performedif (await oso.authorize(user, action, resource)) { // Action is allowed}
You should instantiate one client and share it across your application. Under the hood, it reuses connections to avoid paying the cost of negotiating a new connection on every request.
Specifying an Oso Fallback host
If you have deployed Oso Fallback nodes to your infrastructure, you may specify the host when instantiating the Oso Cloud client.
// Assumes Oso Fallback is hosted at http://localhost:8080const oso = new Oso("https://cloud.osohq.com", YOUR_API_KEY, { fallbackUrl: "http://localhost:8080",});
Passing application entities into the client
Under the hood, Oso Cloud represents an entity in your application as a combination of a type and an ID, which together uniquely identify the entity.
The Node client represents these entities as objects with both type
and id
properties.
For example:
const alice = { type: "User", id: "alice" };const anvilsRepository = { type: "Repository", id: "anvils" };
You will pass objects like these into nearly every function call you make to the Node client.
Centralized Authorization Data API
Oso Cloud clients provide an API to manage authorization data stored directly in Oso Cloud.
Add fact: oso.tell(name, ...args)
Adds a fact named name
with the provided arguments. Example:
await oso.tell("has_role", { type: "User", id: "bob" }, "owner", { type: "Organization", id: "acme",});
For Oso Cloud developer accounts, bulkTell
, bulkDelete
, and bulk
calls
are limited to 20 facts. If you attempt to send more than 20 facts, these
functions will throw an error.
Add many facts: oso.bulkTell([...[name, ...args]])
Adds many facts at once. Example:
await oso.bulkTell([ [ "has_role", { type: "User", id: "bob" }, "owner", { type: "Organization", id: "acme" }, ], [ "has_role", { type: "User", id: "bob" }, "maintainer", { type: "Repository", id: "anvils" }, ],]);
Delete fact: oso.delete(name, ...args)
Deletes a fact. Does not throw an error when the fact is not found. Example:
await oso.delete("has_role", { type: "User", id: "bob" }, "maintainer", { type: "Repository", id: "anvils",});
Delete many facts: oso.bulkDelete([...[name, ...args]])
Deletes many facts at once. Does not throw an error when some of the facts are not found. Example:
await oso.bulkDelete([ [ "has_role", { type: "User", id: "bob" }, "owner", { type: "Organization", id: "acme" }, ], [ "has_role", { type: "User", id: "bob" }, "maintainer", { type: "Repository", id: "anvils" }, ],]);
Transactionally delete and add facts: oso.bulk(delete, tell)
Deletes and adds many facts in one atomic transaction.
The deletions are performed before the adds.
null
can be used as a wildcard in facts in delete
.
Does not throw an error when the facts to delete are not found.
Example:
await oso.bulk( [ // All `has_role` facts linking User:bob and Repository:anvils will be deleted. [ "has_role", { type: "User", id: "bob" }, null, { type: "Repository", id: "anvils" }, ], ], [ // This fact will be added. [ "has_role", { type: "User", id: "bob" }, "maintainer", { type: "Repository", id: "anvils" }, ], ]);
Get facts: oso.get(name, ...args)
For Oso Cloud developer accounts, Get
calls are limited to 1000 results. If
you have more than 1000 facts, the function will throw an error.
Get facts that are stored in Oso Cloud. Can be used to check the existence of a particular fact, or used to fetch all facts that have a particular argument:
// Get one fact:await oso.get("has_role", { type: "User", id: "bob" }, "admin", { type: "Repository", id: "anvils",});// => [// [// "has_role",// {type: "User", id: "bob"},// {type: "String", id: "admin"},// {type: "Repository", id: "anvils"}// ]// ]// List all role-related facts on the `anvils` repoawait oso.get("has_role", null, null, { type: "Repository", id: "anvils" });// => [// [// "has_role",// {type: "User", id: "bob"},// {type: "String", id: "admin"},// {type: "Repository", id: "anvils"}// ],// //...other has_role facts// ]
Note that null
behaves like a wildcard: passing null, null, anvils
means
"find all facts where anvils
is the third argument, regardless of other
arguments".
oso.get()
only returns facts you've added. If you want to return a list of
authorized resources, use the Check API. For example, to answer
"on which resources can a given user perform a given action", use
oso.list()
.
Check API
For Oso Cloud developer accounts, the number of context facts per request is limited to 20; and the number of records returned is limited to 1000.
Context facts
The Check AP lets you provide context facts with each request. When Oso Cloud performs a check, it considers the request's context facts in addition to any other centralized authorization data. Context facts are only used in the API call in which they're provided-- they do not persist across requests.
For more details, see Context Facts.
Check a permission: oso.authorize(actor, action, resource)
Determines whether or not an action is allowed, based on a combination of authorization data and policy logic. Example:
const alice = { type: "User", id: "alice" };const anvilsRepository = { type: "Repository", id: "anvils" };const authorized = await oso.authorize(alice, "read", anvilsRepository);if (!authorized) { throw new Error("Action is not allowed");}
You may provide an array of context facts as an optional fourth argument to this method. Example:
const issue = { type: "Issue", id: "anvils-1" };const authorized = await oso.authorize(alice, "read", issue, [ ["has_relation", issue, "parent", anvilsRepository], // a context fact]);
Check authorized resources: oso.authorizeResources(actor, action, resources)
Returns a subset of resources
on which an actor can perform a particular action.
Ordering and duplicates, if any exist, are preserved.
For Oso Cloud developer accounts, the number of input resources is limited to 1000.
Example:
const alice = { type: "User", id: "alice" };const anvilsRepository = { type: "Repository", id: "anvils" };const acmeRepository = { type: "Repository", id: "acme" };await oso.authorizeResources(alice, "read", [anvilsRepository, acmeRepository]);// => [acmeRepository]
You may provide an array of context facts as an optional fourth argument to this method. Example:
const issueOnAcmeRepository = { type: "Issue", id: "acme-1" };const issueOnAnvilsRepository = { type: "Issue", id: "anvils-2" };await oso.authorizeResources( alice, "read", [issueOnAnvilsRepository, issueOnAcmeRepository], [ // context facts ["has_relation", issueOnAnvilsRepository, "parent", anvilsRepository][ ("has_relation", issueOnAcmeRepository, "parent", acmeRepository) ], ]);// => [issueOnAcmeRepository]
List authorized resources: oso.list(actor, action, resourceType)
Fetches a list of resource IDs on which an actor can perform a particular action. Example:
const alice = { type: "User", id: "alice" };const repositoryIds = await oso.list(alice, "read", "Repository");// => ["acme"]
You may provide an array of context facts as an optional fourth argument to this method. Example:
const anvilsRepository = { type: "Repository", id: "anvils" };const acmeRepository = { type: "Repository", id: "acme" };const issueOnAcmeRepository = { type: "Issue", id: "acme-1" };const issueOnAnvilsRepository = { type: "Issue", id: "anvils-2" };const repositoryIds = await oso.list(alice, "read", "Issue", [ // context facts ["has_relation", issueOnAnvilsRepository, "parent", anvilsRepository][ ("has_relation", issueOnAcmeRepository, "parent", acmeRepository) ],]);// => ["acme-1"]
List authorized actions: oso.actions(actor, resource)
Fetches a list of actions which an actor can perform on a particular resource. Example:
const alice = { type: "User", id: "alice" };const acmeRepository = { type: "Repository", id: "acme" };await oso.actions(user, acmeRepository);// => ["read"]
You may provide an array of context facts as an optional third argument to this method. Example:
const issueOnAcmeRepository = { type: "Issue", id: "acme-1" };await oso.actions(alice, issueOnAcmeRepository, [ ["has_relation", issueOnAcmeRepository, "parent", acmeRepository], // a context fact]);// => ["read"]
In bulk: oso.bulkActions(actor, resources)
Same as oso.actions
, but takes an array of resources and returns an array of
authorized actions for each resource. Every resource must have the same type.
Example:
const alice = { type: "User", id: "alice" };const acmeRepository = { type: "Repository", id: "acme" };const otherRepository = { type: "Repository", id: "other" };await oso.bulkActions(alice, [acmeRepository, otherRepository]);// => [["read"], ["read", "write"]]
You may provide an array of context facts as an optional third argument to this method. Example:
const issue1 = { type: "Issue", id: "acme-1" };const issue2 = { type: "Issue", id: "acme-1" };await oso.bulkActions( alice, [issue1, issue2], [ ["has_relation", issue1, "parent", acmeRepository], // context facts ["has_relation", issue2, "parent", acmeRepository], ]);// => [["read"], ["read", "write"]]
Query for anything: oso.query(predicate, ...args)
Query Oso Cloud for any predicate and any combination of concrete and wildcard arguments.
Unlike oso.get
, which only lists facts you've added, you can use oso.query
to list derived
information about any rule in your policy.
Example:
// Query for all the repos `User:bob` can `read`await oso.query("allow", { type: "User", id: "bob" }, "read", { type: "Repository",});// => [// ["allow", {type: "User", id: "bob"}, "read", {type: "Repository", id: "acme"}],// ["allow", {type: "User", id: "bob"}, "read", {type: "Repository", id: "anvils"}],// ]// Query for all the objects `User:admin` can `read`await oso.query("allow", { type: "User", id: "admin" }, "read", null);// => [// // `User:admin` can `read` anything// ["allow", {type: "User", id: "admin"}, "read", null],// ]
You may pass a list of context facts with the query_with_context
method.
Example:
// Query for all the repos `User:bob` can `write` derived from context factsawait oso.query_with_context( ["allow", { type: "User", id: "bob" }, "write", { type: "Repository" }], { contextFacts: [ "has_permission", { type: "User", id: "bob" }, "write", { type: "Repository", id: "anvils" }, ], });// => [// ["allow", {type: "User", id: "bob"}, "write", {type: "Repo", id: "anvils"}],// ]
Note that null
behaves like a wildcard. Passing "allow", null, null, anvils
means "find
anyone who can do anything to anvils
". null
also behaves like a wildcard
in return values from oso.query
. Additionally, if you want to query over
all instances of a particular type, pass an object with a type
property but
no id
property. For example, "allow", bob, "read", {type: "Repository"}
will query for all the objects of type "Repository"
that bob
can read
.
[Learn more about how to query Oso Cloud.][query]
Local Check API
The local check API lets you perform authorization using data that's distributed across Oso Cloud and your own database. When you instantiate the Oso Cloud client, provide the path to the YAML file that specifies how to resolve facts in your database.
const oso = new Oso(..., { dataBindings: "path/to/data_bindings.yaml"});
For more information, see the guide on filtering lists with local data.
List authorized resources with local data: oso.listLocal(actor, action, resource_type, column)
Fetches a filter that can be applied to a database query to return just the resources on which an actor can perform an action. Example with Kysely (opens in a new tab):
const alice = { type: "User", id: "alice" };const authorized_issues = await db .selectFrom("issues") .where(sql.raw<boolean>(await oso.listLocal(alice, "read", "Issue", "id"))) .selectAll() .execute();
You may use the Kysely query builder (opens in a new tab) to combine this authorization filter with other things such as ordering and pagination.
Check a permission with local data: oso.authorizeLocal(actor, action, resource)
Fetches a query that can be run against your database to determine whether an actor can perform an action on a resource. Example with Kysely (opens in a new tab):
const alice = { type: "User", id: "alice" };const swage_issue = { type: "Issue", id: "swage" };const query = await oso.authorizeLocal(alice, "read", swage_issue);const { allowed } = (await sql.raw<AuthorizeResult>(query).execute(db)).rows[0];if (!allowed) { throw new Error("Action is not allowed");}
List authorized actions with local data: oso.actionsLocal(actor, resource)
Fetches a query that can be run against your database to fetch the actions an actor can perform on a resource. Example with Kysely (opens in a new tab):
const alice = { type: "User", id: "alice" };const swage_issue = { type: "Issue", id: "swage" };const query = await oso.actionsLocal(alice, swage_issue);const result = await sql.raw<{ actions: string }>(query).execute(db);const actions = result.rows.map(({ actions }) => actions);
Policy API
Update the active policy: oso.policy(policy)
Updates the policy in Oso Cloud. The string passed into this method should be written in Polar. Example:
await oso.policy("actor User {}");
This command will run any tests defined in your policy. If one or more of these tests fail, your policy will not be updated.
Get policy metadata: oso.getPolicyMetadata()
Returns metadata about the currently active policy. Example:
metadata = await oso.getPolicyMetadata();
returns:
resources: { Organization: { roles: ["admin", "member"], permissions: [ "add_member", "read", "repository.create", "repository.delete", "repository.read", ], relations: {}, }, User: { roles: [], permissions: [], relations: {}, }, global: { roles: [], permissions: [], relations: {}, },}
See the Policy Metadata guide for more information on use cases.
Debugging
Request logs can be found in the Oso Cloud dashboard, but sometimes it's useful to print timings from the client so you can diagnose network problems. There are two configuration parameters you can set
var oso = new Oso(url, apiKey, { debug: { print: true } });
will print debug messages to stdout.
var oso = new Oso(url, apiKey, { debug: { file: "oso_debug.txt" } });
will append debug messages to the file you specify.
In both cases the messages will look like this. They show the route, the response code, the total request time and how much time was spent in Oso Cloud processing the request vs. network transport.
[oso] /facts 200 total: 4ms, server: 3ms network: 1ms[oso] /policy 200 total: 9ms, server: 3ms network: 6ms...
Talk to an Oso Engineer
If you'd like to learn more about using Oso Cloud in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer. We're happy to help.