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
- Variables
- func FieldOptions(req proto.Message) (any, string, error)
- func MarkScopeValidated(ctx context.Context) context.Context
- func MethodOptions(info *grpc.UnaryServerInfo) (objectKey string, action Action, defaultEffect Effect)
- func WasScopeValidated(ctx context.Context) bool
- type Action
- type AuditLogger
- type AuthorizeParams
- type AuthzDecision
- type AuthzObject
- type AuthzOption
- func WithAuditLogger(logger AuditLogger) AuthzOption
- func WithObjectFetcher(objectKey string, fetcher ObjectFetcher) AuthzOption
- func WithObjectFetcherFn(objectKey string, fetcher func(ctx context.Context, key any) (any, error)) AuthzOption
- func WithPolicy(effect Effect, role Role, action Action) AuthzOption
- func WithRoleDescriber(objectKey string, describer RoleDescriber) AuthzOption
- func WithRoleDescriberFn(objectKey string, ...) AuthzOption
- func WithRoleHierarchy(roles ...Role) AuthzOption
- func WithTypeRegistration[K comparable, T any](objectKey string, fetcher TypedObjectFetcher[K, T], ...) AuthzOption
- func WithTypedObjectFetcher[K comparable, T any](objectKey string, fetcher TypedObjectFetcher[K, T]) AuthzOption
- func WithTypedRoleDescriber[T any](objectKey string, describer TypedRoleDescriber[T]) AuthzOption
- type AuthzPlugin
- func (ap *AuthzPlugin) Authorize(ctx context.Context, cfg AuthorizeParams) error
- func (ap *AuthzPlugin) DebugHandler(resp http.ResponseWriter, req *http.Request)
- func (ap *AuthzPlugin) DefinePolicy(effect Effect, role Role, action Action)
- func (ap *AuthzPlugin) Deps() []string
- func (ap *AuthzPlugin) DetermineEffect(action Action, roles []Role, defaultEffect Effect) (Effect, []PolicyEvaluation)
- func (ap *AuthzPlugin) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, ...) (resp interface{}, err error)
- func (ap *AuthzPlugin) Name() string
- func (ap *AuthzPlugin) RegisterObjectFetcher(objectKey string, fetcher ObjectFetcher)
- func (ap *AuthzPlugin) RegisterRoleDescriber(objectKey string, describer RoleDescriber)
- func (ap *AuthzPlugin) RoleHierarchy(role Role) []Role
- func (ap *AuthzPlugin) RoleTree() map[Role][]Role
- func (ap *AuthzPlugin) ServerOptions() []prefab.ServerOption
- func (ap *AuthzPlugin) SetRoleHierarchy(roles ...Role)
- type Builder
- func (b *Builder) Build() *AuthzPlugin
- func (b *Builder) WithObjectFetcher(objectKey string, fetcher ObjectFetcher) *Builder
- func (b *Builder) WithObjectFetcherFn(objectKey string, fetcher func(ctx context.Context, key any) (any, error)) *Builder
- func (b *Builder) WithPolicy(effect Effect, role Role, action Action) *Builder
- func (b *Builder) WithRoleDescriber(objectKey string, describer RoleDescriber) *Builder
- func (b *Builder) WithRoleDescriberFn(objectKey string, ...) *Builder
- func (b *Builder) WithRoleHierarchy(roles ...Role) *Builder
- func (b *Builder) WithTypedObjectFetcher(objectKey string, fetcher interface{}) *Builder
- func (b *Builder) WithTypedRoleDescriber(objectKey string, describer interface{}) *Builder
- type Effect
- type ObjectFetcher
- type ObjectFetcherFn
- type OwnedObject
- type PolicyEvaluation
- type Role
- type RoleDescriber
- type RoleDescriberFn
- type Scope
- type ScopedObject
- type TypedBuilder
- func (tb *TypedBuilder[T]) Build() *AuthzPlugin
- func (tb *TypedBuilder[T]) WithIntObjectFetcher(objectKey string, fetcher func(ctx context.Context, key int) (T, error)) *TypedBuilder[T]
- func (tb *TypedBuilder[T]) WithPolicy(effect Effect, role Role, action Action) *TypedBuilder[T]
- func (tb *TypedBuilder[T]) WithRoleDescriber(objectKey string, ...) *TypedBuilder[T]
- func (tb *TypedBuilder[T]) WithRoleHierarchy(roles ...Role) *TypedBuilder[T]
- func (tb *TypedBuilder[T]) WithStringObjectFetcher(objectKey string, fetcher func(ctx context.Context, key string) (T, error)) *TypedBuilder[T]
- type TypedObjectFetcher
- func ComposeFetchers[K comparable, T any](fetchers ...TypedObjectFetcher[K, T]) TypedObjectFetcher[K, T]
- func DefaultFetcher[K comparable, T any](fetcher TypedObjectFetcher[K, T], defaultValue T) TypedObjectFetcher[K, T]
- func Fetcher[K comparable, T any](fetch func(context.Context, K) (T, error)) TypedObjectFetcher[K, T]
- func MapFetcher[K comparable, T any](m map[K]T) TypedObjectFetcher[K, T]
- func TransformKey[K1, K2 comparable, T any](transform func(K1) K2, fetcher TypedObjectFetcher[K2, T]) TypedObjectFetcher[K1, T]
- func ValidatedFetcher[K comparable, T any](fetcher TypedObjectFetcher[K, T], validate func(T) error) TypedObjectFetcher[K, T]
- type TypedRoleDescriber
- func ConditionalRole[T any](role Role, ...) TypedRoleDescriber[T]
- func GlobalRole[T any](role Role, predicate func(context.Context) bool) TypedRoleDescriber[T]
- func IdentityOwnershipRole[T any](role Role, ...) TypedRoleDescriber[T]
- func MembershipRoles[T any](getScopeID func(T) string, ...) TypedRoleDescriber[T]
- func OwnershipRole[T any](role Role, getOwnerID func(T) string) TypedRoleDescriber[T]
- func StaticRole[T any](role Role, predicate func(context.Context, auth.Identity, T, Scope) bool) TypedRoleDescriber[T]
- func StaticRoles[T any](getRoles func(context.Context, auth.Identity, T, Scope) []Role) TypedRoleDescriber[T]
- func ValidateScope[T any](describer TypedRoleDescriber[T]) TypedRoleDescriber[T]
Constants ¶
const ( RoleAdmin = Role("admin") RoleEditor = Role("editor") RoleViewer = Role("viewer") RoleOwner = Role("owner") RoleUser = Role("user") )
Common predefined roles.
const ( ActionCreate = Action("create") ActionRead = Action("read") ActionUpdate = Action("update") ActionDelete = Action("delete") ActionList = Action("list") )
Common predefined actions.
const PluginName = "authz"
Constant name for identifying the core Authz plugin.
Variables ¶
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.
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.
var ( ErrPermissionDenied = errors.Codef(codes.PermissionDenied, "you are not authorized to perform this action") ErrUnauthenticated = errors.Codef(codes.Unauthenticated, "the requested action requires authentication") )
var File_plugins_authz_authz_proto protoreflect.FileDescriptor
Functions ¶
func FieldOptions ¶
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
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
WasScopeValidated returns true if scope validation was explicitly performed.
Types ¶
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 (*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) 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) 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
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
WithRoleHierarchy adds a role hierarchy to the builder.
func (*Builder) WithTypedObjectFetcher ¶ added in v0.2.0
WithTypedObjectFetcher adds a type-safe object fetcher using a helper function.
func (*Builder) WithTypedRoleDescriber ¶ added in v0.2.0
WithTypedRoleDescriber adds a type-safe role describer using a helper function.
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
ObjectFetcherFn adapts a function to the ObjectFetcher interface.
func (ObjectFetcherFn) FetchObject ¶ added in v0.2.0
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
PolicyEvaluation represents a single policy evaluation result.
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..
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
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)