Organizations: multi-user customer accounts, roles, email invites #6

Closed
opened 2026-07-29 22:34:08 +00:00 by Cordy · 1 comment
Owner

Solves the bus-factor problem Nikola identified: a public-sector IT department where one person holds the account — that person leaves or is on vacation and nobody can reach the license dashboard.

Model (agreed direction)

  • Org entity. The first user registering creates their org implicitly (org name settable later in the dashboard; defaults to their email domain or "Personal").
  • Roles: admin and member. The creating user is admin. Admins can: invite users by email, promote/demote admin, remove members, rename the org, manage license labels. Members can: view licenses/keys, view billing.
  • Invites: admin enters an email → single-use invite token (7-day expiry) emailed as a login link → invitee sets their password on accept and lands in the org. (Depends on the email-infrastructure issue.)
  • Licenses attach to the org, not to an individual email. The Paddle webhook keeps linking by buyer email → resolves to that account's org.
  • Safety rails: an org must always have ≥1 admin (last admin cannot demote/remove themselves); removing a member revokes their sessions.
  • Migration: existing accounts become single-user orgs (self as admin) on first load — mechanical, no data loss.

Site half: an "Organization" dashboard section — member list with roles, invite form, rename, leave/remove.

This is the largest item of the batch (~the size of the original accounts feature). Store stays atomic-JSON; the shapes (orgs, invites, membership on accounts) slot into the existing store.

Depends on: email infrastructure issue.

Acceptance: register → org exists, self is admin; invite → mail → set password → member sees org licenses; promote/demote/remove enforced incl. last-admin rail; webhook-issued license visible to all org members; existing accounts migrated transparently.

Solves the bus-factor problem Nikola identified: a public-sector IT department where one person holds the account — that person leaves or is on vacation and nobody can reach the license dashboard. **Model** (agreed direction) - **Org** entity. The first user registering creates their org implicitly (org name settable later in the dashboard; defaults to their email domain or "Personal"). - **Roles**: `admin` and `member`. The creating user is admin. Admins can: invite users by email, promote/demote admin, remove members, rename the org, manage license labels. Members can: view licenses/keys, view billing. - **Invites**: admin enters an email → single-use invite token (7-day expiry) emailed as a login link → invitee sets their password on accept and lands in the org. (Depends on the email-infrastructure issue.) - **Licenses attach to the org**, not to an individual email. The Paddle webhook keeps linking by buyer email → resolves to that account's org. - **Safety rails**: an org must always have ≥1 admin (last admin cannot demote/remove themselves); removing a member revokes their sessions. - **Migration**: existing accounts become single-user orgs (self as admin) on first load — mechanical, no data loss. **Site half**: an "Organization" dashboard section — member list with roles, invite form, rename, leave/remove. This is the largest item of the batch (~the size of the original accounts feature). Store stays atomic-JSON; the shapes (`orgs`, `invites`, membership on accounts) slot into the existing store. **Depends on:** email infrastructure issue. **Acceptance:** register → org exists, self is admin; invite → mail → set password → member sees org licenses; promote/demote/remove enforced incl. last-admin rail; webhook-issued license visible to all org members; existing accounts migrated transparently.
Author
Owner

Shipped and deployed (PR #8).

  • Org entity + admin/member roles; registration creates a single-member admin org; startup + lazy migration backfills existing accounts and adopts email-linked licenses (webhook purchases attach when the buyer registers, no separate linking step).
  • Invites: admin enters email + role → 7-day single-use token emailed as account.html?invite=… → invitee sets password (or existing safe-to-move account joins) → lands in the org. Pending invites listable/revocable by admins.
  • Promote/demote/remove with the last-admin rail; removal revokes the member's sessions and parks them in a fresh personal org; members may leave.
  • GET /v1/licenses is org-scoped (all members see keys); label management is admin-only. Rename via POST /v1/org/name.
  • Site: full Organization dashboard section (members with role chips, promote/demote/remove/leave, invite form, pending-invite revoke, rename) + invite-accept landing.

Acceptance covered by unit tests (register→org, invite→accept→member visibility, rails, migration); live invite-mail smoke pending alongside the #5 reset-mail test.

Shipped and deployed (PR #8). - Org entity + admin/member roles; registration creates a single-member admin org; startup + lazy migration backfills existing accounts and adopts email-linked licenses (webhook purchases attach when the buyer registers, no separate linking step). - Invites: admin enters email + role → 7-day single-use token emailed as `account.html?invite=…` → invitee sets password (or existing safe-to-move account joins) → lands in the org. Pending invites listable/revocable by admins. - Promote/demote/remove with the **last-admin rail**; removal revokes the member's sessions and parks them in a fresh personal org; members may leave. - `GET /v1/licenses` is org-scoped (all members see keys); label management is admin-only. Rename via `POST /v1/org/name`. - Site: full Organization dashboard section (members with role chips, promote/demote/remove/leave, invite form, pending-invite revoke, rename) + invite-accept landing. Acceptance covered by unit tests (register→org, invite→accept→member visibility, rails, migration); live invite-mail smoke pending alongside the #5 reset-mail test.
Cordy closed this issue 2026-07-30 00:15:18 +00:00
Sign in to join this conversation.
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: Cordy/cairn-license-server#6
No description provided.