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/
This commit is contained in:
45
docs/adr/ADR-003-identity-context-ownership.md
Normal file
45
docs/adr/ADR-003-identity-context-ownership.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ADR-003: Identity Context Ownership
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Global ADR-003 establishes that context helpers must live with their data owners.
|
||||
`Identity` is defined in the `rbac` package. Its context key, storage function, and
|
||||
retrieval function must also live here to avoid requiring any other package to know
|
||||
the key type.
|
||||
|
||||
If the context key for `Identity` were defined elsewhere (e.g. in an `httpauth`
|
||||
module), any package that wanted to retrieve an identity from context would need to
|
||||
import `httpauth` — creating a coupling that does not make sense for packages that
|
||||
have nothing to do with HTTP authentication.
|
||||
|
||||
## Decision
|
||||
|
||||
The unexported type `authContextKey struct{}` and the package-level variable
|
||||
`authKey = authContextKey{}` are defined in `identity.go` within the `rbac` package.
|
||||
Two exported functions manage the context lifecycle:
|
||||
|
||||
- `SetInContext(ctx context.Context, id Identity) context.Context` — stores the
|
||||
identity value under `authKey`.
|
||||
- `FromContext(ctx context.Context) (Identity, bool)` — retrieves it, returning
|
||||
`false` when absent.
|
||||
|
||||
The key type is unexported (`authContextKey`), which prevents any external package
|
||||
from constructing or comparing the key directly — only `rbac` can write or read the
|
||||
identity in context. This eliminates the risk of key collisions with other packages
|
||||
that might also use an empty struct as a context key.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Any package that needs to read the authenticated identity imports only `rbac` —
|
||||
not `httpauth`, not any HTTP package.
|
||||
- `httpauth` middleware stores the identity via `rbac.SetInContext`; domain handlers
|
||||
retrieve it via `rbac.FromContext`. The two call sites share a single, well-known
|
||||
contract.
|
||||
- The unexported key type guarantees that no external package can accidentally shadow
|
||||
or overwrite the identity value by using the same key.
|
||||
- `PermissionProvider.ResolveMask` receives `ctx` explicitly; implementations that
|
||||
need the `TenantID` call `rbac.FromContext(ctx)` to obtain it — no need to thread
|
||||
tenant ID as a separate parameter.
|
||||
Reference in New Issue
Block a user