canery

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 4 Imported by: 0

Image README

canery

canery is a small authorization core built around checks shaped as:

  • subject
  • action
  • resource
  • scope

It stays generic on purpose:

  • it is not a full IAM system
  • it does not store permissions by itself
  • it does not define product semantics such as workspace, organization, or task

Authorization data comes from pluggable readers and resolvers. Product semantics belong in thin adapter packages outside the core package.

Engine is the default shipped evaluator. It applies one membership-scoped pipeline over the generic request model, but it is not the only authorization strategy the package is meant to support over time.

Design Goals

  • Keep the low-level request model explicit
  • Provide a fluent builder for readability
  • Delegate storage and lookup to pluggable interfaces
  • Avoid hardcoding product semantics into the core engine

Quick Start

Direct request API:

ok, err := authorizer.Check(ctx, canery.Request{
  Subject:  canery.ActorRef{Type: "user", ID: userID},
  Action:   canery.Action("delete"),
  Resource: canery.ResourceRef{Type: "document", ID: documentID},
  Scope:    canery.ScopeRef{Type: "project", ID: projectID},
})

Preferred naming notes:

  • ActorRef is a readability alias for Subject
  • Actor(...) is the preferred generic helper; User(...) is only a convenience helper
  • ResourceRef and ScopeRef remain the explicit request types
  • Target(...) and In(...) are readability wrappers around On(...) and Within(...)

If you want the explicit decision object instead of only the boolean outcome:

decision, err := authorizer.CheckDecision(ctx, canery.Request{
  Subject:  canery.Actor("user", userID),
  Action:   canery.Action("delete"),
  Resource: canery.Resource("document", documentID),
  Scope:    canery.Scope("project", projectID),
})

Decision can carry a small generic explanation of the outcome:

decision, err := authorizer.CheckDecision(ctx, canery.Request{
  Subject:  canery.Actor("user", userID),
  Action:   canery.Action("delete"),
  Resource: canery.Resource("document", documentID),
  Scope:    canery.Scope("project", projectID),
})

if decision.Allowed {
  fmt.Println(decision.Source) // "direct" or "group"
} else {
  fmt.Println(decision.Source) // "none"
  fmt.Println(decision.Reason) // generic explanation, such as "no matching permission"
}

If you need lightweight debugging context, CheckTrace also returns the high-level evaluation path without logging anything automatically:

decision, trace, err := authorizer.CheckTrace(ctx, canery.Request{
  Subject:  canery.Actor("user", userID),
  Action:   canery.Action("delete"),
  Resource: canery.Resource("document", documentID),
  Scope:    canery.Scope("project", projectID),
})

for _, step := range trace.Steps {
  fmt.Println(step.Name, step.Result)
}

_ = decision

The same low-level request can also use thin helper constructors:

ok, err := authorizer.Check(ctx, canery.Request{
  Subject:  canery.Actor("user", userID),
  Action:   canery.Action("delete"),
  Resource: canery.Resource("document", documentID),
  Scope:    canery.Scope("project", projectID),
})

Fluent builder API:

ok, err := authorizer.
  For(canery.ActorRef{Type: "user", ID: userID}).
  Can(canery.Action("delete")).
  Target(canery.ResourceRef{Type: "document", ID: documentID}).
  In(canery.ScopeRef{Type: "project", ID: projectID}).
  Check(ctx)

Builder calls can also use the same helper constructors:

ok, err := authorizer.
  For(canery.Actor("user", userID)).
  Can(canery.Action("delete")).
  Target(canery.Resource("document", documentID)).
  In(canery.Scope("project", projectID)).
  Check(ctx)

For the same subject/resource/scope, you can also evaluate multiple actions at once:

result, err := authorizer.
  For(canery.Actor("user", userID)).
  CanMany(canery.Action("view"), canery.Action("update"), canery.Action("delete")).
  Target(canery.Resource("document", documentID)).
  In(canery.Scope("project", projectID)).
  Check(ctx)

canUpdate, _ := result.Allowed(canery.Action("update"))

For repeated low-level checks, the engine also exposes a batch API:

decisions, err := engine.BatchCheck(ctx, []canery.Request{
  {
    Subject:  canery.Actor("user", userID),
    Action:   canery.Action("view"),
    Resource: canery.Resource("document", documentID),
    Scope:    canery.Scope("project", projectID),
  },
  {
    Subject:  canery.Actor("user", userID),
    Action:   canery.Action("delete"),
    Resource: canery.Resource("document", documentID),
    Scope:    canery.Scope("project", projectID),
  },
})

If you want to group rules around a resource type or another higher-level concept, you can wrap the base authorizer with optional policies:

authorizer := canery.NewPolicyAuthorizer(
  baseAuthorizer,
  canery.ForResourceType("document", canery.PolicyFunc(func(ctx context.Context, request canery.Request, next canery.DecisionEvaluator) (canery.Decision, error) {
    if request.Action == canery.Action("archive") {
      return canery.Decision{
        Allowed: false,
        Reason:  "policy matched",
        Source:  canery.DecisionSourceNone,
      }, nil
    }
    return next.CheckDecision(ctx, request)
  })),
)

Other generic match helpers are also available when policy organization wants to follow a resource, scope, or action-oriented shape:

authorizer := canery.NewPolicyAuthorizer(
  baseAuthorizer,
  canery.ForScopeType("project", projectPolicy),
  canery.ForActionOnResourceType(canery.Action("archive"), "document", archivePolicy),
)

These helpers stay additive and matcher-based. They are meant to improve policy organization, not to introduce framework conventions or policy auto-discovery.

ResourceRef.ID may be empty for create-style checks where the resource does not exist yet.

Validation stays strict for required fields:

  • subject requires both Type and ID
  • action requires a non-empty value
  • resource requires Type
  • scope requires both Type and ID

When validation fails, the engine returns a structured ValidationError that still satisfies errors.Is(err, canery.ErrInvalidRequest).

Architecture

flowchart LR
  B[Builder] --> R[Request]
  P[PolicyAuthorizer] --> E[Engine]
  R --> E[Engine]
  E --> M[MembershipReader]
  E --> G[GroupReader]
  E --> P[PermissionReader]
  E --> S[ResourceScopeResolver]

Evaluation Flow

flowchart TD
  A[Check request] --> B[Validate request]
  B --> C{Resource ID present?}
  C -- yes --> D[Verify resource belongs to scope]
  C -- no --> E[Verify subject belongs to scope]
  D --> E
  E --> F[Check direct subject permission]
  F --> G{Allowed?}
  G -- yes --> H[Allow]
  G -- no --> I[Resolve groups in scope]
  I --> J[Check group permissions]
  J --> K{Any allowed?}
  K -- yes --> H
  K -- no --> L[Deny]

This is the default Engine flow, not a statement that every future canery.Authorizer must evaluate requests in exactly this order.

Reader And Resolver Contracts

  • MembershipReader Confirms whether the subject belongs to the requested scope.
  • GroupReader Resolves the groups the subject belongs to within that scope.
  • PermissionReader Resolves allow decisions for direct subjects and groups.
  • ResourceScopeResolver Confirms that a concrete resource belongs to the requested scope.

The engine does not store permissions internally and does not talk directly to a database or service. Those concerns belong to the reader and resolver implementations.

Generic Core vs App Adapters

Keep canery generic and move application ergonomics into a separate adapter package owned by the application using it.

The adapter layer should stay thin. It mostly gives names to app concepts while still building plain canery requests underneath:

package projectauthz

import "github.com/rluders/canery"

const EditDocument = canery.Action("edit")

func User(id string) canery.Subject {
  return canery.Actor("user", id)
}

func ProjectScope(id string) canery.ScopeRef {
  return canery.Scope("project", id)
}

func Document(id string) canery.ResourceRef {
  return canery.Resource("document", id)
}

That keeps the core reusable while still making call sites pleasant:

ok, err := authorizer.
  For(projectauthz.User(userID)).
  Can(projectauthz.EditDocument).
  Target(projectauthz.Document(documentID)).
  In(projectauthz.ProjectScope(projectID)).
  Check(ctx)

Non-user subjects, alternate storage backends, and evaluators that are not membership-first are all valid uses of the core package. Those variations should be expressed through adapters and alternate evaluators, not by pushing application semantics into canery.

License

MIT

Image Documentation

Overview

Package canery provides a small, reusable authorization core built around generic subjects, actions, resources, and scopes.

The primitive API is request-based: callers build a Request and ask an Authorizer to evaluate it. A fluent Builder is also provided for readability, but it is only a thin layer over the same Request model. Small helper constructors such as Actor, Resource, and Scope are also available for ergonomics, but they remain thin wrappers over the exported structs. User is available as a convenience helper, not as the preferred generic entrypoint.

Authorization state is not stored inside the package. Evaluation is delegated to pluggable readers and resolvers so the package can stay storage-agnostic and reusable across projects.

canery is not a full IAM system. It does not model tenants, roles, directories, or product semantics by itself. Those concerns belong in adapters and backends built on top of the core package.

Engine is the default shipped evaluator. It applies one membership-scoped evaluation pipeline over the shared request model, but the model itself is intended to remain broad enough for alternate evaluators later.

Index

Examples

Constants

View Source
const (
	// DecisionSourceDirect indicates that a direct subject permission matched.
	DecisionSourceDirect = "direct"
	// DecisionSourceGroup indicates that a group-derived permission matched.
	DecisionSourceGroup = "group"
	// DecisionSourceNone indicates that no permission source produced an allow.
	DecisionSourceNone = "none"
)

Variables

View Source
var (
	// ErrInvalidRequest indicates that a request is missing required fields.
	ErrInvalidRequest = errors.New("canery: invalid request")
	// ErrMissingMembership indicates that the engine has no MembershipReader.
	ErrMissingMembership = errors.New("canery: missing membership reader")
	// ErrMissingGroupReader indicates that the engine has no GroupReader.
	ErrMissingGroupReader = errors.New("canery: missing group reader")
	// ErrMissingPermissions indicates that the engine has no PermissionReader.
	ErrMissingPermissions = errors.New("canery: missing permission reader")
	// ErrMissingScopeResolver indicates that the engine cannot validate a
	// concrete resource against a scope because no resolver was configured.
	ErrMissingScopeResolver = errors.New("canery: missing resource scope resolver")
)
View Source
var ErrMissingAuthorizer = errors.New("canery: missing authorizer")

Functions

This section is empty.

Types

type Action

type Action string

Action identifies the operation being requested on a resource.

type ActionDecision

type ActionDecision struct {
	Allowed bool
	Err     error
}

ActionDecision contains the authorization outcome for a single action.

type ActorRef

type ActorRef = Subject

ActorRef is a readability alias for Subject.

Subject remains the core request field name for backward compatibility.

type Authorizer

type Authorizer interface {
	// CheckDecision evaluates a low-level request directly and returns the
	// explicit decision object.
	CheckDecision(ctx context.Context, request Request) (Decision, error)
	// CheckTrace evaluates a low-level request directly and also returns a
	// high-level trace of the evaluation flow for debugging.
	CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)
	// Check evaluates a low-level request directly.
	Check(ctx context.Context, request Request) (bool, error)
	// For starts a fluent builder for the given subject.
	For(subject Subject) Builder
}

Authorizer evaluates authorization requests and exposes a fluent request builder rooted on a subject.

Engine is the default implementation shipped by the package, but the interface is intentionally broad enough for alternate evaluators over the same request model.

type BatchCheckError

type BatchCheckError struct {
	Index   int
	Request Request
	Err     error
}

BatchCheckError identifies the request that caused a batch evaluation to fail.

func (BatchCheckError) Error

func (e BatchCheckError) Error() string

func (BatchCheckError) Unwrap

func (e BatchCheckError) Unwrap() error

type Builder

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

Builder incrementally assembles a Request before evaluating it through an Authorizer.

func (Builder) Can

func (b Builder) Can(action Action) Builder

Can sets the action being requested.

func (Builder) CanMany

func (b Builder) CanMany(actions ...Action) MultiActionBuilder

CanMany starts a multi-action request for the same subject, resource, and scope.

Example
engine := NewEngine(
	exampleMembershipReader{},
	exampleGroupReader{},
	examplePermissionReader{},
	exampleScopeResolver{},
)

result, err := engine.
	For(User("user-1")).
	CanMany(Action("update"), Action("delete")).
	Target(Resource("document", "doc-1")).
	In(Scope("project", "project-1")).
	Check(context.Background())

canUpdate, _ := result.Allowed(Action("update"))
canDelete, _ := result.Allowed(Action("delete"))
fmt.Println(err == nil, canUpdate, canDelete)
Output:
true true false

func (Builder) Check

func (b Builder) Check(ctx context.Context) (bool, error)

Check evaluates the built request through the configured Authorizer.

func (Builder) In

func (b Builder) In(scope ScopeRef) Builder

In is a readability wrapper around Within.

func (Builder) On

func (b Builder) On(resource ResourceRef) Builder

On sets the resource being targeted.

func (Builder) Request

func (b Builder) Request() Request

Request returns the low-level request represented by the builder.

Example
engine := NewEngine(
	exampleMembershipReader{},
	exampleGroupReader{},
	examplePermissionReader{},
	exampleScopeResolver{},
)

request := engine.
	For(ActorRef{Type: "user", ID: "user-1"}).
	Can(Action("create")).
	Target(ResourceRef{Type: "document"}).
	In(ScopeRef{Type: "project", ID: "project-1"}).
	Request()

fmt.Println(request.Subject.Type, request.Action, request.Resource.Type, request.Resource.ID == "")
Output:
user create document true

func (Builder) Target

func (b Builder) Target(resource ResourceRef) Builder

Target is a readability wrapper around On.

func (Builder) Within

func (b Builder) Within(scope ScopeRef) Builder

Within sets the scope in which the request should be evaluated.

type Decision

type Decision struct {
	Allowed bool
	Reason  string
	Source  string
}

Decision is the explicit authorization outcome for a single request.

Reason and Source provide generic explanation metadata about how the decision was reached without exposing backend-specific storage details.

type DecisionEvaluator

type DecisionEvaluator interface {
	// CheckDecision evaluates a low-level request directly and returns the
	// explicit decision object.
	CheckDecision(ctx context.Context, request Request) (Decision, error)
}

DecisionEvaluator is the minimal continuation interface used by policies to delegate back to the next policy or the wrapped authorizer.

type Engine

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

Engine is the default Authorizer implementation backed by pluggable readers and a resource scope resolver.

Engine intentionally implements one default evaluation strategy: a membership-scoped pipeline that validates the request, optionally checks a concrete resource against the requested scope, confirms scope membership, and then evaluates direct and group-based permissions.

The request model and readers are broader than this one strategy. Future evaluators can reuse the same Request, Decision, and reader interfaces while applying a different evaluation order.

func NewEngine

func NewEngine(memberships MembershipReader, groups GroupReader, permissions PermissionReader, resolver ResourceScopeResolver) *Engine

NewEngine constructs an Engine that evaluates requests using the provided membership, group, permission, and resource-scope backends.

func (*Engine) BatchCheck

func (e *Engine) BatchCheck(ctx context.Context, requests []Request) ([]Decision, error)

BatchCheck evaluates a slice of requests and returns one explicit decision per request.

It reuses the same validation and evaluation path as Check. If a request is invalid, BatchCheck stops and returns a BatchCheckError identifying the failing item.

func (*Engine) Check

func (e *Engine) Check(ctx context.Context, request Request) (bool, error)

Check evaluates a low-level request directly and preserves the original boolean API for backward compatibility.

Example
engine := NewEngine(
	exampleMembershipReader{},
	exampleGroupReader{},
	examplePermissionReader{},
	exampleScopeResolver{},
)

ok, err := engine.Check(context.Background(), Request{
	Subject:  ActorRef{Type: "user", ID: "user-1"},
	Action:   Action("update"),
	Resource: ResourceRef{Type: "document", ID: "doc-1"},
	Scope:    ScopeRef{Type: "project", ID: "project-1"},
})
fmt.Println(ok, err == nil)
Output:
true true

func (*Engine) CheckDecision

func (e *Engine) CheckDecision(ctx context.Context, request Request) (Decision, error)

CheckDecision evaluates a request in this order:

  1. validate required request fields
  2. verify resource-to-scope membership when a resource ID is provided
  3. verify subject membership in the scope
  4. check direct subject permissions
  5. resolve groups and check group permissions

The first matching allow returns an allowed Decision. Missing matches return a denied Decision. The engine does not implement deny rules or policy precedence beyond this order.

func (*Engine) CheckTrace

func (e *Engine) CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)

CheckTrace evaluates a request and also returns a high-level trace that can help callers debug how the final decision was reached.

func (*Engine) For

func (e *Engine) For(subject Subject) Builder

For starts a fluent authorization request for the given subject.

Example
engine := NewEngine(
	exampleMembershipReader{},
	exampleGroupReader{},
	examplePermissionReader{},
	exampleScopeResolver{},
)

ok, err := engine.
	For(ActorRef{Type: "user", ID: "user-1"}).
	Can(Action("update")).
	Target(ResourceRef{Type: "document", ID: "doc-1"}).
	In(ScopeRef{Type: "project", ID: "project-1"}).
	Check(context.Background())
fmt.Println(ok, err == nil)
Output:
true true

type GroupReader

type GroupReader interface {
	GroupsForSubject(ctx context.Context, subject Subject, scope ScopeRef) ([]GroupRef, error)
}

GroupReader resolves the groups a subject belongs to within a given scope.

Today the scope is a single ScopeRef. A future hierarchical model may use that scope as the leaf context that determines which inherited or local groups should be visible.

type GroupRef

type GroupRef struct {
	Type string
	ID   string
}

GroupRef identifies a derived or persisted group that can carry permissions.

type MembershipReader

type MembershipReader interface {
	HasMembership(ctx context.Context, subject Subject, scope ScopeRef) (bool, error)
}

MembershipReader reports whether a subject belongs to a given scope.

The public API stays single-scope for now. Implementations can already treat that scope as the current boundary in a broader hierarchy if they need to prepare for future nesting support.

type MultiActionBuilder

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

MultiActionBuilder incrementally assembles a repeated request over multiple actions for the same subject, resource, and scope.

func (MultiActionBuilder) Check

Check evaluates each action through the configured Authorizer and returns one decision per action.

func (MultiActionBuilder) In

In is a readability wrapper around Within.

func (MultiActionBuilder) On

On sets the resource being targeted for all actions.

func (MultiActionBuilder) Requests

func (b MultiActionBuilder) Requests() []Request

Requests returns one low-level request per action represented by the builder.

func (MultiActionBuilder) Target

Target is a readability wrapper around On.

func (MultiActionBuilder) Within

Within sets the scope in which all actions should be evaluated.

type MultiActionResult

type MultiActionResult struct {
	Decisions map[Action]ActionDecision
}

MultiActionResult contains the decision for each requested action.

func (MultiActionResult) Allowed

func (r MultiActionResult) Allowed(action Action) (bool, bool)

Allowed returns the allow decision for a specific action and whether that action was present in the result.

func (MultiActionResult) Error

func (r MultiActionResult) Error(action Action) (error, bool)

Error returns the evaluation error for a specific action and whether that action was present in the result.

type PermissionReader

type PermissionReader interface {
	HasPermission(ctx context.Context, principal PrincipalRef, request Request) (bool, error)
}

PermissionReader reports whether a principal is allowed to perform a request.

The principal may represent either the subject directly or a group previously resolved for that subject.

type Policy

type Policy interface {
	CheckDecision(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)
}

Policy can wrap authorization decisions for a matched request before delegating to the next evaluator.

Policies are optional and compose on top of the core request-driven authorizer. They can handle a request directly or call next.CheckDecision to continue the evaluation chain.

type PolicyAuthorizer

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

PolicyAuthorizer wraps an Authorizer with an ordered set of optional policies.

Policies run only when their binding matches the request. A matched policy can return a decision directly or delegate to the next evaluator.

func NewPolicyAuthorizer

func NewPolicyAuthorizer(base Authorizer, bindings ...PolicyBinding) *PolicyAuthorizer

NewPolicyAuthorizer constructs an Authorizer that evaluates matched policies before falling back to the wrapped base authorizer.

func (*PolicyAuthorizer) Check

func (a *PolicyAuthorizer) Check(ctx context.Context, request Request) (bool, error)

Check preserves the original boolean API for backward compatibility.

func (*PolicyAuthorizer) CheckDecision

func (a *PolicyAuthorizer) CheckDecision(ctx context.Context, request Request) (Decision, error)

CheckDecision evaluates the request through the matching policy chain and then the wrapped authorizer.

func (*PolicyAuthorizer) CheckTrace

func (a *PolicyAuthorizer) CheckTrace(ctx context.Context, request Request) (Decision, Trace, error)

CheckTrace evaluates the request through the matching policy chain and also returns a high-level trace for debugging.

func (*PolicyAuthorizer) For

func (a *PolicyAuthorizer) For(subject Subject) Builder

For starts a fluent authorization request for the given subject.

type PolicyBinding

type PolicyBinding struct {
	Match  RequestMatcher
	Policy Policy
}

PolicyBinding associates a request matcher with a policy.

func ForAction

func ForAction(action Action, policy Policy) PolicyBinding

ForAction creates a policy binding that applies to one action.

func ForActionInScopeType

func ForActionInScopeType(action Action, scopeType string, policy Policy) PolicyBinding

ForActionInScopeType creates a policy binding that applies when both the action and scope type match.

func ForActionOnResourceType

func ForActionOnResourceType(action Action, resourceType string, policy Policy) PolicyBinding

ForActionOnResourceType creates a policy binding that applies when both the action and resource type match.

func ForResourceType

func ForResourceType(resourceType string, policy Policy) PolicyBinding

ForResourceType creates a policy binding that applies to one resource type.

func ForScopeType

func ForScopeType(scopeType string, policy Policy) PolicyBinding

ForScopeType creates a policy binding that applies to one scope type.

func MatchRequests

func MatchRequests(match RequestMatcher, policy Policy) PolicyBinding

MatchRequests creates a policy binding backed by an arbitrary request matcher.

type PolicyFunc

type PolicyFunc func(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)

PolicyFunc adapts a function to the Policy interface.

func (PolicyFunc) CheckDecision

func (f PolicyFunc) CheckDecision(ctx context.Context, request Request, next DecisionEvaluator) (Decision, error)

CheckDecision calls f(ctx, request, next).

type PrincipalKind

type PrincipalKind string

PrincipalKind distinguishes direct subject checks from group-based checks.

const (
	// PrincipalKindSubject represents a direct subject principal.
	PrincipalKindSubject PrincipalKind = "subject"
	// PrincipalKindGroup represents a group principal.
	PrincipalKindGroup PrincipalKind = "group"
)

type PrincipalRef

type PrincipalRef struct {
	Kind PrincipalKind
	Type string
	ID   string
}

PrincipalRef identifies the principal used by a PermissionReader.

Principals can represent either the subject directly or a group resolved for that subject within a scope.

func GroupPrincipal

func GroupPrincipal(group GroupRef) PrincipalRef

GroupPrincipal converts a group into a principal for group-based permission evaluation.

func SubjectPrincipal

func SubjectPrincipal(subject Subject) PrincipalRef

SubjectPrincipal converts a subject into a principal for direct permission evaluation.

type Request

type Request struct {
	Subject  Subject
	Action   Action
	Resource ResourceRef
	Scope    ScopeRef
}

Request is the low-level authorization primitive evaluated by an Authorizer.

type RequestMatcher

type RequestMatcher func(Request) bool

RequestMatcher decides whether a policy binding applies to a request.

type ResourceRef

type ResourceRef struct {
	Type string
	ID   string
}

ResourceRef identifies the target resource for a check.

ID may be left empty for create-style checks where the resource does not yet exist and authorization is based on resource type and scope alone.

func Resource

func Resource(kind string, id string) ResourceRef

Resource returns a ResourceRef for the given resource kind and identifier.

type ResourceScopeResolver

type ResourceScopeResolver interface {
	ResourceInScope(ctx context.Context, resource ResourceRef, scope ScopeRef) (bool, error)
}

ResourceScopeResolver verifies that a resource belongs to the provided scope.

It is only required when a request targets a concrete resource ID. Implementations should treat the provided scope as the request's current boundary. That leaves room for future evolution toward ancestor-aware scope resolution without changing the request model today.

type ScopeRef

type ScopeRef struct {
	Type string
	ID   string
}

ScopeRef identifies the boundary in which the request is evaluated.

The current API models one explicit scope per request. The engine keeps its internals structured so that this boundary can evolve toward hierarchical scope evaluation later without changing the request shape immediately.

func Scope

func Scope(kind string, id string) ScopeRef

Scope returns a ScopeRef for the given scope kind and identifier.

type Subject

type Subject struct {
	Type string
	ID   string
}

Subject identifies the actor asking to perform an action.

func Actor

func Actor(kind string, id string) Subject

Actor returns a Subject for the given actor kind and identifier.

Example
engine := NewEngine(
	exampleMembershipReader{},
	exampleGroupReader{},
	examplePermissionReader{},
	exampleScopeResolver{},
)

ok, err := engine.Check(context.Background(), Request{
	Subject:  Actor("user", "user-1"),
	Action:   Action("update"),
	Resource: Resource("document", "doc-1"),
	Scope:    Scope("project", "project-1"),
})
fmt.Println(ok, err == nil)
Output:
true true

func User

func User(id string) Subject

User returns a Subject for a human user actor.

It is a convenience helper. Generic package usage should prefer Actor when a user-specific helper is not materially clearer.

type Trace

type Trace struct {
	Steps []TraceStep
}

Trace captures high-level evaluation steps for a single authorization check.

It is intended for debugging and inspection only. The trace stays generic and does not expose storage-specific details.

type TraceStep

type TraceStep struct {
	Name   string
	Result string
}

TraceStep records one high-level step in an authorization evaluation.

type ValidationError

type ValidationError struct {
	Field string
	Code  string
}

ValidationError identifies a specific invalid request field with a stable field/code pair.

func (ValidationError) Error

func (e ValidationError) Error() string

func (ValidationError) Unwrap

func (e ValidationError) Unwrap() error

Image Directories

Path Synopsis
examples
batch-check command

Jump to

Keyboard shortcuts

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