Files
rbac/CLAUDE.md
Rene Nochebuena 0864f031a1 feat(rbac): initial stable release v0.9.0
Foundational identity and permission types for role-based access control — bit-set PermissionMask, immutable Identity value type, and PermissionProvider interface.

What's included:
- `Identity` value type with NewIdentity / WithTenant constructors and SetInContext / FromContext context helpers
- `Permission` (int64 bit position) and `PermissionMask` (int64 bit-set) with O(1) Has and non-mutating Grant
- `PermissionProvider` interface for DB-backed ResolveMask(ctx, uid, resource) resolution

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 13:25:43 -06:00

114 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rbac
Foundational identity and permission types for role-based access control.
## Purpose
`rbac` defines the `Identity` value type (authenticated principal), the
`PermissionMask` bit-set type (resolved capabilities), and the `PermissionProvider`
interface (DB-backed permission resolution). Every other module that needs to carry
or inspect an authenticated identity imports this package; it has no dependencies of
its own.
## Tier & Dependencies
**Tier:** 0
**Imports:** `context` (stdlib only)
**Must NOT import:** `logz`, `xerrors`, `launcher`, or any other micro-lib module.
## Key Design Decisions
- Permissions are bit positions (062) packed into an `int64` mask for O(1) checks
and compact storage. See `docs/adr/ADR-001-bitset-permissions.md`.
- `Identity` is a value type — copied on every enrichment, never a pointer — to
prevent nil bugs and accidental mutation from concurrent middleware. See
`docs/adr/ADR-002-identity-value-type.md`.
- `rbac` owns the context key for `Identity` (`authContextKey{}`). Any package that
needs an identity imports only `rbac`, not an HTTP package. See
`docs/adr/ADR-003-identity-context-ownership.md`.
- `TenantID` is an optional enrichment field on `Identity`. Multi-tenancy is not
modelled in the core; `PermissionProvider` implementations retrieve the tenant from
`rbac.FromContext(ctx)` when needed.
## Patterns
**Identity lifecycle:**
```go
// Step 1: create from auth token (in httpauth middleware)
id := rbac.NewIdentity(uid, displayName, email)
// Step 2: optionally enrich with tenant (in tenant middleware)
id = id.WithTenant(tenantID)
// Store in context
ctx = rbac.SetInContext(ctx, id)
// Retrieve anywhere downstream
id, ok := rbac.FromContext(ctx)
if !ok {
// no authenticated user in context
}
```
**Defining application permissions:**
```go
// In your domain package — define once, use everywhere
const (
PermRead rbac.Permission = 0
PermWrite rbac.Permission = 1
PermDelete rbac.Permission = 2
)
```
**Checking permissions:**
```go
mask, err := provider.ResolveMask(ctx, id.UID, "orders")
if err != nil { ... }
if !mask.Has(PermWrite) {
return xerrors.New(xerrors.ErrPermissionDenied, "write access required")
}
```
**Building masks in tests:**
```go
mask := rbac.PermissionMask(0).Grant(PermRead).Grant(PermWrite)
```
**In-memory PermissionProvider for tests:**
```go
type staticProvider struct{ mask rbac.PermissionMask }
func (p *staticProvider) ResolveMask(_ context.Context, _, _ string) (rbac.PermissionMask, error) {
return p.mask, nil
}
```
## What to Avoid
- Do not store `*Identity` in context — always store the value type. The type
assertion in `FromContext` relies on `Identity` being stored as a value.
- Do not define permission constants in this package. Domain permissions belong in
the domain package that owns the resource.
- Do not add role-string logic here. The bit-set model deliberately avoids the
role-to-permission mapping table problem.
- Do not call `rbac.FromContext` from within `rbac` itself — the context helpers are
for consumers, not for internal use.
- Permissions 63 and above are silently ignored by `Has` and `Grant`. Keep constants
in the range 062.
## Testing Notes
- `compliance_test.go` enforces at compile time that `Identity.WithTenant` returns
`Identity` (not `*Identity`) and that `PermissionMask` exposes `Has` and `Grant`
with the correct typed signatures.
- `identity_test.go` covers `NewIdentity`, `WithTenant` immutability,
`SetInContext`/`FromContext` round-trips, and the zero-value absent case.
- `permission_test.go` covers `Has`, `Grant`, and boundary conditions (negative
positions, position >= 63).
- No external dependencies — run with plain `go test`.