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/
2.0 KiB
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 underauthKey.FromContext(ctx context.Context) (Identity, bool)— retrieves it, returningfalsewhen 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— nothttpauth, not any HTTP package. httpauthmiddleware stores the identity viarbac.SetInContext; domain handlers retrieve it viarbac.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.ResolveMaskreceivesctxexplicitly; implementations that need theTenantIDcallrbac.FromContext(ctx)to obtain it — no need to thread tenant ID as a separate parameter.