rbac

package module
v0.0.8 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 6 Imported by: 1

README

tinywasm/rbac

Role-based authorization runtime for TinyWasm applications. Authentication and sessions belong to tinywasm/auth. Both are siblings that depend only on tinywasm/user and never on each other.

flowchart TD
    U[user] --> R[rbac]
    U --> A[auth]
    R --> C[app]
    A --> C

Documentation

  • Architecture — Dependency rules and authorization mechanics

Usage

Every table — and every call — carries a projectID: one Service over one database serves every consuming project (misitio, mjosefa-cms, ...) without their roles/permissions colliding or leaking into each other.

Schema reconciliation (rbac.Migrate) is performed once at deploy time, not inside rbac.New:

import (
    "github.com/tinywasm/rbac"
    "github.com/tinywasm/model"
    "github.com/tinywasm/orm"
    "github.com/tinywasm/sqlite"
    "github.com/tinywasm/sqlt"
)

conn, _ := sqlite.Open("app.db")
_ = rbac.Migrate(conn, sqlt.NewCompiler())

db := orm.New(conn)
svc, _ := rbac.New(db)

const projectID = "misitio"

_ = svc.CreateRole(projectID, "role_admin", "admin", "Administrator", "")
_ = svc.CreatePermission(projectID, "service_catalog:crud", "catalog", "service_catalog", model.AllActions)
_ = svc.AssignPermission(projectID, "role_admin", "service_catalog:crud")
_ = svc.AssignRole(projectID, string(subjectID), "role_admin")

if svc.Can(projectID, string(subjectID), "service_catalog", model.Read) {
    // granted
}

Empty or unknown subject IDs are denied — rbac never persists a "user" row, so a subject with no assignments simply resolves to zero permissions, not an error. Malformed stored actions deny and surface an error (never a silent false, nil).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = fmt.Err("rbac", "not", "found")

ErrNotFound reports that a role or permission id has no matching row.

View Source
var PermissionModel = model.Definition{
	Name: "permission",
	Fields: model.Fields{
		{Name: "project_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "id", Type: model.Text(), DB: &model.FieldDB{PK: true}},
		{Name: "name", Type: model.Text()},
		{Name: "resource", Type: model.Text()},
		{Name: "action", Type: model.Text()},
	},
}
View Source
var Permission_ = struct {
	ProjectId string
	Id        string
	Name      string
	Resource  string
	Action    string
}{
	ProjectId: "project_id",
	Id:        "id",
	Name:      "name",
	Resource:  "resource",
	Action:    "action",
}
View Source
var RoleModel = model.Definition{
	Name: "role",
	Fields: model.Fields{
		{Name: "project_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "id", Type: model.Text(), DB: &model.FieldDB{PK: true}},
		{Name: "code", Type: model.Text()},
		{Name: "name", Type: model.Text()},
		{Name: "description", Type: model.Text()},

		{Name: "session_ttl", Type: model.Int()},
	},
}
View Source
var RolePermissionModel = model.Definition{
	Name: "role_permission",
	Fields: model.Fields{
		{Name: "project_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "role_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "permission_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
	},
}
View Source
var RolePermission_ = struct {
	ProjectId    string
	RoleId       string
	PermissionId string
}{
	ProjectId:    "project_id",
	RoleId:       "role_id",
	PermissionId: "permission_id",
}
View Source
var Role_ = struct {
	ProjectId   string
	Id          string
	Code        string
	Name        string
	Description string
	SessionTtl  string
}{
	ProjectId:   "project_id",
	Id:          "id",
	Code:        "code",
	Name:        "name",
	Description: "description",
	SessionTtl:  "session_ttl",
}
View Source
var UserRoleModel = model.Definition{
	Name: "user_role",
	Fields: model.Fields{
		{Name: "project_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "user_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
		{Name: "role_id", Type: model.Text(), DB: &model.FieldDB{PK: true}, NotNull: true},
	},
}
View Source
var UserRole_ = struct {
	ProjectId string
	UserId    string
	RoleId    string
}{
	ProjectId: "project_id",
	UserId:    "user_id",
	RoleId:    "role_id",
}

Functions

func Migrate added in v0.0.7

func Migrate(conn ddl.Execer, ddlCompiler ddl.Compiler) error

Migrate reconciles the database schema this package owns: Role, Permission, UserRole and RolePermission, in dependency order.

It is deliberately NOT called by New. Schema reconciliation is deploy-time work — running it per process start costs a network round trip per model, which in a Cloudflare Worker is paid again on every isolate cold start (measured at 8.5–10.4 s across ~14 models in veltylabs/iam). Call this once from a migration binary, then let New assume the schema exists.

conn is a ddl.Execer, not an *orm.DB, so a deploy-time transport that can only execute DDL satisfies it — goflare.NewD1Migrator returns exactly that. An *orm.DB's RawConn() also satisfies it, for local/test callers:

// deploy time, against D1's HTTP API:
conn, _ := goflare.NewD1Migrator(accountID, databaseID, apiToken)
err := rbac.Migrate(conn, sqlt.NewCompiler())

// local dev / tests, against an in-memory or sqlite DB:
err := rbac.Migrate(db.RawConn(), db.RawConn().(ddl.Compiler))

Types

type Permission added in v0.0.2

type Permission struct {
	ProjectId string
	Id        string
	Name      string
	Resource  string
	Action    string
}

func ReadOnePermission added in v0.0.5

func ReadOnePermission(qb *orm.QB, model *Permission) (*Permission, error)

func (*Permission) DecodeFields added in v0.0.5

func (m *Permission) DecodeFields(r model.FieldReader)

func (*Permission) EncodeFields added in v0.0.5

func (m *Permission) EncodeFields(w model.FieldWriter)

func (*Permission) IsNil added in v0.0.5

func (m *Permission) IsNil() bool

func (*Permission) ModelName added in v0.0.5

func (m *Permission) ModelName() string

func (*Permission) Pointers added in v0.0.5

func (m *Permission) Pointers() []any

func (*Permission) Schema added in v0.0.5

func (m *Permission) Schema() []model.Field

func (*Permission) Validate added in v0.0.5

func (m *Permission) Validate(action byte) error

type PermissionList added in v0.0.5

type PermissionList []*Permission

func ReadAllPermission added in v0.0.5

func ReadAllPermission(qb *orm.QB) (PermissionList, error)

func (*PermissionList) Append added in v0.0.5

func (s *PermissionList) Append() model.Fielder

func (*PermissionList) At added in v0.0.5

func (s *PermissionList) At(i int) model.Fielder

func (*PermissionList) DecodeFields added in v0.0.5

func (s *PermissionList) DecodeFields(_ model.FieldReader)

func (*PermissionList) EncodeFields added in v0.0.5

func (s *PermissionList) EncodeFields(_ model.FieldWriter)

func (*PermissionList) IsNil added in v0.0.5

func (s *PermissionList) IsNil() bool

func (*PermissionList) Len added in v0.0.5

func (s *PermissionList) Len() int

func (*PermissionList) Pointers added in v0.0.5

func (s *PermissionList) Pointers() []any

func (*PermissionList) Schema added in v0.0.5

func (s *PermissionList) Schema() []model.Field

type RBACObject added in v0.0.5

type RBACObject interface {
	HandlerName() string
	AllowedRoles(action model.Action) []model.RoleCode
}

type Role added in v0.0.2

type Role struct {
	ProjectId   string
	Id          string
	Code        string
	Name        string
	Description string
	SessionTtl  int64
}

func ReadOneRole added in v0.0.5

func ReadOneRole(qb *orm.QB, model *Role) (*Role, error)

func (*Role) DecodeFields added in v0.0.5

func (m *Role) DecodeFields(r model.FieldReader)

func (*Role) EncodeFields added in v0.0.5

func (m *Role) EncodeFields(w model.FieldWriter)

func (*Role) IsNil added in v0.0.5

func (m *Role) IsNil() bool

func (*Role) ModelName added in v0.0.5

func (m *Role) ModelName() string

func (*Role) Pointers added in v0.0.5

func (m *Role) Pointers() []any

func (*Role) Schema added in v0.0.5

func (m *Role) Schema() []model.Field

func (*Role) Validate added in v0.0.5

func (m *Role) Validate(action byte) error

type RoleList added in v0.0.5

type RoleList []*Role

func ReadAllRole added in v0.0.5

func ReadAllRole(qb *orm.QB) (RoleList, error)

func (*RoleList) Append added in v0.0.5

func (s *RoleList) Append() model.Fielder

func (*RoleList) At added in v0.0.5

func (s *RoleList) At(i int) model.Fielder

func (*RoleList) DecodeFields added in v0.0.5

func (s *RoleList) DecodeFields(_ model.FieldReader)

func (*RoleList) EncodeFields added in v0.0.5

func (s *RoleList) EncodeFields(_ model.FieldWriter)

func (*RoleList) IsNil added in v0.0.5

func (s *RoleList) IsNil() bool

func (*RoleList) Len added in v0.0.5

func (s *RoleList) Len() int

func (*RoleList) Pointers added in v0.0.5

func (s *RoleList) Pointers() []any

func (*RoleList) Schema added in v0.0.5

func (s *RoleList) Schema() []model.Field

type RolePermission added in v0.0.5

type RolePermission struct {
	ProjectId    string
	RoleId       string
	PermissionId string
}

func ReadOneRolePermission added in v0.0.5

func ReadOneRolePermission(qb *orm.QB, model *RolePermission) (*RolePermission, error)

func (*RolePermission) DecodeFields added in v0.0.5

func (m *RolePermission) DecodeFields(r model.FieldReader)

func (*RolePermission) EncodeFields added in v0.0.5

func (m *RolePermission) EncodeFields(w model.FieldWriter)

func (*RolePermission) IsNil added in v0.0.5

func (m *RolePermission) IsNil() bool

func (*RolePermission) ModelName added in v0.0.5

func (m *RolePermission) ModelName() string

func (*RolePermission) Pointers added in v0.0.5

func (m *RolePermission) Pointers() []any

func (*RolePermission) Schema added in v0.0.5

func (m *RolePermission) Schema() []model.Field

func (*RolePermission) Validate added in v0.0.5

func (m *RolePermission) Validate(action byte) error

type RolePermissionList added in v0.0.5

type RolePermissionList []*RolePermission

func ReadAllRolePermission added in v0.0.5

func ReadAllRolePermission(qb *orm.QB) (RolePermissionList, error)

func (*RolePermissionList) Append added in v0.0.5

func (s *RolePermissionList) Append() model.Fielder

func (*RolePermissionList) At added in v0.0.5

func (*RolePermissionList) DecodeFields added in v0.0.5

func (s *RolePermissionList) DecodeFields(_ model.FieldReader)

func (*RolePermissionList) EncodeFields added in v0.0.5

func (s *RolePermissionList) EncodeFields(_ model.FieldWriter)

func (*RolePermissionList) IsNil added in v0.0.5

func (s *RolePermissionList) IsNil() bool

func (*RolePermissionList) Len added in v0.0.5

func (s *RolePermissionList) Len() int

func (*RolePermissionList) Pointers added in v0.0.5

func (s *RolePermissionList) Pointers() []any

func (*RolePermissionList) Schema added in v0.0.5

func (s *RolePermissionList) Schema() []model.Field

type Service added in v0.0.5

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

Service owns role, permission, and subject-assignment persistence, scoped by project — every table carries project_id, so one Service over one database serves every consuming project (see ARCHITECTURE.md).

func New

func New(db *orm.DB) (*Service, error)

New creates an authorization service over the injected database.

func (*Service) AssignPermission added in v0.0.5

func (m *Service) AssignPermission(projectID, roleID, permissionID string) error

func (*Service) AssignRole added in v0.0.5

func (m *Service) AssignRole(projectID, userID, roleID string) error

func (*Service) Can added in v0.0.5

func (s *Service) Can(projectID, subjectID string, resource model.Resource, action model.Action) bool

Can reports whether subjectID has a grant for resource/action within projectID.

func (*Service) CanSubject added in v0.0.5

func (s *Service) CanSubject(projectID string, id user.SubjectID, resource model.Resource, action model.Action) bool

CanSubject is the typed alias for Can that accepts the stable user.SubjectID.

func (*Service) CreatePermission added in v0.0.5

func (m *Service) CreatePermission(projectID, id, name string, resource model.Resource, action model.Action) error

func (*Service) CreateRole added in v0.0.5

func (m *Service) CreateRole(projectID, id string, code model.RoleCode, name, description string) error

func (*Service) DeletePermission added in v0.0.5

func (m *Service) DeletePermission(projectID, id string) error

func (*Service) DeleteRole added in v0.0.5

func (m *Service) DeleteRole(projectID, id string) error

func (*Service) GetPermission added in v0.0.5

func (m *Service) GetPermission(projectID, id string) (*Permission, error)

func (*Service) GetRole added in v0.0.5

func (m *Service) GetRole(projectID, id string) (*Role, error)

func (*Service) GetRoleByCode added in v0.0.5

func (m *Service) GetRoleByCode(projectID string, code model.RoleCode) (*Role, error)

func (*Service) GetUserRoles added in v0.0.5

func (m *Service) GetUserRoles(projectID, userID string) ([]Role, error)

func (*Service) HasPermission added in v0.0.5

func (m *Service) HasPermission(projectID, subjectID string, resource model.Resource, action model.Action) (bool, error)

func (*Service) Register added in v0.0.5

func (m *Service) Register(projectID string, handlers ...RBACObject) error

Register builds permissions from handlers' declared resource/action grants and assigns them to the roles those handlers name — policy stays with the caller (see README: "Policy belongs to the consumer"); rbac only persists what Register is told.

func (*Service) RevokeRole added in v0.0.5

func (m *Service) RevokeRole(projectID, userID, roleID string) error

func (*Service) SetRoleSessionTTL added in v0.0.5

func (m *Service) SetRoleSessionTTL(projectID, id string, ttl int64) error

SetRoleSessionTTL sets the role's SessionTtl (seconds; 0 reverts to "use the caller's default"). See RoleModel's session_ttl comment for the most-restrictive-wins policy a caller applies across a user's roles.

type UserRole added in v0.0.5

type UserRole struct {
	ProjectId string
	UserId    string
	RoleId    string
}

func ReadOneUserRole added in v0.0.5

func ReadOneUserRole(qb *orm.QB, model *UserRole) (*UserRole, error)

func (*UserRole) DecodeFields added in v0.0.5

func (m *UserRole) DecodeFields(r model.FieldReader)

func (*UserRole) EncodeFields added in v0.0.5

func (m *UserRole) EncodeFields(w model.FieldWriter)

func (*UserRole) IsNil added in v0.0.5

func (m *UserRole) IsNil() bool

func (*UserRole) ModelName added in v0.0.5

func (m *UserRole) ModelName() string

func (*UserRole) Pointers added in v0.0.5

func (m *UserRole) Pointers() []any

func (*UserRole) Schema added in v0.0.5

func (m *UserRole) Schema() []model.Field

func (*UserRole) Validate added in v0.0.5

func (m *UserRole) Validate(action byte) error

type UserRoleList added in v0.0.5

type UserRoleList []*UserRole

func ReadAllUserRole added in v0.0.5

func ReadAllUserRole(qb *orm.QB) (UserRoleList, error)

func (*UserRoleList) Append added in v0.0.5

func (s *UserRoleList) Append() model.Fielder

func (*UserRoleList) At added in v0.0.5

func (s *UserRoleList) At(i int) model.Fielder

func (*UserRoleList) DecodeFields added in v0.0.5

func (s *UserRoleList) DecodeFields(_ model.FieldReader)

func (*UserRoleList) EncodeFields added in v0.0.5

func (s *UserRoleList) EncodeFields(_ model.FieldWriter)

func (*UserRoleList) IsNil added in v0.0.5

func (s *UserRoleList) IsNil() bool

func (*UserRoleList) Len added in v0.0.5

func (s *UserRoleList) Len() int

func (*UserRoleList) Pointers added in v0.0.5

func (s *UserRoleList) Pointers() []any

func (*UserRoleList) Schema added in v0.0.5

func (s *UserRoleList) Schema() []model.Field

Jump to

Keyboard shortcuts

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