diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index c27eabb..7ce60d7 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -8,8 +8,6 @@ updates: dependencies: patterns: - "*" - exclude-patterns: - - "eslint" - package-ecosystem: "github-actions" directory: "/" schedule: @@ -18,3 +16,5 @@ updates: dependencies: patterns: - "*" + exclude-patterns: + - "eslint" diff --git a/CHANGELOG.md b/CHANGELOG.md index 2000866..8c61725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v0.4.0 + +### [0.4.0](https://github.com/openfga/js-sdk/compare/v0.3.5...v0.4.0) (2024-04-30) + +- feat: support the [ListUsers](https://github.com/openfga/rfcs/blob/main/20231214-listUsers-api.md) endpoint (#97) +- feat!: support overriding storeId per request (#97) + `OpenFgaClient` now supports specifying the storeId in the options to override it per request + + [BREAKING CHANGE] the underlying `OpenFgaApi` now expects `storeId` as the first param on relevant methods, + if you are still using this class, make sure you update your references when needed. + ## v0.3.5 ### [0.3.5](https://github.com/openfga/js-sdk/compare/v0.3.4...v0.3.5) (2024-03-19) diff --git a/README.md b/README.md index 1b3dfc1..edd4075 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ This is an autogenerated JavaScript SDK for OpenFGA. It provides a wrapper aroun - [Expand](#expand) - [List Objects](#list-objects) - [List Relations](#list-relations) + - [List Users](#list-users) - [Assertions](#assertions) - [Read Assertions](#read-assertions) - [Write Assertions](#write-assertions) @@ -97,7 +98,7 @@ const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores` - authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request + authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request }); ``` @@ -109,7 +110,7 @@ const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores` - authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request + authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request credentials: { method: CredentialsMethod.ApiToken, config: { @@ -127,7 +128,7 @@ const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores` - authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request + authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request credentials: { method: CredentialsMethod.ClientCredentials, config: { @@ -462,7 +463,7 @@ const { responses } = await fgaClient.batchCheck([{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "viewer", object: "document:roadmap", - contextualTuples: [{ + contextual_tuples: [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "writer", object: "document:roadmap" @@ -491,7 +492,7 @@ responses = [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "viewer", object: "document:roadmap", - contextualTuples: [{ + contextual_tuples: [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "writer", object: "document:roadmap" @@ -537,7 +538,7 @@ const response = await fgaClient.listObjects({ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "viewer", type: "document", - contextualTuples: [{ + contextual_tuples: [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "writer", object: "document:budget" @@ -563,7 +564,7 @@ const response = await fgaClient.listRelations({ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", object: "document:roadmap", relations: ["can_view", "can_edit", "can_delete"], - contextualTuples: [{ + contextual_tuples: [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "writer", object: "document:roadmap" @@ -573,6 +574,49 @@ const response = await fgaClient.listRelations({ // response.relations = ["can_view", "can_edit"] ``` +##### List Users + +List the users who have a certain relation to a particular type. + +[API Documentation](https://openfga.dev/api/service#/Relationship%20Queries/ListUsers) + +```js +const options = {}; + +// To override the authorization model id for this request +options.authorization_model_id = "1uHxCSuTP0VKPYSnkq1pbb1jeZw"; + +// Only a single filter is allowed for the time being +const userFilters = [{type: "user"}]; +// user filters can also be of the form +// const userFilters = [{type: "team", relation: "member"}]; + +const response = await fgaClient.listUsers({ + object: { + type: "document", + id: "roadmap" + }, + relation: "can_read", + user_filters: userFilters, + context: { + "view_count": 100 + }, + contextualTuples: + [{ + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "editor", + object: "folder:product" + }, { + user: "folder:product", + relation: "parent", + object: "document:roadmap" + }] +}, options); + +// response.users = [{object: {type: "user", id: "81684243-9356-4421-8fbf-a4f8d36aa31b"}}, {userset: { type: "user" }}, ...] +// response.excluded_users = [ {object: {type: "user", id: "4a455e27-d15a-4434-82e0-136f9c2aa4cf"}}, ... ] +``` + #### Assertions ##### Read Assertions @@ -622,9 +666,11 @@ const response = await fgaClient.writeAssertions([{ ### Retries -By default API requests are retried up to 15 times on 429 and 5xx errors and credential requests are retried to 3 times on 429 and 5xx errors. In both instances they will wait a minimum of 100 milliseconds between requests and up to a maximum of +If a network request fails with a 429 or 5xx error from the server, the SDK will automatically retry the request up to 15 times with a minimum wait time of 100 milliseconds between each attempt. + +To customize this behavior, create an object with `maxRetry` and `minWaitInMs` properties. `maxRetry` determines the maximum number of retries (up to 15), while `minWaitInMs` sets the minimum wait time between retries in milliseconds. -In order to change the behavior for API requests, pass a `retryParams` object in the `OpenFgaClient` constructor with a `maxRetry` property to control the amount of retries and a `minWaitInMs` to control the minimum wait time between retried requests. +Apply your custom retry values by setting to `retryParams` on the to the configuration object passed to the `OpenFgaClient` call. ```javascript const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk'; @@ -632,7 +678,7 @@ const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required storeId: process.env.FGA_STORE_ID, // not needed when calling `CreateStore` or `ListStores` - authorizationModelId: process.env.FGA_AUTHORIZATION_MODEL_ID, // Optional, can be overridden per request + authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request retryParams: { maxRetry: 3, // retry up to 3 times on API requests minWaitInMs: 250 // wait a minimum of 250 milliseconds between requests diff --git a/api.ts b/api.ts index 6e5e7ef..66ed108 100644 --- a/api.ts +++ b/api.ts @@ -21,6 +21,7 @@ import { toPathString, createRequestFunction, RequestArgs, + CallResult, PromiseResult } from "./common"; import { Configuration } from "./configuration"; @@ -48,6 +49,7 @@ import { ExpandRequest, ExpandRequestTupleKey, ExpandResponse, + FgaObject, GetStoreResponse, InternalErrorCode, InternalErrorMessageResponse, @@ -55,11 +57,14 @@ import { ListObjectsRequest, ListObjectsResponse, ListStoresResponse, + ListUsersRequest, + ListUsersResponse, Metadata, Node, Nodes, NotFoundErrorCode, NullValue, + ObjectOrUserset, ObjectRelation, PathUnknownErrorMessageResponse, ReadAssertionsResponse, @@ -83,11 +88,17 @@ import { TupleToUserset, TypeDefinition, TypeName, + TypedWildcard, + UnprocessableContentErrorCode, + UnprocessableContentMessageResponse, + User, + UserTypeFilter, Users, Userset, UsersetTree, UsersetTreeDifference, UsersetTreeTupleToUserset, + UsersetUser, Usersets, ValidationErrorMessageResponse, WriteAssertionsRequest, @@ -106,20 +117,21 @@ import { export const OpenFgaApiAxiosParamCreator = function (configuration: Configuration, credentials: Credentials) { return { /** - * The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn\'t. + * The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with exclusion in the model A Check for a userset can yield results that must be treated carefully if the model involves exclusion. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. * @summary Check whether a user is authorized to access an object + * @param {string} storeId * @param {CheckRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - check: async (body: CheckRequest, options: any = {}): Promise => { + check: async (storeId: string, body: CheckRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("check", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("check", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/check" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -151,9 +163,9 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio * @throws { FgaError } */ createStore: async (body: CreateStoreRequest, options: any = {}): Promise => { - + // verify required parameter 'body' is not null or undefined + assertParamExists("createStore", "body", body); const localVarPath = "/stores" - ; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -182,15 +194,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. * @summary Delete a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - deleteStore: async (options: any = {}): Promise => { - assertParamExists("Configuration", "storeId", configuration.storeId); + deleteStore: async (storeId: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("deleteStore", "storeId", storeId); const localVarPath = "/stores/{store_id}" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -214,18 +227,19 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. * @summary Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + * @param {string} storeId * @param {ExpandRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - expand: async (body: ExpandRequest, options: any = {}): Promise => { + expand: async (storeId: string, body: ExpandRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("expand", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("expand", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/expand" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -252,15 +266,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * Returns an OpenFGA store by its identifier * @summary Get a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - getStore: async (options: any = {}): Promise => { - assertParamExists("Configuration", "storeId", configuration.storeId); + getStore: async (storeId: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("getStore", "storeId", storeId); const localVarPath = "/stores/{store_id}" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -282,20 +297,21 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio }; }, /** - * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. + * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. * @summary List all objects of the given type that the user has a relation with + * @param {string} storeId * @param {ListObjectsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - listObjects: async (body: ListObjectsRequest, options: any = {}): Promise => { + listObjects: async (storeId: string, body: ListObjectsRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("listObjects", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("listObjects", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/list-objects" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -328,9 +344,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio * @throws { FgaError } */ listStores: async (pageSize?: number, continuationToken?: string, options: any = {}): Promise => { - const localVarPath = "/stores" - ; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -361,21 +375,61 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio options: localVarRequestOptions, }; }, + /** + * + * @summary List all users of the given type that the object has a relation with + * @param {string} storeId + * @param {ListUsersRequest} body + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + listUsers: async (storeId: string, body: ListUsersRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("listUsers", "storeId", storeId); + // verify required parameter 'body' is not null or undefined + assertParamExists("listUsers", "body", body); + const localVarPath = "/stores/{store_id}/list-users" + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter["Content-Type"] = "application/json"; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn\'t guarantee order by any field. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). * @summary Get tuples from the store that matches a query, without following userset rewrite rules + * @param {string} storeId * @param {ReadRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - read: async (body: ReadRequest, options: any = {}): Promise => { + read: async (storeId: string, body: ReadRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("read", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("read", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/read" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -402,17 +456,18 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Read assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAssertions: async (authorizationModelId: string, options: any = {}): Promise => { + readAssertions: async (storeId: string, authorizationModelId: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("readAssertions", "storeId", storeId); // verify required parameter 'authorizationModelId' is not null or undefined assertParamExists("readAssertions", "authorizationModelId", authorizationModelId); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/assertions/{authorization_model_id}" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - .replace(`{${"authorization_model_id"}}`, encodeURIComponent(String(authorizationModelId))); + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))).replace(`{${"authorization_model_id"}}`, encodeURIComponent(String(authorizationModelId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -437,17 +492,18 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). * @summary Return a particular version of an authorization model + * @param {string} storeId * @param {string} id * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAuthorizationModel: async (id: string, options: any = {}): Promise => { + readAuthorizationModel: async (storeId: string, id: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("readAuthorizationModel", "storeId", storeId); // verify required parameter 'id' is not null or undefined assertParamExists("readAuthorizationModel", "id", id); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/authorization-models/{id}" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - .replace(`{${"id"}}`, encodeURIComponent(String(id))); + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))).replace(`{${"id"}}`, encodeURIComponent(String(id))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -472,17 +528,18 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA\'s response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store\'s authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` * @summary Return all the authorization models for a particular store + * @param {string} storeId * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAuthorizationModels: async (pageSize?: number, continuationToken?: string, options: any = {}): Promise => { - assertParamExists("Configuration", "storeId", configuration.storeId); + readAuthorizationModels: async (storeId: string, pageSize?: number, continuationToken?: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("readAuthorizationModels", "storeId", storeId); const localVarPath = "/stores/{store_id}/authorization-models" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -514,18 +571,19 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. * @summary Return a list of all the tuple changes + * @param {string} storeId * @param {string} [type] * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - readChanges: async (type?: string, pageSize?: number, continuationToken?: string, options: any = {}): Promise => { - assertParamExists("Configuration", "storeId", configuration.storeId); + readChanges: async (storeId: string, type?: string, pageSize?: number, continuationToken?: string, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("readChanges", "storeId", storeId); const localVarPath = "/stores/{store_id}/changes" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -559,20 +617,21 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio }; }, /** - * The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` + * The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` * @summary Add or delete tuples from the store + * @param {string} storeId * @param {WriteRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - write: async (body: WriteRequest, options: any = {}): Promise => { + write: async (storeId: string, body: WriteRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("write", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("write", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/write" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -599,20 +658,21 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Upsert assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {WriteAssertionsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - writeAssertions: async (authorizationModelId: string, body: WriteAssertionsRequest, options: any = {}): Promise => { + writeAssertions: async (storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("writeAssertions", "storeId", storeId); // verify required parameter 'authorizationModelId' is not null or undefined assertParamExists("writeAssertions", "authorizationModelId", authorizationModelId); // verify required parameter 'body' is not null or undefined assertParamExists("writeAssertions", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/assertions/{authorization_model_id}" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - .replace(`{${"authorization_model_id"}}`, encodeURIComponent(String(authorizationModelId))); + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))).replace(`{${"authorization_model_id"}}`, encodeURIComponent(String(authorizationModelId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -640,18 +700,19 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio /** * The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model\'s ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA\'s response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` * @summary Create a new authorization model + * @param {string} storeId * @param {WriteAuthorizationModelRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - writeAuthorizationModel: async (body: WriteAuthorizationModelRequest, options: any = {}): Promise => { + writeAuthorizationModel: async (storeId: string, body: WriteAuthorizationModelRequest, options: any = {}): Promise => { + // verify required parameter 'storeId' is not null or undefined + assertParamExists("writeAuthorizationModel", "storeId", storeId); // verify required parameter 'body' is not null or undefined assertParamExists("writeAuthorizationModel", "body", body); - assertParamExists("Configuration", "storeId", configuration.storeId); const localVarPath = "/stores/{store_id}/authorization-models" - .replace(`{${"store_id"}}`, encodeURIComponent(String(configuration.storeId))) - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + .replace(`{${"store_id"}}`, encodeURIComponent(String(storeId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; if (configuration) { @@ -686,14 +747,15 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: const localVarAxiosParamCreator = OpenFgaApiAxiosParamCreator(configuration, credentials); return { /** - * The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn\'t. + * The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with exclusion in the model A Check for a userset can yield results that must be treated carefully if the model involves exclusion. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. * @summary Check whether a user is authorized to access an object + * @param {string} storeId * @param {CheckRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async check(body: CheckRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.check(body, options); + async check(storeId: string, body: CheckRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.check(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** @@ -710,43 +772,47 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: /** * Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. * @summary Delete a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - async deleteStore(options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.deleteStore(options); + async deleteStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteStore(storeId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. * @summary Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + * @param {string} storeId * @param {ExpandRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async expand(body: ExpandRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.expand(body, options); + async expand(storeId: string, body: ExpandRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.expand(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * Returns an OpenFGA store by its identifier * @summary Get a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - async getStore(options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getStore(options); + async getStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getStore(storeId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** - * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. + * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. * @summary List all objects of the given type that the user has a relation with + * @param {string} storeId * @param {ListObjectsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async listObjects(body: ListObjectsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.listObjects(body, options); + async listObjects(storeId: string, body: ListObjectsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listObjects(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** @@ -761,96 +827,116 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: const localVarAxiosArgs = await localVarAxiosParamCreator.listStores(pageSize, continuationToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, + /** + * + * @summary List all users of the given type that the object has a relation with + * @param {string} storeId + * @param {ListUsersRequest} body + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + async listUsers(storeId: string, body: ListUsersRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers(storeId, body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); + }, /** * The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn\'t guarantee order by any field. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). * @summary Get tuples from the store that matches a query, without following userset rewrite rules + * @param {string} storeId * @param {ReadRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async read(body: ReadRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.read(body, options); + async read(storeId: string, body: ReadRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.read(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Read assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {*} [options] Override http request option. * @throws { FgaError } */ - async readAssertions(authorizationModelId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.readAssertions(authorizationModelId, options); + async readAssertions(storeId: string, authorizationModelId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.readAssertions(storeId, authorizationModelId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). * @summary Return a particular version of an authorization model + * @param {string} storeId * @param {string} id * @param {*} [options] Override http request option. * @throws { FgaError } */ - async readAuthorizationModel(id: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModel(id, options); + async readAuthorizationModel(storeId: string, id: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModel(storeId, id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA\'s response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store\'s authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` * @summary Return all the authorization models for a particular store + * @param {string} storeId * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - async readAuthorizationModels(pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModels(pageSize, continuationToken, options); + async readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModels(storeId, pageSize, continuationToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. * @summary Return a list of all the tuple changes + * @param {string} storeId * @param {string} [type] * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - async readChanges(type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.readChanges(type, pageSize, continuationToken, options); + async readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.readChanges(storeId, type, pageSize, continuationToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** - * The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` + * The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` * @summary Add or delete tuples from the store + * @param {string} storeId * @param {WriteRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async write(body: WriteRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.write(body, options); + async write(storeId: string, body: WriteRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.write(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Upsert assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {WriteAssertionsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async writeAssertions(authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.writeAssertions(authorizationModelId, body, options); + async writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.writeAssertions(storeId, authorizationModelId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, /** * The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model\'s ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA\'s response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` * @summary Create a new authorization model + * @param {string} storeId * @param {WriteAuthorizationModelRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - async writeAuthorizationModel(body: WriteAuthorizationModelRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = await localVarAxiosParamCreator.writeAuthorizationModel(body, options); + async writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = await localVarAxiosParamCreator.writeAuthorizationModel(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); }, }; @@ -864,14 +950,15 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent const localVarFp = OpenFgaApiFp(configuration, credentials); return { /** - * The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn\'t. + * The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with exclusion in the model A Check for a userset can yield results that must be treated carefully if the model involves exclusion. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. * @summary Check whether a user is authorized to access an object + * @param {string} storeId * @param {CheckRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - check(body: CheckRequest, options?: any): PromiseResult { - return localVarFp.check(body, options).then((request) => request(axios)); + check(storeId: string, body: CheckRequest, options?: any): PromiseResult { + return localVarFp.check(storeId, body, options).then((request) => request(axios)); }, /** * Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. @@ -886,40 +973,44 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent /** * Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. * @summary Delete a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - deleteStore(options?: any): PromiseResult { - return localVarFp.deleteStore(options).then((request) => request(axios)); + deleteStore(storeId: string, options?: any): PromiseResult { + return localVarFp.deleteStore(storeId, options).then((request) => request(axios)); }, /** * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. * @summary Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + * @param {string} storeId * @param {ExpandRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - expand(body: ExpandRequest, options?: any): PromiseResult { - return localVarFp.expand(body, options).then((request) => request(axios)); + expand(storeId: string, body: ExpandRequest, options?: any): PromiseResult { + return localVarFp.expand(storeId, body, options).then((request) => request(axios)); }, /** * Returns an OpenFGA store by its identifier * @summary Get a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } */ - getStore(options?: any): PromiseResult { - return localVarFp.getStore(options).then((request) => request(axios)); + getStore(storeId: string, options?: any): PromiseResult { + return localVarFp.getStore(storeId, options).then((request) => request(axios)); }, /** - * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. + * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. * @summary List all objects of the given type that the user has a relation with + * @param {string} storeId * @param {ListObjectsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - listObjects(body: ListObjectsRequest, options?: any): PromiseResult { - return localVarFp.listObjects(body, options).then((request) => request(axios)); + listObjects(storeId: string, body: ListObjectsRequest, options?: any): PromiseResult { + return localVarFp.listObjects(storeId, body, options).then((request) => request(axios)); }, /** * Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores. @@ -932,89 +1023,108 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent listStores(pageSize?: number, continuationToken?: string, options?: any): PromiseResult { return localVarFp.listStores(pageSize, continuationToken, options).then((request) => request(axios)); }, + /** + * + * @summary List all users of the given type that the object has a relation with + * @param {string} storeId + * @param {ListUsersRequest} body + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + listUsers(storeId: string, body: ListUsersRequest, options?: any): PromiseResult { + return localVarFp.listUsers(storeId, body, options).then((request) => request(axios)); + }, /** * The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn\'t guarantee order by any field. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). * @summary Get tuples from the store that matches a query, without following userset rewrite rules + * @param {string} storeId * @param {ReadRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - read(body: ReadRequest, options?: any): PromiseResult { - return localVarFp.read(body, options).then((request) => request(axios)); + read(storeId: string, body: ReadRequest, options?: any): PromiseResult { + return localVarFp.read(storeId, body, options).then((request) => request(axios)); }, /** * The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Read assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAssertions(authorizationModelId: string, options?: any): PromiseResult { - return localVarFp.readAssertions(authorizationModelId, options).then((request) => request(axios)); + readAssertions(storeId: string, authorizationModelId: string, options?: any): PromiseResult { + return localVarFp.readAssertions(storeId, authorizationModelId, options).then((request) => request(axios)); }, /** * The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). * @summary Return a particular version of an authorization model + * @param {string} storeId * @param {string} id * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAuthorizationModel(id: string, options?: any): PromiseResult { - return localVarFp.readAuthorizationModel(id, options).then((request) => request(axios)); + readAuthorizationModel(storeId: string, id: string, options?: any): PromiseResult { + return localVarFp.readAuthorizationModel(storeId, id, options).then((request) => request(axios)); }, /** * The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA\'s response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store\'s authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` * @summary Return all the authorization models for a particular store + * @param {string} storeId * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - readAuthorizationModels(pageSize?: number, continuationToken?: string, options?: any): PromiseResult { - return localVarFp.readAuthorizationModels(pageSize, continuationToken, options).then((request) => request(axios)); + readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): PromiseResult { + return localVarFp.readAuthorizationModels(storeId, pageSize, continuationToken, options).then((request) => request(axios)); }, /** * The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. * @summary Return a list of all the tuple changes + * @param {string} storeId * @param {string} [type] * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } */ - readChanges(type?: string, pageSize?: number, continuationToken?: string, options?: any): PromiseResult { - return localVarFp.readChanges(type, pageSize, continuationToken, options).then((request) => request(axios)); + readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): PromiseResult { + return localVarFp.readChanges(storeId, type, pageSize, continuationToken, options).then((request) => request(axios)); }, /** - * The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` + * The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` * @summary Add or delete tuples from the store + * @param {string} storeId * @param {WriteRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - write(body: WriteRequest, options?: any): PromiseResult { - return localVarFp.write(body, options).then((request) => request(axios)); + write(storeId: string, body: WriteRequest, options?: any): PromiseResult { + return localVarFp.write(storeId, body, options).then((request) => request(axios)); }, /** * The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Upsert assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {WriteAssertionsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - writeAssertions(authorizationModelId: string, body: WriteAssertionsRequest, options?: any): PromiseResult { - return localVarFp.writeAssertions(authorizationModelId, body, options).then((request) => request(axios)); + writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): PromiseResult { + return localVarFp.writeAssertions(storeId, authorizationModelId, body, options).then((request) => request(axios)); }, /** * The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model\'s ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA\'s response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` * @summary Create a new authorization model + * @param {string} storeId * @param {WriteAuthorizationModelRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } */ - writeAuthorizationModel(body: WriteAuthorizationModelRequest, options?: any): PromiseResult { - return localVarFp.writeAuthorizationModel(body, options).then((request) => request(axios)); + writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): PromiseResult { + return localVarFp.writeAuthorizationModel(storeId, body, options).then((request) => request(axios)); }, }; }; @@ -1027,15 +1137,16 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent */ export class OpenFgaApi extends BaseAPI { /** - * The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn\'t. + * The Check API returns whether a given user has a relationship with a given object in a given store. The `user` field of the request can be a specific target, such as `user:anne`, or a userset (set of users) such as `group:marketing#member` or a type-bound public access `user:*`. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. Each of these tuples may have an associated `condition`. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will return whether the relationship exists in the field `allowed`. Some exceptions apply, but in general, if a Check API responds with `{allowed: true}`, then you can expect the equivalent ListObjects query to return the object, and viceversa. For example, if `Check(user:anne, reader, document:2021-budget)` responds with `{allowed: true}`, then `ListObjects(user:anne, reader, document)` may include `document:2021-budget` in the response. ## Examples ### Querying with contextual tuples In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Querying usersets Some Checks will always return `true`, even without any tuples. For example, for the following authorization model ```python model schema 1.1 type user type document relations define reader: [user] ``` the following query ```json { \"tuple_key\": { \"user\": \"document:2021-budget#reader\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } } ``` will always return `{ \"allowed\": true }`. This is because usersets are self-defining: the userset `document:2021-budget#reader` will always have the `reader` relation with `document:2021-budget`. ### Querying usersets with exclusion in the model A Check for a userset can yield results that must be treated carefully if the model involves exclusion. For example, for the following authorization model ```python model schema 1.1 type user type group relations define member: [user] type document relations define blocked: [user] define reader: [group#member] but not blocked ``` the following query ```json { \"tuple_key\": { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"group:finance\" }, { \"user\": \"group:finance#member\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, { \"user\": \"user:anne\", \"relation\": \"blocked\", \"object\": \"document:2021-budget\" } ] }, } ``` will return `{ \"allowed\": true }`, even though a specific user of the userset `group:finance#member` does not have the `reader` relationship with the given object. * @summary Check whether a user is authorized to access an object + * @param {string} storeId * @param {CheckRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public check(body: CheckRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).check(body, options).then((request) => request(this.axios)); + public check(storeId: string, body: CheckRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).check(storeId, body, options).then((request) => request(this.axios)); } /** @@ -1046,54 +1157,58 @@ export class OpenFgaApi extends BaseAPI { * @throws { FgaError } * @memberof OpenFgaApi */ - public createStore(body: CreateStoreRequest, options?: any) { + public createStore(body: CreateStoreRequest, options?: any): Promise> { return OpenFgaApiFp(this.configuration, this.credentials).createStore(body, options).then((request) => request(this.axios)); } /** * Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. * @summary Delete a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public deleteStore(options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).deleteStore(options).then((request) => request(this.axios)); + public deleteStore(storeId: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).deleteStore(storeId, options).then((request) => request(this.axios)); } /** * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. * @summary Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + * @param {string} storeId * @param {ExpandRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public expand(body: ExpandRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).expand(body, options).then((request) => request(this.axios)); + public expand(storeId: string, body: ExpandRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).expand(storeId, body, options).then((request) => request(this.axios)); } /** * Returns an OpenFGA store by its identifier * @summary Get a store + * @param {string} storeId * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public getStore(options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).getStore(options).then((request) => request(this.axios)); + public getStore(storeId: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).getStore(storeId, options).then((request) => request(this.axios)); } /** - * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. + * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To arrive at a result, the API uses: an authorization model, explicit tuples written through the Write API, contextual tuples present in the request, and implicit tuples that exist by virtue of applying set theory (such as `document:2021-budget#viewer@document:2021-budget#viewer`; the set of users who are viewers of `document:2021-budget` are the set of users who are the viewers of `document:2021-budget`). An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. Each of these tuples may have an associated `condition`. You may also provide a `context` object that will be used to evaluate the conditioned tuples in the system. It is strongly recommended to provide a value for all the input parameters of all the conditions, to ensure that all tuples be evaluated correctly. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `:` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. The objects given will not be sorted, and therefore two identical calls can give a given different set of objects. * @summary List all objects of the given type that the user has a relation with + * @param {string} storeId * @param {ListObjectsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public listObjects(body: ListObjectsRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).listObjects(body, options).then((request) => request(this.axios)); + public listObjects(storeId: string, body: ListObjectsRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).listObjects(storeId, body, options).then((request) => request(this.axios)); } /** @@ -1105,62 +1220,80 @@ export class OpenFgaApi extends BaseAPI { * @throws { FgaError } * @memberof OpenFgaApi */ - public listStores(pageSize?: number, continuationToken?: string, options?: any) { + public listStores(pageSize?: number, continuationToken?: string, options?: any): Promise> { return OpenFgaApiFp(this.configuration, this.credentials).listStores(pageSize, continuationToken, options).then((request) => request(this.axios)); } + /** + * + * @summary List all users of the given type that the object has a relation with + * @param {string} storeId + * @param {ListUsersRequest} body + * @param {*} [options] Override http request option. + * @throws { FgaError } + * @memberof OpenFgaApi + */ + public listUsers(storeId: string, body: ListUsersRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).listUsers(storeId, body, options).then((request) => request(this.axios)); + } + /** * The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn\'t guarantee order by any field. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). * @summary Get tuples from the store that matches a query, without following userset rewrite rules + * @param {string} storeId * @param {ReadRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public read(body: ReadRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).read(body, options).then((request) => request(this.axios)); + public read(storeId: string, body: ReadRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).read(storeId, body, options).then((request) => request(this.axios)); } /** * The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Read assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public readAssertions(authorizationModelId: string, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).readAssertions(authorizationModelId, options).then((request) => request(this.axios)); + public readAssertions(storeId: string, authorizationModelId: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).readAssertions(storeId, authorizationModelId, options).then((request) => request(this.axios)); } /** * The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). * @summary Return a particular version of an authorization model + * @param {string} storeId * @param {string} id * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public readAuthorizationModel(id: string, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).readAuthorizationModel(id, options).then((request) => request(this.axios)); + public readAuthorizationModel(storeId: string, id: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).readAuthorizationModel(storeId, id, options).then((request) => request(this.axios)); } /** * The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA\'s response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store\'s authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` * @summary Return all the authorization models for a particular store + * @param {string} storeId * @param {number} [pageSize] * @param {string} [continuationToken] * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public readAuthorizationModels(pageSize?: number, continuationToken?: string, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).readAuthorizationModels(pageSize, continuationToken, options).then((request) => request(this.axios)); + public readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).readAuthorizationModels(storeId, pageSize, continuationToken, options).then((request) => request(this.axios)); } /** * The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. When reading a write tuple change, if it was conditioned, the condition will be returned. When reading a delete tuple change, the condition will NOT be returned regardless of whether it was originally conditioned or not. * @summary Return a list of all the tuple changes + * @param {string} storeId * @param {string} [type] * @param {number} [pageSize] * @param {string} [continuationToken] @@ -1168,45 +1301,48 @@ export class OpenFgaApi extends BaseAPI { * @throws { FgaError } * @memberof OpenFgaApi */ - public readChanges(type?: string, pageSize?: number, continuationToken?: string, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).readChanges(type, pageSize, continuationToken, options).then((request) => request(this.axios)); + public readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).readChanges(storeId, type, pageSize, continuationToken, options).then((request) => request(this.axios)); } /** - * The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` + * The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` * @summary Add or delete tuples from the store + * @param {string} storeId * @param {WriteRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public write(body: WriteRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).write(body, options).then((request) => request(this.axios)); + public write(storeId: string, body: WriteRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).write(storeId, body, options).then((request) => request(this.axios)); } /** * The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * @summary Upsert assertions for an authorization model ID + * @param {string} storeId * @param {string} authorizationModelId * @param {WriteAssertionsRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public writeAssertions(authorizationModelId: string, body: WriteAssertionsRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).writeAssertions(authorizationModelId, body, options).then((request) => request(this.axios)); + public writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).writeAssertions(storeId, authorizationModelId, body, options).then((request) => request(this.axios)); } /** * The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model\'s ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA\'s response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` * @summary Create a new authorization model + * @param {string} storeId * @param {WriteAuthorizationModelRequest} body * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public writeAuthorizationModel(body: WriteAuthorizationModelRequest, options?: any) { - return OpenFgaApiFp(this.configuration, this.credentials).writeAuthorizationModel(body, options).then((request) => request(this.axios)); + public writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).writeAuthorizationModel(storeId, body, options).then((request) => request(this.axios)); } } diff --git a/apiModel.ts b/apiModel.ts index eb19b5d..e147046 100644 --- a/apiModel.ts +++ b/apiModel.ts @@ -471,6 +471,25 @@ export interface ExpandResponse { */ tree?: UsersetTree; } +/** + * Object represents an OpenFGA Object. An Object is composed of a type and identifier (e.g. \'document:1\') See https://openfga.dev/docs/concepts#what-is-an-object + * @export + * @interface FgaObject + */ +export interface FgaObject { + /** + * + * @type {string} + * @memberof FgaObject + */ + type: string; + /** + * + * @type {string} + * @memberof FgaObject + */ + id: string; +} /** * * @export @@ -649,6 +668,68 @@ export interface ListStoresResponse { */ continuation_token: string; } +/** + * + * @export + * @interface ListUsersRequest + */ +export interface ListUsersRequest { + /** + * + * @type {string} + * @memberof ListUsersRequest + */ + authorization_model_id?: string; + /** + * + * @type {FgaObject} + * @memberof ListUsersRequest + */ + object: FgaObject; + /** + * + * @type {string} + * @memberof ListUsersRequest + */ + relation: string; + /** + * + * @type {Array} + * @memberof ListUsersRequest + */ + user_filters: Array; + /** + * + * @type {Array} + * @memberof ListUsersRequest + */ + contextual_tuples?: Array; + /** + * Additional request context that will be used to evaluate any ABAC conditions encountered in the query evaluation. + * @type {object} + * @memberof ListUsersRequest + */ + context?: object; +} +/** + * + * @export + * @interface ListUsersResponse + */ +export interface ListUsersResponse { + /** + * + * @type {Array} + * @memberof ListUsersResponse + */ + users: Array; + /** + * + * @type {Array} + * @memberof ListUsersResponse + */ + excluded_users: Array; +} /** * * @export @@ -747,6 +828,25 @@ export enum NullValue { NullValue = 'NULL_VALUE' } +/** + * + * @export + * @interface ObjectOrUserset + */ +export interface ObjectOrUserset { + /** + * + * @type {FgaObject} + * @memberof ObjectOrUserset + */ + object?: FgaObject; + /** + * + * @type {UsersetUser} + * @memberof ObjectOrUserset + */ + userset?: UsersetUser; +} /** * * @export @@ -1254,6 +1354,95 @@ export enum TypeName { Ipaddress = 'TYPE_NAME_IPADDRESS' } +/** + * + * @export + * @interface TypedWildcard + */ +export interface TypedWildcard { + /** + * + * @type {string} + * @memberof TypedWildcard + */ + type: string; +} +/** + * + * @export + * @enum {string} + */ + +export enum UnprocessableContentErrorCode { + NoThrottledErrorCode = 'no_throttled_error_code', + ThrottledTimeoutError = 'throttled_timeout_error' +} + +/** + * + * @export + * @interface UnprocessableContentMessageResponse + */ +export interface UnprocessableContentMessageResponse { + /** + * + * @type {UnprocessableContentErrorCode} + * @memberof UnprocessableContentMessageResponse + */ + code?: UnprocessableContentErrorCode; + /** + * + * @type {string} + * @memberof UnprocessableContentMessageResponse + */ + message?: string; +} + + +/** + * + * @export + * @interface User + */ +export interface User { + /** + * + * @type {FgaObject} + * @memberof User + */ + object?: FgaObject; + /** + * + * @type {UsersetUser} + * @memberof User + */ + userset?: UsersetUser; + /** + * + * @type {TypedWildcard} + * @memberof User + */ + wildcard?: TypedWildcard; +} +/** + * + * @export + * @interface UserTypeFilter + */ +export interface UserTypeFilter { + /** + * + * @type {string} + * @memberof UserTypeFilter + */ + type: string; + /** + * + * @type {string} + * @memberof UserTypeFilter + */ + relation?: string; +} /** * * @export @@ -1361,6 +1550,31 @@ export interface UsersetTreeTupleToUserset { */ computed: Array; } +/** + * + * @export + * @interface UsersetUser + */ +export interface UsersetUser { + /** + * + * @type {string} + * @memberof UsersetUser + */ + type: string; + /** + * + * @type {string} + * @memberof UsersetUser + */ + id: string; + /** + * + * @type {string} + * @memberof UsersetUser + */ + relation: string; +} /** * * @export diff --git a/base.ts b/base.ts index 78d1fbc..586a620 100644 --- a/base.ts +++ b/base.ts @@ -61,12 +61,4 @@ export class BaseAPI { }); } } - - public get storeId() { - return this.configuration.storeId; - } - - public set storeId(storeId: string) { - this.configuration.storeId = storeId; - } } diff --git a/client.ts b/client.ts index 62247ef..9d2ca5a 100644 --- a/client.ts +++ b/client.ts @@ -28,6 +28,8 @@ import { ListObjectsRequest, ListObjectsResponse, ListStoresResponse, + ListUsersRequest, + ListUsersResponse, ReadAssertionsResponse, ReadAuthorizationModelResponse, ReadAuthorizationModelsResponse, @@ -52,10 +54,46 @@ import { } from "./utils"; import { isWellFormedUlidString } from "./validation"; -export type ClientConfiguration = (UserConfigurationParams | Configuration) & { +export type UserClientConfigurationParams = UserConfigurationParams & { + storeId?: string; authorizationModelId?: string; } +export class ClientConfiguration extends Configuration { + /** + * provide storeId + * + * @type {string} + * @memberof ClientConfiguration + */ + storeId?: string; + /** + * provide authorizationModelId + * + * @type {string} + * @memberof ClientConfiguration + */ + authorizationModelId?: string; + + constructor(params: UserClientConfigurationParams = {} as unknown as UserConfigurationParams) { + super(params); + this.storeId = params.storeId!; + this.authorizationModelId = params.authorizationModelId!; + } + + public isValid(): boolean { + super.isValid(); + if (this.storeId && !isWellFormedUlidString(this.storeId)) { + throw new FgaValidationError("storeId", "storeId must be in ULID format"); + } + if (this.authorizationModelId && !isWellFormedUlidString(this.authorizationModelId)) { + throw new FgaValidationError("authorizationModelId", "authorizationModelId must be in ULID format"); + } + + return true; + } +} + const DEFAULT_MAX_METHOD_PARALLEL_REQS = 10; const CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method"; const CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id"; @@ -65,11 +103,16 @@ export interface ClientRequestOpts { headers?: Record; } +export interface StoreIdOpts { + storeId?: string; +} + export interface AuthorizationModelIdOpts { authorizationModelId?: string; } -export type ClientRequestOptsWithAuthZModelId = ClientRequestOpts & AuthorizationModelIdOpts; +export type ClientRequestOptsWithStoreId = ClientRequestOpts & StoreIdOpts; +export type ClientRequestOptsWithAuthZModelId = ClientRequestOpts & StoreIdOpts & AuthorizationModelIdOpts; export type PaginationOptions = { pageSize?: number, continuationToken?: string; }; @@ -139,6 +182,9 @@ export type ClientReadRequest = ReadRequestTupleKey; export type ClientListObjectsRequest = Omit & { contextualTuples?: Array }; +export type ClientListUsersRequest = Omit & { + contextualTuples?: Array +}; export type ClientListRelationsRequest = Omit & { relations?: string[], }; @@ -147,20 +193,42 @@ export type ClientWriteAssertionsRequest = (CheckRequestTupleKey & Pick { - return this.api.getStore(options); + async getStore(options: ClientRequestOptsWithStoreId = {}): PromiseResult { + return this.api.getStore(this.getStoreId(options)!, options); } /** * DeleteStore - Delete a store - * @param {ClientRequestOpts} [options] + * @param {ClientRequestOptsWithStoreId} [options] * @param {object} [options.headers] - Custom headers to send alongside the request * @param {object} [options.retryParams] - Override the retry parameters for this request * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ - async deleteStore(options: ClientRequestOpts = {}): PromiseResult { - return this.api.deleteStore(options); + async deleteStore(options: ClientRequestOptsWithStoreId = {}): PromiseResult { + return this.api.deleteStore(this.getStoreId(options)!, options); } /************************ @@ -256,21 +324,21 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ - async readAuthorizationModels(options: ClientRequestOpts & PaginationOptions = {}): PromiseResult { - return this.api.readAuthorizationModels(options.pageSize, options.continuationToken, options); + async readAuthorizationModels(options: ClientRequestOptsWithStoreId & PaginationOptions = {}): PromiseResult { + return this.api.readAuthorizationModels(this.getStoreId(options)!, options.pageSize, options.continuationToken, options); } /** * WriteAuthorizationModel - Create a new version of the authorization model * @param {WriteAuthorizationModelRequest} body - * @param {ClientRequestOpts} [options] + * @param {ClientRequestOptsWithStoreId} [options] * @param {object} [options.headers] - Custom headers to send alongside the request * @param {object} [options.retryParams] - Override the retry parameters for this request * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ - async writeAuthorizationModel(body: WriteAuthorizationModelRequest, options: ClientRequestOpts = {}): PromiseResult { - return this.api.writeAuthorizationModel(body, options); + async writeAuthorizationModel(body: WriteAuthorizationModelRequest, options: ClientRequestOptsWithStoreId = {}): PromiseResult { + return this.api.writeAuthorizationModel(this.getStoreId(options)!, body, options); } /** @@ -287,7 +355,7 @@ export class OpenFgaClient extends BaseAPI { if (!authorizationModelId) { throw new FgaRequiredParamError("ClientConfiguration", "authorizationModelId"); } - return this.api.readAuthorizationModel(authorizationModelId, options); + return this.api.readAuthorizationModel(this.getStoreId(options)!, authorizationModelId, options); } /** @@ -323,8 +391,8 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ - async readChanges(body?: ClientReadChangesRequest, options: ClientRequestOpts & PaginationOptions = {}): PromiseResult { - return this.api.readChanges(body?.type, options.pageSize, options.continuationToken, options); + async readChanges(body?: ClientReadChangesRequest, options: ClientRequestOptsWithStoreId & PaginationOptions = {}): PromiseResult { + return this.api.readChanges(this.getStoreId(options)!, body?.type, options.pageSize, options.continuationToken, options); } /** @@ -338,7 +406,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ - async read(body: ClientReadRequest = {}, options: ClientRequestOpts & PaginationOptions = {}): PromiseResult { + async read(body: ClientReadRequest = {}, options: ClientRequestOptsWithStoreId & PaginationOptions = {}): PromiseResult { const readRequest: ReadRequest = { page_size: options.pageSize, continuation_token: options.continuationToken, @@ -346,7 +414,7 @@ export class OpenFgaClient extends BaseAPI { if (body.user || body.object || body.relation) { readRequest.tuple_key = body; } - return this.api.read(readRequest, options); + return this.api.read(this.getStoreId(options)!, readRequest, options); } /** @@ -382,7 +450,7 @@ export class OpenFgaClient extends BaseAPI { if (deletes?.length) { apiBody.deletes = { tuple_keys: deletes }; } - await this.api.write(apiBody, options); + await this.api.write(this.getStoreId(options)!, apiBody, options); return { writes: writes?.map(tuple => ({ tuple_key: tuple, @@ -496,7 +564,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ async check(body: ClientCheckRequest, options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { - return this.api.check({ + return this.api.check(this.getStoreId(options)!, { tuple_key: { user: body.user, relation: body.relation, @@ -561,7 +629,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ async expand(body: ClientExpandRequest, options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { - return this.api.expand({ + return this.api.expand(this.getStoreId(options)!, { authorization_model_id: this.getAuthorizationModelId(options), tuple_key: body, }, options); @@ -578,7 +646,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ async listObjects(body: ClientListObjectsRequest, options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { - return this.api.listObjects({ + return this.api.listObjects(this.getStoreId(options)!, { authorization_model_id: this.getAuthorizationModelId(options), user: body.user, relation: body.relation, @@ -624,6 +692,27 @@ export class OpenFgaClient extends BaseAPI { return { relations: batchCheckResults.responses.filter(result => result.allowed).map(result => result._request.relation) }; } + /** + * ListUsers - List the objects of a particular type that the user has a certain relation to (evaluates) + * @param {ClientListUsersRequest} body + * @param {ClientRequestOptsWithAuthZModelId} [options] + * @param {string} [options.authorizationModelId] - Overrides the authorization model id in the configuration + * @param {object} [options.headers] - Custom headers to send alongside the request + * @param {object} [options.retryParams] - Override the retry parameters for this request + * @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request + * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated + */ + async listUsers(body: ClientListUsersRequest, options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { + return this.api.listUsers(this.getStoreId(options)!, { + authorization_model_id: this.getAuthorizationModelId(options), + relation: body.relation, + object: body.object, + user_filters: body.user_filters, + context: body.context, + contextual_tuples: body.contextualTuples || [], + }, options); + } + /************** * Assertions * **************/ @@ -638,7 +727,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ async readAssertions(options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { - return this.api.readAssertions(this.getAuthorizationModelId(options)!, options); + return this.api.readAssertions(this.getStoreId(options)!, this.getAuthorizationModelId(options)!, options); } /** @@ -652,7 +741,7 @@ export class OpenFgaClient extends BaseAPI { * @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated */ async writeAssertions(assertions: ClientWriteAssertionsRequest, options: ClientRequestOptsWithAuthZModelId = {}): PromiseResult { - return this.api.writeAssertions(this.getAuthorizationModelId(options)!, { + return this.api.writeAssertions(this.getStoreId(options)!, this.getAuthorizationModelId(options)!, { assertions: assertions.map(assertion => ({ tuple_key: { user: assertion.user, diff --git a/configuration.ts b/configuration.ts index 85408ff..af5e3b6 100644 --- a/configuration.ts +++ b/configuration.ts @@ -21,7 +21,7 @@ const DEFAULT_MAX_RETRY = 15; // default minimum wait period in retry - but will backoff exponentially const DEFAULT_MIN_WAIT_MS = 100; -const DEFAULT_USER_AGENT = "openfga-sdk js/0.3.5"; +const DEFAULT_USER_AGENT = "openfga-sdk js/0.4.0"; export interface RetryParams { maxRetry?: number; @@ -38,7 +38,6 @@ export interface UserConfigurationParams { * @deprecated Replace usage of `apiScheme` + `apiHost` with `apiUrl` */ apiHost?: string; - storeId?: string; credentials?: CredentialsConfig; baseOptions?: any; retryParams?: RetryParams; @@ -74,7 +73,7 @@ export class Configuration { * @type {string} * @memberof Configuration */ - private static sdkVersion = "0.3.5"; + private static sdkVersion = "0.4.0"; /** * provide the full api URL (e.g. `https://api.fga.example`) @@ -100,13 +99,6 @@ export class Configuration { * @deprecated */ apiHost: string; - /** - * provide storeId - * - * @type {string} - * @memberof Configuration - */ - storeId: string; /** * base options for axios calls * @@ -133,7 +125,6 @@ export class Configuration { this.apiScheme = params.apiScheme || this.apiScheme; this.apiHost = params.apiHost!; this.apiUrl = params.apiUrl!; - this.storeId = params.storeId!; const credentialParams = params.credentials; @@ -193,15 +184,11 @@ export class Configuration { if (!isWellFormedUriString(this.getBasePath())) { throw new FgaValidationError( this.apiUrl ? - `Configuration.apiUrl (${this.apiUrl}) is not a valid URI (${this.getBasePath()})` : + `Configuration.apiUrl (${this.apiUrl}) is not a valid URI (${this.getBasePath()})` : `Configuration.apiScheme (${this.apiScheme}) and Configuration.apiHost (${this.apiHost}) do not form a valid URI (${this.getBasePath()})` ); } - if (this.storeId && !isWellFormedUlidString(this.storeId)) { - throw new FgaValidationError("storeId"); - } - if (this.retryParams?.maxRetry && this.retryParams.maxRetry > 15) { throw new FgaValidationError("Configuration.retryParams.maxRetry exceeds maximum allowed limit of 15"); } diff --git a/example/README.md b/example/README.md index 924be45..829bf65 100644 --- a/example/README.md +++ b/example/README.md @@ -28,7 +28,7 @@ Steps 2. In the Example `package.json` change the `@openfga/sdk` dependency from a semver range like below ```json "dependencies": { - "@openfga/sdk": "^0.3.5" + "@openfga/sdk": "^0.4.0" } ``` to a `file:` reference like below diff --git a/example/example1/example1.mjs b/example/example1/example1.mjs index 808a7f8..52b4898 100644 --- a/example/example1/example1.mjs +++ b/example/example1/example1.mjs @@ -17,7 +17,7 @@ async function main () { 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, + authorizationModelId: process.env.FGA_MODEL_ID, // optional, recommended for production, credentials }); diff --git a/example/example1/package.json b/example/example1/package.json index 1f36dd5..4c75681 100644 --- a/example/example1/package.json +++ b/example/example1/package.json @@ -9,7 +9,7 @@ "start": "node example1.mjs" }, "dependencies": { - "@openfga/sdk": "^0.3.5" + "@openfga/sdk": "^0.4.0" }, "engines": { "node": ">=16.13.0" diff --git a/package-lock.json b/package-lock.json index c8d0819..fe395c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,28 @@ { "name": "@openfga/sdk", - "version": "0.3.5", + "version": "0.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@openfga/sdk", - "version": "0.3.5", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { - "axios": "^1.6.7", + "axios": "^1.6.8", "tiny-async-pool": "^2.1.0" }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.11.28", + "@types/node": "^20.12.7", "@types/tiny-async-pool": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", "jest": "^29.7.0", "nock": "^13.5.4", "ts-jest": "^29.1.2", - "typescript": "^5.4.2" + "typescript": "^5.4.5" }, "engines": { "node": ">=14.17.0" @@ -827,9 +827,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -5649,9 +5649,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "@istanbuljs/load-nyc-config": { diff --git a/package.json b/package.json index 5763601..14e3e66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openfga/sdk", - "version": "0.3.5", + "version": "0.4.0", "description": "JavaScript and Node.js SDK for OpenFGA", "author": "OpenFGA", "keywords": [ @@ -22,20 +22,20 @@ "lint:fix": "eslint . --ext .ts --fix" }, "dependencies": { - "axios": "^1.6.7", + "axios": "^1.6.8", "tiny-async-pool": "^2.1.0" }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.11.28", + "@types/node": "^20.12.7", "@types/tiny-async-pool": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", "jest": "^29.7.0", "nock": "^13.5.4", "ts-jest": "^29.1.2", - "typescript": "^5.4.2" + "typescript": "^5.4.5" }, "files": [ "CHANGELOG.md", diff --git a/tests/client.test.ts b/tests/client.test.ts index e701281..ddd9abc 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -19,7 +19,8 @@ import { FgaApiError, FgaApiAuthenticationError, FgaValidationError, - OpenFgaClient + OpenFgaClient, + ListUsersResponse, } from "../index"; import { baseConfig, defaultConfiguration, getNocks } from "./helpers"; @@ -39,10 +40,55 @@ describe("OpenFGA Client", () => { nock.cleanAll(); }); + describe("Configuration", () => { + it("should throw an error if the storeId is not in a valid format", async () => { + expect( + () => new OpenFgaClient({ ...baseConfig, storeId: "abcsa"! }) + ).toThrowError(FgaValidationError); + }); + + it("should require storeId when calling endpoints that require it", () => { + const fgaClient = new OpenFgaClient({ ...baseConfig, storeId: undefined!, credentials: undefined! }); + expect( + fgaClient.readAuthorizationModels() + ).rejects.toThrow(); + }); + + it("should accept the store and model IDs on initialization", async () => { + const fgaClient = new OpenFgaClient({ + apiUrl: defaultConfiguration.apiUrl, + storeId: defaultConfiguration.storeId, + authorizationModelId: defaultConfiguration.authorizationModelId, + }); + expect(fgaClient.storeId).toBe(defaultConfiguration.storeId); + expect(fgaClient.authorizationModelId).toBe(defaultConfiguration.authorizationModelId); + }); + + it("should allow updating the storeId after initialization", async () => { + const fgaClient = new OpenFgaClient({ + apiUrl: defaultConfiguration.apiUrl + }); + expect(fgaClient.storeId).toBe(undefined); + fgaClient.storeId = defaultConfiguration.storeId; + expect(fgaClient.storeId).toBe(defaultConfiguration.storeId); + }); + + it("should allow updating the authorizationModelId after initialization", async () => { + const fgaClient = new OpenFgaClient({ + apiUrl: defaultConfiguration.apiUrl, + storeId: defaultConfiguration.storeId, + }); + expect(fgaClient.authorizationModelId).toBe(undefined); + fgaClient.authorizationModelId = defaultConfiguration.authorizationModelId; + expect(fgaClient.authorizationModelId).toBe(defaultConfiguration.authorizationModelId); + }); + }); + + /* Stores */ describe("ListStores", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the ListStores API", async () => { const store = { id: "some-id", name: "some-name" }; const scope = nocks.listStores(defaultConfiguration.getBasePath(), { continuation_token: "", @@ -81,8 +127,8 @@ describe("OpenFGA Client", () => { }); describe("GetStore", () => { - it("should properly call the OpenFga API", async () => { - const store = { id: defaultConfiguration.storeId, name: "some-name" }; + it("should properly call the GetStore API", async () => { + const store = { id: defaultConfiguration.storeId!, name: "some-name" }; const scope = nocks.getStore(store.id, defaultConfiguration.getBasePath(), { ...store, created_at: "2023-11-02T15:27:47.951Z", @@ -95,10 +141,29 @@ describe("OpenFGA Client", () => { expect(scope.isDone()).toBe(true); expect(response).toMatchObject(store); }); + + + + it("should allow overriding the store ID", async () => { + const overriddenStoreId = "01HWD53SDGYRXHBXTYA10PF6T4"; + + const store = { id: overriddenStoreId, name: "some-name" }; + const scope = nocks.getStore(store.id, defaultConfiguration.getBasePath(), { + ...store, + created_at: "2023-11-02T15:27:47.951Z", + updated_at: "2023-11-02T15:27:47.951Z", + }); + + expect(scope.isDone()).toBe(false); + const response = await fgaClient.getStore({ storeId: overriddenStoreId }); + + expect(scope.isDone()).toBe(true); + expect(response).toMatchObject(store); + }); }); describe("DeleteStore", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the DeleteStore API", async () => { const scope = nocks.deleteStore(defaultConfiguration.storeId!); expect(scope.isDone()).toBe(false); @@ -110,7 +175,7 @@ describe("OpenFGA Client", () => { /* Authorization Models */ describe("ReadAuthorizationModels", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the ReadAuthorizationModels API", async () => { const scope = nocks.readAuthorizationModels(defaultConfiguration.storeId!); expect(scope.isDone()).toBe(false); @@ -124,7 +189,7 @@ describe("OpenFGA Client", () => { }); describe("WriteAuthorizationModel", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the WriteAuthorizationModel API", async () => { const authorizationModel = { schema_version: "1.1", type_definitions: [ @@ -147,7 +212,7 @@ describe("OpenFGA Client", () => { }); describe("ReadAuthorizationModel", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the ReadAuthorizationModel API", async () => { const modelId = "01H0THVNGCSAZ6SAQVTHPH3F0Q"; const scope = nocks.readSingleAuthzModel(defaultConfiguration.storeId!, modelId); @@ -166,7 +231,7 @@ describe("OpenFGA Client", () => { }); describe("ReadLatestAuthorizationModel", () => { - it("should properly call the OpenFga API", async () => { + it("should properly call the ReadLatestAuthorizationModel API", async () => { const modelId = "01H0THVNGCSAZ6SAQVTHPH3F0Q"; const scope = nock(defaultConfiguration.getBasePath()) .get(`/stores/${defaultConfiguration.storeId!}/authorization-models`) @@ -191,7 +256,7 @@ describe("OpenFGA Client", () => { /* Relationship Tuples */ describe("ReadChanges", () => { - it("should properly call the OpenFga ReadChanges API", async () => { + it("should properly call the ReadChanges API", async () => { const type = "repo"; const pageSize = 25; const continuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="; @@ -205,7 +270,7 @@ describe("OpenFGA Client", () => { expect(response).toMatchObject({ changes: expect.arrayContaining([]) }); }); - it("should properly call the OpenFga ReadChanges API with no type", async () => { + it("should properly call the ReadChanges API with no type", async () => { const pageSize = 25; const continuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="; @@ -220,7 +285,7 @@ describe("OpenFGA Client", () => { }); describe("Read", () => { - it("should properly call the OpenFga Read API", async () => { + it("should properly call the Read API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -237,7 +302,7 @@ describe("OpenFGA Client", () => { }); describe("Write", () => { - it("should properly call the OpenFga Write API", async () => { + it("should properly call the Write API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -365,7 +430,7 @@ describe("OpenFGA Client", () => { } }); - it("should properly call the OpenFga Write API when providing one empty array", async () => { + it("should properly call the Write API when providing one empty array", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -388,7 +453,7 @@ describe("OpenFGA Client", () => { }); describe("WriteTuples", () => { - it("should properly call the OpenFga Write API", async () => { + it("should properly call the Write API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -407,7 +472,7 @@ describe("OpenFGA Client", () => { }); describe("DeleteTuples", () => { - it("should properly call the OpenFga Write API", async () => { + it("should properly call the Write API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -427,7 +492,7 @@ describe("OpenFGA Client", () => { /* Relationship Queries */ describe("Check", () => { - it("should properly call the OpenFga Check API", async () => { + it("should properly call the Check API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -444,7 +509,7 @@ describe("OpenFGA Client", () => { }); describe("BatchCheck", () => { - it("should properly call the OpenFga Check API", async () => { + it("should properly call the Check API", async () => { const tuples = [{ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -491,7 +556,7 @@ describe("OpenFGA Client", () => { }); describe("Expand", () => { - it("should properly call the OpenFga Expand API", async () => { + it("should properly call the Expand API", async () => { const tuple = { user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", relation: "admin", @@ -697,9 +762,108 @@ describe("OpenFGA Client", () => { }); }); + describe("ListUsers", () => { + it("should call the api and return the response", async () => { + const mockedResponse: ListUsersResponse = { + users: [{ + object: { + type: "user", + id: "81684243-9356-4421-8fbf-a4f8d36aa31b" + }, + }, { + userset: { + type: "team", + id: "engineering", + relation: "member" + }, + }, { + wildcard: { + type: "employee" + } + }], + excluded_users: [{ + object: { + type: "user", + id: "76cebb86-6569-4440-b653-db3525a85831" + }, + }, { + userset: { + type: "team", + id: "marketing", + relation: "member" + }, + }] }; + const scope = nocks.listUsers(baseConfig.storeId!, mockedResponse); + + expect(scope.isDone()).toBe(false); + const response = await fgaClient.listUsers({ + object: { + type: "document", + id: "roadmap" + }, + relation: "can_read", + user_filters: [{ + type: "user" + }, { + type: "team", + relation: "member" + }], + context: {}, + contextualTuples: + [{ + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "editor", + object: "folder:product" + }, { + user: "folder:product", + relation: "parent", + object: "document:roadmap" + }] + }, { + authorizationModelId: "01GAHCE4YVKPQEKZQHT2R89MQV", + }); + + expect(scope.isDone()).toBe(true); + expect(response.users).toHaveLength(mockedResponse.users.length); + expect(response.users[0]).toMatchObject({ + object: { + type: "user", + id: "81684243-9356-4421-8fbf-a4f8d36aa31b" + }, + }); + expect(response.users[1]).toMatchObject({ + userset: { + type: "team", + id: "engineering", + relation: "member" + }, + }); + expect(response.users[2]).toMatchObject({ + wildcard: { + type: "employee" + } + }); + expect(response.excluded_users).toHaveLength(mockedResponse.excluded_users.length); + expect(response.excluded_users[0]).toMatchObject({ + object: { + type: "user", + id: "76cebb86-6569-4440-b653-db3525a85831" + }, + }); + expect(response.excluded_users[1]).toMatchObject({ + userset: { + type: "team", + id: "marketing", + relation: "member" + }, + }); + expect(response).toEqual(mockedResponse); + }); + }); + /* Assertions */ describe("ReadAssertions", () => { - it("should properly call the OpenFga ReadAssertions API", async () => { + it("should properly call the ReadAssertions API", async () => { const modelId = "01H0THVNGCSAZ6SAQVTHPH3F0Q"; const scope = nocks.readAssertions(defaultConfiguration.storeId!, modelId); @@ -715,7 +879,7 @@ describe("OpenFGA Client", () => { }); describe("WriteAssertions", () => { - it("should properly call the OpenFga WriteAssertions API", async () => { + it("should properly call the WriteAssertions API", async () => { const modelId = "01H0THVNGCSAZ6SAQVTHPH3F0Q"; const scope = nocks.writeAssertions(defaultConfiguration.storeId!, modelId); diff --git a/tests/helpers/default-config.ts b/tests/helpers/default-config.ts index 0c8a1ab..61024cf 100644 --- a/tests/helpers/default-config.ts +++ b/tests/helpers/default-config.ts @@ -11,19 +11,21 @@ */ -import { Configuration, UserConfigurationParams } from "../../configuration"; +import { ClientConfiguration, UserClientConfigurationParams } from "../../client"; import { CredentialsMethod } from "../../credentials"; export const OPENFGA_STORE_ID = "01H0H015178Y2V4CX10C2KGHF4"; -export const OPENFGA_API_URL = "http://api.fga.example"; +export const OPENFGA_MODEL_ID = "01HWBBMZTT7F1M97DVXQK4Z7J3"; +export const OPENFGA_API_URL = "https://api.fga.example"; export const OPENFGA_API_TOKEN_ISSUER = "tokenissuer.fga.example"; export const OPENFGA_API_AUDIENCE = "https://api.fga.example/"; export const OPENFGA_CLIENT_ID = "01H0H3D8TD07EWAQHXY9BWJG3V"; export const OPENFGA_CLIENT_SECRET = "this-is-very-secret"; export const OPENFGA_API_TOKEN = "fga_abcdef"; -export const baseConfig: UserConfigurationParams = { +export const baseConfig: UserClientConfigurationParams = { storeId: OPENFGA_STORE_ID, + authorizationModelId: OPENFGA_MODEL_ID, apiUrl: OPENFGA_API_URL, credentials: { method: CredentialsMethod.ClientCredentials, @@ -36,4 +38,4 @@ export const baseConfig: UserConfigurationParams = { } }; -export const defaultConfiguration = new Configuration(baseConfig); +export const defaultConfiguration = new ClientConfiguration(baseConfig); diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index cf6cbe9..d2af65d 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -22,6 +22,7 @@ import { GetStoreResponse, ListObjectsResponse, ListStoresResponse, + ListUsersResponse, ReadAssertionsResponse, ReadAuthorizationModelResponse, ReadAuthorizationModelsResponse, @@ -216,6 +217,11 @@ export const getNocks = ((nock: typeof Nock) => ({ .post(`/stores/${storeId}/list-objects`) .reply(200, responseBody); }, + listUsers: (storeId: string, responseBody: ListUsersResponse, basePath = defaultConfiguration.getBasePath()) => { + return nock(basePath) + .post(`/stores/${storeId}/list-users`) + .reply(200, responseBody); + }, readAssertions: (storeId: string, modelId: string, assertions: ReadAssertionsResponse["assertions"] = [], basePath = defaultConfiguration.getBasePath()) => { return nock(basePath) .get(`/stores/${storeId}/assertions/${modelId}`) diff --git a/tests/index.test.ts b/tests/index.test.ts index 2de1f53..daef01a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -32,9 +32,7 @@ import { AuthCredentialsConfig } from "../credentials"; import { baseConfig, defaultConfiguration, - OPENFGA_API_URL, OPENFGA_API_TOKEN_ISSUER, - OPENFGA_STORE_ID } from "./helpers/default-config"; import { getNocks } from "./helpers/nocks"; @@ -45,23 +43,10 @@ describe("OpenFGA SDK", function () { describe("initializing the sdk", () => { it("should not require storeId in configuration", () => { expect( - () => new OpenFgaApi({ ...baseConfig, storeId: undefined! }) + () => new OpenFgaApi({ ...baseConfig, storeId: undefined } as any) ).not.toThrowError(); }); - it("should throw an error if the storeId is not in a valid format", async () => { - expect( - () => new OpenFgaApi({ ...baseConfig, storeId: "abcsa"! }) - ).toThrowError(FgaValidationError); - }); - - it("should require storeId when calling endpoints that require it", () => { - const fgaApi = new OpenFgaApi({ ...baseConfig, storeId: undefined!, credentials: undefined! }); - expect( - fgaApi.readAuthorizationModels() - ).rejects.toThrow(); - }); - it("should require host in configuration", () => { expect( () => new OpenFgaApi({ ...baseConfig, apiUrl: undefined! }) @@ -105,7 +90,6 @@ describe("OpenFGA SDK", function () { expect( () => new OpenFgaApi({ - storeId: baseConfig.storeId!, apiUrl: baseConfig.apiUrl, }) ).not.toThrowError(); @@ -115,7 +99,6 @@ describe("OpenFGA SDK", function () { expect( () => new OpenFgaApi({ - storeId: baseConfig.storeId!, apiUrl: baseConfig.apiUrl, credentials: { method: CredentialsMethod.ApiToken as any @@ -199,7 +182,7 @@ describe("OpenFGA SDK", function () { const fgaApi = new OpenFgaApi(baseConfig); expect(scope.isDone()).toBe(false); - await fgaApi.readAuthorizationModels(); + await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(true); @@ -213,7 +196,7 @@ describe("OpenFGA SDK", function () { const fgaApi = new OpenFgaApi(baseConfig); expect(scope.isDone()).toBe(false); - await fgaApi.readAuthorizationModels(); + await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(true); @@ -222,7 +205,7 @@ describe("OpenFGA SDK", function () { nocks.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(false); - await fgaApi.readAuthorizationModels(); + await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(false); @@ -238,7 +221,7 @@ describe("OpenFGA SDK", function () { expect(scope1.isDone()).toBe(false); expect(scope2.isDone()).toBe(false); - await fgaApi.readAuthorizationModels(); + await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope1.isDone()).toBe(true); expect(scope2.isDone()).toBe(true); @@ -251,12 +234,11 @@ describe("OpenFGA SDK", function () { nocks.readAuthorizationModels(baseConfig.storeId!); const fgaApi = new OpenFgaApi({ - storeId: baseConfig.storeId!, apiUrl: baseConfig.apiUrl, }); expect(scope.isDone()).toBe(false); - await fgaApi.readAuthorizationModels(); + await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(false); @@ -267,15 +249,6 @@ describe("OpenFGA SDK", function () { const configuration = new Configuration(baseConfig); expect(() => new OpenFgaApi(configuration)).not.toThrowError(); }); - - it("should allow updating the storeId after initialization", async () => { - const fgaApi = new OpenFgaApi({ - apiUrl: OPENFGA_API_URL - }); - expect(fgaApi.storeId).toBe(undefined); - fgaApi.storeId = OPENFGA_STORE_ID; - expect(fgaApi.storeId).toBe(OPENFGA_STORE_ID); - }); }); describe("error handling", () => { @@ -322,13 +295,13 @@ describe("OpenFGA SDK", function () { it("should throw FgaApiValidationError", async () => { await expect( - fgaApi.check({ tuple_key: tupleKey }) + fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }) ).rejects.toThrow(FgaApiValidationError); }); it("FgaApiValidationError should have correct fields", async () => { try { - await fgaApi.check({ tuple_key: tupleKey }); + await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }); } catch (err) { expect(err).toBeInstanceOf(FgaApiValidationError); if (err instanceof FgaApiValidationError) { @@ -377,7 +350,7 @@ describe("OpenFGA SDK", function () { it("should throw FgaApiRateLimitExceededError", async () => { await expect( - fgaApi.check({ tuple_key: tupleKey }, {}) + fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }, {}) ).rejects.toThrow(FgaApiRateLimitExceededError); }); }); @@ -420,7 +393,7 @@ describe("OpenFGA SDK", function () { }); it("should return allowed", async () => { - const result = await fgaApi.check({ tuple_key: tupleKey, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}, {}); + const result = await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}, {}); expect(result.allowed).toBe(true); }); @@ -459,6 +432,7 @@ describe("OpenFGA SDK", function () { it("should return allowed", async () => { const result = await fgaApi.check( + baseConfig.storeId!, { tuple_key: tupleKey }, { retryParams: GetDefaultRetryParams(2, 10) } ); @@ -497,7 +471,7 @@ describe("OpenFGA SDK", function () { }); await expect( - fgaApi.check({ tuple_key: tupleKey }, { retryParams: { maxRetry: 0 }}) + fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }, { retryParams: { maxRetry: 0 }}) ).rejects.toThrow(FgaApiInternalError); }); @@ -527,7 +501,7 @@ describe("OpenFGA SDK", function () { allowed: true, }); - const response = await fgaApi.check({ tuple_key: tupleKey }); + const response = await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }); expect(response.allowed).toBe(true); }); }); @@ -562,7 +536,7 @@ describe("OpenFGA SDK", function () { it("should throw FgaApiNotFoundError", async () => { await expect( - fgaApi.check({ tuple_key: tupleKey }) + fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }) ).rejects.toThrow(FgaApiNotFoundError); }); }); @@ -600,7 +574,7 @@ describe("OpenFGA SDK", function () { it("should throw FgaApiAuthenticationError", async () => { fgaApi = new OpenFgaApi({ ...baseConfig }); await expect( - fgaApi.check({ tuple_key: tupleKey }) + fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }) ).rejects.toThrow(FgaApiAuthenticationError); }); }); @@ -622,7 +596,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.check(baseConfig.storeId!, tupleKey); expect(scope.isDone()).toBe(false); - result = await fgaApi.check({ tuple_key: tupleKey }); + result = await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey }); expect(scope.isDone()).toBe(true); nock.cleanAll(); @@ -666,7 +640,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.check(baseConfig.storeId!, tuple); expect(scope.isDone()).toBe(false); - const data = await fgaApi.check({ tuple_key: tuple }); + const data = await fgaApi.check(baseConfig.storeId!, { tuple_key: tuple }); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({ allowed: expect.any(Boolean) }); @@ -683,10 +657,12 @@ describe("OpenFGA SDK", function () { const scope = nocks.write(baseConfig.storeId!); expect(scope.isDone()).toBe(false); - const data = await fgaApi.write({ - writes: { tuple_keys: [tuple] }, - authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1", - }); + const data = await fgaApi.write( + baseConfig.storeId!, + { + writes: { tuple_keys: [tuple] }, + authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1", + }); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({}); @@ -703,10 +679,12 @@ describe("OpenFGA SDK", function () { const scope = nocks.delete(baseConfig.storeId!, tuple); expect(scope.isDone()).toBe(false); - const data = await fgaApi.write({ - deletes: { tuple_keys: [tuple] }, - authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1", - }); + const data = await fgaApi.write( + baseConfig.storeId!, + { + deletes: { tuple_keys: [tuple] }, + authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1", + }); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({}); @@ -723,7 +701,9 @@ describe("OpenFGA SDK", function () { const scope = nocks.expand(baseConfig.storeId!, tuple); expect(scope.isDone()).toBe(false); - const data = await fgaApi.expand({ tuple_key: tuple, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}); + const data = await fgaApi.expand( + baseConfig.storeId!, + { tuple_key: tuple, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({}); @@ -740,7 +720,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.read(baseConfig.storeId!, tuple); expect(scope.isDone()).toBe(false); - const data = await fgaApi.read({ tuple_key: tuple }); + const data = await fgaApi.read(baseConfig.storeId!, { tuple_key: tuple }); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({}); @@ -762,6 +742,7 @@ describe("OpenFGA SDK", function () { expect(scope.isDone()).toBe(false); const data = await fgaApi.writeAuthorizationModel( + baseConfig.storeId!, authorizationModel ); @@ -776,7 +757,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.readSingleAuthzModel(baseConfig.storeId!, configId); expect(scope.isDone()).toBe(false); - const data = await fgaApi.readAuthorizationModel(configId); + const data = await fgaApi.readAuthorizationModel(baseConfig.storeId!, configId); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({ @@ -794,7 +775,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.readAuthorizationModels(baseConfig.storeId!, defaultConfiguration.getBasePath(), [{ id: "1", schema_version: "1.1", type_definitions: []}]); expect(scope.isDone()).toBe(false); - const data = await fgaApi.readAuthorizationModels(); + const data = await fgaApi.readAuthorizationModels(baseConfig.storeId!); expect(scope.isDone()).toBe(true); expect(data).toMatchObject({ @@ -812,7 +793,7 @@ describe("OpenFGA SDK", function () { const scope = nocks.readChanges(baseConfig.storeId!, type, pageSize, continuationToken); expect(scope.isDone()).toBe(false); - const response = await fgaApi.readChanges(type, pageSize, continuationToken); + const response = await fgaApi.readChanges(baseConfig.storeId!, type, pageSize, continuationToken); expect(scope.isDone()).toBe(true); expect(response).toMatchObject({ changes: expect.arrayContaining([]) }); @@ -825,24 +806,26 @@ describe("OpenFGA SDK", function () { const scope = nocks.listObjects(baseConfig.storeId!, mockedResponse); expect(scope.isDone()).toBe(false); - const response = await fgaApi.listObjects({ - authorization_model_id: "01GAHCE4YVKPQEKZQHT2R89MQV", - user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - relation: "can_read", - type: "document", - contextual_tuples: { - tuple_keys: - [{ - user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", - relation: "editor", - object: "folder:product" - }, { - user: "folder:product", - relation: "parent", - object: "document:roadmap" - }] - } - }); + const response = await fgaApi.listObjects( + baseConfig.storeId!, + { + authorization_model_id: "01GAHCE4YVKPQEKZQHT2R89MQV", + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "can_read", + type: "document", + contextual_tuples: { + tuple_keys: + [{ + user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", + relation: "editor", + object: "folder:product" + }, { + user: "folder:product", + relation: "parent", + object: "document:roadmap" + }] + } + }); expect(scope.isDone()).toBe(true); expect(response.objects).toHaveLength(mockedResponse.objects.length);