Help us shape the future of Go! Take the 2026 Go Developer Survey

rbac

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package rbac provides role-based access control for AuthSome.

Roles contain named sets of permissions (action + resource pairs). Users are assigned roles, optionally scoped to an organization. The HasPermission check walks the user's role assignments to determine access.

When Warden is available, all RBAC operations delegate to the Warden authorization engine. The types in this package serve as the API facade.

Index

Constants

View Source
const (
	PlatformUserSlug  = "platform-user"
	PlatformAdminSlug = "platform-admin"
	PlatformOwnerSlug = "platform-owner"

	// SuperAdminSlug documents the intended slug for a future super-admin
	// role that spans all platform capabilities. It is declared here to
	// prevent accidental reuse of the name in unrelated roles and to serve
	// as a single source of truth when the role is eventually introduced.
	// TODO: Add SuperAdminSlug to IsPlatformRole when the warden DSL defines
	// the role and it is wired up in the authorization system.
	SuperAdminSlug = "super-admin"
)

Platform role slugs. These are ONLY created under the platform app, never for regular tenant apps. They grant cross-app access.

Slugs are kebab-case so they pass the warden DSL slug convention regex (^[a-z][a-z0-9-]{0,62}$). Existing deployments that have the old underscore-separated rows in their roles table will need to either re-bootstrap or migrate the rows manually — the bootstrap is idempotent at the slug level, so old rows aren't touched and the new rows live alongside them until cleaned up.

View Source
const (
	AppOwnerSlug = "owner"
	AppAdminSlug = "admin"
	AppUserSlug  = "user"
)

App-scoped role slugs. Created for every app during bootstrap.

Variables

View Source
var (
	ErrRoleNotFound        = errors.New("rbac: role not found")
	ErrPermissionNotFound  = errors.New("rbac: permission not found")
	ErrRoleAlreadyAssigned = errors.New("rbac: role already assigned")
	ErrCyclicHierarchy     = errors.New("rbac: cyclic role hierarchy detected")

	// ErrSystemRoleImmutable refuses a change to a role declared
	// is_system, as warden's own contract and REST paths refuse it.
	ErrSystemRoleImmutable = errors.New("rbac: system role cannot be changed")
)

Store errors.

Functions

func IsPlatformRole

func IsPlatformRole(slug string) bool

IsPlatformRole returns true if the slug is a platform-scoped role.

func PermissionString

func PermissionString(action, resource string) string

PermissionString renders an action on a resource as the single string a route declares to Forge.

Permissions are a pair everywhere else in authsome: RequirePermission takes (action, resource), and rbac.Permission stores them in two columns. Forge's WithAllPermissions takes one string per permission, because that string ends up in the OpenAPI document as x-forge-authz and then as a constant in every generated client. Flattening it in one function keeps the two sides from drifting into "manage:user" here and "user:manage" three files away, which nothing would catch: both are valid strings, and a client generated from one silently fails to match a server checking the other.

Action first, matching the argument order of RequirePermission, so a declaration reads in the same order as the middleware call it mirrors.

func ToWardenAssignment

func ToWardenAssignment(ur *UserRole, tenantID string) *wardenassign.Assignment

ToWardenAssignment converts an authsome RBAC UserRole to a warden Assignment.

func ToWardenPermission

func ToWardenPermission(p *Permission, tenantID string) *wardenperm.Permission

ToWardenPermission converts an authsome RBAC Permission to a warden Permission.

func ToWardenRole

func ToWardenRole(r *Role) *wardenrole.Role

ToWardenRole converts an authsome RBAC Role to a warden Role.

Types

type Permission

type Permission struct {
	ID       string `json:"id"`
	RoleID   string `json:"role_id"`
	Action   string `json:"action"`   // e.g. "read", "write", "delete", "admin"
	Resource string `json:"resource"` // e.g. "user", "org", "document"
}

Permission represents a single action on a resource granted by a role.

func FromWardenPermission

func FromWardenPermission(wp *wardenperm.Permission, roleID string) *Permission

FromWardenPermission converts a warden Permission to an authsome RBAC Permission. The roleID is provided because warden permissions are not directly associated with a single role (they are attached via a join table).

type Role

type Role struct {
	ID          string    `json:"id"`
	AppID       string    `json:"app_id"`
	EnvID       string    `json:"env_id"`
	ParentID    string    `json:"parent_id,omitempty"` // Empty = root role (no parent)
	Name        string    `json:"name"`
	Slug        string    `json:"slug"`
	Description string    `json:"description,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`

	// NamespacePath is the warden namespace the role was read from. Warden
	// resolves ParentID (a slug) in this namespace only. It is read-only:
	// ToWardenRole places a role by EnvID, not by this field.
	NamespacePath string `json:"-"`
}

Role represents a named collection of permissions.

func FromWardenRole

func FromWardenRole(wr *wardenrole.Role) *Role

FromWardenRole converts a warden Role to an authsome RBAC Role.

type Store

type Store interface {
	// Role CRUD
	CreateRole(ctx context.Context, r *Role) error
	GetRole(ctx context.Context, appID, roleID string) (*Role, error)
	// GetRoles loads several roles in one call; ids that do not resolve
	// are left out rather than failing the whole call.
	GetRoles(ctx context.Context, appID string, roleIDs []string) ([]*Role, error)
	GetRoleBySlug(ctx context.Context, appID string, slug string) (*Role, error)
	UpdateRole(ctx context.Context, r *Role) error
	DeleteRole(ctx context.Context, appID, roleID string) error
	ListRoles(ctx context.Context, appID string) ([]*Role, error)

	// Permission management
	AddPermission(ctx context.Context, appID string, p *Permission) error
	RemovePermission(ctx context.Context, appID, permID string) error
	ListRolePermissions(ctx context.Context, appID, roleID string) ([]*Permission, error)

	// Role assignment
	AssignUserRole(ctx context.Context, appID string, ur *UserRole) error
	UnassignUserRole(ctx context.Context, appID, userID, roleID string) error
	ListUserRoles(ctx context.Context, userID string) ([]*Role, error)
	ListUserRolesForApp(ctx context.Context, appID string, userID string) ([]*Role, error)

	// Hierarchy
	GetRoleChildren(ctx context.Context, appID, roleID string) ([]*Role, error)

	// Permission check — walks the parent chain for inherited permissions.
	HasPermission(ctx context.Context, userID string, action, resource string) (bool, error)
}

Store persists RBAC data. All IDs are plain strings — the rbac package serves as a DTO facade. Implementations convert to/from their internal typed-ID formats as needed.

Every by-ID operation takes the owning app. A call whose app does not own the row behaves as if the row did not exist, so holding another tenant's ID reads, changes and deletes nothing.

type UserRole

type UserRole struct {
	UserID     string    `json:"user_id"`
	RoleID     string    `json:"role_id"`
	OrgID      string    `json:"org_id,omitempty"` // empty = global scope
	AssignedAt time.Time `json:"assigned_at"`
}

UserRole represents a role assigned to a user, optionally scoped to an org.

type WardenStore

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

WardenStore implements rbac.Store by delegating to a Warden authorization engine. Role/permission CRUD is performed via Warden's store. HasPermission uses warden.Engine.Check() which evaluates the full RBAC+ReBAC+ABAC stack.

func NewWardenStore

func NewWardenStore(eng *warden.Engine) *WardenStore

NewWardenStore creates a new WardenStore backed by the given Warden engine.

func (*WardenStore) AddPermission

func (s *WardenStore) AddPermission(ctx context.Context, appID string, p *Permission) error

func (*WardenStore) AssignUserRole

func (s *WardenStore) AssignUserRole(ctx context.Context, appID string, ur *UserRole) error

func (*WardenStore) CreateRole

func (s *WardenStore) CreateRole(ctx context.Context, r *Role) error

func (*WardenStore) DeleteRole

func (s *WardenStore) DeleteRole(ctx context.Context, appID, roleID string) error

func (*WardenStore) GetRole

func (s *WardenStore) GetRole(ctx context.Context, appID, roleID string) (*Role, error)

func (*WardenStore) GetRoleBySlug

func (s *WardenStore) GetRoleBySlug(ctx context.Context, appID, slug string) (*Role, error)

func (*WardenStore) GetRoleChildren

func (s *WardenStore) GetRoleChildren(ctx context.Context, appID, roleID string) ([]*Role, error)

func (*WardenStore) GetRoles

func (s *WardenStore) GetRoles(ctx context.Context, appID string, roleIDs []string) ([]*Role, error)

GetRoles implements Store with one warden round trip. Ids that do not parse, no longer exist, or belong to another app are left out.

func (*WardenStore) HasPermission

func (s *WardenStore) HasPermission(ctx context.Context, userID, action, resource string) (bool, error)

func (*WardenStore) ListRolePermissions

func (s *WardenStore) ListRolePermissions(ctx context.Context, appID, roleID string) ([]*Permission, error)

func (*WardenStore) ListRoles

func (s *WardenStore) ListRoles(ctx context.Context, appID string) ([]*Role, error)

func (*WardenStore) ListUserRoles

func (s *WardenStore) ListUserRoles(ctx context.Context, userID string) ([]*Role, error)

func (*WardenStore) ListUserRolesForApp

func (s *WardenStore) ListUserRolesForApp(ctx context.Context, appID, userID string) ([]*Role, error)

func (*WardenStore) RemovePermission

func (s *WardenStore) RemovePermission(ctx context.Context, appID, permID string) error

func (*WardenStore) UnassignUserRole

func (s *WardenStore) UnassignUserRole(ctx context.Context, appID, userID, roleID string) error

func (*WardenStore) UpdateRole

func (s *WardenStore) UpdateRole(ctx context.Context, r *Role) error

UpdateRole writes the fields authsome edits, name and description, onto the stored warden role and keeps everything else. Warden's store replaces the whole row, and a Role has no room for the namespace, parent, default flag, member cap or metadata, so writing a converted Role would wipe them. Slug stays too, since child roles find their parent by it.

A system role is refused, as warden's own write paths refuse it. On success r is refreshed from what was written.

Jump to

Keyboard shortcuts

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