authz

package
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2025 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package authz provides a plugin for implementing role-based access control (RBAC). It uses protocol buffer annotations to define authorization rules that are enforced by a GRPC interceptor.

Getting Started.

The simplest way to get started with authorization is to define policies and register object fetchers and role describers:

authzPlugin := authz.Plugin(
	// Define policies (effect, role, action)
	authz.WithPolicy(authz.Allow, authz.RoleViewer, authz.Action("documents.view")),
	authz.WithPolicy(authz.Allow, authz.RoleEditor, authz.Action("documents.edit")),
	authz.WithPolicy(authz.Allow, authz.RoleAdmin, authz.Action("*")),

	// Register object fetcher
	authz.WithObjectFetcher("document", authz.AsObjectFetcher(
		authz.Fetcher(db.GetDocumentByID),
	)),

	// Register role describer
	authz.WithRoleDescriber("document", authz.Compose(
		authz.OwnershipRole(authz.RoleOwner, func(d *Document) string {
			return d.OwnerID
		}),
	)),
)

// Add to your Prefab server.
server := prefab.New(
	prefab.WithPlugin(authzPlugin),
	// Other plugins and options.
)

Core Concepts

Authz Policies are defined in terms of roles and actions, both of which are application defined strings. For example, an "editor" role might be allowed to perform the "document.edit" action.

Roles are context dependent and determined by application provided functions called "Role Describers". Role Describers return a list of roles for a given authenticated identity and object. For example, a user may have the role "owner" for a specific document and "admin" for their workspace.

Role Describers can chose to restrict whether a role is granted, based on other attributes. For example, an "admin" role could only be granted if the request comes from a specific IP address.

Role Describers can also be configured to accept a `scope` from the request. This is optional and is intended to simplify the implementation of multi-tenant systems or systems where a user might be part of multiple workspaces or groups, each with different permissions. The scope represents the "container" of the object being accessed (e.g., Document=Object, Folder=Scope).

To map an incoming request to a resource, the Authz plugin uses "Object Fetchers". Fetchers can be registered against a key, which can be an arbitrary string, or derived from `reflect.Type`. The fetcher is then called with the value of a request parameter, per the field option.

RPCs can be configured with a default effect of Allow. For example, a page might be configured to allow all users to view it, except those on mobile devices.

Protocol Buffer Annotations

To use authorization, you need to annotate your protocol buffer definitions:

rpc GetDocument(GetDocumentRequest) returns (GetDocumentResponse) {
  option (prefab.authz.action) = "documents.view";
  option (prefab.authz.resource) = "document";
  option (prefab.authz.default_effect) = "deny"; // Optional, defaults to "deny"
}

message GetDocumentRequest {
  string org_id = 1 [(prefab.authz.scope) = true]; // Optional scope (e.g., workspace, org).
  string document_id = 2 [(prefab.authz.id) = true]; // Required to identify resource.
}

Common Patterns

This package provides several common patterns to simplify authorization setup:

- Builder pattern: Use `NewBuilder()` for a fluent configuration interface. - Predefined roles: `RoleAdmin`, `RoleEditor`, `RoleViewer`, etc. - Common CRUD actions: `ActionCreate`, `ActionRead`, etc. - Type-safe interfaces: Use the typed helpers for compile-time type safety.

Role Describer Patterns

The package provides composable, type-safe patterns for building role describers that eliminate boilerplate and manual type assertions:

authz.Compose(
    // Grant owner role if user owns the document
    authz.OwnershipRole(authz.RoleOwner, func(doc *Document) string {
        return doc.OwnerID
    }),

    // Grant viewer role if document is published
    authz.StaticRole(authz.RoleViewer, func(_ context.Context, _ auth.Identity, doc *Document) bool {
        return doc.Published
    }),

    // Grant roles based on organization membership
    authz.MembershipRoles(
        func(doc *Document) string { return doc.OrgID },
        func(ctx context.Context, orgID string, identity auth.Identity) ([]authz.Role, error) {
            org, err := fetchOrg(ctx, orgID)
            if err != nil {
                return nil, err
            }
            return org.GetUserRoles(ctx, identity.Subject)
        },
    ),
)

Available patterns: - Compose: Combines multiple describers with automatic scope validation - OwnershipRole: Grants role if user owns the resource - IdentityOwnershipRole: Grants role when identity resolves to owner (async, workspace-scoped) - ConditionalRole: Grants role based on async predicate (for database queries) - StaticRole: Grants role based on sync predicate (for simple conditions) - StaticRoles: Returns multiple roles based on conditions - GlobalRole: Grants role based on context only (e.g., superuser checks) - MembershipRoles: Grants roles based on scope membership with automatic scope validation

See role_patterns.go for detailed documentation on each pattern.

Object Fetcher Patterns

The package provides composable, type-safe patterns for building object fetchers that eliminate boilerplate and manual type assertions:

// Simple map-based fetcher (common for tests/examples)
builder.WithObjectFetcher("document", authz.AsObjectFetcher(
    authz.MapFetcher(staticDocuments),
))

// Database fetcher with type safety
builder.WithObjectFetcher("user", authz.AsObjectFetcher(
    authz.Fetcher(func(ctx context.Context, id string) (*User, error) {
        return db.GetUserByID(ctx, id)
    }),
))

// Composed fetcher with caching and validation
builder.WithObjectFetcher("org", authz.AsObjectFetcher(
    authz.ComposeFetchers(
        authz.MapFetcher(cache),           // Try cache first
        authz.ValidatedFetcher(            // Then validated DB fetch
            authz.Fetcher(db.GetOrgByID),
            func(org *Org) error {
                if org.Deleted {
                    return errors.NewC("org deleted", codes.NotFound)
                }
                return nil
            },
        ),
    ),
))

Available patterns: - Fetcher: Type-safe wrapper for fetch functions - MapFetcher: Fetch from static maps - ValidatedFetcher: Add validation to fetched objects - ComposeFetchers: Try multiple fetchers in order (cache → DB → API) - TransformKey: Transform key before fetching - DefaultFetcher: Return default instead of error

See fetcher_patterns.go for detailed documentation on each pattern.

Role Hierarchy

You can establish a role hierarchy where parent roles inherit child roles:

authz.WithRoleHierarchy(RoleAdmin, RoleEditor, RoleViewer, RoleUser)

In this example, admins inherit all editor permissions, editors inherit viewer permissions, and viewers inherit user permissions.

Examples

For complete examples, see: - examples/authz/custom/authzexample.go (fully custom configuration) - examples/authz/builder/authzexample.go (common builder pattern) - examples/authz/patterns/authzexample.go (role describer patterns)

Index

Constants

View Source
const (
	RoleAdmin  = Role("admin")
	RoleEditor = Role("editor")
	RoleViewer = Role("viewer")
	RoleOwner  = Role("owner")
	RoleUser   = Role("user")
)

Common predefined roles.

View Source
const (
	ActionCreate = Action("create")
	ActionRead   = Action("read")
	ActionUpdate = Action("update")
	ActionDelete = Action("delete")
	ActionList   = Action("list")
)

Common predefined actions.

View Source
const PluginName = "authz"

Constant name for identifying the core Authz plugin.

Variables

View Source
var (
	// optional string action = 50011;
	E_Action = &file_plugins_authz_authz_proto_extTypes[0]
	// optional string resource = 50012;
	E_Resource = &file_plugins_authz_authz_proto_extTypes[1]
	// optional string default_effect = 50013;
	E_DefaultEffect = &file_plugins_authz_authz_proto_extTypes[2]
)

Extension fields to descriptorpb.MethodOptions.

View Source
var (
	// optional bool id = 50021;
	E_Id = &file_plugins_authz_authz_proto_extTypes[3]
	// optional bool domain = 50022;
	E_Domain = &file_plugins_authz_authz_proto_extTypes[4] // Deprecated: use scope instead
	// optional bool scope = 50023;
	E_Scope = &file_plugins_authz_authz_proto_extTypes[5]
)

Extension fields to descriptorpb.FieldOptions.

View Source
var (
	ErrPermissionDenied = errors.Codef(codes.PermissionDenied, "you are not authorized to perform this action")
	ErrUnauthenticated  = errors.Codef(codes.Unauthenticated, "the requested action requires authentication")
)
View Source
var File_plugins_authz_authz_proto protoreflect.FileDescriptor

Functions

func FieldOptions

func FieldOptions(req proto.Message) (any, string, error)

FieldOptions returns proto fields that are tagged with Authz related options. It returns the object ID and scope string.

func MarkScopeValidated added in v0.3.0

func MarkScopeValidated(ctx context.Context) context.Context

MarkScopeValidated marks the context as having performed explicit scope validation. This is used by role describers to indicate they checked that the object's scope matches the authorization scope parameter.

func MethodOptions

func MethodOptions(info *grpc.UnaryServerInfo) (objectKey string, action Action, defaultEffect Effect)

MethodOptions returns Authz related method options from the method descriptor. associated with the given info..

func WasScopeValidated added in v0.3.0

func WasScopeValidated(ctx context.Context) bool

WasScopeValidated returns true if scope validation was explicitly performed.

Types

type Action

type Action string

type AuditLogger added in v0.3.0

type AuditLogger func(ctx context.Context, decision AuthzDecision)

AuditLogger is a function that receives authorization decisions for audit logging.

type AuthorizeParams

type AuthorizeParams struct {
	ObjectKey     string
	ObjectID      any
	Scope         Scope
	Action        Action
	DefaultEffect Effect
	Info          string
}

Parameters for the Authorize method.

type AuthzDecision added in v0.3.0

type AuthzDecision struct {
	Action            Action
	Resource          string
	ObjectID          any
	Scope             Scope
	ScopeValidated    bool // Whether scope was explicitly validated by role describer
	Identity          auth.Identity
	Roles             []Role
	Effect            Effect
	DefaultEffect     Effect
	Reason            string
	EvaluatedPolicies []PolicyEvaluation
}

AuthzDecision captures the complete authorization decision context. This is useful for debugging, audit logging, and providing detailed error messages.

type AuthzObject added in v0.2.0

type AuthzObject interface {
	// AuthzType returns a string identifier for the object type
	AuthzType() string
}

AuthzObject is the base interface for all objects used in authorization. While not strictly necessary, it is recommended to implement this interface for type safety.

type AuthzOption

type AuthzOption func(*AuthzPlugin)

Configuration options for the Authz Plugin.

func WithAuditLogger added in v0.3.0

func WithAuditLogger(logger AuditLogger) AuthzOption

WithAuditLogger configures an audit logger to receive all authorization decisions. The audit logger is called for both allowed and denied requests, providing complete visibility into authorization decisions for compliance and security monitoring.

Example:

authz.WithAuditLogger(func(ctx context.Context, decision authz.AuthzDecision) {
    log.Printf("authz: user=%s action=%s resource=%s effect=%s",
        decision.Identity.Subject, decision.Action, decision.Resource, decision.Effect)
})

func WithObjectFetcher

func WithObjectFetcher(objectKey string, fetcher ObjectFetcher) AuthzOption

WithObjectFetcher adds an object fetcher to the plugin.

func WithObjectFetcherFn added in v0.2.0

func WithObjectFetcherFn(objectKey string, fetcher func(ctx context.Context, key any) (any, error)) AuthzOption

WithObjectFetcherFn adds a function-based object fetcher to the plugin.

func WithPolicy

func WithPolicy(effect Effect, role Role, action Action) AuthzOption

WithPolicy adds an Authz policy to the plugin.

func WithRoleDescriber

func WithRoleDescriber(objectKey string, describer RoleDescriber) AuthzOption

WithRoleDescriber adds a role describer to the plugin.

func WithRoleDescriberFn added in v0.2.0

func WithRoleDescriberFn(objectKey string, describer func(ctx context.Context, subject auth.Identity, object any, scope Scope) ([]Role, error)) AuthzOption

WithRoleDescriberFn adds a function-based role describer to the plugin.

func WithRoleHierarchy

func WithRoleHierarchy(roles ...Role) AuthzOption

WithRoleHierarchy configures the plugin with a hierarchy of roles.

The first role is the most powerful, and the last role has no hierarchy from a single call. Multiple calls can be used to define a tree hierarchies.

Example:

WithRoleHierarchy("owner", "admin", "editor", "viewer", "member")
WithRoleHierarchy("suggester", "viewer")

In this example, the "owner" role is an "admin", "editor", "viewer", and "member". An "admin" is an "editor", "viewer", and "member". An "editor" is also a "viewer" and a "member".

A "suggester" is a "viewer" and a "member", since the ancestry of "viewer" was defined by the previous call.

func WithTypeRegistration added in v0.2.0

func WithTypeRegistration[K comparable, T any](objectKey string, fetcher TypedObjectFetcher[K, T], describer TypedRoleDescriber[T]) AuthzOption

WithTypeRegistration registers a role describer and object fetcher for a specific type.. Since Go doesn't support generic methods, this is provided as a package function..

func WithTypedObjectFetcher added in v0.2.0

func WithTypedObjectFetcher[K comparable, T any](objectKey string, fetcher TypedObjectFetcher[K, T]) AuthzOption

WithTypedObjectFetcher adds a type-safe object fetcher to the builder.. Since Go doesn't support generic methods, this is provided as a package function..

func WithTypedRoleDescriber added in v0.2.0

func WithTypedRoleDescriber[T any](objectKey string, describer TypedRoleDescriber[T]) AuthzOption

WithTypedRoleDescriber adds a type-safe role describer to the builder.. Since Go doesn't support generic methods, this is provided as a package function..

type AuthzPlugin

type AuthzPlugin struct {
	// contains filtered or unexported fields
}

AuthzPlugin provides functionality for authorizing requests and access to resources.

func Plugin

func Plugin(opts ...AuthzOption) *AuthzPlugin

Plugin returns a new AuthzPlugin.

func (*AuthzPlugin) Authorize

func (ap *AuthzPlugin) Authorize(ctx context.Context, cfg AuthorizeParams) error

Authorize takes the configuration and verifies that the caller is authorized to perform the action on the object.

func (*AuthzPlugin) DebugHandler

func (ap *AuthzPlugin) DebugHandler(resp http.ResponseWriter, req *http.Request)

DebugHandler renders information about registered policies and roles.

func (*AuthzPlugin) DefinePolicy

func (ap *AuthzPlugin) DefinePolicy(effect Effect, role Role, action Action)

DefinePolicy defines an policy which allows/denies the given role to perform the action.

func (*AuthzPlugin) Deps

func (ap *AuthzPlugin) Deps() []string

From plugin.DependentPlugin.

func (*AuthzPlugin) DetermineEffect

func (ap *AuthzPlugin) DetermineEffect(action Action, roles []Role, defaultEffect Effect) (Effect, []PolicyEvaluation)

DetermineEffect determines if a user can perform an action using AWS IAM-style precedence:

1. Explicit Deny: If ANY role has a Deny policy for the action → Deny 2. Explicit Allow: If ANY role has an Allow policy (and no Deny) → Allow 3. Default Effect: If no policies match → Use the RPC's default_effect

This precedence model makes authorization predictable and secure: - Deny policies can block access even when other roles would grant it - Useful for creating "blocklist" roles or temporary access restrictions - Aligns with AWS IAM and other industry-standard access control systems

Example: User has roles [admin, blocked-user]

  • Policy: admin → Allow write
  • Policy: blocked-user → Deny write
  • Result: Deny (explicit deny wins)

Returns the final effect and a list of evaluated policies for debugging/auditing.

func (*AuthzPlugin) Interceptor

func (ap *AuthzPlugin) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)

Interceptor that enforces authorization policies configured on the GRPC service descriptors.

This interceptor: 1. Uses method options to get object key and action. 2. Uses proto field options to get an object id and optionally scope. 3. Fetches the object based on the object key and id (ObjectFetcher). 4. Gets the user's role relative to the object (RoleDescriber). 5. Checks if the role can perform the action on the object.

func (*AuthzPlugin) Name

func (ap *AuthzPlugin) Name() string

From plugin.Plugin.

func (*AuthzPlugin) RegisterObjectFetcher

func (ap *AuthzPlugin) RegisterObjectFetcher(objectKey string, fetcher ObjectFetcher)

RegisterObjectFetcher registers an object fetcher for a specified object key. '*' can be used as a wildcard to match any key which doesn't have a more specific fetcher.

func (*AuthzPlugin) RegisterRoleDescriber

func (ap *AuthzPlugin) RegisterRoleDescriber(objectKey string, describer RoleDescriber)

RegisterRoleDescriber registers a role describer for a specified object key. '*' can be used as a wildcard to match any key which doesn't have a more specific describer.

func (*AuthzPlugin) RoleHierarchy

func (ap *AuthzPlugin) RoleHierarchy(role Role) []Role

RoleHierarchy returns the ancestry of a single role.

func (*AuthzPlugin) RoleTree

func (ap *AuthzPlugin) RoleTree() map[Role][]Role

RoleTree returns the hierarchy of roles in tree form.

func (*AuthzPlugin) ServerOptions

func (ap *AuthzPlugin) ServerOptions() []prefab.ServerOption

From prefab.OptionProvider, registers an additional interceptor.

func (*AuthzPlugin) SetRoleHierarchy

func (ap *AuthzPlugin) SetRoleHierarchy(roles ...Role)

SetRoleHierarchy sets the hierarchy of roles.

type Builder added in v0.2.0

type Builder struct {
	// contains filtered or unexported fields
}

Builder provides a fluent interface for configuring the authz plugin.

func NewBuilder added in v0.2.0

func NewBuilder() *Builder

NewBuilder creates a new builder for the authz plugin.

func NewCommonBuilder added in v0.2.0

func NewCommonBuilder() *Builder

NewCommonBuilder creates a new builder with common configuration including predefined roles and a standard role hierarchy with CRUD permissions.

Role hierarchy: Admin > Editor > Viewer > User

Default permissions: - Admin can do everything - Editor can create, read, update, list - Viewer can read and list - User has minimal permissions

The builder can be further customized by adding additional policies, object fetchers, and role describers.

func (*Builder) Build added in v0.2.0

func (b *Builder) Build() *AuthzPlugin

Build finalizes the builder and returns the configured plugin.

func (*Builder) WithObjectFetcher added in v0.2.0

func (b *Builder) WithObjectFetcher(objectKey string, fetcher ObjectFetcher) *Builder

WithObjectFetcher adds an object fetcher to the builder.

func (*Builder) WithObjectFetcherFn added in v0.2.0

func (b *Builder) WithObjectFetcherFn(objectKey string, fetcher func(ctx context.Context, key any) (any, error)) *Builder

WithObjectFetcherFn adds a function-based object fetcher to the builder.

func (*Builder) WithPolicy added in v0.2.0

func (b *Builder) WithPolicy(effect Effect, role Role, action Action) *Builder

WithPolicy adds a policy to the builder.

func (*Builder) WithRoleDescriber added in v0.2.0

func (b *Builder) WithRoleDescriber(objectKey string, describer RoleDescriber) *Builder

WithRoleDescriber adds a role describer to the builder.

func (*Builder) WithRoleDescriberFn added in v0.2.0

func (b *Builder) WithRoleDescriberFn(objectKey string, describer func(ctx context.Context, subject auth.Identity, object any, scope Scope) ([]Role, error)) *Builder

WithRoleDescriberFn adds a function-based role describer to the builder.

func (*Builder) WithRoleHierarchy added in v0.2.0

func (b *Builder) WithRoleHierarchy(roles ...Role) *Builder

WithRoleHierarchy adds a role hierarchy to the builder.

func (*Builder) WithTypedObjectFetcher added in v0.2.0

func (b *Builder) WithTypedObjectFetcher(objectKey string, fetcher interface{}) *Builder

WithTypedObjectFetcher adds a type-safe object fetcher using a helper function.

func (*Builder) WithTypedRoleDescriber added in v0.2.0

func (b *Builder) WithTypedRoleDescriber(objectKey string, describer interface{}) *Builder

WithTypedRoleDescriber adds a type-safe role describer using a helper function.

type Effect

type Effect int
const (
	Deny Effect = iota
	Allow
)

func (Effect) Reverse

func (e Effect) Reverse() Effect

func (Effect) String

func (e Effect) String() string

type ObjectFetcher

type ObjectFetcher interface {
	// FetchObject retrieves an object based on the provided key
	FetchObject(ctx context.Context, key any) (any, error)
}

ObjectFetcher is an interface for fetching objects based on a request parameter.

func AsObjectFetcher added in v0.2.0

func AsObjectFetcher[K comparable, T any](fetcher TypedObjectFetcher[K, T]) ObjectFetcher

AsObjectFetcher converts a TypedObjectFetcher to the ObjectFetcher interface.

type ObjectFetcherFn added in v0.2.0

type ObjectFetcherFn func(ctx context.Context, key any) (any, error)

ObjectFetcherFn adapts a function to the ObjectFetcher interface.

func (ObjectFetcherFn) FetchObject added in v0.2.0

func (f ObjectFetcherFn) FetchObject(ctx context.Context, key any) (any, error)

FetchObject implements the ObjectFetcher interface.

type OwnedObject added in v0.2.0

type OwnedObject interface {
	AuthzObject
	// OwnerID returns the ID of the object's owner
	OwnerID() string
}

OwnedObject represents objects that have an owner.

type PolicyEvaluation added in v0.3.0

type PolicyEvaluation struct {
	Role   Role
	Effect Effect
}

PolicyEvaluation represents a single policy evaluation result.

type Role

type Role string

type RoleDescriber

type RoleDescriber interface {
	// DescribeRoles determines the roles a subject has relative to an object in a scope
	DescribeRoles(ctx context.Context, subject auth.Identity, object any, scope Scope) ([]Role, error)
}

RoleDescriber is an interface for describing roles relative to a type.

func AsRoleDescriber added in v0.2.0

func AsRoleDescriber[T any](describer TypedRoleDescriber[T]) RoleDescriber

AsRoleDescriber converts a TypedRoleDescriber to the RoleDescriber interface.

func Compose added in v0.3.0

func Compose[T any](describers ...TypedRoleDescriber[T]) RoleDescriber

Compose combines multiple typed role describers into a single RoleDescriber. All describers are called and their results are merged.

If the object implements ScopedObject, the scope is automatically validated before calling any describers. If the scope doesn't match, an empty role list is returned.

Type safety: The generic type T ensures all describers operate on the same object type, eliminating manual type assertions.

Example:

authz.Compose(
    authz.RoleOwner.IfOwner(func(doc *Document) string { return doc.OwnerID }),
    authz.StaticRole(authz.RoleViewer, func(_ context.Context, _ auth.Identity, doc *Document) bool {
        return doc.Published
    }),
)

type RoleDescriberFn added in v0.2.0

type RoleDescriberFn func(ctx context.Context, subject auth.Identity, object any, scope Scope) ([]Role, error)

RoleDescriberFn adapts a function to the RoleDescriber interface..

func (RoleDescriberFn) DescribeRoles added in v0.2.0

func (f RoleDescriberFn) DescribeRoles(ctx context.Context, subject auth.Identity, object any, scope Scope) ([]Role, error)

DescribeRoles implements the RoleDescriber interface.

type Scope added in v0.2.0

type Scope string

Scope defines the context in which authorization occurs (e.g., organization, workspace).

type ScopedObject added in v0.2.0

type ScopedObject interface {
	AuthzObject
	// ScopeID returns the scope ID
	ScopeID() string
}

ScopedObject represents objects that belong to a specific scope.

type TypedBuilder added in v0.2.0

type TypedBuilder[T any] struct {
	// contains filtered or unexported fields
}

TypedBuilder is a specialized builder wrapper for working with a specific type. This allows for compile-time type checking.

func NewTypedBuilder added in v0.2.0

func NewTypedBuilder[T any]() *TypedBuilder[T]

NewTypedBuilder creates a new typed builder for a specific type.

func (*TypedBuilder[T]) Build added in v0.2.0

func (tb *TypedBuilder[T]) Build() *AuthzPlugin

Build finalizes the typed builder and returns the plugin.

func (*TypedBuilder[T]) WithIntObjectFetcher added in v0.2.0

func (tb *TypedBuilder[T]) WithIntObjectFetcher(objectKey string, fetcher func(ctx context.Context, key int) (T, error)) *TypedBuilder[T]

WithIntObjectFetcher adds a type-safe object fetcher with int keys.

func (*TypedBuilder[T]) WithPolicy added in v0.2.0

func (tb *TypedBuilder[T]) WithPolicy(effect Effect, role Role, action Action) *TypedBuilder[T]

WithPolicy adds a policy to the builder.

func (*TypedBuilder[T]) WithRoleDescriber added in v0.2.0

func (tb *TypedBuilder[T]) WithRoleDescriber(objectKey string, describer func(ctx context.Context, subject auth.Identity, object T, scope Scope) ([]Role, error)) *TypedBuilder[T]

WithRoleDescriber adds a type-safe role describer.

func (*TypedBuilder[T]) WithRoleHierarchy added in v0.2.0

func (tb *TypedBuilder[T]) WithRoleHierarchy(roles ...Role) *TypedBuilder[T]

WithRoleHierarchy adds a role hierarchy to the builder.

func (*TypedBuilder[T]) WithStringObjectFetcher added in v0.2.0

func (tb *TypedBuilder[T]) WithStringObjectFetcher(objectKey string, fetcher func(ctx context.Context, key string) (T, error)) *TypedBuilder[T]

WithStringObjectFetcher adds a type-safe object fetcher with string keys.

type TypedObjectFetcher added in v0.2.0

type TypedObjectFetcher[K comparable, T any] func(ctx context.Context, key K) (T, error)

TypedObjectFetcher is a function type for fetching objects with type safety.

func ComposeFetchers added in v0.3.0

func ComposeFetchers[K comparable, T any](fetchers ...TypedObjectFetcher[K, T]) TypedObjectFetcher[K, T]

ComposeFetchers tries multiple fetchers in order until one succeeds. This is useful for implementing fallback strategies like: - Try cache, then database - Try primary database, then replica - Try local store, then remote API

Returns the first successful result, or the last error if all fail.

Example:

authz.ComposeFetchers(
    authz.MapFetcher(cache),           // Try cache first
    authz.Fetcher(db.GetDocumentByID), // Then database
    authz.Fetcher(api.FetchDocument),  // Finally remote API
)

func DefaultFetcher added in v0.3.0

func DefaultFetcher[K comparable, T any](
	fetcher TypedObjectFetcher[K, T],
	defaultValue T,
) TypedObjectFetcher[K, T]

DefaultFetcher wraps a fetcher to return a default value instead of an error. This is useful for optional resources or graceful degradation.

Example:

// Return a guest user if user not found
authz.DefaultFetcher(
    authz.Fetcher(db.GetUserByID),
    &User{ID: "guest", Name: "Guest User"},
)

func Fetcher added in v0.3.0

func Fetcher[K comparable, T any](fetch func(context.Context, K) (T, error)) TypedObjectFetcher[K, T]

Fetcher creates a type-safe object fetcher from a function. This is the foundational pattern - it wraps your fetch logic with type safety, eliminating manual type assertions.

Use this when you have a function that fetches an object by ID, whether from a database, API, cache, or any other source.

Example:

// Database fetch
authz.Fetcher(func(ctx context.Context, id string) (*Document, error) {
    return db.GetDocumentByID(ctx, id)
})

// Or pass the function directly if signatures match
authz.Fetcher(db.GetDocumentByID)

func MapFetcher added in v0.3.0

func MapFetcher[K comparable, T any](m map[K]T) TypedObjectFetcher[K, T]

MapFetcher creates an object fetcher from a static map. This is commonly used in examples and tests, or for small static datasets.

Returns NotFound error if the key doesn't exist in the map.

Example:

staticDocuments := map[string]*Document{
    "1": {ID: "1", Title: "Doc 1"},
    "2": {ID: "2", Title: "Doc 2"},
}
authz.MapFetcher(staticDocuments)

func TransformKey added in v0.3.0

func TransformKey[K1, K2 comparable, T any](
	transform func(K1) K2,
	fetcher TypedObjectFetcher[K2, T],
) TypedObjectFetcher[K1, T]

TransformKey wraps a fetcher to transform the key before fetching. This is useful when the incoming key format differs from what the fetcher expects.

Example:

// Convert string IDs to int IDs
authz.TransformKey(
    func(id string) int { return parseID(id) },
    authz.Fetcher(db.GetDocumentByNumericID),
)

func ValidatedFetcher added in v0.3.0

func ValidatedFetcher[K comparable, T any](
	fetcher TypedObjectFetcher[K, T],
	validate func(T) error,
) TypedObjectFetcher[K, T]

ValidatedFetcher wraps an object fetcher with validation logic. The validator is called after a successful fetch and can reject the object by returning an error.

This is useful for enforcing additional constraints like soft-deletes, status checks, or business rules.

Example:

authz.ValidatedFetcher(
    authz.Fetcher(db.GetDocumentByID),
    func(doc *Document) error {
        if doc.Deleted {
            return errors.NewC("document deleted", codes.NotFound)
        }
        if doc.Archived {
            return errors.NewC("document archived", codes.PermissionDenied)
        }
        return nil
    },
)

type TypedRoleDescriber added in v0.2.0

type TypedRoleDescriber[T any] func(ctx context.Context, subject auth.Identity, object T, scope Scope) ([]Role, error)

TypedRoleDescriber is a function type for describing roles with type safety.

func ConditionalRole added in v0.3.0

func ConditionalRole[T any](role Role, predicate func(context.Context, auth.Identity, T, Scope) (bool, error)) TypedRoleDescriber[T]

ConditionalRole grants a role if an async predicate returns true. Use this pattern when role assignment requires I/O, database queries, or other async operations.

Example:

const author = authz.Role("author")
author.When(func(ctx context.Context, subject auth.Identity, note *Note, scope authz.Scope) (bool, error) {
    user, err := fetchUser(ctx, subject.Subject)
    if err != nil {
        return false, err
    }
    return note.CreatedBy == user.ID, nil
})

func GlobalRole added in v0.3.0

func GlobalRole[T any](role Role, predicate func(context.Context) bool) TypedRoleDescriber[T]

GlobalRole grants a role based on context only, ignoring object and subject. This is useful for global overrides like superuser checks.

Example:

const superuser = authz.Role("superuser")
superuser.IfGlobal(func(ctx context.Context) bool {
    return isSuperUser(ctx, secret)
})

func IdentityOwnershipRole added in v0.3.0

func IdentityOwnershipRole[T any](
	role Role,
	resolveUserID func(context.Context, auth.Identity, T) (string, error),
	getOwnerID func(T) string,
) TypedRoleDescriber[T]

IdentityOwnershipRole grants a role when the identity resolves to the object's owner. This pattern handles cases where identity-to-user mapping is workspace-scoped and requires async resolution (e.g., database lookup). Returns no roles for anonymous users (zero-value Identity).

Unlike OwnershipRole which does simple string comparison (identity.Subject == ownerID), this pattern: - Supports async identity-to-user resolution (e.g., database lookup) - Handles workspace/scope-scoped user identities - Gracefully handles NotFound errors when identity isn't mapped in the workspace

Example (workspace-scoped users):

authz.IdentityOwnershipRole(authz.RoleOwner,
    func(ctx context.Context, subject auth.Identity, note *Note) (string, error) {
        // Resolve identity to workspace-scoped user ID
        q := models.FromContext(ctx)
        viewer, err := q.UserByIdentityAndWorkspace(ctx, &models.UserByIdentityAndWorkspaceParams{
            WorkspaceID:      note.WorkspaceID,
            IdentitySub:      subject.Subject,
            IdentityProvider: subject.Provider,
        })
        if err != nil {
            return "", err
        }
        return viewer.ID, nil
    },
    func(note *Note) string { return note.OwnerID },
)

func MembershipRoles added in v0.3.0

func MembershipRoles[T any](getScopeID func(T) string, getRoles func(context.Context, string, auth.Identity) ([]Role, error)) TypedRoleDescriber[T]

MembershipRoles returns roles based on membership in a scope (e.g., organization, workspace). The scope ID is extracted from the object and MUST match the authorization scope parameter for security. Returns an error if scope doesn't match, preventing scope confusion attacks. Returns no roles for anonymous users (zero-value Identity).

This pattern is useful when roles are inherited from a parent resource (e.g., organization membership grants roles on all org documents).

Security: This function validates that object.scopeID == scope to prevent attacks where a request to /api/orgs/123/documents/456 could access a document that belongs to org 999.

Example:

authz.MembershipRoles(
    func(doc *Document) string { return doc.OrgID },
    func(ctx context.Context, orgID string, subject auth.Identity) ([]authz.Role, error) {
        org, err := fetchOrg(ctx, orgID)
        if err != nil {
            return nil, err
        }
        return org.GetUserRoles(ctx, subject.Subject)
    },
)

func OwnershipRole added in v0.3.0

func OwnershipRole[T any](role Role, getOwnerID func(T) string) TypedRoleDescriber[T]

OwnershipRole grants a role if the subject owns the object. This is a common pattern for granting elevated permissions to resource creators. Returns no roles for anonymous users (zero-value Identity).

Example:

authz.RoleOwner.IfOwner(func(doc *Document) string {
    return doc.OwnerID
})

func StaticRole added in v0.3.0

func StaticRole[T any](role Role, predicate func(context.Context, auth.Identity, T, Scope) bool) TypedRoleDescriber[T]

StaticRole grants a role if a sync predicate returns true. Use this pattern when role assignment is based purely on object attributes without requiring async operations.

Example:

authz.StaticRole(authz.RoleViewer, func(_ context.Context, _ auth.Identity, doc *Document, _ authz.Scope) bool {
    return doc.Published
})

func StaticRoles added in v0.3.0

func StaticRoles[T any](getRoles func(context.Context, auth.Identity, T, Scope) []Role) TypedRoleDescriber[T]

StaticRoles returns multiple roles based on object attributes. Use this pattern when you need to return different roles based on conditions.

Example:

authz.StaticRoles(func(_ context.Context, subject auth.Identity, pr *PullRequest, _ authz.Scope) []authz.Role {
    var roles []authz.Role
    for _, reviewer := range pr.Reviewers {
        if reviewer == subject.Subject {
            roles = append(roles, "reviewer")
        }
    }
    return roles
})

func ValidateScope added in v0.3.0

func ValidateScope[T any](describer TypedRoleDescriber[T]) TypedRoleDescriber[T]

ValidateScope wraps a role describer to validate scope before calling it. If the object implements ScopedObject and the scope doesn't match, returns empty roles. Otherwise, delegates to the wrapped describer.

Note: Compose already does this automatically, so this is only needed if you're not using Compose.

Example:

authz.ValidateScope(myDescriber)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL