Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/builder/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { v1 } from '@authzed/authzed-node'
import { LookupResult, Operation, parseReference, SpiceDBClient } from './types'

// Do we really need to have a builder pattern for this?
// I mean, lookup-resources and lookup-subjects need to be able to take a resource or subject and a permission and a resource or subject type and return a list of results.

/**
* Lookup operation for finding accessible resources or subjects with permissions
*/
Expand Down Expand Up @@ -54,14 +57,19 @@ export class LookupOperation implements Operation<LookupResult[]> {
throw new Error('Lookup operation requires permission')
}

if (this.lookupType === 'resources' && this.subjectFilter) {
if (
this.lookupType === 'resources' &&
this.resourceFilter?.type &&
this.subjectFilter?.id &&
this.permission
) {
const request = v1.LookupResourcesRequest.create({
resourceObjectType: this.resourceFilter?.type || 'document',
resourceObjectType: this.resourceFilter.type,
permission: this.permission,
subject: v1.SubjectReference.create({
object: v1.ObjectReference.create({
objectType: this.subjectFilter.type,
objectId: this.subjectFilter.id!,
objectId: this.subjectFilter.id,
}),
}),
consistency: this.consistency,
Expand All @@ -73,7 +81,7 @@ export class LookupOperation implements Operation<LookupResult[]> {
for (const result of stream) {
if (result.resourceObjectId) {
results.push({
type: this.resourceFilter?.type || 'document',
type: this.resourceFilter.type,
id: result.resourceObjectId,
permissionship: result.permissionship,
})
Expand All @@ -82,14 +90,19 @@ export class LookupOperation implements Operation<LookupResult[]> {
return results
}

if (this.lookupType === 'subjects' && this.resourceFilter?.id) {
if (
this.lookupType === 'subjects' &&
this.resourceFilter?.id &&
this.subjectFilter?.type &&
this.permission
) {
const request = v1.LookupSubjectsRequest.create({
resource: v1.ObjectReference.create({
objectType: this.resourceFilter.type,
objectId: this.resourceFilter.id,
}),
permission: this.permission,
subjectObjectType: this.subjectFilter?.type || 'user',
subjectObjectType: this.subjectFilter.type,
consistency: this.consistency,
})

Expand All @@ -99,7 +112,7 @@ export class LookupOperation implements Operation<LookupResult[]> {
for (const result of stream) {
if (result.subject?.subjectObjectId) {
results.push({
type: this.subjectFilter?.type || 'user',
type: this.subjectFilter.type,
id: result.subject.subjectObjectId,
permissionship: result.subject.permissionship,
})
Expand Down
58 changes: 58 additions & 0 deletions src/generate-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export type Resource<T extends string> = \`\${T}:\${string}\`;
code += generateCheck(def)
// Generate find operations
code += generateFind(def)
// Generate lookup operations
code += generateLookup(def)
code += ` },\n`
}

Expand Down Expand Up @@ -134,6 +136,62 @@ function generateCheck(def: AugmentedObjectTypeDefinition): string {
return code
}

// --------------- LOOKUP OPERATIONS ---------------
// This should put a "lookup" operation for each definition with resources and subjects attributes each.
// Example:
// lookup: {
// resources: (subject: Subject<string>) => PermissionOperations.lookup('resourcesAccessibleBy').subject(subject),
// subjects: (resource: Resource<string>) => PermissionOperations.lookup('subjectsWithAccessTo').resource(resource),
// }
// Here are two examples of what they should be able to do, but we need something more generic.
// Example 1:
// PermissionOperations.lookup()
// .subjectsWithAccessTo(`space_old:${spaceId}`)
// .withPermission('collected')
// .ofType('collection')
// .withConsistency(assignResult || '')
// .execute(spice)

// Example 2:
// PermissionOperations.lookup()
// .resourcesAccessibleBy(`collection:${collectionId1}`)
// .withPermission('collected')
// .ofType('space_old')
// .withConsistency(assignResult || '')
// .execute(spice)

// lookup: {
// resources: (subject: Subject<string>) => PermissionOperations.lookup('resourcesAccessibleBy').subject(subject),
// subjects: (resource: Resource<string>) => PermissionOperations.lookup('subjectsWithAccessTo').resource(resource),
// }

// The lookup operation should be able to lookup resources by subject and subjects by resource.
// Can you think of a pattern that would allow us to generate the lookup operations for each definition with resources and subjects attributes each?
function generateLookup(def: AugmentedObjectTypeDefinition): string {
let code = ' lookup: {\n'
const permissionLiterals = [
...new Set(def.permissions.map(p => `'${p.name}'`)),
].join(' | ')

for (const operation of ['resources', 'subjects']) {
const typeLiterals = [
...new Set(
def.relations.flatMap(r => r.types.map(t => `'${t.typeName}'`)),
),
].join(' | ')

if (operation === 'resources') {
const subjectTypes = `Subject<${typeLiterals || 'never'}>`
code += ` ${operation}: (subject: ${subjectTypes}, permission: ${permissionLiterals}, resourceType: ${typeLiterals}) => PermissionOperations.lookup().resourcesAccessibleBy(subject).withPermission(permission).ofType(resourceType),\n`
} else {
const resourceTypes = `Resource<${typeLiterals || 'never'}>`
code += ` ${operation}: (resource: ${resourceTypes}, permission: ${permissionLiterals}, subjectType: ${typeLiterals}) => PermissionOperations.lookup().subjectsWithAccessTo(resource).withPermission(permission).ofType(subjectType),\n`
}
}
code += ' },\n'
return code
}

function toPascalCase(name: string): string {
return name
.split(/[_\-\s]+/)
Expand Down