diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index c25f9f0..f293a27 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -26,6 +26,10 @@ credentials/credentials.ts credentials/index.ts credentials/types.ts errors.ts +example/Makefile +example/README.md +example/example1/example1.mjs +example/example1/package.json git_push.sh index.ts package-lock.json diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 0000000..f0099a0 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,14 @@ +all: run + +project_name=example1 +openfga_version=latest + +setup: + npm --prefix "${project_name}" install + +run: + npm --prefix "${project_name}" run start + +run-openfga: + docker pull docker.io/openfga/openfga:${openfga_version} && \ + docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version} run \ No newline at end of file diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..9c6e3ab --- /dev/null +++ b/example/README.md @@ -0,0 +1,42 @@ +## Examples of using the OpenFGA JS SDK + +A set of Examples on how to call the OpenFGA JS SDK + +### Examples +Example 1: +A bare-bones example. It creates a store, and runs a set of calls against it including creating a model, writing tuples and checking for access. + +### Running the Examples + +Prerequisites: +- `docker` +- `make` +- `Node.js` 16.13.0+ + +#### Run using a published SDK + +Steps +1. Clone/Copy the example folder +2. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done) +3. Run `make setup` to install dependencies +4. Run `make run` to run the example + +#### Run using a local unpublished SDK build + +Steps +1. Build the SDK +2. In the Example `package.json` change the `@openfga/sdk` dependency from a semver range like below +```json +"dependencies": { + "@openfga/sdk": "^0.3.1" + } +``` +to a `file:` reference like below +```json +"dependencies": { + "@openfga/sdk": "file:../../" + } +``` +3. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done) +4. Run `make setup` to install dependencies +5. Run `make run` to run the example \ No newline at end of file diff --git a/example/example1/example1.mjs b/example/example1/example1.mjs new file mode 100644 index 0000000..2ec5a32 --- /dev/null +++ b/example/example1/example1.mjs @@ -0,0 +1,213 @@ +import { Credentials, CredentialsMethod, FgaApiValidationError, OpenFgaClient, TypeName } from "@openfga/sdk"; + +async function main () { + let credentials; + if (process.env.FGA_CLIENT_ID) { + credentials = new Credentials({ + method: CredentialsMethod.ClientCredentials, + config: { + clientId: process.env.FGA_CLIENT_ID, + clientSecret: process.env.FGA_CLIENT_SECRET, + apiAudience: process.env.FGA_API_AUDIENCE, + apiTokenIssuer: process.env.FGA_API_TOKEN_ISSUER + } + }); + } + + const fgaClient = new OpenFgaClient({ + apiUrl: process.env.FGA_API_URL || "http://localhost:8080", + storeId: process.env.FGA_STORE_ID, // not needed when calling `createStore` or `listStores` + authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // optional, recommended for production, + credentials + }); + + console.log("Listing stores"); + const initialStores = await fgaClient.listStores(); + console.log(`Stores count: ${initialStores.stores.length}`); + + console.log("Creating Test Store"); + const createdStore = await fgaClient.createStore({name: "Test Store"}); + const store = createdStore; + console.log(`Test Store ID: ${store.id}`); + + // Set the store ID + fgaClient.storeId = store.id; + + console.log("Listing Stores"); + const { stores } = await fgaClient.listStores(); + console.log(`Stores count: ${stores.length}`); + + console.log("Getting Current Store"); + const currentStore = await fgaClient.getStore(); + console.log(`Current Store Name ${currentStore.name}`); + + console.log("Reading Authorization Models"); + const { authorization_models } = await fgaClient.readAuthorizationModels(); + console.log(`Models Count: ${authorization_models.length}`); + + console.log("Reading Latest Authorization Model"); + const { authorization_model } = await fgaClient.readLatestAuthorizationModel(); + if (authorization_model) { + console.log(`Latest Authorization Model ID: ${latestModel.authorization_model.id}`); + } else { + console.log("Latest Authorization Model not found"); + } + + console.log("Writing an Authorization Model"); + const model = await fgaClient.writeAuthorizationModel({ + schema_version: "1.1", + type_definitions: [ + { + type: "user" + }, + { + type: "document", + relations: { + writer: { this: {} }, + viewer: { + union: { + child: [ + { this: {} }, + { + computedUserset: { + relation: "writer" + } + } + ] + } + } + }, + metadata: { + relations: { + writer: { + directly_related_user_types: [ + { type: "user" }, + { type: "user", condition: "ViewCountLessThan200" } + ] + }, + viewer: { + directly_related_user_types: [ + { type: "user" } + ] + } + } + } + }, + ], + conditions: { + "ViewCountLessThan200": { + name: "ViewCountLessThan200", + expression: "ViewCount < 200", + parameters: { + ViewCount: { + type_name: TypeName.Int + }, + Type: { + type_name: TypeName.String + }, + Name: { + type_name: TypeName.String + } + } + } + } + }); + const authorizationModelId = model.authorization_model_id; + console.log(`Authorization Model ID: ${authorizationModelId}`); + + console.log("Reading Authorization Models"); + const models = await fgaClient.readAuthorizationModels(); + console.log(`Models Count: ${models.authorization_models.length}`); + + console.log("Reading Latest Authorization Model"); + const latestModel = await fgaClient.readLatestAuthorizationModel(); + console.log(`Latest Authorization Model ID: ${latestModel.authorization_model.id}`); + + console.log("Writing Tuples"); + await fgaClient.write({ + writes: [ + { + user: "user:anne", + relation: "writer", + object: "document:roadmap", + condition: { + name: "ViewCountLessThan200", + context: { + Name: "Roadmap", + Type: "document" + } + } + } + ] + }, { authorizationModelId }); + console.log("Done Writing Tuples"); + + // Set the model ID + fgaClient.authorizationModelId = authorizationModelId; + + console.log("Reading Tuples"); + const { tuples } = await fgaClient.read(); + console.log(`Read Tuples: ${JSON.stringify(tuples)}`); + + console.log("Reading Tuple Changes"); + const { changes } = await fgaClient.readChanges(); + console.log(`Tuple Changes: ${JSON.stringify(changes)}`); + + console.log("Checking for access"); + try { + const { allowed } = await fgaClient.check({ + user: "user:anne", + relation: "viewer", + object: "document:roadmap" + }); + console.log(`Allowed: ${allowed}`); + } catch (error) { + if (error instanceof FgaApiValidationError) { + console.log(`Failed due to ${error.apiErrorMessage}`); + } else { + throw error; + } + } + + console.log("Checking for access with context"); + const { allowed } = await fgaClient.check({ + user: "user:anne", + relation: "viewer", + object: "document:roadmap", + context: { + ViewCount: 100 + } + }); + console.log(`Allowed: ${allowed}`); + + console.log("Writing Assertions"); + await fgaClient.writeAssertions([ + { + user: "user:carl", + relation: "writer", + object: "document:budget", + expectation: true + }, + { + user: "user:carl", + relation: "writer", + object: "document:roadmap", + expectation: false + } + ]); + console.log("Assertions Updated"); + + console.log("Reading Assertions"); + const { assertions} = await fgaClient.readAssertions(); + console.log(`Assertions: ${JSON.stringify(assertions)}`); + + console.log("Deleting Current Store"); + await fgaClient.deleteStore(); + console.log(`Deleted Store Count: ${store.id}`); +} + +main() + .catch(error => { + console.error(`error: ${error}`); + process.exitCode = 1; + }); \ No newline at end of file diff --git a/example/example1/package.json b/example/example1/package.json new file mode 100644 index 0000000..45e387a --- /dev/null +++ b/example/example1/package.json @@ -0,0 +1,17 @@ +{ + "name": "example1", + "private": "true", + "version": "1.0.0", + "description": "A bare bones example of using the OpenFGA SDK", + "author": "OpenFGA", + "license": "Apache-2.0", + "scripts": { + "start": "node example1.mjs" + }, + "dependencies": { + "@openfga/sdk": "^0.3.1" + }, + "engines": { + "node": ">=16.13.0" + } +}