Skip to content

feat(admin): HTTP endpoints for roles + user-roles + workflows + policies#15

Merged
haydercyber merged 2 commits into
mainfrom
feat/admin-rbac-endpoints
May 28, 2026
Merged

feat(admin): HTTP endpoints for roles + user-roles + workflows + policies#15
haydercyber merged 2 commits into
mainfrom
feat/admin-rbac-endpoints

Conversation

@haydercyber

Copy link
Copy Markdown
Contributor

Summary

Piece 3a of the dynamic-workflow vertical slice (Piece 3 split into three sub-PRs: 3a admin CRUD, 3b request flow, 3c agent wrap retrieval).

This PR is pure HTTP over the four tables from Piece 2. Admin UI (Piece 5) calls these endpoints to manage roles, role assignments, workflow templates, and policy rules.

18 endpoints under /api/v1

POST   /roles                       create
GET    /roles                       list (includes system seeds)
GET    /roles/:id                   get
PUT    /roles/:id/permissions       replace permission list
DELETE /roles/:id                   404 missing, 409 system

POST   /user-roles                  grant
DELETE /user-roles/:id              revoke
GET    /users/:userID/roles         list a user's assignments

POST   /workflows                   create
GET    /workflows                   list
GET    /workflows/:id               get
PUT    /workflows/:id               update (excludes is_default flip)
DELETE /workflows/:id               404 missing, 409 system

POST   /policies                    create
GET    /policies                    list (priority DESC)
GET    /policies/:id                get
PUT    /policies/:id                update
DELETE /policies/:id                404 missing, 409 system

Decisions worth flagging

Decision Why
All admin endpoints under the existing admin route group Real RBAC (RequirePermission middleware) layers on top when the auth design ships; no route reshape needed.
is_system rows are 404-aware (missing) vs 409-aware (system) on Delete ErrSystemRow → HTTP 409 with clear message. UI distinguishes "can't" from "doesn't exist".
TTLs exposed as integer seconds in JSON Helm-friendly, matches typical REST conventions, no client-side parsing.
All TTLs validated > 0 at the handler layer Zero TTLs would silently never expire — guard at the boundary.
is_default flip NOT in Update Atomic swap needs a transaction; defer to its own endpoint to avoid the partial unique index admitting zero defaults briefly during swap.
Single Admin struct holds all four repositories One constructor call in main; no risk of forgetting to wire one entity.

Tests

13 handler tests using Fiber's app.Test:

TestRoles_CreateGetUpdateDelete                  PASS
TestRoles_DeleteSystemRoleReturns409             PASS
TestRoles_ListReturnsAllIncludingSystem          PASS
TestRoles_ListAfterManyCreates                   PASS
TestUserRoles_GrantRevoke                        PASS
TestWorkflows_CreateGetUpdateDelete              PASS
TestWorkflows_RejectsZeroTTL                     PASS
TestWorkflows_DeleteSystemReturns409             PASS
TestPolicies_CreateListDelete                    PASS
TestPolicies_DeleteSystemReturns409              PASS
TestAdmin_NotFoundReturns404                     PASS
TestAdmin_InvalidUUIDReturns400                  PASS
TestAdmin_MalformedJSONBodyReturns400            PASS

Live e2e walkthrough

$ curl /api/v1/roles
  admin        system=true perms=['role.edit','workflow.edit','policy.edit',...]
  approver     system=true perms=['secret.approve','audit.read']
  developer    system=true perms=['secret.request','audit.read']

$ curl -X POST /api/v1/workflows -d '{"name":"strict-prod","min_approvers":2, ...}'
  → 201

$ curl -X POST /api/v1/policies -d '{"name":"prod-only","selector":{"environment":"prod"},"workflow_id":"...","priority":500}'
  → 201

$ curl /api/v1/policies
  pri= 500 sel={'environment': 'prod'} → workflow_id=b8492330…   ← ours
  pri=   0 sel={}                       → workflow_id=a8498a4f…   ← seed match-all

$ curl -X POST /api/v1/user-roles -d '{"user_id":"alice@example.com","role_id":"...","scope":{"environment":"prod"}}'
  → 201

$ curl /api/v1/users/alice@example.com/roles
  user=alice@example.com role=5ca48300… scope={'environment': 'prod'}

$ curl -X DELETE /api/v1/roles/<admin-role-id>
  → HTTP 409   ← is_system protection

What's next (Pieces 3b + 3c)

Piece Adds
3b Request flow — POST /requests (dev submits new key values with embedded wraps), POST /requests/:id/approve (approver action that resolves policy + creates sync_job), reject + cancel
3c GET /agents/:id/wraps/:wrap_id under AgentAuth — agent's wrap retrieval. WrapService.Retrieve already has single-shot semantics; this is the thin HTTP layer.

Test plan

  • CI green (4 jobs)
  • Local: 13 tests + full suite green with -p 1
  • Live curl walkthrough demonstrates the surface
  • Reviewer reads internal/handlers/admin.go::adminErr and confirms ErrNotFound → 404, ErrSystemRow → 409, default → 500
  • Reviewer reads bodyToWorkflow / workflowToBody and confirms TTL second↔Duration conversions are symmetric

…cies

Piece 3a of the dynamic-workflow vertical slice (Piece 3 split into
three sub-PRs: 3a admin CRUD, 3b request flow, 3c agent wrap retrieval).

This PR is pure HTTP over the four tables from Piece 2 (#14). Admin UI
(Piece 5) calls these endpoints to manage roles, role assignments,
workflow templates, and policy rules.

Endpoints added under /api/v1
-----------------------------

  POST   /roles                       create
  GET    /roles                       list (includes system)
  GET    /roles/:id                   get
  PUT    /roles/:id/permissions       replace permission list
  DELETE /roles/:id                   404 missing, 409 system

  POST   /user-roles                  grant
  DELETE /user-roles/:id              revoke
  GET    /users/:userID/roles         list a user's assignments

  POST   /workflows                   create
  GET    /workflows                   list
  GET    /workflows/:id               get
  PUT    /workflows/:id               update (excludes is_default flip)
  DELETE /workflows/:id               404 missing, 409 system

  POST   /policies                    create
  GET    /policies                    list (priority DESC)
  GET    /policies/:id                get
  PUT    /policies/:id                update
  DELETE /policies/:id                404 missing, 409 system

Decisions worth flagging in review
----------------------------------

  - All admin endpoints under the existing admin route group. Real
    RBAC (RequirePermission middleware) layers on top when the auth
    design ships; no route reshape needed.
  - is_system rows are 404-aware (missing) vs 409-aware (system row)
    on Delete. ErrSystemRow → 409 with a clear message.
  - TTLs exposed as integer seconds in JSON — Helm-friendly, matches
    typical REST conventions, no parsing on the client side.
  - All TTLs validated > 0 at the handler layer (not just at the DB
    CHECK). Zero TTLs would silently never expire.
  - is_default flip NOT included in Update. Atomic swap needs a
    transaction; defer to its own endpoint to avoid the partial
    unique index admitting zero defaults briefly during the swap.
  - Admin handler struct holds all four repositories — single
    constructor call in main; no risk of forgetting to wire one.
  - Bodies use snake_case JSON tags (matches existing conventions
    like agent_secret, wrap_id, correlation_id).

Tests
-----

  13 handler tests using Fiber's app.Test helper:
    TestRoles_CreateGetUpdateDelete
    TestRoles_DeleteSystemRoleReturns409
    TestRoles_ListReturnsAllIncludingSystem
    TestRoles_ListAfterManyCreates
    TestUserRoles_GrantRevoke
    TestWorkflows_CreateGetUpdateDelete
    TestWorkflows_RejectsZeroTTL
    TestWorkflows_DeleteSystemReturns409
    TestPolicies_CreateListDelete
    TestPolicies_DeleteSystemReturns409
    TestAdmin_NotFoundReturns404
    TestAdmin_InvalidUUIDReturns400
    TestAdmin_MalformedJSONBodyReturns400

Verification
------------

  Offline: go build / vet / test -race ./... clean.
  Live: 13 new tests + full suite green with -p 1.

  curl walkthrough demonstrates:
    - List seed roles (admin / approver / developer)
    - Create custom strict-prod workflow
    - Create matching prod-only policy at priority 500
    - List policies in priority DESC order (ours first, seed match-all last)
    - Grant alice@example.com the approver role scoped to environment=prod
    - DELETE the seed admin role → HTTP 409 (is_system protection)
The GET after PUT only uses the body; the response status isn't checked.
Switching the assignment to _ silences staticcheck SA4006.
@haydercyber
haydercyber merged commit 53d4803 into main May 28, 2026
4 checks passed
@haydercyber
haydercyber deleted the feat/admin-rbac-endpoints branch May 28, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant