Giter Site home page Giter Site logo

swantzter / authzed-ts Goto Github PK

View Code? Open in Web Editor NEW
1.0 2.0 0.0 159 KB

A type-safe wrapper for @authzed/authzed-node using code generation

Home Page: https://swantzter.github.io/authzed-ts/

License: MIT License

JavaScript 2.16% TypeScript 97.84%
authzed authzed-client spicedb spicedb-client

authzed-ts's Introduction

Authzed TS

This project wraps @authzed/autzed-node and provides a higher level interface with optional type-safety through code generation.

The API is meant to somewhat mimic the zed-cli.

Usage

Code generation

npm install --save-dev @authzed-ts/generator

You'll need your schema either in a .zed or .zaml file (.yaml also works, it just needs to have a schema property in the root of the document) containing your schema, or you can provide a token to fetch it from a SpiceDB server.

This package only provides the command zed-ts-gen which you can use to create a ts file with the necessary types to make @authzed-ts/client type-safe, for details on how to do that, see ``

Client

npm install @authzed-ts/client

Whilst the client does work without the code-generation piece and provides a more abstracted interface to @authzed/authzed-node out of the box, it's of course recommended to use it with the code generation, so assuming you've generated an output file at ./client-type.ts you can then create your client as follows

import { v1 } from '@authzed/authzed-node'
import { ZedClient } from '@authzed-ts/client'
import { ZedClientType } from './client-type'

// You pass a client you created yourself so you can have full control over auth
// and certificates, etc. which are created using different methods on `v1`
const _client = v1.NewClient()

export const zedClient = new ZedClient<ZedClientType>(_client)

// the order is like a permission tuple, resource, permission, subject
void zedClient.checkPermission(['document', 1], 'group', ['group', 1])

void zedClient.lookupResources('document', 'group', ['group', 1], { caveatContext: { a: 1, c: { d: 'e' } } })
void zedClient.lookupResources('document', 'group', ['group', 1])

void zedClient.writeRelationships([
  zedClient.defineWriteRelationship(['document', 1], 'group', ['group', 1]),
  zedClient.defineWriteRelationship(['document', 1], 'reader', ['user', 1])
])

authzed-ts's People

Contributors

swantzter avatar

Stargazers

 avatar

Watchers

 avatar  avatar

authzed-ts's Issues

[client] alternative usage pattern

There was some comparison of abstractions in the Autzed discord, this client currently opts for TS tuples with [objectType, objectId], but another pattern that came up, which might be fairly realistic to implement on top of this client using the same codegen output types processed another way and a proxy or some object with wildcard getters. Examples of the result, freely extraplorated by me based on the first one as example

p.document('id1').view.check(p.user('abc'))

p.document('id1').edit.assert(p.user('abc'))

p.document.view.resources(p.user('def'), { info: 'this is the caveat context' })

[generation] resolve subject relations

Currently if a relation is defined with the # in them, the actual subject types aren't resolved but instead the left hand side of the # gets added as a valid subject

What needs to happen in the code below is that if objRef.target != null, it should be resolved by looking at the object type named on the left hand side (objRef.name), finding a permission or relation on said object named the right hand side (objRef.target), if it's a permission it has to be expanded with the arrow expression logic from #2 until it boils down to one or more object types that can be added as subject types

for (const relation of definition.relations) {
currentObj.relations[relation.name] ??= {}
for (const objRef of relation.objectReferences) {
currentObj.relations[relation.name][objRef.name] ??= {
caveat: [],
wildcard: false
}
if (objRef.caveat) currentObj.relations[relation.name][objRef.name].caveat.push(objRef.caveat)
if (objRef.wildcard) currentObj.relations[relation.name][objRef.name].wildcard = true
}
}

It is likely this needs to happen in a second pass to be able to validate that everything exists - or we just skip validations for these and hope for the best

[generator] Resolve arrow references in permissions

Basically, we need recursion.

When an arrow reference (relation->relationOrPermission) is encountered we need to look at the relation on the current object definition, and from that expand the types it references until we have reached all possible object types that can be the subject type.

Given this schema:

definition user {}

definition robot {}

definition lev1 {
    relation member: user | robot
}

definition lev2 {
    relation rel: lev1
    relation direct_member: user
    permission member = rel->member + user
}

definition lev3 {
    relation rel: lev2 | user
    permission member = rel->member
}

to resolve the member permission on lev3 we need to look at the relation from the left hand side of the arrow expression: rel, which points to either a lev2 or a user.
We must then try to find the right hand side of the arrow expression on either of these relations.

On user it is not found either as a permission or relation. Thus we discard this branch

On lev2 however we find a permission named member, and, since it is a permission we must expand it.
It has a direct reference we can resolve to get our first path { objectType: 'lev2', relation: 'direct_member' } and another arrow expression we must expand the same way we just did. We look at the relation defined on lev2 that's references on the right hand side of this new arrow expression: rel, which points to a lev1

On lev1 we try to find a permission named member, we don't, so we try to find a relation named member and we do! this means our search is complete and we get our second path: { objectType: 'lev1', relation: 'member' }

these two relations gets added as the resulting array at the TODO marker here:

if (objRef.target) {
const key = `${objRef.name}->${objRef.target}`
// We've already processed this reference, but it might've been included again in another parenthesis or what have you
if (key in currentObj.permissions[permission.name]) continue
const resolvedRelation = currentObj.relations[objRef.name]
if (!resolvedRelation) throw new TypeError(`Left hand of arrow expression must reference a relation in the same object, '${key}' on '${definition.name}' does not`)
// TODO: resolveArrowReference(definitions.objects, resolvedRelation, objRef.target)
} else {

basically: currentObj.permissions[permission.name] ??= resolveArrowReference(whateverArgsAreNeeded)

[client+generator] writePermissions should take tuples with subject relation

likely dependent on #3

Today given a schema

definition user {}

definition group {
  relation memeber: user
}

definition document {
  relation reader: user | group#member
}

We'd expect to be able to write the relation zedClient.defineWriteRelationship(['document', 1], 'reader', ['group', 1, 'member']), but here group is a forbidden subject type. maybe special typings are needed for the subject tuple in three-item form?

[client] assertPermission

basically the same as checkPermission, but instead of returing true or false it throws if the permission isn't granted.

It's probably best implemented as a very thin wrapper over checkPermission and it might be possible to reference the types of checkPermission as to avoid having to compy-paste

[generator] Add CEL tokens to Lexer, skip caveat function body

As it stands the parser is unable to handle schemas with caveats with function bodies. After dome discussion it seems what needs to be done is simply adding all the tokens CEL has (or maybe just one token with a regex matching all currently uncovered CEL tokens, if you want to be lazy) and add them, and the existing tokens that are also valid zed tokens to a category token.

then you'd simply add a this.MANY(() => this.CONSUME(CelToken)) to the commented out line here:

public caveatDefinition = this.RULE('caveatDefinition', () => {
this.CONSUME(Caveat)
this.SUBRULE(this.typePath)
this.CONSUME(LParen)
this.AT_LEAST_ONE_SEP({
SEP: Comma,
DEF: () => {
this.SUBRULE(this.caveatParameter)
}
})
this.CONSUME(RParen)
this.CONSUME(LCurly)
// TODO: this.SUBRULE(this.caveatExpression)
this.CONSUME(RCurly)
})

The caveatExpression could then also be removed:

public caveatExpression = this.RULE('caveatExpression', () => {
// TODO: preferably we just skip this, I do'nt want to implement a CEL parser too
})
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.