Documentation
¶
Overview ¶
Package authz provides a pluggable authorization interface for SystemForge applications.
The package defines a core Authorizer interface that applications use for access control, with multiple backend implementations available:
- simple: Role hierarchy and permission mappings (no external dependencies)
- spicedb: SpiceDB-based ReBAC (Zanzibar-style relationship-based access control)
Applications depend only on the Authorizer interface, allowing backends to be swapped without changing application code.
Index ¶
- Constants
- Variables
- func ValidateAppSchema(app, schema string) error
- func ValidateScope(app, scope string) error
- func ValidateVocabulary(v AppVocabulary) error
- type Action
- type AppVocabulary
- type Authorizer
- type Decision
- type DecisionAuthorizer
- type ErrorResponse
- type FeatureAuthorizer
- type Middleware
- func (m *Middleware) RequireAction(resourceType ResourceType, action Action) func(http.Handler) http.Handler
- func (m *Middleware) RequireAllActions(resourceType ResourceType, actions ...Action) func(http.Handler) http.Handler
- func (m *Middleware) RequireAnyAction(resourceType ResourceType, actions ...Action) func(http.Handler) http.Handler
- func (m *Middleware) RequireResourceAction(extractor ResourceExtractor, action Action) func(http.Handler) http.Handler
- type OrgAuthorizer
- type OrgMiddleware
- type PlatformAuthorizer
- type PlatformMiddleware
- type Principal
- type PrincipalType
- type RelationshipSyncer
- type Resource
- type ResourceExtractor
- type ResourceType
- type RoleHierarchy
- type RolePermissions
- type SyncMode
- type VocabularyRegistry
Constants ¶
const BaseSpiceDBSchema = `` /* 1039-byte string literal not displayed */
BaseSpiceDBSchema is the SystemForge-owned foundation of every composed ReBAC deployment: principals, the organization membership ladder, and the platform. Applications never redefine these; they attach via an "{app}_org" facet definition that references organization and adds the app's domain roles (see docs/authz-conventions.md for the pattern and a worked example).
Variables ¶
var ( ErrMissingResourceID = &middlewareError{msg: "missing resource ID"} ErrInvalidResourceID = &middlewareError{msg: "invalid resource ID"} )
Error types for resource extraction.
var DefaultRoleHierarchy = RoleHierarchy{
"owner": 100,
"admin": 80,
"editor": 60,
"member": 40,
"viewer": 20,
"guest": 10,
}
DefaultRoleHierarchy provides a common role hierarchy for multi-tenant SaaS apps.
var DefaultRolePermissions = RolePermissions{
"owner": {
"org.delete", "org.manage", "org.settings",
"member.invite", "member.remove", "member.role.change", "member.list",
"resource.create", "resource.read", "resource.update", "resource.delete",
},
"admin": {
"org.settings",
"member.invite", "member.remove", "member.role.change", "member.list",
"resource.create", "resource.read", "resource.update", "resource.delete",
},
"editor": {
"member.list",
"resource.create", "resource.read", "resource.update", "resource.delete",
},
"member": {
"member.list",
"resource.create", "resource.read", "resource.update",
},
"viewer": {
"resource.read",
},
}
DefaultRolePermissions provides a common permission mapping.
Functions ¶
func ValidateAppSchema ¶
ValidateAppSchema checks an app's SpiceDB fragment: every definition it declares must be prefixed "{app}_", so fragments can never collide with the shared base definitions or with another app. An empty schema is valid.
func ValidateScope ¶
ValidateScope checks one scope against the "{app}:{resource}:{verb}" (or "{app}:admin") convention for the given app.
func ValidateVocabulary ¶
func ValidateVocabulary(v AppVocabulary) error
ValidateVocabulary checks a vocabulary against every convention: app name format, scope format and prefix, hierarchy covering all roles, and schema definition prefixing.
Types ¶
type AppVocabulary ¶
type AppVocabulary interface {
// App returns the application identifier (e.g. "actionforge").
App() string
// Roles maps each role name to its granted dotted permissions.
Roles() RolePermissions
// Hierarchy orders the app's roles; every role in Roles() must appear.
Hierarchy() RoleHierarchy
// Scopes lists every OAuth scope the app defines.
Scopes() []string
// SpiceDBSchema returns the app's ReBAC schema fragment, or "" if the app
// only supports the simple role-hierarchy provider.
SpiceDBSchema() string
}
AppVocabulary is the authorization vocabulary a Forge application registers with the platform: its roles, permissions, OAuth scopes, and (optionally) its SpiceDB schema fragment. Registering a vocabulary is what makes an app administrable — the IAM console renders roles/permissions/scopes directly from the registry, and the composed SpiceDB schema is assembled from the registered fragments.
Conventions (normative; see docs/authz-conventions.md):
- App names are short lowercase identifiers ("actionforge").
- Scopes are "{app}:{resource}:{verb}" plus the blanket "{app}:admin".
- Roles reuse the platform ladder (owner/admin/editor/member/viewer) wherever it fits; app domain roles (e.g. "approver") are permitted and must appear in both Roles() and Hierarchy().
- SpiceDB fragments define only "{app}_"-prefixed definitions and attach to the shared base definitions (principal, organization, platform) via an "{app}_org" facet — never by redefining the base.
type Authorizer ¶
type Authorizer interface {
// Can checks if a principal can perform an action on a resource.
// This is the primary authorization method.
Can(ctx context.Context, principal Principal, action Action, resource Resource) (bool, error)
// CanAll checks if a principal can perform all specified actions on a resource.
CanAll(ctx context.Context, principal Principal, actions []Action, resource Resource) (bool, error)
// CanAny checks if a principal can perform any of the specified actions on a resource.
CanAny(ctx context.Context, principal Principal, actions []Action, resource Resource) (bool, error)
// Filter returns only the resources the principal can access with the given action.
Filter(ctx context.Context, principal Principal, action Action, resources []Resource) ([]Resource, error)
}
Authorizer defines the core authorization interface. All authorization backends must implement this interface.
type Decision ¶
type Decision struct {
// Allowed indicates whether the action is permitted.
Allowed bool
// Reason provides context for the decision (useful for debugging/auditing).
Reason string
// PolicyID identifies which policy made the decision (if applicable).
PolicyID string
}
Decision represents an authorization decision with optional explanation.
type DecisionAuthorizer ¶
type DecisionAuthorizer interface {
Authorizer
// Decide returns a detailed authorization decision.
Decide(ctx context.Context, principal Principal, action Action, resource Resource) (Decision, error)
}
DecisionAuthorizer provides detailed authorization decisions.
type ErrorResponse ¶
ErrorResponse represents an error response body.
type FeatureAuthorizer ¶
type FeatureAuthorizer interface {
Authorizer
// CanWithFeature checks both permission and feature flag.
CanWithFeature(ctx context.Context, principal Principal, action Action, resource Resource, feature string) (bool, error)
}
FeatureAuthorizer extends authorization with feature flag awareness.
type Middleware ¶
type Middleware struct {
// contains filtered or unexported fields
}
Middleware wraps an Authorizer for HTTP middleware use.
func NewMiddleware ¶
func NewMiddleware(authorizer Authorizer) *Middleware
NewMiddleware creates a new authorization middleware.
func (*Middleware) RequireAction ¶
func (m *Middleware) RequireAction(resourceType ResourceType, action Action) func(http.Handler) http.Handler
RequireAction returns middleware that checks if the user can perform an action on a resource type.
func (*Middleware) RequireAllActions ¶
func (m *Middleware) RequireAllActions(resourceType ResourceType, actions ...Action) func(http.Handler) http.Handler
RequireAllActions returns middleware that checks if the user can perform all of the specified actions on a resource type.
func (*Middleware) RequireAnyAction ¶
func (m *Middleware) RequireAnyAction(resourceType ResourceType, actions ...Action) func(http.Handler) http.Handler
RequireAnyAction returns middleware that checks if the user can perform any of the specified actions on a resource type.
func (*Middleware) RequireResourceAction ¶
func (m *Middleware) RequireResourceAction(extractor ResourceExtractor, action Action) func(http.Handler) http.Handler
RequireResourceAction returns middleware that extracts a resource from the request and checks if the user can perform an action on it.
type OrgAuthorizer ¶
type OrgAuthorizer interface {
Authorizer
// CanForOrg checks permission scoped to a specific organization.
CanForOrg(ctx context.Context, principal Principal, orgID uuid.UUID, action Action, resource Resource) (bool, error)
// GetRole returns the principal's role in an organization.
GetRole(ctx context.Context, principal Principal, orgID uuid.UUID) (string, error)
// IsMember checks if a principal is a member of an organization.
IsMember(ctx context.Context, principal Principal, orgID uuid.UUID) (bool, error)
}
OrgAuthorizer extends Authorizer with organization-scoped methods. Use this for multi-tenant SaaS applications.
type OrgMiddleware ¶
type OrgMiddleware struct {
// contains filtered or unexported fields
}
OrgMiddleware wraps an OrgAuthorizer for organization-scoped middleware.
func NewOrgMiddleware ¶
func NewOrgMiddleware(authorizer OrgAuthorizer) *OrgMiddleware
NewOrgMiddleware creates a new organization-scoped authorization middleware.
func (*OrgMiddleware) RequireMembership ¶
func (m *OrgMiddleware) RequireMembership() func(http.Handler) http.Handler
RequireMembership returns middleware that requires the user to be a member of the current organization.
func (*OrgMiddleware) RequireRole ¶
func (m *OrgMiddleware) RequireRole(role string, hierarchy RoleHierarchy) func(http.Handler) http.Handler
RequireRole returns middleware that requires a specific role in the current organization.
type PlatformAuthorizer ¶
type PlatformAuthorizer interface {
OrgAuthorizer
// IsPlatformAdmin checks if a principal has platform-wide admin access.
IsPlatformAdmin(ctx context.Context, principal Principal) (bool, error)
}
PlatformAuthorizer extends OrgAuthorizer with platform-level checks.
type PlatformMiddleware ¶
type PlatformMiddleware struct {
// contains filtered or unexported fields
}
PlatformMiddleware wraps a PlatformAuthorizer for platform-level middleware.
func NewPlatformMiddleware ¶
func NewPlatformMiddleware(authorizer PlatformAuthorizer) *PlatformMiddleware
NewPlatformMiddleware creates a new platform-level authorization middleware.
func (*PlatformMiddleware) RequirePlatformAdmin ¶
func (m *PlatformMiddleware) RequirePlatformAdmin() func(http.Handler) http.Handler
RequirePlatformAdmin returns middleware that requires platform admin status.
type Principal ¶
type Principal struct {
// ID is the unique identifier of the principal.
ID uuid.UUID
// Type identifies the kind of principal (user, service, api_key, etc.).
Type PrincipalType
// Attributes contains additional principal attributes for ABAC.
Attributes map[string]any
}
Principal represents an entity requesting access (user, service, etc.).
func NewServicePrincipal ¶
NewServicePrincipal creates a Principal for a service.
func NewUserPrincipal ¶
NewUserPrincipal creates a Principal for a user.
func NewUserPrincipalWithAttrs ¶
NewUserPrincipalWithAttrs creates a Principal for a user with attributes.
type PrincipalType ¶
type PrincipalType string
PrincipalType identifies the kind of principal.
const ( PrincipalTypeUser PrincipalType = "user" PrincipalTypeService PrincipalType = "service" PrincipalTypeAPIKey PrincipalType = "api_key" PrincipalTypeSystem PrincipalType = "system" )
type RelationshipSyncer ¶
type RelationshipSyncer interface {
// AddOrgMembership registers a principal's membership in an organization with a specific role.
// Called when a user joins an organization or is invited.
AddOrgMembership(ctx context.Context, principalID, orgID uuid.UUID, role string) error
// RemoveOrgMembership removes a principal's membership from an organization.
// Called when a user leaves or is removed from an organization.
RemoveOrgMembership(ctx context.Context, principalID, orgID uuid.UUID, role string) error
// UpdateOrgMembership changes a principal's role in an organization.
// Implemented as remove old role + add new role atomically.
UpdateOrgMembership(ctx context.Context, principalID, orgID uuid.UUID, oldRole, newRole string) error
// RegisterPrincipal creates a principal entity in the authorization system.
// Called when a new user, application, agent, or service principal is created.
RegisterPrincipal(ctx context.Context, principalID uuid.UUID) error
// UnregisterPrincipal removes a principal and all its relationships from the authorization system.
// Called when a principal is deleted.
UnregisterPrincipal(ctx context.Context, principalID uuid.UUID) error
// RegisterOrganization creates an organization in the authorization system with an initial owner.
// Called when a new organization is created.
RegisterOrganization(ctx context.Context, orgID, ownerID uuid.UUID) error
// UnregisterOrganization removes an organization and all its relationships from the authorization system.
// Called when an organization is deleted.
UnregisterOrganization(ctx context.Context, orgID uuid.UUID) error
// SetPlatformAdmin grants or revokes platform admin privileges for a principal.
SetPlatformAdmin(ctx context.Context, principalID uuid.UUID, isAdmin bool) error
}
RelationshipSyncer defines the interface for syncing identity changes to an authorization backend. Identity services use this interface to keep the authorization layer in sync with membership and principal lifecycle events.
type Resource ¶
type Resource struct {
// Type identifies the kind of resource (course, organization, etc.).
Type ResourceType
// ID is the unique identifier of the resource (nil for type-level checks).
ID *uuid.UUID
// OwnerID is the owner of the resource (for ownership-based access).
OwnerID *uuid.UUID
// OrgID is the organization this resource belongs to.
OrgID *uuid.UUID
// Attributes contains additional resource attributes for ABAC.
Attributes map[string]any
}
Resource represents something being accessed.
func NewOrgResource ¶
func NewOrgResource(resourceType ResourceType, orgID uuid.UUID) Resource
NewOrgResource creates a Resource scoped to an organization.
func NewOwnedResource ¶
func NewOwnedResource(resourceType ResourceType, id, ownerID uuid.UUID) Resource
NewOwnedResource creates a Resource with an owner.
func NewResource ¶
func NewResource(resourceType ResourceType) Resource
NewResource creates a Resource with the given type.
func NewResourceWithID ¶
func NewResourceWithID(resourceType ResourceType, id uuid.UUID) Resource
NewResourceWithID creates a Resource with type and ID.
type ResourceExtractor ¶
ResourceExtractor is a function that extracts a resource from an HTTP request. Use this for routes that include resource IDs in the path.
func WithResourceID ¶
func WithResourceID(resourceType ResourceType, pathParamName string, getParam func(r *http.Request, key string) string) ResourceExtractor
WithResourceID creates a ResourceExtractor that parses a resource ID from a URL path parameter.
type ResourceType ¶
type ResourceType string
ResourceType identifies the kind of resource.
const ( ResourceTypeOrganization ResourceType = "organization" ResourceTypeMember ResourceType = "member" ResourceTypeUser ResourceType = "user" )
Common resource type constants.
type RoleHierarchy ¶
RoleHierarchy defines a hierarchy of roles where higher values indicate more access. Roles with higher hierarchy values can access resources requiring lower levels.
func (RoleHierarchy) CanAccess ¶
func (h RoleHierarchy) CanAccess(userRole, requiredRole string) bool
CanAccess checks if a user role can access resources requiring the target role. Higher hierarchy values can access lower ones.
func (RoleHierarchy) IsHigherOrEqual ¶
func (h RoleHierarchy) IsHigherOrEqual(role1, role2 string) bool
IsHigherOrEqual checks if role1 is higher than or equal to role2.
func (RoleHierarchy) Level ¶
func (h RoleHierarchy) Level(role string) int
Level returns the hierarchy level for a role, or 0 if not found.
type RolePermissions ¶
RolePermissions maps roles to their granted permissions.
func (RolePermissions) GetPermissions ¶
func (rp RolePermissions) GetPermissions(role string) []string
GetPermissions returns all permissions for a role.
func (RolePermissions) HasAllPermissions ¶
func (rp RolePermissions) HasAllPermissions(role string, permissions []string) bool
HasAllPermissions checks if a role has all of the specified permissions.
func (RolePermissions) HasAnyPermission ¶
func (rp RolePermissions) HasAnyPermission(role string, permissions []string) bool
HasAnyPermission checks if a role has any of the specified permissions.
func (RolePermissions) HasPermission ¶
func (rp RolePermissions) HasPermission(role, permission string) bool
HasPermission checks if a role has a specific permission.
type SyncMode ¶
type SyncMode string
SyncMode determines how sync failures are handled.
const ( // SyncModeStrict fails the operation if authorization sync fails. // Use when authorization must be consistent with identity. SyncModeStrict SyncMode = "strict" // SyncModeEventual logs sync failures but allows the operation to succeed. // Use when eventual consistency is acceptable and a retry mechanism exists. SyncModeEventual SyncMode = "eventual" )
type VocabularyRegistry ¶
type VocabularyRegistry struct {
// contains filtered or unexported fields
}
VocabularyRegistry holds the vocabularies of every app in a deployment. A composed deployment registers each Forge app at startup; registration validates the vocabulary so convention drift fails fast rather than surfacing as authorization bugs.
func NewVocabularyRegistry ¶
func NewVocabularyRegistry() *VocabularyRegistry
NewVocabularyRegistry returns an empty registry.
func (*VocabularyRegistry) AllScopes ¶
func (r *VocabularyRegistry) AllScopes() []string
AllScopes returns every registered scope, sorted, for consent screens and principal AllowedScopes configuration.
func (*VocabularyRegistry) Apps ¶
func (r *VocabularyRegistry) Apps() []string
Apps returns the registered app names in registration order.
func (*VocabularyRegistry) ComposeSpiceDBSchema ¶
func (r *VocabularyRegistry) ComposeSpiceDBSchema() (string, error)
ComposeSpiceDBSchema assembles the deployment's full SpiceDB schema: the SystemForge base followed by every registered app's fragment in registration order. It fails on any duplicate definition — impossible when fragments follow the "{app}_" prefix convention, but verified anyway so a bad fragment cannot silently shadow another app's (or the base's) definitions.
func (*VocabularyRegistry) Get ¶
func (r *VocabularyRegistry) Get(app string) (AppVocabulary, bool)
Get returns the vocabulary registered for app.
func (*VocabularyRegistry) Register ¶
func (r *VocabularyRegistry) Register(v AppVocabulary) error
Register validates and adds an app's vocabulary. Registering the same app twice is an error.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package noop provides a no-operation authorization syncer for deployments that don't require authorization backend synchronization.
|
Package noop provides a no-operation authorization syncer for deployments that don't require authorization backend synchronization. |
|
Package providertest provides conformance tests for authz.Provider implementations.
|
Package providertest provides conformance tests for authz.Provider implementations. |
|
Package simple provides a simple role-based authorization provider.
|
Package simple provides a simple role-based authorization provider. |
|
Package spicedb provides SpiceDB-based authorization for SystemForge.
|
Package spicedb provides SpiceDB-based authorization for SystemForge. |