Skip to content

Commit ea24bf8

Browse files
dougrathboneclaude
andcommitted
feat: enforce agent owner in UI, rename shared section, add auth docs
- Hide delete button for non-owners in MainPanel - Rename sidebar section from "Shared with Me" to "Shared Agents" - Document Okta multi-user auth and sharing model in CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9391899 commit ea24bf8

3 files changed

Lines changed: 72 additions & 10 deletions

File tree

CLAUDE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,63 @@ npm start # Run production server
4141
- Server → Client: `{ type: 'response', id, result }` or `{ type: 'error', id, error }`
4242
- Server → All: `{ type: 'event', channel, payload }` (broadcasts)
4343

44+
## Authentication & Multi-User
45+
46+
Conduit supports multi-user authentication via Okta OIDC. When Okta is not configured, it runs in **dev bypass mode** — identical to the original single-user behavior with zero configuration.
47+
48+
**Dev mode** (default): No login required. A synthetic `dev-user` owns all entities. Set no env vars.
49+
50+
**Production mode**: Set these environment variables to enable Okta OIDC:
51+
52+
| Variable | Description |
53+
|----------|-------------|
54+
| `CONDUIT_OKTA_ISSUER` | Okta issuer URL (e.g., `https://company.okta.com/oauth2/default`) |
55+
| `CONDUIT_OKTA_CLIENT_ID` | OIDC application client ID |
56+
| `CONDUIT_OKTA_CLIENT_SECRET` | OIDC application client secret |
57+
| `CONDUIT_OKTA_REDIRECT_URI` | Callback URL (default: `http://localhost:7456/auth/callback`) |
58+
| `CONDUIT_SESSION_SECRET` | Secret for signing session cookies |
59+
| `CONDUIT_SESSION_TTL_MS` | Session lifetime in ms (default: 86400000 / 24h) |
60+
61+
**Auth flow**: OIDC Authorization Code + PKCE. Sessions stored in SQLite. Groups synced from Okta ID token `groups` claim on each login.
62+
63+
**Auth routes** (HTTP, not WebSocket):
64+
- `GET /auth/login` — redirects to Okta
65+
- `GET /auth/callback` — exchanges code, creates session
66+
- `POST /auth/logout` — destroys session
67+
- `GET /auth/me` — returns current user + groups
68+
69+
**Auth files**:
70+
- `src/server/auth/config.ts` — env var reading, `isAuthEnabled()`
71+
- `src/server/auth/okta.ts` — OIDC client (openid-client v6)
72+
- `src/server/auth/middleware.ts` — session validation middleware
73+
- `src/server/auth/devBypass.ts` — dev mode synthetic user
74+
- `src/server/auth/routes.ts` — Express auth router
75+
76+
## Ownership & Sharing
77+
78+
Every entity (agents, publish targets, repositories, global MCP servers) has an `ownerId` column linking to a user. Triggers inherit visibility from their parent agent.
79+
80+
**Ownership rules:**
81+
- Entities are owned by whoever creates them (`ownerId` set on creation)
82+
- Only the owner can delete an entity or modify its shares
83+
- Shared users can view, edit, and run — but not delete or reshare
84+
85+
**Sharing model**: Polymorphic `shares` table maps `(entityType, entityId)``(user | group | everyone)`.
86+
87+
**Visibility rule** — a user sees an entity if any of:
88+
1. They own it
89+
2. It's shared directly with them
90+
3. It's shared with a group they belong to
91+
4. It's shared with everyone
92+
93+
**Sharing files**:
94+
- `src/main/db/queries/access.ts` — visibility queries (`getVisibleEntityIds`, `canAccessEntity`, `isEntityOwner`)
95+
- `src/main/db/queries/shares.ts` — share CRUD
96+
- `src/renderer/components/ShareDialog.tsx` — sharing UI modal
97+
- `src/renderer/hooks/useShares.ts` — TanStack Query hooks for shares
98+
99+
**Frontend**: The sidebar splits entities into "My Agents" / "Shared Agents" sections. The share button and delete button only appear for owners.
100+
44101
## Data Storage
45102

46103
All data lives under `~/.conduit/` (or `$CONDUIT_DATA_DIR`):

src/renderer/components/agents/AgentList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function AgentList() {
146146
{sharedAgents.length > 0 && (
147147
<>
148148
<div className="text-[10px] font-medium uppercase tracking-wider text-[var(--text-secondary)] px-3 py-1.5">
149-
Shared with Me <span className="ml-1 opacity-60">{sharedAgents.length}</span>
149+
Shared Agents <span className="ml-1 opacity-60">{sharedAgents.length}</span>
150150
</div>
151151
{sharedAgents.map((agent) => (
152152
<AgentItem

src/renderer/components/layout/MainPanel.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { RunDetail } from '@renderer/components/runs/RunDetail'
1010
import { TerminalPane } from '@renderer/components/layout/TerminalPane'
1111
import { useAgent, useDeleteAgent } from '@renderer/hooks/useAgents'
1212
import { useRuns } from '@renderer/hooks/useRuns'
13+
import { useAuth } from '@renderer/contexts/AuthContext'
1314
import { useUIStore } from '@renderer/store/ui'
1415
import { cn } from '@renderer/lib/utils'
1516
import { api } from '@renderer/lib/ipc'
@@ -25,7 +26,9 @@ export function MainPanel({ agentId }: MainPanelProps) {
2526
const { data: agent } = useAgent(agentId)
2627
const { data: runs } = useRuns(agentId)
2728
const deleteAgent = useDeleteAgent()
29+
const { user } = useAuth()
2830
const { activeRunId, setActiveRun, selectAgent } = useUIStore()
31+
const isOwner = agent?.ownerId === user?.id
2932
const queryClient = useQueryClient()
3033

3134
const [tab, setTab] = useState<Tab>('configure')
@@ -119,15 +122,17 @@ export function MainPanel({ agentId }: MainPanelProps) {
119122
setTab('runs')
120123
}}
121124
/>
122-
<Button
123-
variant="ghost"
124-
size="sm"
125-
onClick={handleDeleteAgent}
126-
className="text-[var(--text-secondary)] hover:text-red-400 px-1.5"
127-
title="Delete agent"
128-
>
129-
<Trash2 className="h-4 w-4" />
130-
</Button>
125+
{isOwner && (
126+
<Button
127+
variant="ghost"
128+
size="sm"
129+
onClick={handleDeleteAgent}
130+
className="text-[var(--text-secondary)] hover:text-red-400 px-1.5"
131+
title="Delete agent"
132+
>
133+
<Trash2 className="h-4 w-4" />
134+
</Button>
135+
)}
131136
</div>
132137
</div>
133138

0 commit comments

Comments
 (0)