cerbos / cerbos-sdk-javascript

JavaScript SDK for authorization via Cerbos
https://cerbos.dev
Apache License 2.0
72 stars 10 forks source link

typesafe attributes in client calls #540

Open mdugue opened 1 year ago

mdugue commented 1 year ago

We recently started stepping into cerbos as an AuthZ solutions and are very happy so far with the onboarding experience and the clean yet powerfull modelling possibilities. Great job 💪

I was wondering if there is a way to provide type safety to the attributes that get provided to Principal and Resource in @cerbos/grpc or @cerbos/http? One example that came to my mind is the usage of the schemas.

If we provide a schema for a resource policy, this schema could also be used for generating stricter swagger / OpenAPI SDKs. Are there any approaches / ideas / hints for achieving this or similar?

Thanks a lot in advance!

haines commented 1 year ago

Hi @mdugue! Being able to generate "smarter" clients from your actual policies and schemas is definitely a use case we are interested in supporting, but isn't likely to be implemented imminently.

For now, the approach I would take would be to generate TypeScript types from your principal and resource schemas (using e.g. json-schema-to-typescript), and create a wrapper around the Cerbos GRPC/HTTP client that uses the generate types to define type-safe versions of the API methods you want to use.

mdugue commented 1 year ago

Hey @haines, thanks a lot for your reply. Meanwhile we have started exploring this field and created an interim solution. Instead of creating a wrapper we opted for generating TypeScript Declaration files, that allows using the original cerbos API.

It's basically a codegen CLI npx @estino/cerbos-ts-codegen that outputs sth like:

// cerbos.d.ts
import type {
    Principal,
    IsAllowedRequest,
} from "@cerbos/core/lib/types/external"

declare module "@cerbos/http" {
    interface HTTP {
        isAllowed(request: isAllowedParams): Promise<boolean>
    }
    export interface ContractPrincipal {
        department: "tech" | "legal"
        organisation: string
        [k: string]: unknown
    }
    export interface Contract {
        organisation: string
        [k: string]: unknown
    }

    type isAllowedParamsContract = {
        principal: Principal & {
            attributes?: ContractPrincipal
        }
        resource: { kind: "contract"; attributes: Contract }
        action: "view" | "edit"
    }
    type isAllowedParams = IsAllowedRequest & isAllowedParamsContract
}

You can find more details on https://github.com/optiscaners/cerbos-ts-codegen What do you think about this approach, is it worth digging deeper here or do you see any architectonical disadvantages?