Files
rbac/docs/adr/ADR-003-identity-context-ownership.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

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 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.