diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 299f4ac..22b3399 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -47,9 +47,11 @@ jobs: with: useLockFile: false - name: Run lint - run: CI=true yarn lint + run: "CI=true yarn lint" + - name: Run prettier + run: "CI=true yarn run prettier src -c" - name: Run Yarn tests - run: CI=true yarn only-run-tests + run: "CI=true yarn only-run-tests" build-js-client: name: Build and Test JS client runs-on: "depot-ubuntu-24.04-small" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..11f8159 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/authzedapi diff --git a/package.json b/package.json index 9cd2d2a..7ff4cb4 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@typescript-eslint/parser": "^8.29.1", "eslint": "^9.24.0", "grpc-tools": "^1.13.0", + "prettier": "^3.5.3", "rollup": "^4.40.0", "tsc-esm-fix": "^3.1.2", "typescript": "^5.8", diff --git a/src/__utils__/helpers.ts b/src/__utils__/helpers.ts index 063076d..d41ad77 100644 --- a/src/__utils__/helpers.ts +++ b/src/__utils__/helpers.ts @@ -1,9 +1,9 @@ /** * Generates a random token with a prefix to support * unique, idempotent local testing. - * @param prefix - * @returns + * @param prefix + * @returns */ export function generateTestToken(prefix: string): string { - return `${prefix}-${Date.now().toString()}` -} \ No newline at end of file + return `${prefix}-${Date.now().toString()}`; +} diff --git a/src/full-promises.test.ts b/src/full-promises.test.ts index 307027d..88f1a4f 100644 --- a/src/full-promises.test.ts +++ b/src/full-promises.test.ts @@ -1,14 +1,18 @@ import { ClientSecurity } from "./util.js"; import * as v1 from "./v1.js"; import { Consistency } from "./v1.js"; -import { generateTestToken } from './__utils__/helpers.js' -import { describe, it, expect } from 'vitest' +import { generateTestToken } from "./__utils__/helpers.js"; +import { describe, it, expect } from "vitest"; describe("a check following a write of schema and relationships", () => { it("should succeed", async () => { // Write the schema. - const token = generateTestToken('full-promises') - const { promises: v1client } = v1.NewClient(token, "localhost:50051", ClientSecurity.INSECURE_LOCALHOST_ALLOWED); + const token = generateTestToken("full-promises"); + const { promises: v1client } = v1.NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); const writeSchemaRequest = v1.WriteSchemaRequest.create({ schema: ` @@ -21,7 +25,7 @@ describe("a check following a write of schema and relationships", () => { `, }); - await v1client.writeSchema(writeSchemaRequest) + await v1client.writeSchema(writeSchemaRequest); // Create the relationship between the resource and the user. const resource = v1.ObjectReference.create({ objectType: "test/resource", @@ -54,7 +58,7 @@ describe("a check following a write of schema and relationships", () => { updates: [update], }); - const response = await v1client.writeRelationships(writeRequest) + const response = await v1client.writeRelationships(writeRequest); expect(response).toBeTruthy(); const checkPermissionRequest = v1.CheckPermissionRequest.create({ @@ -66,12 +70,14 @@ describe("a check following a write of schema and relationships", () => { oneofKind: "fullyConsistent", fullyConsistent: true, }, - }) + }), }); - const permissionResponse = await v1client.checkPermission(checkPermissionRequest) + const permissionResponse = await v1client.checkPermission( + checkPermissionRequest, + ); expect(permissionResponse?.permissionship).toBe( - v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION + v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION, ); }); }); diff --git a/src/full.test.ts b/src/full.test.ts index 1a6b47b..cd083dc 100644 --- a/src/full.test.ts +++ b/src/full.test.ts @@ -1,17 +1,22 @@ import { ClientSecurity } from "./util.js"; import * as v1 from "./v1.js"; import { Consistency } from "./v1.js"; -import { generateTestToken } from './__utils__/helpers.js' -import { describe, it, expect } from 'vitest' +import { generateTestToken } from "./__utils__/helpers.js"; +import { describe, it, expect } from "vitest"; describe("a check following a write of schema and relationships", () => { - it("should succeed", () => new Promise((done) => { - // Write the schema. - const token = generateTestToken('full-test') - const v1client = v1.NewClient(token, "localhost:50051", ClientSecurity.INSECURE_LOCALHOST_ALLOWED); + it("should succeed", () => + new Promise((done) => { + // Write the schema. + const token = generateTestToken("full-test"); + const v1client = v1.NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); - const writeSchemaRequest = v1.WriteSchemaRequest.create({ - schema: ` + const writeSchemaRequest = v1.WriteSchemaRequest.create({ + schema: ` definition test/user {} definition test/resource { @@ -19,67 +24,67 @@ describe("a check following a write of schema and relationships", () => { permission view = viewer } `, - }); - - v1client.writeSchema(writeSchemaRequest, function (err) { - expect(err).toBe(null); - - // Create the relationship between the resource and the user. - const resource = v1.ObjectReference.create({ - objectType: "test/resource", - objectId: "someresource", - }); - - // Create the user reference. - const userref = v1.ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", - }); - - const user = v1.SubjectReference.create({ - object: userref, }); - const relationship = v1.Relationship.create({ - resource, - relation: "viewer", - subject: user, - }); + v1client.writeSchema(writeSchemaRequest, function (err) { + expect(err).toBe(null); - const update = v1.RelationshipUpdate.create({ - operation: v1.RelationshipUpdate_Operation.CREATE, - relationship: relationship, - }); + // Create the relationship between the resource and the user. + const resource = v1.ObjectReference.create({ + objectType: "test/resource", + objectId: "someresource", + }); - // Write the relationship. - const writeRequest = v1.WriteRelationshipsRequest.create({ - updates: [update], - }); + // Create the user reference. + const userref = v1.ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }); - v1client.writeRelationships(writeRequest, function (err, response) { - expect(err).toBe(null); - expect(response).toBeTruthy(); + const user = v1.SubjectReference.create({ + object: userref, + }); - const checkPermissionRequest = v1.CheckPermissionRequest.create({ + const relationship = v1.Relationship.create({ resource, - permission: "view", + relation: "viewer", subject: user, - consistency: Consistency.create({ - requirement: { - oneofKind: "fullyConsistent", - fullyConsistent: true, - }, - }) }); - v1client.checkPermission(checkPermissionRequest, (err, response) => { + const update = v1.RelationshipUpdate.create({ + operation: v1.RelationshipUpdate_Operation.CREATE, + relationship: relationship, + }); + + // Write the relationship. + const writeRequest = v1.WriteRelationshipsRequest.create({ + updates: [update], + }); + + v1client.writeRelationships(writeRequest, function (err, response) { expect(err).toBe(null); - expect(response?.permissionship).toBe( - v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION - ); - done(); + expect(response).toBeTruthy(); + + const checkPermissionRequest = v1.CheckPermissionRequest.create({ + resource, + permission: "view", + subject: user, + consistency: Consistency.create({ + requirement: { + oneofKind: "fullyConsistent", + fullyConsistent: true, + }, + }), + }); + + v1client.checkPermission(checkPermissionRequest, (err, response) => { + expect(err).toBe(null); + expect(response?.permissionship).toBe( + v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION, + ); + done(); + }); }); }); - }); - })); + })); }); diff --git a/src/index.ts b/src/index.ts index 421adad..e5867b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ "use strict"; -export * as protobuf from './protobuf.js'; -export * as v1 from './v1.js'; +export * as protobuf from "./protobuf.js"; +export * as v1 from "./v1.js"; diff --git a/src/protobuf.ts b/src/protobuf.ts index c9fae72..fae7c71 100644 --- a/src/protobuf.ts +++ b/src/protobuf.ts @@ -1,4 +1,4 @@ -export * from './authzedapi/google/protobuf/descriptor.js'; -export * from './authzedapi/google/protobuf/duration.js'; -export * from './authzedapi/google/protobuf/struct.js'; -export * from './authzedapi/google/protobuf/timestamp.js'; +export * from "./authzedapi/google/protobuf/descriptor.js"; +export * from "./authzedapi/google/protobuf/duration.js"; +export * from "./authzedapi/google/protobuf/struct.js"; +export * from "./authzedapi/google/protobuf/timestamp.js"; diff --git a/src/types.ts b/src/types.ts index b393eb2..afc2022 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ import { ClientWritableStream, Metadata, ServiceError, -} from '@grpc/grpc-js'; +} from "@grpc/grpc-js"; /** * Picks all function properties of a type that except for those with a specific return type @@ -37,7 +37,7 @@ export type OmitBaseMethods = { export type StreamCall = ( request: T, metadata?: Metadata | CallOptions, - options?: CallOptions + options?: CallOptions, ) => ClientReadableStream; /** @@ -54,8 +54,8 @@ export type WritableStreamCall = ( | undefined, callback?: ( err: ServiceError | null, - value?: U | undefined - ) => void | undefined + value?: U | undefined, + ) => void | undefined, ) => ClientWritableStream; /** @@ -65,7 +65,7 @@ export type UnaryCall = ( request: T, metadata: Metadata | CallOptions, options: Partial, - callback: (err: ServiceError | null, res?: U) => void + callback: (err: ServiceError | null, res?: U) => void, ) => ClientUnaryCall; /** @@ -74,7 +74,7 @@ export type UnaryCall = ( export type PromisifiedCall = ( request: T, metadata?: Metadata, - options?: Partial + options?: Partial, ) => Promise; /** diff --git a/src/util.ts b/src/util.ts index e226aa9..38c4ba4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -9,7 +9,7 @@ import { GrpcUri } from "@grpc/grpc-js/build/src/uri-parser.js"; class ComposedChannelCredentials extends grpc.ChannelCredentials { constructor( private channelCredentials: KnownInsecureChannelCredentialsImpl, - private callCreds: grpc.CallCredentials + private callCreds: grpc.CallCredentials, ) { super(); // NOTE: leaving this here to show what changed from the upstream. @@ -20,11 +20,10 @@ class ComposedChannelCredentials extends grpc.ChannelCredentials { */ } compose(callCredentials: grpc.CallCredentials) { - const combinedCallCredentials = - this.callCreds.compose(callCredentials); + const combinedCallCredentials = this.callCreds.compose(callCredentials); return new ComposedChannelCredentials( this.channelCredentials, - combinedCallCredentials + combinedCallCredentials, ); } _isSecure(): boolean { @@ -35,7 +34,7 @@ class ComposedChannelCredentials extends grpc.ChannelCredentials { _createSecureConnector( channelTarget: GrpcUri, options: grpc.ChannelOptions, - callCredentials?: grpc.CallCredentials + callCredentials?: grpc.CallCredentials, ): SecureConnector { return { connect: async (socket: net.Socket) => { @@ -43,7 +42,7 @@ class ComposedChannelCredentials extends grpc.ChannelCredentials { }, waitForReady: async () => {}, getCallCredentials: () => callCredentials || this.callCreds, - destroy: () => {} + destroy: () => {}, }; } @@ -65,7 +64,6 @@ class ComposedChannelCredentials extends grpc.ChannelCredentials { // Create our own known insecure channel creds. // See https://github.com/grpc/grpc-node/issues/543 for why this is necessary. class KnownInsecureChannelCredentialsImpl extends grpc.ChannelCredentials { - compose(callCredentials: grpc.CallCredentials): grpc.ChannelCredentials { return new ComposedChannelCredentials(this, callCredentials); } @@ -77,10 +75,10 @@ class KnownInsecureChannelCredentialsImpl extends grpc.ChannelCredentials { return other instanceof KnownInsecureChannelCredentialsImpl; } - _createSecureConnector( + _createSecureConnector( channelTarget: GrpcUri, options: grpc.ChannelOptions, - callCredentials: grpc.CallCredentials + callCredentials: grpc.CallCredentials, ): SecureConnector { return { connect: async (socket: net.Socket) => { @@ -88,7 +86,7 @@ class KnownInsecureChannelCredentialsImpl extends grpc.ChannelCredentials { }, waitForReady: async () => {}, getCallCredentials: () => callCredentials, - destroy: () => {} + destroy: () => {}, }; } } @@ -110,10 +108,10 @@ export enum PreconnectServices { function createClientCreds( endpoint: string, token: string, - security: ClientSecurity = ClientSecurity.SECURE + security: ClientSecurity = ClientSecurity.SECURE, ): grpc.ChannelCredentials { const metadata = new grpc.Metadata(); - metadata.set('authorization', 'Bearer ' + token); + metadata.set("authorization", "Bearer " + token); const creds = []; @@ -121,12 +119,12 @@ function createClientCreds( security === ClientSecurity.SECURE || security === ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS || (security === ClientSecurity.INSECURE_LOCALHOST_ALLOWED && - endpoint.startsWith('localhost:')) + endpoint.startsWith("localhost:")) ) { creds.push( grpc.credentials.createFromMetadataGenerator((_, callback) => { callback(null, metadata); - }) + }), ); } @@ -134,52 +132,52 @@ function createClientCreds( security === ClientSecurity.SECURE ? grpc.credentials.createSsl() : new KnownInsecureChannelCredentialsImpl(), - ...creds + ...creds, ); } function createClientCredsWithCustomCert( token: string, - certificate: Buffer + certificate: Buffer, ): grpc.ChannelCredentials { const metadata = new grpc.Metadata(); - metadata.set('authorization', 'Bearer ' + token); + metadata.set("authorization", "Bearer " + token); const creds = []; creds.push( grpc.credentials.createFromMetadataGenerator((_, callback) => { callback(null, metadata); - }) + }), ); return grpc.credentials.combineChannelCredentials( grpc.credentials.createSsl(certificate), - ...creds + ...creds, ); } function promisifyStream( fn: (req: P1) => grpc.ClientReadableStream, - bind: P3 + bind: P3, ): (req: P1) => Promise { return (req: P1) => { return new Promise((resolve, reject) => { const results: P2[] = []; const stream = fn.bind(bind)(req); - stream.on('data', function (response: P2) { + stream.on("data", function (response: P2) { results.push(response); }); - stream.on('error', function (e) { + stream.on("error", function (e) { return reject(e); }); - stream.on('end', function () { + stream.on("end", function () { return resolve(results); }); - stream.on('status', function (status) { + stream.on("status", function (status) { if (status.code !== grpc.status.OK) { return reject(status); } diff --git a/src/v1-promise.test.ts b/src/v1-promise.test.ts index 8f50b0b..64c17fa 100644 --- a/src/v1-promise.test.ts +++ b/src/v1-promise.test.ts @@ -1,6 +1,6 @@ -import * as grpc from '@grpc/grpc-js'; -import { generateTestToken } from './__utils__/helpers.js'; -import { Struct } from './authzedapi/google/protobuf/struct.js'; +import * as grpc from "@grpc/grpc-js"; +import { generateTestToken } from "./__utils__/helpers.js"; +import { Struct } from "./authzedapi/google/protobuf/struct.js"; import { BulkExportRelationshipsRequest, BulkImportRelationshipsRequest, @@ -19,47 +19,47 @@ import { SubjectReference, WriteRelationshipsRequest, WriteSchemaRequest, -} from './v1.js'; -import { describe, it, expect, beforeEach } from 'vitest' +} from "./v1.js"; +import { describe, it, expect, beforeEach } from "vitest"; -describe('a check with an unknown namespace', () => { - it('should raise a failed precondition', async () => { +describe("a check with an unknown namespace", () => { + it("should raise a failed precondition", async () => { const resource = ObjectReference.create({ - objectType: 'test/somenamespace', - objectId: 'bar', + objectType: "test/somenamespace", + objectId: "bar", }); const testUser = ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + objectType: "test/user", + objectId: "someuser", }); const checkPermissionRequest = CheckPermissionRequest.create({ resource, - permission: 'someperm', + permission: "someperm", subject: SubjectReference.create({ object: testUser, }), }); const { promises: client } = NewClient( - generateTestToken('v1-promise-test-unknown'), - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + generateTestToken("v1-promise-test-unknown"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); try { await client.checkPermission(checkPermissionRequest); - throw new Error('Error should be thrown'); + throw new Error("Error should be thrown"); } catch (err) { expect((err as grpc.ServiceError)?.code).toBe( - grpc.status.FAILED_PRECONDITION + grpc.status.FAILED_PRECONDITION, ); client.close(); } }); }); -describe('a check with an known namespace', () => { +describe("a check with an known namespace", () => { const schemaRequest = WriteSchemaRequest.create({ schema: `definition test/user {} @@ -71,13 +71,13 @@ describe('a check with an known namespace', () => { }); const resource = ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }); const testUser = ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + objectType: "test/user", + objectId: "someuser", }); const writeRequest = WriteRelationshipsRequest.create({ @@ -85,7 +85,7 @@ describe('a check with an known namespace', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'viewer', + relation: "viewer", subject: SubjectReference.create({ object: testUser, }), @@ -97,23 +97,23 @@ describe('a check with an known namespace', () => { const checkPermissionRequest = CheckPermissionRequest.create({ resource, - permission: 'view', + permission: "view", subject: SubjectReference.create({ object: testUser, }), consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), }); - it('should succeed', async () => { + it("should succeed", async () => { const { promises: client } = NewClient( - generateTestToken('v1-promise-namespace'), - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + generateTestToken("v1-promise-namespace"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const schemaResponse = await client.writeSchema(schemaRequest); @@ -124,52 +124,52 @@ describe('a check with an known namespace', () => { const checkResponse = await client.checkPermission(checkPermissionRequest); expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.HAS_PERMISSION + CheckPermissionResponse_Permissionship.HAS_PERMISSION, ); client.close(); }); - it('should succeed with full signatures', async () => { + it("should succeed with full signatures", async () => { const { promises: client } = NewClient( - generateTestToken('v1-promise-namespace'), - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + generateTestToken("v1-promise-namespace"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const schemaResponse = await client.writeSchema( schemaRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(schemaResponse).toBeTruthy(); const response = await client.writeRelationships( writeRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(response).toBeTruthy(); const checkResponse = await client.checkPermission( checkPermissionRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.HAS_PERMISSION + CheckPermissionResponse_Permissionship.HAS_PERMISSION, ); client.close(); }); - describe('with caveated relations', () => { - it('should succeed', async () => { + describe("with caveated relations", () => { + it("should succeed", async () => { // Write some schema. const { promises: client } = NewClient( - generateTestToken('v1-promise-caveats'), - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + generateTestToken("v1-promise-caveats"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const schemaRequest = WriteSchemaRequest.create({ @@ -190,19 +190,19 @@ describe('a check with an known namespace', () => { const schemaResponse = await client.writeSchema( schemaRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(schemaResponse).toBeTruthy(); // Write a relationship. const resource = ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }); const testUser = ObjectReference.create({ - objectType: 'test/user', - objectId: 'specialuser', + objectType: "test/user", + objectId: "specialuser", }); const writeRequest = WriteRelationshipsRequest.create({ @@ -210,12 +210,12 @@ describe('a check with an known namespace', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'caveated_viewer', + relation: "caveated_viewer", subject: SubjectReference.create({ object: testUser, }), optionalCaveat: ContextualizedCaveat.create({ - caveatName: 'has_special_attribute', + caveatName: "has_special_attribute", }), }), operation: RelationshipUpdate_Operation.CREATE, @@ -226,20 +226,20 @@ describe('a check with an known namespace', () => { const response = await client.writeRelationships( writeRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(response).toBeTruthy(); // Call check when user has special attribute. let checkPermissionRequest = CheckPermissionRequest.create({ resource, - permission: 'view', + permission: "view", subject: SubjectReference.create({ object: testUser, }), consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), @@ -249,22 +249,22 @@ describe('a check with an known namespace', () => { let checkResponse = await client.checkPermission( checkPermissionRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.HAS_PERMISSION + CheckPermissionResponse_Permissionship.HAS_PERMISSION, ); // Call check when user does not have the special attribute. checkPermissionRequest = CheckPermissionRequest.create({ resource, - permission: 'view', + permission: "view", subject: SubjectReference.create({ object: testUser, }), consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), @@ -274,22 +274,22 @@ describe('a check with an known namespace', () => { checkResponse = await client.checkPermission( checkPermissionRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.NO_PERMISSION + CheckPermissionResponse_Permissionship.NO_PERMISSION, ); // Call check when user's special attribute is unspecified. checkPermissionRequest = CheckPermissionRequest.create({ resource, - permission: 'view', + permission: "view", subject: SubjectReference.create({ object: testUser, }), consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), @@ -299,10 +299,10 @@ describe('a check with an known namespace', () => { checkResponse = await client.checkPermission( checkPermissionRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.CONDITIONAL_PERMISSION + CheckPermissionResponse_Permissionship.CONDITIONAL_PERMISSION, ); client.close(); @@ -310,19 +310,19 @@ describe('a check with an known namespace', () => { }); }); -describe('Lookup APIs', () => { +describe("Lookup APIs", () => { let token: string; const lookupSubjectRequest = LookupSubjectsRequest.create({ resource: ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }), - permission: 'view', - subjectObjectType: 'test/user', + permission: "view", + subjectObjectType: "test/user", consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), @@ -331,26 +331,26 @@ describe('Lookup APIs', () => { const lookupResourceRequest = LookupResourcesRequest.create({ subject: SubjectReference.create({ object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + objectType: "test/user", + objectId: "someuser", }), }), - permission: 'view', - resourceObjectType: 'test/document', + permission: "view", + resourceObjectType: "test/document", consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), }); beforeEach(async () => { - token = generateTestToken('v1-promise-lookup'); + token = generateTestToken("v1-promise-lookup"); const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const request = WriteSchemaRequest.create({ @@ -366,8 +366,8 @@ describe('Lookup APIs', () => { await client.writeSchema(request); const resource = ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }); const writeRequest = WriteRelationshipsRequest.create({ @@ -375,11 +375,11 @@ describe('Lookup APIs', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'viewer', + relation: "viewer", subject: SubjectReference.create({ object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + objectType: "test/user", + objectId: "someuser", }), }), }), @@ -388,11 +388,11 @@ describe('Lookup APIs', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'viewer', + relation: "viewer", subject: SubjectReference.create({ object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser2', + objectType: "test/user", + objectId: "someuser2", }), }), }), @@ -405,65 +405,69 @@ describe('Lookup APIs', () => { client.close(); }); - it('can lookup subjects', async () => { + it("can lookup subjects", async () => { const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const result = await client.lookupSubjects(lookupSubjectRequest); - expect(['someuser', 'someuser2']).toContain(result[0].subject?.subjectObjectId); + expect(["someuser", "someuser2"]).toContain( + result[0].subject?.subjectObjectId, + ); client.close(); }); - it('can lookup resources', async () => { + it("can lookup resources", async () => { const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const resStream = await client.lookupResources(lookupResourceRequest); - expect(resStream[0].resourceObjectId).toEqual('somedocument'); + expect(resStream[0].resourceObjectId).toEqual("somedocument"); client.close(); }); - it('can lookup using full signatures', async () => { + it("can lookup using full signatures", async () => { const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const result = await client.lookupSubjects( lookupSubjectRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, + ); + expect(["someuser", "someuser2"]).toContain( + result[0].subject?.subjectObjectId, ); - expect(['someuser', 'someuser2']).toContain(result[0].subject?.subjectObjectId); const resStream = await client.lookupResources( lookupResourceRequest, new grpc.Metadata(), - {} as grpc.CallOptions + {} as grpc.CallOptions, ); - expect(resStream[0].resourceObjectId).toEqual('somedocument'); + expect(resStream[0].resourceObjectId).toEqual("somedocument"); client.close(); }); }); -describe('Experimental Service', () => { +describe("Experimental Service", () => { let token: string; beforeEach(async () => { - token = generateTestToken('v1-experimental-service'); + token = generateTestToken("v1-experimental-service"); const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const request = WriteSchemaRequest.create({ @@ -480,71 +484,72 @@ describe('Experimental Service', () => { client.close(); }); - it('can bulk import relationships', () => new Promise((done, fail) => { - const { promises: client } = NewClient( - token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); + it("can bulk import relationships", () => + new Promise((done, fail) => { + const { promises: client } = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); - const writeStream = client.bulkImportRelationships((err, value) => { - if (err) { - fail(err); - } + const writeStream = client.bulkImportRelationships((err, value) => { + if (err) { + fail(err); + } - expect(value?.numLoaded).toEqual('2'); - client.close(); - done(); - }); + expect(value?.numLoaded).toEqual("2"); + client.close(); + done(); + }); - writeStream.on('error', (e) => { - fail(e); - }); + writeStream.on("error", (e) => { + fail(e); + }); - const resource = ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', - }); - writeStream.write( - BulkImportRelationshipsRequest.create({ - relationships: [ - Relationship.create({ - resource: resource, - relation: 'viewer', - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + const resource = ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }); + writeStream.write( + BulkImportRelationshipsRequest.create({ + relationships: [ + Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }), }), }), - }), - Relationship.create({ - resource: resource, - relation: 'viewer', - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser2', + Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser2", + }), }), }), - }), - ], - }) - ); + ], + }), + ); - writeStream.end(); - })); + writeStream.end(); + })); - it('can bulk export relationships', async () => { + it("can bulk export relationships", async () => { const { promises: client } = NewClient( token, - 'localhost:50051', - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, ); const resource = ObjectReference.create({ - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }); const writeRequest = WriteRelationshipsRequest.create({ @@ -552,11 +557,11 @@ describe('Experimental Service', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'viewer', + relation: "viewer", subject: SubjectReference.create({ object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser', + objectType: "test/user", + objectId: "someuser", }), }), }), @@ -565,11 +570,11 @@ describe('Experimental Service', () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: 'viewer', + relation: "viewer", subject: SubjectReference.create({ object: ObjectReference.create({ - objectType: 'test/user', - objectId: 'someuser2', + objectType: "test/user", + objectId: "someuser2", }), }), }), @@ -584,34 +589,34 @@ describe('Experimental Service', () => { BulkExportRelationshipsRequest.create({ consistency: Consistency.create({ requirement: { - oneofKind: 'fullyConsistent', + oneofKind: "fullyConsistent", fullyConsistent: true, }, }), - }) + }), ); expect(resStream[0].relationships).toEqual([ { - relation: 'viewer', + relation: "viewer", resource: { - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }, subject: { - optionalRelation: '', - object: { objectType: 'test/user', objectId: 'someuser' }, + optionalRelation: "", + object: { objectType: "test/user", objectId: "someuser" }, }, }, { - relation: 'viewer', + relation: "viewer", resource: { - objectType: 'test/document', - objectId: 'somedocument', + objectType: "test/document", + objectId: "somedocument", }, subject: { - optionalRelation: '', - object: { objectType: 'test/user', objectId: 'someuser2' }, + optionalRelation: "", + object: { objectType: "test/user", objectId: "someuser2" }, }, }, ]); diff --git a/src/v1.test.ts b/src/v1.test.ts index f1e7c47..d40a70a 100644 --- a/src/v1.test.ts +++ b/src/v1.test.ts @@ -26,169 +26,62 @@ import { WriteRelationshipsResponse, WriteSchemaRequest, } from "./v1.js"; -import { describe, it, expect, beforeEach } from 'vitest' +import { describe, it, expect, beforeEach } from "vitest"; describe("a check with an unknown namespace", () => { - it("should raise a failed precondition", () => new Promise((done) => { - const resource = ObjectReference.create({ - objectType: "test/somenamespace", - objectId: "bar", - }); - - const testUser = ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", - }); - - const checkPermissionRequest = CheckPermissionRequest.create({ - resource, - permission: "someperm", - subject: SubjectReference.create({ - object: testUser, - }), - }); - - const client = NewClient( - generateTestToken("v1-test-unknown"), - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); - client.checkPermission(checkPermissionRequest, function (err, response) { - expect(response).toBe(undefined); - expect(err?.code).toBe(grpc.status.FAILED_PRECONDITION); - client.close(); - done(); - }); - })); -}); - -describe("a check with an known namespace", () => { - it("should succeed", () => new Promise((done) => { - // Write some schema. - const client = NewClient( - generateTestToken("v1-namespace"), - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED, - PreconnectServices.PERMISSIONS_SERVICE | PreconnectServices.SCHEMA_SERVICE - ); - - const request = WriteSchemaRequest.create({ - schema: `definition test/user {} - - definition test/document { - relation viewer: test/user - permission view = viewer - } - `, - }); - - new Promise((resolve) => { - client.writeSchema(request, function (err, response) { - expect(err).toBe(null); - resolve(response); + it("should raise a failed precondition", () => + new Promise((done) => { + const resource = ObjectReference.create({ + objectType: "test/somenamespace", + objectId: "bar", }); - }) - .then((schemaResponse) => { - expect(schemaResponse).toBeTruthy(); - - return new Promise((resolve) => { - // Write a relationship. - const resource = ObjectReference.create({ - objectType: "test/document", - objectId: "somedocument", - }); - - const testUser = ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", - }); - - const writeRequest = WriteRelationshipsRequest.create({ - updates: [ - RelationshipUpdate.create({ - relationship: Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: testUser, - }), - }), - operation: RelationshipUpdate_Operation.CREATE, - }), - ], - }); - client.writeRelationships(writeRequest, function (err, response) { - expect(err).toBe(null); - resolve({ response, resource, testUser }); - }); - }); - }) - .then((vals) => { - const { response, resource, testUser } = vals as { - response: WriteRelationshipsResponse; - resource: ObjectReference; - testUser: ObjectReference; - }; - expect(response).toBeTruthy(); - - return new Promise((resolve) => { - // Call check. - const checkPermissionRequest = CheckPermissionRequest.create({ - resource, - permission: "view", - subject: SubjectReference.create({ - object: testUser, - }), - consistency: Consistency.create({ - requirement: { - oneofKind: "fullyConsistent", - fullyConsistent: true, - }, - }), - }); + const testUser = ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }); - client.checkPermission( - checkPermissionRequest, - function (err, response) { - expect(err).toBe(null); - resolve(response); - } - ); - }); - }) - .then((response) => { - const checkResponse = response as CheckPermissionResponse; - expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.HAS_PERMISSION - ); + const checkPermissionRequest = CheckPermissionRequest.create({ + resource, + permission: "someperm", + subject: SubjectReference.create({ + object: testUser, + }), + }); + const client = NewClient( + generateTestToken("v1-test-unknown"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + client.checkPermission(checkPermissionRequest, function (err, response) { + expect(response).toBe(undefined); + expect(err?.code).toBe(grpc.status.FAILED_PRECONDITION); client.close(); done(); }); - })); + })); +}); - describe("with caveated relations", () => { - it("should succeed", () => new Promise((done) => { +describe("a check with an known namespace", () => { + it("should succeed", () => + new Promise((done) => { // Write some schema. const client = NewClient( - generateTestToken("v1-namespace-caveats"), + generateTestToken("v1-namespace"), "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + PreconnectServices.PERMISSIONS_SERVICE | + PreconnectServices.SCHEMA_SERVICE, ); const request = WriteSchemaRequest.create({ schema: `definition test/user {} - caveat has_special_attribute(special bool) { - special == true - } - - definition test/document { - relation viewer: test/user - relation caveated_viewer: test/user with has_special_attribute - permission view = viewer + caveated_viewer - } + definition test/document { + relation viewer: test/user + permission view = viewer + } `, }); @@ -210,7 +103,7 @@ describe("a check with an known namespace", () => { const testUser = ObjectReference.create({ objectType: "test/user", - objectId: "specialuser", + objectId: "someuser", }); const writeRequest = WriteRelationshipsRequest.create({ @@ -218,13 +111,10 @@ describe("a check with an known namespace", () => { RelationshipUpdate.create({ relationship: Relationship.create({ resource: resource, - relation: "caveated_viewer", + relation: "viewer", subject: SubjectReference.create({ object: testUser, }), - optionalCaveat: ContextualizedCaveat.create({ - caveatName: "has_special_attribute", - }), }), operation: RelationshipUpdate_Operation.CREATE, }), @@ -246,7 +136,7 @@ describe("a check with an known namespace", () => { expect(response).toBeTruthy(); return new Promise((resolve) => { - // Call check when user has special attribute. + // Call check. const checkPermissionRequest = CheckPermissionRequest.create({ resource, permission: "view", @@ -259,7 +149,6 @@ describe("a check with an known namespace", () => { fullyConsistent: true, }, }), - context: Struct.fromJson({ special: true }), }); client.checkPermission( @@ -267,401 +156,529 @@ describe("a check with an known namespace", () => { function (err, response) { expect(err).toBe(null); resolve(response); - } + }, ); }); }) .then((response) => { const checkResponse = response as CheckPermissionResponse; expect(checkResponse?.permissionship).toBe( - CheckPermissionResponse_Permissionship.HAS_PERMISSION + CheckPermissionResponse_Permissionship.HAS_PERMISSION, ); client.close(); done(); }); })); + + describe("with caveated relations", () => { + it("should succeed", () => + new Promise((done) => { + // Write some schema. + const client = NewClient( + generateTestToken("v1-namespace-caveats"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + + const request = WriteSchemaRequest.create({ + schema: `definition test/user {} + + caveat has_special_attribute(special bool) { + special == true + } + + definition test/document { + relation viewer: test/user + relation caveated_viewer: test/user with has_special_attribute + permission view = viewer + caveated_viewer + } + `, + }); + + new Promise((resolve) => { + client.writeSchema(request, function (err, response) { + expect(err).toBe(null); + resolve(response); + }); + }) + .then((schemaResponse) => { + expect(schemaResponse).toBeTruthy(); + + return new Promise((resolve) => { + // Write a relationship. + const resource = ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }); + + const testUser = ObjectReference.create({ + objectType: "test/user", + objectId: "specialuser", + }); + + const writeRequest = WriteRelationshipsRequest.create({ + updates: [ + RelationshipUpdate.create({ + relationship: Relationship.create({ + resource: resource, + relation: "caveated_viewer", + subject: SubjectReference.create({ + object: testUser, + }), + optionalCaveat: ContextualizedCaveat.create({ + caveatName: "has_special_attribute", + }), + }), + operation: RelationshipUpdate_Operation.CREATE, + }), + ], + }); + + client.writeRelationships(writeRequest, function (err, response) { + expect(err).toBe(null); + resolve({ response, resource, testUser }); + }); + }); + }) + .then((vals) => { + const { response, resource, testUser } = vals as { + response: WriteRelationshipsResponse; + resource: ObjectReference; + testUser: ObjectReference; + }; + expect(response).toBeTruthy(); + + return new Promise((resolve) => { + // Call check when user has special attribute. + const checkPermissionRequest = CheckPermissionRequest.create({ + resource, + permission: "view", + subject: SubjectReference.create({ + object: testUser, + }), + consistency: Consistency.create({ + requirement: { + oneofKind: "fullyConsistent", + fullyConsistent: true, + }, + }), + context: Struct.fromJson({ special: true }), + }); + + client.checkPermission( + checkPermissionRequest, + function (err, response) { + expect(err).toBe(null); + resolve(response); + }, + ); + }); + }) + .then((response) => { + const checkResponse = response as CheckPermissionResponse; + expect(checkResponse?.permissionship).toBe( + CheckPermissionResponse_Permissionship.HAS_PERMISSION, + ); + + client.close(); + done(); + }); + })); }); }); describe("Lookup APIs", () => { let token: string; - beforeEach(() => new Promise((done) => { - token = generateTestToken("v1-lookup"); - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); + beforeEach( + () => + new Promise((done) => { + token = generateTestToken("v1-lookup"); + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); - const request = WriteSchemaRequest.create({ - schema: `definition test/user {} + const request = WriteSchemaRequest.create({ + schema: `definition test/user {} definition test/document { relation viewer: test/user permission view = viewer } `, - }); - - const resource = ObjectReference.create({ - objectType: "test/document", - objectId: "somedocument", - }); - - const writeRequest = WriteRelationshipsRequest.create({ - updates: [ - RelationshipUpdate.create({ - relationship: Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", + }); + + const resource = ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }); + + const writeRequest = WriteRelationshipsRequest.create({ + updates: [ + RelationshipUpdate.create({ + relationship: Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }), + }), }), + operation: RelationshipUpdate_Operation.CREATE, }), - }), - operation: RelationshipUpdate_Operation.CREATE, - }), - RelationshipUpdate.create({ - relationship: Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser2", + RelationshipUpdate.create({ + relationship: Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser2", + }), + }), }), + operation: RelationshipUpdate_Operation.CREATE, }), - }), - operation: RelationshipUpdate_Operation.CREATE, + ], + }); + + client.writeSchema(request, function (err) { + expect(err).toBe(null); + + client.writeRelationships(writeRequest, function (err) { + expect(err).toBe(null); + done(); + }); + }); + }), + ); + + it("can lookup subjects", () => + new Promise((done, fail) => { + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + + const request = LookupSubjectsRequest.create({ + resource: ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }), + permission: "view", + subjectObjectType: "test/user", + consistency: Consistency.create({ + requirement: { + oneofKind: "fullyConsistent", + fullyConsistent: true, + }, }), - ], - }); + }); - client.writeSchema(request, function (err) { - expect(err).toBe(null); + const resStream = client.lookupSubjects(request); - client.writeRelationships(writeRequest, function (err) { - expect(err).toBe(null); + resStream.on("data", function (subject: LookupSubjectsResponse) { + expect(["someuser", "someuser2"]).toContain( + subject.subject?.subjectObjectId, + ); + }); + + resStream.on("end", function () { + client.close(); done(); }); - }); - })); - - it("can lookup subjects", () => new Promise((done, fail) => { - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); - - const request = LookupSubjectsRequest.create({ - resource: ObjectReference.create({ - objectType: "test/document", - objectId: "somedocument", - }), - permission: "view", - subjectObjectType: "test/user", - consistency: Consistency.create({ - requirement: { - oneofKind: "fullyConsistent", - fullyConsistent: true, - }, - }), - }); - - const resStream = client.lookupSubjects(request); - - resStream.on("data", function (subject: LookupSubjectsResponse) { - expect(["someuser", "someuser2"]).toContain(subject.subject?.subjectObjectId); - }); - - resStream.on("end", function () { - client.close(); - done(); - }); - - resStream.on("error", function (e) { - client.close(); - fail(e); - }); - - resStream.on("status", function (status) { - expect(status.code).toEqual(grpc.status.OK); - }); - })); - - it("can lookup resources", () => new Promise((done, fail) => { - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); - - const request = LookupResourcesRequest.create({ - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", + + resStream.on("error", function (e) { + client.close(); + fail(e); + }); + + resStream.on("status", function (status) { + expect(status.code).toEqual(grpc.status.OK); + }); + })); + + it("can lookup resources", () => + new Promise((done, fail) => { + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + + const request = LookupResourcesRequest.create({ + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }), }), - }), - permission: "view", - resourceObjectType: "test/document", - consistency: Consistency.create({ - requirement: { - oneofKind: "fullyConsistent", - fullyConsistent: true, - }, - }), - }); + permission: "view", + resourceObjectType: "test/document", + consistency: Consistency.create({ + requirement: { + oneofKind: "fullyConsistent", + fullyConsistent: true, + }, + }), + }); - const resStream = client.lookupResources(request); + const resStream = client.lookupResources(request); - resStream.on("data", function (response: LookupResourcesResponse) { - expect(response.resourceObjectId).toEqual("somedocument"); - }); + resStream.on("data", function (response: LookupResourcesResponse) { + expect(response.resourceObjectId).toEqual("somedocument"); + }); - resStream.on("end", function () { - client.close(); - done(); - }); + resStream.on("end", function () { + client.close(); + done(); + }); - resStream.on("error", function (e) { - client.close(); - fail(e); - }); + resStream.on("error", function (e) { + client.close(); + fail(e); + }); - resStream.on("status", function (status) { - expect(status.code).toEqual(grpc.status.OK); - }); - })); + resStream.on("status", function (status) { + expect(status.code).toEqual(grpc.status.OK); + }); + })); }); describe("a check with a negative timeout", () => { - it("should fail immediately", () => new Promise((done) => { - const resource = ObjectReference.create({ - objectType: "test/somenamespace", - objectId: "bar", - }); - - const testUser = ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", - }); - - const checkPermissionRequest = CheckPermissionRequest.create({ - resource, - permission: "someperm", - subject: SubjectReference.create({ - object: testUser, - }), - }); - - const client = NewClient( - generateTestToken("v1-test-unknown"), - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED, - PreconnectServices.NONE, - { - interceptors: [deadlineInterceptor(-100)], - } - ); - client.checkPermission(checkPermissionRequest, function (err, response) { - expect(response).toBe(undefined); - expect(err?.code).toBe(grpc.status.DEADLINE_EXCEEDED); - client.close(); - done(); - }); - })); + it("should fail immediately", () => + new Promise((done) => { + const resource = ObjectReference.create({ + objectType: "test/somenamespace", + objectId: "bar", + }); + + const testUser = ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }); + + const checkPermissionRequest = CheckPermissionRequest.create({ + resource, + permission: "someperm", + subject: SubjectReference.create({ + object: testUser, + }), + }); + + const client = NewClient( + generateTestToken("v1-test-unknown"), + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + PreconnectServices.NONE, + { + interceptors: [deadlineInterceptor(-100)], + }, + ); + client.checkPermission(checkPermissionRequest, function (err, response) { + expect(response).toBe(undefined); + expect(err?.code).toBe(grpc.status.DEADLINE_EXCEEDED); + client.close(); + done(); + }); + })); }); describe("Experimental Service", () => { let token: string; - beforeEach(() => new Promise((done) => { - token = generateTestToken("v1-experimental-service"); - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); + beforeEach( + () => + new Promise((done) => { + token = generateTestToken("v1-experimental-service"); + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); - const request = WriteSchemaRequest.create({ - schema: `definition test/user {} + const request = WriteSchemaRequest.create({ + schema: `definition test/user {} definition test/document { relation viewer: test/user permission view = viewer } `, - }); - - client.writeSchema(request, function (err) { - expect(err).toBe(null); - client.close(); - done(); - }); - })); - - it("can bulk import relationships", () => new Promise((done, fail) => { - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); - - const writeStream = client.bulkImportRelationships((err, value) => { - if (err) { - fail(err); - } + }); - expect(value?.numLoaded).toEqual("2"); - client.close(); - done(); - }); - - writeStream.on("error", (e) => { - fail(e); - }); - - const resource = ObjectReference.create({ - objectType: "test/document", - objectId: "somedocument", - }); - - writeStream.write( - BulkImportRelationshipsRequest.create({ - relationships: [ - Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", + client.writeSchema(request, function (err) { + expect(err).toBe(null); + client.close(); + done(); + }); + }), + ); + + it("can bulk import relationships", () => + new Promise((done, fail) => { + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + + const writeStream = client.bulkImportRelationships((err, value) => { + if (err) { + fail(err); + } + + expect(value?.numLoaded).toEqual("2"); + client.close(); + done(); + }); + + writeStream.on("error", (e) => { + fail(e); + }); + + const resource = ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }); + + writeStream.write( + BulkImportRelationshipsRequest.create({ + relationships: [ + Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }), }), }), - }), - Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser2", + Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser2", + }), }), }), - }), - ], - }) - ); - - writeStream.end(); - })); - - it("can bulk export relationships", () => new Promise((done, fail) => { - const client = NewClient( - token, - "localhost:50051", - ClientSecurity.INSECURE_LOCALHOST_ALLOWED - ); - - const resource = ObjectReference.create({ - objectType: "test/document", - objectId: "somedocument", - }); - - const writeRequest = WriteRelationshipsRequest.create({ - updates: [ - RelationshipUpdate.create({ - relationship: Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser", + ], + }), + ); + + writeStream.end(); + })); + + it("can bulk export relationships", () => + new Promise((done, fail) => { + const client = NewClient( + token, + "localhost:50051", + ClientSecurity.INSECURE_LOCALHOST_ALLOWED, + ); + + const resource = ObjectReference.create({ + objectType: "test/document", + objectId: "somedocument", + }); + + const writeRequest = WriteRelationshipsRequest.create({ + updates: [ + RelationshipUpdate.create({ + relationship: Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser", + }), }), }), + operation: RelationshipUpdate_Operation.CREATE, }), - operation: RelationshipUpdate_Operation.CREATE, - }), - RelationshipUpdate.create({ - relationship: Relationship.create({ - resource: resource, - relation: "viewer", - subject: SubjectReference.create({ - object: ObjectReference.create({ - objectType: "test/user", - objectId: "someuser2", + RelationshipUpdate.create({ + relationship: Relationship.create({ + resource: resource, + relation: "viewer", + subject: SubjectReference.create({ + object: ObjectReference.create({ + objectType: "test/user", + objectId: "someuser2", + }), }), }), + operation: RelationshipUpdate_Operation.CREATE, }), - operation: RelationshipUpdate_Operation.CREATE, - }), - ], - }); - - client.writeRelationships(writeRequest, function (err) { - expect(err).toBe(null); - - const resStream = client.bulkExportRelationships( - BulkExportRelationshipsRequest.create({ - consistency: Consistency.create({ - requirement: { - oneofKind: "fullyConsistent", - fullyConsistent: true, - }, - }), - }) - ); + ], + }); - resStream.on( - "data", - function (response: BulkExportRelationshipsResponse) { - expect(response.relationships).toEqual([ - { - relation: "viewer", - resource: { - objectType: "test/document", - objectId: "somedocument", - }, - subject: { - optionalRelation: "", - object: { objectType: "test/user", objectId: "someuser" }, + client.writeRelationships(writeRequest, function (err) { + expect(err).toBe(null); + + const resStream = client.bulkExportRelationships( + BulkExportRelationshipsRequest.create({ + consistency: Consistency.create({ + requirement: { + oneofKind: "fullyConsistent", + fullyConsistent: true, }, - }, - { - relation: "viewer", - resource: { - objectType: "test/document", - objectId: "somedocument", + }), + }), + ); + + resStream.on( + "data", + function (response: BulkExportRelationshipsResponse) { + expect(response.relationships).toEqual([ + { + relation: "viewer", + resource: { + objectType: "test/document", + objectId: "somedocument", + }, + subject: { + optionalRelation: "", + object: { objectType: "test/user", objectId: "someuser" }, + }, }, - subject: { - optionalRelation: "", - object: { objectType: "test/user", objectId: "someuser2" }, + { + relation: "viewer", + resource: { + objectType: "test/document", + objectId: "somedocument", + }, + subject: { + optionalRelation: "", + object: { objectType: "test/user", objectId: "someuser2" }, + }, }, - }, - ]); - } - ); + ]); + }, + ); - resStream.on("end", function () { - client.close(); - done(); - }); + resStream.on("end", function () { + client.close(); + done(); + }); - resStream.on("error", function (e) { - client.close(); - fail(e); - }); + resStream.on("error", function (e) { + client.close(); + fail(e); + }); - resStream.on("status", function (status) { - expect(status.code).toEqual(grpc.status.OK); + resStream.on("status", function (status) { + expect(status.code).toEqual(grpc.status.OK); + }); }); - }); - })); + })); }); diff --git a/src/v1.ts b/src/v1.ts index 1ae4bc0..e107e3f 100644 --- a/src/v1.ts +++ b/src/v1.ts @@ -4,19 +4,19 @@ import { promisify } from "util"; import * as grpc from "@grpc/grpc-js"; -import { ExperimentalServiceClient } from './authzedapi/authzed/api/v1/experimental_service.grpc-client.js'; -import { PermissionsServiceClient } from './authzedapi/authzed/api/v1/permission_service.grpc-client.js'; -import { SchemaServiceClient } from './authzedapi/authzed/api/v1/schema_service.grpc-client.js'; -import { WatchServiceClient } from './authzedapi/authzed/api/v1/watch_service.grpc-client.js'; -import * as util from './util.js'; +import { ExperimentalServiceClient } from "./authzedapi/authzed/api/v1/experimental_service.grpc-client.js"; +import { PermissionsServiceClient } from "./authzedapi/authzed/api/v1/permission_service.grpc-client.js"; +import { SchemaServiceClient } from "./authzedapi/authzed/api/v1/schema_service.grpc-client.js"; +import { WatchServiceClient } from "./authzedapi/authzed/api/v1/watch_service.grpc-client.js"; +import * as util from "./util.js"; import { ClientSecurity, PreconnectServices, deadlineInterceptor, promisifyStream, -} from './util.js'; +} from "./util.js"; -import type { OmitBaseMethods, PromisifiedClient } from './types.js'; +import type { OmitBaseMethods, PromisifiedClient } from "./types.js"; // A merge of the three generated gRPC clients, with their base methods omitted export type ZedDefaultClientInterface = OmitBaseMethods< @@ -26,7 +26,7 @@ export type ZedDefaultClientInterface = OmitBaseMethods< OmitBaseMethods & OmitBaseMethods & OmitBaseMethods & - Pick; + Pick; // The promisified version of the interface export type ZedPromiseClientInterface = @@ -34,7 +34,7 @@ export type ZedPromiseClientInterface = PromisifiedClient & PromisifiedClient & PromisifiedClient & - Pick; + Pick; // A combined client containing the root gRPC client methods and a promisified set at a "promises" key export type ZedClientInterface = ZedDefaultClientInterface & { @@ -58,7 +58,7 @@ class ZedClient implements ProxyHandler { private endpoint: string, private creds: grpc.ChannelCredentials, preconnect: PreconnectServices, - options: grpc.ClientOptions | undefined + options: grpc.ClientOptions | undefined, ) { this.options = { ...(options ?? {}), @@ -75,7 +75,7 @@ class ZedClient implements ProxyHandler { this.acl = new PermissionsServiceClient( this.endpoint, this.creds, - options + options, ); } if (preconnect & PreconnectServices.SCHEMA_SERVICE) { @@ -88,7 +88,7 @@ class ZedClient implements ProxyHandler { this.experimental = new ExperimentalServiceClient( this.endpoint, this.creds, - options + options, ); } } @@ -97,34 +97,34 @@ class ZedClient implements ProxyHandler { endpoint: string, creds: grpc.ChannelCredentials, preconnect: PreconnectServices, - options: grpc.ClientOptions | undefined + options: grpc.ClientOptions | undefined, ) { return new Proxy( {} as any, - new ZedClient(endpoint, creds, preconnect, options) + new ZedClient(endpoint, creds, preconnect, options), ); } close = () => { [this.acl, this.ns, this.watch, this.experimental].forEach((client) => - client?.close() + client?.close(), ); }; get(_target: object, name: string | symbol) { - if (name === 'close') { + if (name === "close") { return this.close; } // If the name is a symbol, pass it to the underlying gRPC code. // NOTE: it doesn't really matter to which client we give symbols, so just // pick ACL by default since its the most likely be used. - if (typeof name === 'symbol') { + if (typeof name === "symbol") { if (this.acl === undefined) { this.acl = new PermissionsServiceClient( this.endpoint, this.creds, - this.options + this.options, ); } @@ -137,7 +137,7 @@ class ZedClient implements ProxyHandler { this.acl = new PermissionsServiceClient( this.endpoint, this.creds, - this.options + this.options, ); } @@ -149,7 +149,7 @@ class ZedClient implements ProxyHandler { this.ns = new SchemaServiceClient( this.endpoint, this.creds, - this.options + this.options, ); } @@ -161,7 +161,7 @@ class ZedClient implements ProxyHandler { this.watch = new WatchServiceClient( this.endpoint, this.creds, - this.options + this.options, ); } @@ -173,7 +173,7 @@ class ZedClient implements ProxyHandler { this.experimental = new ExperimentalServiceClient( this.endpoint, this.creds, - this.options + this.options, ); } @@ -197,13 +197,16 @@ class ZedPromiseClient implements ProxyHandler { private client: ZedDefaultClientInterface; private promiseCache: Record = {}; private streamMethods = new Set([ - 'readRelationships', - 'lookupResources', - 'lookupSubjects', - 'bulkExportRelationships', - 'exportBulkRelationships', + "readRelationships", + "lookupResources", + "lookupSubjects", + "bulkExportRelationships", + "exportBulkRelationships", + ]); + private writableStreamMethods = new Set([ + "bulkImportRelationships", + "importBulkRelationships", ]); - private writableStreamMethods = new Set(['bulkImportRelationships', 'importBulkRelationships']); constructor(client: ZedDefaultClientInterface) { this.client = client; @@ -220,13 +223,13 @@ class ZedPromiseClient implements ProxyHandler { if (this.streamMethods.has(name as string)) { this.promiseCache[name as string] = promisifyStream( clientMethod, - this.client + this.client, ); } else if (this.writableStreamMethods.has(name as string)) { this.promiseCache[name as string] = clientMethod.bind(this.client); - } else if (typeof clientMethod === 'function') { + } else if (typeof clientMethod === "function") { this.promiseCache[name as string] = promisify( - (this.client as any)[name as string] + (this.client as any)[name as string], ).bind(this.client); } else { return clientMethod; @@ -251,7 +254,7 @@ class ZedCombinedClient implements ProxyHandler { constructor( client: ZedDefaultClientInterface, - promiseClient: ZedPromiseClientInterface + promiseClient: ZedPromiseClientInterface, ) { this.client = client; this.promiseClient = promiseClient; @@ -261,7 +264,7 @@ class ZedCombinedClient implements ProxyHandler { endpoint: string, creds: grpc.ChannelCredentials, preconnect: util.PreconnectServices, - options: grpc.ClientOptions | undefined + options: grpc.ClientOptions | undefined, ) { const client = ZedClient.create(endpoint, creds, preconnect, options); const promiseClient = ZedPromiseClient.create(client); @@ -269,7 +272,7 @@ class ZedCombinedClient implements ProxyHandler { } get(_target: object, name: string | symbol) { - if (name === 'promises') { + if (name === "promises") { return this.promiseClient; } @@ -291,7 +294,7 @@ export function NewClient( endpoint = util.authzedEndpoint, security: ClientSecurity = ClientSecurity.SECURE, preconnect: PreconnectServices = PreconnectServices.PERMISSIONS_SERVICE, - options: grpc.ClientOptions | undefined = undefined + options: grpc.ClientOptions | undefined = undefined, ) { const creds = util.createClientCreds(endpoint, token, security); return NewClientWithChannelCredentials(endpoint, creds, preconnect, options); @@ -311,7 +314,7 @@ export function NewClientWithCustomCert( endpoint = util.authzedEndpoint, certificate: Buffer, preconnect: PreconnectServices = PreconnectServices.PERMISSIONS_SERVICE, - options: grpc.ClientOptions | undefined = undefined + options: grpc.ClientOptions | undefined = undefined, ) { const creds = util.createClientCredsWithCustomCert(token, certificate); return NewClientWithChannelCredentials(endpoint, creds, preconnect, options); @@ -337,20 +340,20 @@ export function NewClientWithChannelCredentials( endpoint = util.authzedEndpoint, creds: grpc.ChannelCredentials, preconnect: PreconnectServices = PreconnectServices.PERMISSIONS_SERVICE, - options: grpc.ClientOptions | undefined = undefined + options: grpc.ClientOptions | undefined = undefined, ): ZedClientInterface { return ZedCombinedClient.create(endpoint, creds, preconnect, options); } -export * from './authzedapi/authzed/api/v1/core.js'; -export * from './authzedapi/authzed/api/v1/experimental_service.js'; -export * from './authzedapi/authzed/api/v1/experimental_service.grpc-client.js'; -export * from './authzedapi/authzed/api/v1/permission_service.js'; -export * from './authzedapi/authzed/api/v1/permission_service.grpc-client.js'; -export * from './authzedapi/authzed/api/v1/schema_service.js'; -export * from './authzedapi/authzed/api/v1/schema_service.grpc-client.js'; -export * from './authzedapi/authzed/api/v1/watch_service.js'; -export * from './authzedapi/authzed/api/v1/watch_service.grpc-client.js'; +export * from "./authzedapi/authzed/api/v1/core.js"; +export * from "./authzedapi/authzed/api/v1/experimental_service.js"; +export * from "./authzedapi/authzed/api/v1/experimental_service.grpc-client.js"; +export * from "./authzedapi/authzed/api/v1/permission_service.js"; +export * from "./authzedapi/authzed/api/v1/permission_service.grpc-client.js"; +export * from "./authzedapi/authzed/api/v1/schema_service.js"; +export * from "./authzedapi/authzed/api/v1/schema_service.grpc-client.js"; +export * from "./authzedapi/authzed/api/v1/watch_service.js"; +export * from "./authzedapi/authzed/api/v1/watch_service.grpc-client.js"; export { ClientSecurity } from "./util.js"; export default {