Sthora
Teams and access

Organizations and members

The scope everything belongs to, the three roles, and the one-user-one-organization rule.

Every project, application, deployment, server, service, alert, and token belongs to an organization. Every read and write is scoped to the organization on your session.

The first organization is created by first-run setup, alongside the first user and their membership.

Roles

RoleCan
OWNEREverything, including renaming the organization, inviting, changing a member's role, and removing a member.
ADMINEverything an owner can do except the organization mutations above. Can list and revoke another member's sessions.
MEMBERRead and act on the organization's resources.

Mutations on the organization itself — rename, member role change, member removal, invite, cancel invitation — require OWNER. Reading the organization, its members, and its invitations requires only a session scoped to it.

Roles are not a security boundary against the machine

Anyone with admin access to the portal has root on the server, because the control plane holds the Docker socket. The roles divide responsibility among people you already trust with the box — they do not contain someone you do not.

One user, one organization

A user belongs to exactly one organization. This is enforced at every path that can create a membership rather than by a database constraint, and both signup and invitation acceptance refuse to give a second one to a user who already has one.

Why it is enforced this way

The schema is many-to-many and stays that way — a @@unique([userId]) would foreclose multi-organization membership forever. What is not built is the switching concept: a session carries exactly one organizationId, and every organization-scoped route trusts it as the scope.

A user with two memberships would silently resolve to their earliest one, on every session and every request, and the second organization would be invisible through that session with no error and no 403. Refusing to create the second membership is what keeps that from happening.

Multi-organization support — the "active organization" a consultant working across several customers would need — is deferred work, not a decision against it.

Members and sessions

GET /users lists the organization's members. GET /users/:id/sessions lists one member's sessions; an owner or admin can list and revoke someone else's, and anyone can manage their own.

Revoking a session ends it server-side rather than only clearing a cookie.

Removing a member

An owner can remove a member. Anything that member created stays with the organization — an API token they left behind stops working, because a token's organization is re-validated against membership on every use.

On this page