Files
agentrhq__authsome/docs/adr/0006-principal-roles-admin-user.md
Manoj Bajaj 63bd4c90aa refactor!: unify local and hosted into a single deployment flow
The daemon branched on AUTHSOME_DEPLOYMENT_MODE and ran two parallel
implementations of nearly every ownership concept. "Local and hosted
behave the same" was asserted in prose (ADR 0006) but never enforced in
code, so the paths were free to drift, and the synthetic local principal
let a second local identity silently inherit the admin principal.

Collapse to one flow, identical for every deployment: authsome init
registers an identity and gets back a browser claim URL; the user
registers email+password (first principal becomes admin); that principal
confirms the claim; PoP calls are then authorized.

- Remove AUTHSOME_DEPLOYMENT_MODE, get_deployment_mode(),
  LOCAL_PRINCIPAL_EMAIL, the Local*/Hosted* resolver and bootstrap
  classes, and the AuthService(deployment_mode=...) parameter.
- OwnershipResolver and IdentityBootstrapService become single concrete
  classes (the former hosted, claim-based implementations).
- Admin gating is purely role-based: non-admin principals are blocked in
  every deployment (previously implicitly allowed in local mode).
- The server UI always requires a hosted browser session; remove the
  vestigial HealthResponse.mode field.
- CLI ensure_identity_ready was already mode-agnostic; it now prints the
  claim URL to stderr for headless use.

Add ADR 0007 recording the decision; amend ADR 0006 and CONTEXT.md.

BREAKING CHANGE: existing local installs have an unclaimed identity under
local@authsome.internal and are rejected until the user registers a
principal (email+password) and claims the identity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: d31cf246f6f2
2026-05-29 13:18:41 +05:30

2.1 KiB

Principal roles: admin / user, first-created principal is admin

Principals need an authorization tier to gate deployment-level operations (audit log access, provider registration/deletion, cross-vault credential revocation) from per-principal operations (own connections, claim accept/reject). We store a role column (admin | user) directly on the principals table. The first Principal created on a server is assigned admin; all subsequent Principals receive user. Role assignment is immutable at creation time (mutation is deferred to a future milestone).

Considered options

Environment variable (AUTHSOME_ADMIN_PRINCIPALS) — the prior approach. Rejected because it requires knowing the PrincipalId before the server starts, cannot be changed without a restart, and is invisible to the UI and route layer.

Separate principal_roles table — considered for future extensibility (multiple roles per principal). Rejected as premature: the role model is binary and a join adds complexity without benefit today.

Default admin account created at server init — would require deciding on credentials before any real user exists. Rejected in favour of first-user-becomes-admin: the first principal to register is admin, all subsequent principals are users. (Originally justified as "zero-config" for an implicit local principal; that implicit-principal path was later removed when local and hosted were unified into a single registration + claim flow — see ADR 0007. First-principal-is-admin remains the rule, now reached the same way in every deployment.)

Consequences

  • AUTHSOME_ADMIN_PRINCIPALS env var and is_admin_principal() are removed entirely.
  • Admin enforcement at the route level uses a get_admin_auth_service FastAPI dependency (parallel to get_protected_auth_service) that raises HTTP 403 for non-admin principals.
  • Admin-only routes: GET /audit/events, POST /providers, DELETE /providers/{provider}, POST /connections/{provider}/revoke.
  • Schema migration: ALTER TABLE principals ADD COLUMN role TEXT NOT NULL DEFAULT 'user', followed by setting the earliest-created principal to 'admin'.