Role-based authorization runtime for WebTyp applications. Authentication and
sessions belong to webtyp/auth. Both are siblings that depend only on
webtyp/user and never on each other.
BREAKING CHANGE:
DeleteRolenow returnsrbac.ErrRoleNotFoundwhen attempting to delete a non-existent role, instead of returningnil.BREAKING CHANGE:
GetRoleByCodenow returnsrbac.ErrRoleNotFound(notorm.ErrNotFound) when the code doesn't exist in the project, andrbac.ErrDuplicateRoleCodeif more than one row matches (should not happen after the unique index, but is now reported instead of silently picking one). Any caller comparingerr == orm.ErrNotFoundafterGetRoleByCodemust switch toerr == rbac.ErrRoleNotFound— the code still compiles either way, so this fails silently at runtime (a 500 where a 404 used to be), not at compile time.
flowchart TD
U[user] --> R[rbac]
U --> A[auth]
R --> C[app]
A --> C
- Architecture — Dependency rules and authorization mechanics
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 (
"webtyp.com/rbac"
"webtyp.com/model"
"webtyp.com/orm"
"webtyp.com/sqlite"
"webtyp.com/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
}Some operations belong to every signed-in subject (e.g. writing in a chatroom, opening a direct message). Multi-tenant systems grant these to rbac.RoleAuthenticated:
const RoleAuthenticated model.RoleCode = "authenticated"A module declares its default grants (DefaultGrants), and the app ensures them at boot time:
err := svc.EnsureGrants(app.TenantID, rbac.RoleAuthenticated, chatroom.DefaultGrants)EnsureGrants is idempotent: it ensures the role exists, creates any missing permissions, and assigns them to the role. Any subject with a non-empty subjectID automatically holds RoleAuthenticated without needing an explicit UserRole record.
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).
| Objetivo | Método |
|---|---|
| Aplicar permisos por defecto a cualquier usuario autenticado | svc.EnsureGrants(projectID, rbac.RoleAuthenticated, grants) |
| Listar roles | svc.ListRoles(projectID) ([]Role, error) (every role of a project, ordered by code) |
| Asignar rol por código | svc.AssignRoleByCode(projectID, userID, roleCode) |
| Revocar rol por código (invalida caché) | svc.RevokeRoleByCode(projectID, userID, roleCode) |
| Listar usuarios de un rol | svc.UsersInRole(projectID, roleCode) |
| Contar usuarios de un rol | svc.RoleUserCount(projectID, roleCode) |
| Eliminar rol y asignaciones por código | svc.DeleteRoleByCode(projectID, roleCode) |
| Detectar roles duplicados antes de migrar | rbac.FindDuplicateRoleCodes(db) |
| Permisos de un sujeto tal como están guardados (para el navegador) | svc.SubjectGrants(projectID, subjectID) ([]model.Grant, error) — evaluar con model.AnyGrant |
Una base de datos demuestra que sirve a rbac ejecutando rbac/conformance.Run en sus propias pruebas.