# v5 Identity, Workspace, and RBAC Model Status: planned for v5.0-alpha.1 Issue: [#334](https://github.com/BradGroux/veritas-kanban/issues/334) ## Purpose Veritas Kanban v5 needs a multi-user identity and authorization model that works in three operating modes: - local single-user desktop mode - LAN or self-hosted server mode - mobile or remote clients paired to a trusted server The model must keep first-run local use simple while giving remote/server mode real user accounts, workspaces, memberships, scoped API keys, agent identities, auditable actor attribution, and route-level plus entity-level permissions. ## Design Principles 1. Local-first stays frictionless. A fresh local install seeds `local` and `local-user`, and the owner can continue using password auth or an admin key. 2. Remote/server mode never relies on localhost bypass. Every request is tied to a human session, device session, service token, or agent token. 3. Human sessions and agent API keys are separate principals. Agents cannot use browser session endpoints and humans cannot inherit agent tool scopes. 4. Authorization is workspace-scoped by default. Cross-workspace reads and writes require explicit owner/admin permission. 5. Every state change records an actor, auth method, workspace, and request id. 6. Existing v4/v5 local auth continues to boot. Migration tightens defaults without invalidating local boards. ## Identity Model ### Actors | Actor type | Principal id | Auth method | Typical use | | ------------------ | --------------------- | --------------------------------------------------- | ----------------------------------------------------- | | `user` | `users.id` | password session, future SSO subject, recovery flow | Browser, desktop, or mobile human user. | | `device` | `device_sessions.id` | paired device session | Desktop remote, mobile/PWA, browser, and CLI pairing. | | `agent` | `agent_identities.id` | scoped agent API token | OpenClaw, local Codex, workflow workers, MCP clients. | | `service` | `api_tokens.id` | scoped service API token | Dashboards, webhooks, import/export jobs. | | `system` | `system` | internal | Migrations, retention jobs, scheduled cleanup. | | `localhost-bypass` | `localhost-bypass` | development bypass only | Local development compatibility, never remote/server. | `req.auth` should evolve from a role-only object into an actor context: ```ts interface AuthContext { actorType: 'user' | 'device' | 'agent' | 'service' | 'system' | 'localhost-bypass'; actorId: string; displayName: string; workspaceId: string; role: WorkspaceRole; permissions: Permission[]; authMethod: 'session' | 'api-key' | 'device-session' | 'recovery' | 'localhost-bypass' | 'system'; sessionId?: string; tokenId?: string; deviceSessionId?: string; deviceId?: string; clientId?: string; clientMode?: string; capabilities?: string[]; degradedReason?: string | null; isLocalhost: boolean; } ``` ### Core Tables The existing SQLite foundation already seeds `workspaces`, `users`, and `workspace_memberships`. The v5 multi-user migration should extend that foundation instead of replacing it. ```sql ALTER TABLE users ADD COLUMN handle TEXT; ALTER TABLE users ADD COLUMN auth_subject TEXT; ALTER TABLE users ADD COLUMN avatar_url TEXT; ALTER TABLE users ADD COLUMN disabled_at TEXT; ALTER TABLE users ADD COLUMN last_seen_at TEXT; ALTER TABLE workspaces ADD COLUMN mode TEXT NOT NULL DEFAULT 'local'; ALTER TABLE workspaces ADD COLUMN created_by TEXT; ALTER TABLE workspaces ADD COLUMN archived_at TEXT; ALTER TABLE workspace_memberships ADD COLUMN status TEXT NOT NULL DEFAULT 'active'; ALTER TABLE workspace_memberships ADD COLUMN invited_by TEXT; ALTER TABLE workspace_memberships ADD COLUMN joined_at TEXT; ALTER TABLE workspace_memberships ADD COLUMN disabled_at TEXT; ``` Role values must expand from the current `owner/admin/member/viewer` seed check to the v5 set: ```text owner, admin, member, reviewer, read-only, agent ``` `viewer` is a legacy synonym and must be migrated to `read-only`. Additional tables: ```sql CREATE TABLE workspace_invitations ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, email TEXT, role TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, invited_by TEXT NOT NULL REFERENCES users(id), created_at TEXT NOT NULL, expires_at TEXT NOT NULL, accepted_by TEXT REFERENCES users(id), accepted_at TEXT, revoked_at TEXT ); CREATE TABLE device_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, device_name TEXT, device_type TEXT NOT NULL DEFAULT 'browser', session_hash TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, last_seen_at TEXT, expires_at TEXT NOT NULL, revoked_at TEXT ); CREATE TABLE agent_identities ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, name TEXT NOT NULL, slug TEXT NOT NULL, default_role TEXT NOT NULL DEFAULT 'agent', description TEXT, created_by TEXT REFERENCES users(id), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, disabled_at TEXT, UNIQUE (workspace_id, slug) ); CREATE TABLE api_tokens ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, actor_type TEXT NOT NULL CHECK (actor_type IN ('agent', 'service')), actor_id TEXT NOT NULL, name TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, scopes_json TEXT NOT NULL, allowed_routes_json TEXT, created_by TEXT REFERENCES users(id), created_at TEXT NOT NULL, expires_at TEXT, revoked_at TEXT, last_used_at TEXT ); CREATE TABLE permission_overrides ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, subject_type TEXT NOT NULL CHECK (subject_type IN ('user', 'agent', 'service')), subject_id TEXT NOT NULL, resource_type TEXT NOT NULL, resource_id TEXT, permission TEXT NOT NULL, effect TEXT NOT NULL CHECK (effect IN ('allow', 'deny')), reason TEXT, created_by TEXT REFERENCES users(id), created_at TEXT NOT NULL, expires_at TEXT ); ``` Indexes: ```sql CREATE INDEX idx_workspace_memberships_user_status ON workspace_memberships(user_id, status); CREATE INDEX idx_workspace_invitations_workspace_email ON workspace_invitations(workspace_id, email, revoked_at, accepted_at); CREATE INDEX idx_device_sessions_user_workspace ON device_sessions(user_id, workspace_id, revoked_at, expires_at); CREATE INDEX idx_agent_identities_workspace_enabled ON agent_identities(workspace_id, disabled_at); CREATE INDEX idx_api_tokens_workspace_actor ON api_tokens(workspace_id, actor_type, actor_id, revoked_at); CREATE INDEX idx_permission_overrides_subject ON permission_overrides(workspace_id, subject_type, subject_id, resource_type); ``` ## Roles | Role | Human session | Agent token | Scope | | ----------- | ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `owner` | Yes | No | Full workspace control, ownership transfer, recovery, billing/release settings, destructive admin operations. | | `admin` | Yes | No | Workspace administration, user invites, settings, integrations, agents, workflows, policies, import/export. Cannot remove last owner. | | `member` | Yes | No | Board work: create/update tasks, comments, work products, workflow runs, and non-admin automation. | | `reviewer` | Yes | No | Review work: read board, comment, approve gates, QA tasks, generate reports. No broad task mutation except review fields. | | `read-only` | Yes | Service token allowed | Read board, reports, metrics, activity, and docs. No writes. | | `agent` | No | Yes | Scoped automation writes. Can mutate assigned tasks, run allowed workflows, post chat/status, and upload allowed artifacts. No admin settings. | `admin` is not a synonym for `owner`. Operations that can permanently lock out a workspace require `owner`. ## Permission Vocabulary Permissions are expressed as `resource:action`. Resources: ```text workspace, user, membership, invitation, session, token, board, task, comment, work_product, workflow, workflow_run, agent, git, policy, setting, integration, report, telemetry, audit, backup, maintenance, admin ``` Actions: ```text read, create, update, delete, execute, approve, export, import, manage ``` Examples: ```text task:update workflow_run:execute token:manage backup:export policy:manage ``` ### Role Permission Baseline | Permission group | owner | admin | member | reviewer | read-only | agent | | ------------------------------------ | ----- | ----- | ------ | ------------------ | --------- | ------ | | Workspace read | yes | yes | yes | yes | yes | scoped | | Workspace settings manage | yes | yes | no | no | no | no | | Users/memberships/invitations manage | yes | yes | no | no | no | no | | Device sessions revoke | yes | yes | own | own | own | no | | API tokens create/revoke | yes | yes | no | no | no | no | | Board/tasks read | yes | yes | yes | yes | yes | scoped | | Tasks create/update/delete | yes | yes | yes | review-only | no | scoped | | Comments/chat create | yes | yes | yes | yes | no | scoped | | Work products create/update/export | yes | yes | yes | review/export only | no | scoped | | Workflow definitions manage | yes | yes | no | no | no | no | | Workflow runs execute/control | yes | yes | yes | approve only | no | scoped | | Gate approvals/QA | yes | yes | yes | yes | no | scoped | | Agent registry/routing manage | yes | yes | no | no | no | no | | Git operations | yes | yes | yes | no | no | scoped | | Policies/tool policies manage | yes | yes | no | no | no | no | | Reports/metrics read | yes | yes | yes | yes | yes | scoped | | Audit read/export | yes | yes | no | no | no | no | | Backup/import/export | yes | yes | no | no | no | no | | Maintenance cleanup | yes | yes | no | no | no | no | | Ownership transfer/delete workspace | yes | no | no | no | no | no | ## Route Permission Matrix The current API has coarse global auth and `authorizeWrite`. v5 should move to route metadata that declares the required permission, then a shared authorization middleware should enforce it against the actor context. | Route family | Read permission | Write/control permission | Notes | | -------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | | `/api/auth/status`, `/api/auth/setup`, `/api/auth/login`, `/api/auth/recover` | public with rate limits | public with rate limits | Setup only when no owner exists. Recover requires recovery key. | | `/api/auth/logout`, `/api/auth/change-password`, `/api/auth/sessions` | `session:read` | `session:manage` | Users manage their own sessions; admins can revoke workspace sessions. | | `/api/tasks`, `/api/backlog`, `/api/tasks/:id/*` | `task:read` | `task:create`, `task:update`, `task:delete` | Entity checks apply for assigned agents and review-only mutation. | | `/api/tasks/:id/comments`, `/api/chat`, `/api/squad` | `comment:read` | `comment:create`, `comment:delete` | Agent tokens can post only as their agent identity. | | `/api/tasks/:id/work-products`, `/api/work-products` | `work_product:read` | `work_product:create`, `work_product:update`, `work_product:delete`, `work_product:export` | Export defaults must apply redaction by role. | | `/api/workflows` | `workflow:read` | `workflow:create`, `workflow:update`, `workflow:delete` | Definition writes are admin by default unless explicitly delegated. | | `/api/workflows/runs`, `/api/automation`, `/api/deliverables` | `workflow_run:read` | `workflow_run:execute`, `workflow_run:update`, `workflow_run:delete` | Agents require token scopes for specific workflow IDs or task IDs. | | `/api/agents`, `/api/agents/register`, `/api/agents/permissions`, `/api/agents/routing` | `agent:read` | `agent:manage` | Agent self-heartbeat is allowed only for matching agent token. | | `/api/github`, `/api/diff`, `/api/preview` | `git:read` | `git:execute` | Diff/preview reads are allowed to members; branch/PR mutation needs scoped write. | | `/api/settings`, `/api/config`, `/api/integrations`, `/api/hooks` | `setting:read` | `setting:manage`, `integration:manage` | Secrets remain redacted unless owner/admin requests a reveal workflow. | | `/api/policies`, `/api/tool-policies`, `/api/delegation` | `policy:read` | `policy:manage` | Policy writes are admin-only. | | `/api/metrics`, `/api/analytics`, `/api/reports`, `/api/summary`, `/api/activity`, `/api/status-history` | `report:read` | `report:export` or `maintenance:manage` | Deletes and retention controls are admin-only. | | `/api/telemetry`, `/api/traces`, `/api/audit` | `telemetry:read` or `audit:read` | `telemetry:create`, `audit:export` | Agent telemetry writes require token scope. Audit records are append-only. | | `/api/sqlite/*` | `backup:read` | `backup:export`, `backup:import`, `admin:manage` | Migration/import are owner/admin only and must emit audit entries. | | `/api/search`, `/api/docs`, `/api/doc-freshness` | `board:read` | `setting:manage` or `maintenance:manage` | Search results are filtered by entity permissions. | | `/api/health`, `/api/health/live`, `/api/health/ready` | public | none | Deep diagnostics require `admin:manage`. | | `/api/webhooks/*` | token-specific | token-specific | Webhooks use signed/shared secrets and map to service actors. | ## Entity-Level Rules Route authorization is necessary but not sufficient. Repository and service methods should enforce entity-level checks before returning rows or mutating state. | Entity | Rule | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Workspace | Actor must have an active membership or token for the workspace. | | Task | Humans can access workspace tasks by role. Agent tokens can access assigned tasks, workflow task context, or tasks allowed by token scope. | | Backlog/archive | Same as task rules. Archive restore/delete require `task:update` or `task:delete`. | | Comment/chat message | Inherits task/session access. Delete requires author, admin, or owner. | | Work product | Inherits source task/run access. Redaction level depends on role and export permission. | | Workflow definition | Read requires workspace access. Mutation requires `workflow:*` admin permission unless owner/admin grants an override. | | Workflow run | Inherits workflow plus source task access. Agents can control only runs they own or are assigned to. | | Attachment/deliverable | Inherits task access. Delete requires uploader, task write permission, admin, or retention cleanup. | | Settings/integration | Read is redacted by default. Secret reveal is owner-only and should be avoided where possible. | | Audit/telemetry | Raw records are owner/admin only. Aggregates may be visible to read/report roles after redaction. | Repository methods should accept `AuthContext` or a narrowed `AuthorizationSubject` instead of relying on global `local` filters once #335 and #336 begin implementation. ## Localhost Bypass Current behavior: - `VERITAS_AUTH_LOCALHOST_BYPASS=true` can allow localhost requests outside production. - `VERITAS_AUTH_LOCALHOST_ROLE` defaults to `read-only`. - Production disables bypass in code. v5 behavior: | Mode | Localhost bypass | | --------------------- | ----------------------------------------------------------------------------------------------- | | Local development | Allowed only when explicitly enabled. Default role remains `read-only`. | | Desktop local app | Avoid bypass by default. The desktop shell should use a local session or keychain-backed token. | | LAN/server | Disabled. Startup should warn if bypass env vars are present. | | Mobile/remote pairing | Disabled. Pairing creates device sessions, not bypass grants. | | Production | Disabled unconditionally. | Remote/server mode should expose an auth diagnostic that reports bypass as `disabled_by_mode` rather than merely echoing environment values. ## Agent API Keys Agent keys are not human sessions. Rules: - Stored only as hashes in `api_tokens.token_hash`. - Bound to `actor_type = 'agent'` and an `agent_identities.id`. - Include `workspace_id`, `scopes_json`, optional `allowed_routes_json`, expiry, and revocation timestamps. - Cannot call password, recovery, invitation, session, or membership endpoints. - Cannot create new API tokens. - Cannot change settings, integrations, policies, or role assignments. - Can write telemetry, status, chat, task updates, work products, and workflow progress only when the token scope allows the target task/workflow. - Must record `tokenId` and `actorId` on audit, activity, status history, task history, workflow runs, work products, and telemetry. Example scopes: ```json { "tasks": ["read", "update", "comment"], "workflowRuns": ["read", "execute", "update"], "workProducts": ["create", "update", "export-redacted"], "telemetry": ["create"], "constraints": { "taskIds": ["VK-123"], "workflowIds": ["qa-review"], "expiresAt": "2026-06-30T00:00:00.000Z" } } ``` ## Actor Attribution Every write path must emit enough actor data to reconstruct who or what changed the system. Store normalized IDs for joins and denormalized display values for durable history. Required fields for new audit/activity/task-history/workflow records: | Field | Description | | -------------------- | -------------------------------------------------------------------- | | `workspace_id` | Workspace where the action occurred. | | `actor_type` | `user`, `agent`, `service`, `system`, or `localhost-bypass`. | | `actor_id` | Stable user, agent, service token, or system id. | | `actor_display_name` | Display value at write time. | | `actor_role` | Effective role at write time. | | `auth_method` | `session`, `api-token`, `recovery`, `localhost-bypass`, or `system`. | | `session_id` | Device/session id for human sessions. | | `token_id` | API token id for agent/service tokens. | | `request_id` | Correlates API logs, audit entries, and telemetry. | | `target_type` | Entity type, such as task, workflow, policy, setting. | | `target_id` | Entity id. | | `redaction_level` | `none`, `standard`, `sensitive`, or `secret-ref-only`. | For backward compatibility, old records without actor columns should render as: ```text actor_type: system actor_id: legacy-migration actor_display_name: Legacy Migration ``` ## Migration Plan 1. Keep the current `local` workspace and `local-user` seed records. 2. Expand role constraints and migrate `viewer` memberships to `read-only`. 3. Add invitation, device session, agent identity, scoped API token, and permission override tables. 4. Convert `VERITAS_ADMIN_KEY` into an owner-capable service token at runtime. Do not persist plaintext env keys. If imported into SQLite, store only a token hash and mark `source = env`. 5. Convert `VERITAS_API_KEYS=name:key:role` entries into scoped `api_tokens`. Role mapping: - `admin` -> service token with admin permissions - `agent` -> agent identity plus scoped agent token - `read-only` -> service token with read/report scopes 6. Preserve password auth and recovery key behavior for the initial owner. 7. Add `AuthContext` to HTTP and WebSocket authentication. 8. Add route permission metadata and enforce role permissions. 9. Add entity-level repository checks for workspace, task, workflow, and work product access. 10. Emit actor attribution for all new writes. Keep legacy rows readable. 11. Add admin diagnostics that flag unsafe remote/server auth configuration. Rollback expectation: - New tables can remain unused if remote/server mode is disabled. - Existing local password and env-key auth must still work after rollback to single-user behavior. - Token imports from env are repeatable because token hashes are unique and env source records can be upserted. ## UX Flows ### First-Run Local Mode 1. User opens the desktop or local web app. 2. App seeds `local` workspace and `local-user` owner if missing. 3. If no password exists, setup asks for an owner password and shows the recovery key once. 4. The user lands on the board without choosing a workspace. 5. Settings shows "Local workspace" and a path to enable server mode later. ### Upgrade Local Board to Server Mode 1. Owner opens Settings -> Access. 2. App shows current auth risks: localhost bypass, weak admin key, missing HTTPS, unrotated env API keys. 3. Owner chooses "Enable server mode". 4. App disables bypass, requires an owner password, creates a server workspace mode flag, and prompts for HTTPS/reverse-proxy guidance. 5. App offers to migrate env API keys into hashed scoped tokens. 6. Audit records the mode change and token migration. ### Invite a Human User 1. Owner/admin enters email or local username and selects a role. 2. App creates `workspace_invitations` with a hashed token and expiry. 3. Invitee accepts, sets password if needed, and joins as an active membership. 4. Audit records invitation created, accepted, and role assigned. ### Create an Agent Token 1. Owner/admin creates or selects an agent identity. 2. Owner/admin chooses task/workflow scopes and expiry. 3. App shows the plaintext token once. 4. Token hash, scopes, creator, and expiry are stored. 5. Agent requests include the token and resolve to `actor_type = agent`. ### Mobile or Remote Device Pairing 1. Owner/admin creates a short-lived pairing payload for a workspace. 2. Device redeems the code/link with its client id, client mode, capabilities, nonce, signed timestamp, and signature. 3. App creates a `device_sessions` row with hashed secret, device metadata, scopes, capabilities, role, and expiry. 4. Session appears in account security UI with last seen, connection state, degraded reason, and revoke/test actions. 5. Remote requests authenticate with `authMethod: device-session` and are never treated as localhost bypass. ### Lost Admin Recovery 1. User chooses recovery. 2. Recovery key verifies against the stored recovery hash. 3. App rotates the password and invalidates device sessions. 4. Owner membership is preserved; recovery cannot create a second owner unless no active owner exists. 5. Audit records recovery without storing the recovery key. ## Implementation Checklist - [ ] Add schema migration for role expansion, invitations, device sessions, agent identities, API tokens, and permission overrides. - [ ] Replace role-only auth with `AuthContext` for HTTP and WebSocket requests. - [ ] Add route permission metadata and a shared permission middleware. - [ ] Add entity-level permission checks in storage/service layers. - [ ] Add actor attribution to write paths and audit/activity records. - [ ] Add user/workspace/member/token management APIs. - [ ] Add UX for first-run owner setup, workspace access, invitations, sessions, and agent tokens. - [ ] Add tests for local compatibility, remote bypass rejection, role matrix, agent token scope enforcement, and audit attribution. ## Out of Scope - External SSO provider implementation. - Billing or organization hierarchy. - Per-field permissions beyond redaction and secret reference rules. - Public unauthenticated boards.