feat(admin): manage organizations from /admin #41

Merged
ryan.mcgee merged 1 commit from feat/admin-organizations into main 2026-10-22 14:41:04 +00:00
Owner

Summary

  • Phase 2 of the system administration panel: organizations. System administrators can list, create, administer, suspend and delete organizations without ever becoming members or seeing tenant data.
  • A server audit log records what they change.

Changes

  • Organizations (/admin/organizations): list with search and paging (name, status, member/node/resource counts); create for a designated owner by email (an existing account becomes admin at once, anyone else gets an admin invitation whose link is shown once); assign an admin; replace an admin (only when another admin is left, so an invited replacement has to join first); suspend and reactivate; delete by typing the slug.
  • Suspension switches an organization off: members get "suspended" instead of the organization, API keys are refused like unknown keys, resource sign-in pages, the ForwardAuth check and maintenance pages stop finding its resources, and its nodes are served an empty configuration with HTTP 200. A refused poll would not work: Traefik keeps serving the last configuration it received when a poll fails.
  • Server audit log (/admin/audit, new server_audit_events table, reversible migration): written in the same transaction as each change, append-only, pruned with the other audit logs, never holds a secret. Identity provider create/update/delete are recorded in it too (Phase 1 left them unrecorded). Member-affecting actions (admin added, suspension) also land in the organization's own log.
  • Every change needs Admin.authorize_change/1 in the context (second factor plus recent login) as well as the page. The owner is invited or promoted through the organization's own functions with an admin scope that exists only for that call.
  • Organization admin pages: /orgs/new stays for TRAEFIK_CONTROL_SIGNUP=open, because removing it would break self-service organizations; system administrators are sent to /admin/organizations/new, and the "New organization" button points there for them.

Verification

  • mix precommit: exit 0. mix credo --strict: no issues. mix dialyzer: passed.
  • mix test: 1670 passed on SQLite and on PostgreSQL; migration rollback and re-apply clean on both.
  • Built and ran the production image: created an organization for an unknown owner (invitation link shown once), redeemed the invitation and seeded a node and resource in the release, then polled /api/traefik/v1/config with the node token: active gave the routers, suspended gave {} with HTTP 200, reactivation restored it; a wrong slug was refused, the right one deleted the organization (the node's next poll then gets 401), and the audit page listed created, suspended, reactivated and deleted.
  • Not exercised: a real Traefik consuming the empty configuration; that is covered by what Traefik's HTTP provider documents, and tests assert the response only.

Notes

  • Deleting an organization leaves its sites up on Traefik instances that keep running, because the node tokens are gone and Traefik keeps its last configuration when a poll fails. The delete page and the README say so; suspending first and waiting for the nodes to poll takes them down. This follows the chosen "typed slug only" delete; making delete suspend first would remove the gap.
  • Accepting an invitation to a suspended organization is still possible; the new member just finds it suspended.
  • Later phases: break-glass join, system administrator management.
## Summary - Phase 2 of the system administration panel: organizations. System administrators can list, create, administer, suspend and delete organizations without ever becoming members or seeing tenant data. - A server audit log records what they change. ## Changes - **Organizations** (`/admin/organizations`): list with search and paging (name, status, member/node/resource counts); create for a designated owner by email (an existing account becomes admin at once, anyone else gets an admin invitation whose link is shown once); assign an admin; replace an admin (only when another admin is left, so an invited replacement has to join first); suspend and reactivate; delete by typing the slug. - **Suspension** switches an organization off: members get "suspended" instead of the organization, API keys are refused like unknown keys, resource sign-in pages, the ForwardAuth check and maintenance pages stop finding its resources, and its nodes are served an **empty configuration with HTTP 200**. A refused poll would not work: Traefik keeps serving the last configuration it received when a poll fails. - **Server audit log** (`/admin/audit`, new `server_audit_events` table, reversible migration): written in the same transaction as each change, append-only, pruned with the other audit logs, never holds a secret. Identity provider create/update/delete are recorded in it too (Phase 1 left them unrecorded). Member-affecting actions (admin added, suspension) also land in the organization's own log. - Every change needs `Admin.authorize_change/1` in the context (second factor plus recent login) as well as the page. The owner is invited or promoted through the organization's own functions with an admin scope that exists only for that call. - Organization admin pages: `/orgs/new` stays for `TRAEFIK_CONTROL_SIGNUP=open`, because removing it would break self-service organizations; system administrators are sent to `/admin/organizations/new`, and the "New organization" button points there for them. ## Verification - `mix precommit`: exit 0. `mix credo --strict`: no issues. `mix dialyzer`: passed. - `mix test`: 1670 passed on SQLite and on PostgreSQL; migration rollback and re-apply clean on both. - Built and ran the production image: created an organization for an unknown owner (invitation link shown once), redeemed the invitation and seeded a node and resource in the release, then polled `/api/traefik/v1/config` with the node token: active gave the routers, suspended gave `{}` with HTTP 200, reactivation restored it; a wrong slug was refused, the right one deleted the organization (the node's next poll then gets 401), and the audit page listed created, suspended, reactivated and deleted. - Not exercised: a real Traefik consuming the empty configuration; that is covered by what Traefik's HTTP provider documents, and tests assert the response only. ## Notes - **Deleting an organization leaves its sites up** on Traefik instances that keep running, because the node tokens are gone and Traefik keeps its last configuration when a poll fails. The delete page and the README say so; suspending first and waiting for the nodes to poll takes them down. This follows the chosen "typed slug only" delete; making delete suspend first would remove the gap. - Accepting an invitation to a suspended organization is still possible; the new member just finds it suspended. - Later phases: break-glass join, system administrator management.
feat(admin): manage organizations from /admin
All checks were successful
Development images / images (push) Successful in 1m48s
CI / format, compile, credo, dialyzer (pull_request) Successful in 1m43s
CI / tests (SQLite) (pull_request) Successful in 2m28s
CI / tests (PostgreSQL) (pull_request) Successful in 2m44s
CI / dependency audit (pull_request) Successful in 44s
0ec866b3e1
- add organizations to the system administration pages: list and
  search, create for a designated owner (an existing account becomes
  admin, anyone else is invited), assign or replace an admin, suspend
  and reactivate, and delete by typing the slug
- a suspended organization is switched off: members cannot open it,
  API keys and resource sign-ins stop, and its nodes get an empty
  configuration with a success status so its sites go down
- add a server audit log (server_audit_events) for what system
  administrators change, written in the same transaction as the change
  and pruned with the other audit logs; record identity provider
  changes in it
- every change needs authorize_change/1 in the context as well as the
  page; the system administrator never becomes a member
- /orgs/new stays for open registration; system administrators are
  sent to /admin/organizations/new
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
White_Ghost_Cyber_Systems/traefik-control!41
No description provided.