feat(server): add user-owned DefenseClaw access#250
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b8571bd54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Index( | ||
| "uq_api_key_credentials_one_live_per_user", | ||
| "namespace_key", | ||
| "user_id", | ||
| unique=True, | ||
| postgresql_where=text("enabled IS TRUE AND revoked_at IS NULL"), | ||
| ), |
There was a problem hiding this comment.
Add the SQLite predicate for live-key uniqueness
In SQLite deployments/tests, postgresql_where is ignored, so this becomes an unconditional unique index on (namespace_key, user_id). After a user rotates or revokes a key, the historical credential row remains in api_key_credentials, and inserting the replacement for the same user will violate this index even though the old row is no longer live. Add the matching sqlite_where predicate here and in the Alembic index so SQLite preserves credential history like Postgres.
Useful? React with 👍 / 👎.
| onSuccess: async () => { | ||
| await queryClient.invalidateQueries({ queryKey: accessQueryKeys.users }); | ||
| }, |
There was a problem hiding this comment.
Invalidate cached grants after role changes
When a member is promoted to admin, the server deletes that user's control grants, but this mutation only invalidates the users list. If the operator had already expanded that user and later changes them back to member in the same session, useUserControlGrants can reuse the stale pre-promotion grant cache and show assignments that no longer exist on the server, with the Save button disabled because the stale signature matches. Invalidate accessQueryKeys.controlGrants(userId) when the role changes (or after every user update).
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Stack note: the feature work is stacked on #249 through
265f0bd. #249 now also contains the CI-onlydc3f0d3fork-secret guard; the equivalent guard is included here. Merge #249 first; the review-only feature range is265f0bd..98a69ba. GitHub cannot select the fork-owned #249 head as an upstream base branch, so the combined Files view also contains #249 until that dependency merges.Problem
DefenseClaw needs one durable user identity for centralized rule assignment and enforcement history, plus one rotatable credential that works in both the SDK and UI. The prior implementation exposed the underlying one-to-many credential model, attached bucket grants to individual keys, and placed DefenseClaw activity under an internal synchronization agent. That made rotation risky, made the normal UI agent-centric, and did not match the intended one-key-per-user product contract.
Solution
One user, one active credential
Issue,Rotate, andRevokeare the supported lifecycle operations; the UI does not offer “Create another key.”User-owned controls and history
access_user_id, not a credential, so they survive rotation and revocation.access_user_id; the credential ID is retained only as an audit/filter dimension.DefenseClaw-first navigation
defenseclaw-policy-syncSDK identity remains available to the implementation but is hidden from normal authenticated navigation.Security boundaries
409responses rather than leaked500s.401transitions so exact spans cannot survive an account switch in browser memory.What changed
server/.../models.py, Alembicserver/.../services/access.pyserver/.../endpoints/admin_access.pyapi_key_idprovenance, resolve trusted administrator attribution, and strip owner metadata from member responsesui/.../access-managementui/pages/controls.tsx,monitor.tsxBreaking changes
AGENT_CONTROL_API_KEYSis rejected when authentication is enabled. Use bootstrapAGENT_CONTROL_ADMIN_API_KEYSplus database-managed user credentials.AGENT_CONTROL_CORS_ORIGINSallowlist; wildcard credentialed CORS is rejected at startup.Open issues / follow-ups
None.
Test plan
98a69ba— 7 successful, 0 failed, 0 pending; merge state clean.make lint— passed.make typecheck— passed.make -C server test— 868 passed with coverage XML generated.cd ui && pnpm lint && pnpm typecheck && pnpm prettify:check && pnpm build— passed.cd ui && CI=1 pnpm exec playwright test --workers=2— 156 passed against the production build.defenseclaw-demo; admin Monitor showed the server-resolved user name and ID on every finding, while the member API returned the same four owned events with no owner-attribution metadata.