AuthCrunch Role Contract Design
Summary
Define the production authorization contract between Caddy/AuthCrunch and the Phoenix application. The feature documents which AuthCrunch claims or forwarded headers Phoenix may trust, maps provider-specific OIDC groups to normalized Nixstasis roles in Caddy/AuthCrunch, and implements the smallest server-side role handling needed to keep LiveView authorization aligned with the edge policy.
Source Of Intent
This feature is seeded from website/docs/planned-features.md entry
authcrunch-role-contract.
Goals
- Inventory AuthCrunch-related Caddy configuration, Phoenix request handling, LiveView session data, and current role/group assumptions.
- Define canonical AuthCrunch-forwarded claims or headers that Phoenix may trust after Caddy has authenticated and authorized browser traffic.
- Define normalized operator roles and the capabilities each role grants for dashboard, devices, remote access, alerts, reports, settings, and E2E surfaces.
- Document behavior for missing, malformed, or insufficient role claims.
- Decide and implement where role-aware UI behavior belongs in Phoenix, reusing existing permission helpers when practical.
- Update deployment and operations docs so operators know which AuthCrunch roles or groups must be configured.
Non-Goals
- Replacing AuthCrunch as the browser authentication and authorization edge.
- Changing the device runtime API authentication contract.
- Treating device API tokens, E2E enablement, or terminal session refs as substitutes for browser/operator authorization.
- Removing Caddy's existing
authorize with entra_policychecks from protected production hosts. - Implementing full multi-tenant RBAC beyond the roles required by current operator surfaces.
- Changing Caddy's public ingress topology or production TLS behavior.
Current Behavior
deploy/compose/caddy/Caddyfileconfigures an AuthCrunch portal, transforms provider-specific OIDC groups intonixstasis/*roles, and applies anentra_policythat allowsAUTHORIZED_ROLESandAUTHORIZED_GROUPS, verifiesJWT_KEY, validates bearer headers, and injects headers with claims.- Public browser hosts
nixstasis.<base-domain>and wildcard device hosts are protected by Caddyauthorize with entra_policy, except for the Go client device protocol routes onnixstasis.<base-domain>. The FRPS dashboard hostfrp-admin.<base-domain>uses the dedicatedfrp_admin_policy, which admits only thenixstasis/adminrole and injects no claim headers. Those device protocol routes bypass AuthCrunch at Caddy and authenticate in Phoenix with registration-issued device credentials where applicable. - Phoenix browser routes use
NixstasisWeb.Plugs.DevicePermissions, which maps trusted AuthCrunch roles into browser permission session state and keeps local-development permissive defaults for direct dev/test requests without AuthCrunch claim headers. NixstasisWeb.Permissionsalready evaluates device and report permission maps, including scoped device IDs and report view/manage flags.- Device runtime APIs continue to authenticate with registration-issued device API tokens, not AuthCrunch claims.
- E2E routes are gated by
NixstasisWeb.Plugs.E2EEnabledand a dedicatedNixstasisWeb.Plugs.E2EAuthorizationrunner credential, not AuthCrunch roles.
Proposed Contract
Trust Boundary
Phoenix must trust operator identity and role claims only when requests come through the supported Caddy/AuthCrunch deployment path. Direct Phoenix access in development may continue to use local defaults, but production docs must not describe those defaults as authorization or expose direct Phoenix access as a supported production browser path.
Caddy remains the production browser authentication and authorization edge.
Phoenix role handling is an application-level capability mapper for LiveView and
channel behavior after Caddy has admitted the request; it must not be documented
as a replacement for Caddy authorize with entra_policy.
Claim Inputs
The implementation must inventory the actual headers Caddy/AuthCrunch forwards
when inject headers with claims is enabled. The final contract should name the
canonical header keys for:
- operator subject or user identifier
- display name or email, if available
- normalized role claims from
X-Token-User-Roles
Provider-specific OIDC group claims are consumed by Caddy/AuthCrunch transforms,
not by Phoenix. Phoenix should consume X-Token-User-Roles only after Caddy has
mapped groups to normalized nixstasis/* roles.
Roles And Capabilities
Define the smallest role set needed by the current product surfaces:
nixstasis/viewer: may view dashboard, devices, alerts, reports, and settings summary pages, but may not start remote access or mutate configuration.nixstasis/operator: may view dashboards, manage devices, start remote access, manage alerts, view reports, and use operational workflows that are safe for day-to-day operations.nixstasis/admin: may perform all operator actions plus manage settings and future privileged configuration surfaces.
Map provider-specific OIDC groups to one or more of these capabilities in
Caddy/AuthCrunch, not Phoenix. Caddy transform user blocks should convert
operator-provided group values into provider-generic nixstasis/viewer,
nixstasis/operator, and nixstasis/admin roles before the request is proxied.
Phoenix should consume only those normalized roles from X-Token-User-Roles.
Phoenix Authorization Shape
Prefer a small Phoenix authorization boundary over scattered LiveView conditionals:
- Parse and normalize trusted AuthCrunch claims in a plug or module used by the browser pipeline.
- Store normalized operator context and permission maps in the session or assigns consumed by LiveViews.
- Reuse
NixstasisWeb.Permissionsfor device and report capability checks where possible. - Keep terminal session authorization tied to server-issued terminal session refs and device permission checks.
- Treat
/api/jsonas an operator/developer resource API rather than a device runtime protocol. Caddy/AuthCrunch protects it on the public host, and Phoenix applies route-level JSON:API role checks as a fail-closed backstop.
Failure Behavior
- Missing role/group claims in production should deny privileged UI behavior and should not silently grant full permissions.
- Malformed claim headers should fail closed, log enough context for operators, and avoid logging raw tokens or full sensitive claim blobs.
- Insufficient roles should show existing unauthorized LiveView behavior where possible instead of exposing data and hiding only buttons.
- Local development may retain permissive defaults only when the request is not using the production AuthCrunch claim path and docs mark the behavior as local development only.
Docs And Pages Likely Affected
deploy/compose/caddy/Caddyfiledeploy/compose/.env.exampledeploy/compose/README.mddeploy/compose/scripts/check_proxy_claims.pydeploy/compose/scripts/validate_stack.shwebsite/docs/architecture.mdwebsite/docs/modules/deployment-compose.mdwebsite/docs/modules/gateway-caddy.mdwebsite/docs/modules/server-web.mdwebsite/docs/client-server-interface.mdwebsite/docs/reference/contracts.mdwebsite/docs/runtime-boundaries.mdwebsite/docs/operations/secret-rotation.mdwebsite/docs/operations/health-checks.mdwebsite/docs/planned-features.md
Code Areas Likely Affected
packages/server/lib/nixstasis_web/plugs/device_permissions.expackages/server/lib/nixstasis_web/operator_context.expackages/server/lib/nixstasis_web/permissions.expackages/server/lib/nixstasis_web/live/**/*packages/server/lib/nixstasis_web/channels/terminal_channel.expackages/server/test/nixstasis_web/**/*deploy/compose/scripts/validate_stack.sh
Validation
- Add tests for claim parsing and role-to-capability mapping.
- Add request or LiveView tests for allowed and denied role scenarios for device detail, remote access, reports, alerts, and settings where implemented.
- Add tests proving malformed or missing production AuthCrunch claims do not grant privileged permissions.
- Confirm direct device runtime API auth remains unchanged.
- Run
deploy/compose/scripts/check_proxy_claims.pyif Compose AuthCrunch contract inputs or Caddy behavior change. - Run
deploy/compose/scripts/validate_stack.sh deploy/compose/.env.examplewhen Compose validation changes and Docker is available; otherwise document why it is not runnable. - Run
mix precommitfrompackages/serverafter server changes. - Run
mdbook build docsafter docs updates. - Run
hk check -abefore feature close-out.
Reconciliation Bookends
- Before implementation, inventory AuthCrunch headers and current permission consumers before deciding final role names or claim parser behavior.
- If repository evidence is insufficient to identify AuthCrunch claim headers, consult AuthCrunch/Caddy security documentation and record the resulting source of truth in the feature docs.
- Update Caddy/deployment docs and Phoenix authorization behavior in the same implementation unit when the final contract is known.
- Before completion, search for
AUTHORIZED_ROLES,AUTHORIZED_GROUPS,AuthCrunch,device_permissions, andreport_permissionsacross docs and server code to ensure the final contract is described consistently. - Keep production authorization docs separate from local development shortcuts.
Risks And Tradeoffs
- Caddy/AuthCrunch may authorize access at the edge while Phoenix still needs role-aware UI behavior; duplicating policy can drift unless the contract is explicit.
- Over-modeling RBAC too early can add complexity before operator needs are proven.
- Trusting forwarded headers is only safe behind the supported Caddy deployment; direct Phoenix exposure must not become a production path.
- Group-to-role mapping requires operator-specific identity-provider group values, but Caddy owns that mapping so Phoenix remains provider-agnostic.