Authentication, Organizations, and Roles
This document describes the implemented security boundary of Bubllio CRM. For the proposed next step and a comparison with other CRM products, see Access-model research.
This document describes the implemented security boundary of Bubllio CRM. For the proposed next step and a comparison with other CRM products, see Access-model research.
Authentication and authorization
Authentication answers “who is making this request?”. Authorization answers “what may this user do inside this organization?”. They are separate checks and both are required.
The API uses JSON Web Tokens through DRF SimpleJWT. Protected API endpoints
require an authenticated user and accept a Bearer access token. Setup,
invitation preview/acceptance, and password-reset endpoints are intentional
exceptions. The Django admin still uses
session authentication. REST endpoints are /api/v1/auth/token/, /api/v1/auth/token/refresh/,
/api/v1/auth/me/, and /api/v1/auth/logout/.
Refresh tokens rotate and are blacklisted after rotation. Logout blacklists the
submitted refresh token. The React frontend keeps both tokens in Zustand with
same-tab sessionStorage persistence. Reloads retain the session; signing out
clears it. Browser-side JavaScript can read sessionStorage, so XSS prevention
remains important.
Membership model
A Django user may belong to multiple organizations and may have a different role in each one:
User 1 -> many OrganizationMemberships <- 1 OrganizationOrganizationMembership contains a UUID, organization, user, role, and
timestamps. The database prevents duplicate membership for the same user and
organization and permits at most one owner per organization.
An installation administrator may create a shared organization. The creator nominates its initial business owner by email; the handoff process is described below. Other users join only by accepting an invitation sent by that organization's owner or administrator.
There are three different questions, which must not be conflated:
- Installation authority: may this account configure the whole deployment or create shared workspaces? Active Django superusers can do both; a workspace creation remains an installation-admin responsibility.
- Workspace membership: which organizations may this account access, and what is its role in each? An installation administrator has no implicit REST membership in workspaces created by somebody else.
- Record visibility: which records inside an accessible workspace are visible? Currently all four workspace roles can read all of that workspace's companies, contacts, automations, and run history. There is no team-, record-, or field-level visibility policy yet.
An Organization is a workspace and security boundary, not a CRM company
record or a department by default. Create a separate workspace when data,
membership, administration, or SMTP configuration should be separate. Put
ordinary customers in Company records within the appropriate workspace.
Organizations created before the membership migration do not have an owner that
can be inferred safely. After upgrading an existing development database, a
Django superuser must add an owner membership through /admin/ before an ordinary
user can access that organization. Do not guess ownership during migration.
Roles and capabilities
| Capability | Owner | Admin | Member | Viewer |
|---|---|---|---|---|
| View companies, contacts, automations, and run history | Yes | Yes | Yes | Yes |
| Create companies and contacts | Yes | Yes | Yes | No |
| Create and test automations | Yes | Yes | No | No |
| View workspace settings and SMTP account metadata | Yes | Yes | Yes | Yes |
| Change workspace settings, SMTP accounts; send SMTP test email | Yes | Yes | No | No |
| List members and pending invitations | Yes | Yes | No | No |
| Invite, change, or remove members/viewers | Yes | Yes | No | No |
| Invite or promote administrators | Yes | No | No | No |
| Change or remove administrators | Yes | No | No | No |
| Transfer ownership | Yes | No | No | No |
| Delete organization | Yes | No | No | No |
Email account passwords are encrypted at rest and never returned by the API. The account list returns account configuration (excluding passwords) to every workspace role; editing it requires owner/admin. A workspace uses its active default SMTP account for invitations, falling back to installation SMTP if it has none. Owners and administrators can also view a tenant-scoped activity history and download a portable JSON export for their workspace. Neither endpoint exposes another workspace, SMTP passwords, authentication secrets, or invitation tokens.
The table describes current endpoints; it is not a promise of per-record CRUD. For example, the current API can create/list companies and contacts, but has no company/contact update or delete endpoint. Account-level profile/password settings belong to the authenticated individual, independently of workspace role.
Authorization is expressed as capabilities in
src/access/permissions.py. This avoids scattering role comparisons
through every view.
Administrators may invite, update, and remove members or viewers. They cannot create, modify, or remove another administrator or owner. Owners may promote members to administrator and transfer ownership. The current owner cannot be removed or demoted directly; ownership must first be transferred to another membership.
A single user can therefore be an owner in workspace A, a viewer in workspace B, and an installation administrator at the same time. These are independent assignments; selecting a different workspace does not carry its role across.
Workspace provisioning
Only an active installation administrator can create a workspace. The backend
checks this rule on every create request, so a stale UI or token cannot grant
access. GET /api/v1/auth/me/ exposes can_create_workspaces for the UI.
Workspace-creation and owner-invitation resend requests share a daily throttle.
Create a shared workspace with POST /api/v1/organizations/ and name, slug,
and owner_email. The installation administrator may omit it to become owner
directly. If the nominated email is the administrator's own email, they
become owner immediately. Otherwise:
- Installation fallback SMTP must be configured. The server atomically creates
the organization, its settings, a provisional creator-owner membership, and
a seven-day, email-bound owner invitation. Failed delivery rolls all of this
back. The response has
owner_invitation_pending: true. - While awaiting acceptance, the workspace is excluded from normal workspace listings and all membership-scoped CRM/settings endpoints, including for the provisional creator. It cannot collect CRM data through the REST API.
- The nominated person accepts through the existing invitation page. A new account can register there; an existing account must sign in with the invited email. Acceptance atomically removes the provisional creator membership, installs the nominated owner, and opens the workspace.
- An installation administrator may view pending workspaces, resend the invitation (invalidating the previous token), or cancel/delete the pending workspace:
GET /api/v1/organizations/provisioning/
POST /api/v1/organizations/provisioning/<organization_id>/
DELETE /api/v1/organizations/provisioning/<organization_id>/Only installation administrators can see or manage pending workspaces.
An unaccepted or expired invitation grants no workspace access. The
database constraint ensures at most one owner; the provisional membership
ensures a pending workspace is not left ownerless. Owner handoff,
shared-workspace creation through this API, resend, cancellation, member
invitations, role changes, removals, and email-connection changes are recorded
in a read-only WorkspaceAccessEvent log visible to installation admins.
Secret values and invitation tokens are not stored in that log.
Tenant-scoped routes
Organization-owned resources use the organization UUID in the URL:
/api/v1/organizations/<organization_id>/companies/
/api/v1/organizations/<organization_id>/contacts/
/api/v1/organizations/<organization_id>/automations/The server resolves that organization through the authenticated user's membership
and required capability. The client does not choose ownership by sending an
organization field in JSON. organization is read-only in company, contact,
and automation serializers.
Queries are always filtered by the resolved organization. A missing membership or
insufficient capability normally returns 404 Not Found; this avoids revealing
whether an inaccessible organization exists.
Automation Access
Automation access uses these same boundaries: members can read rules and run history; owners/admins can create rules and invoke the manual action test. That test performs the action even for an inactive rule. It is not a permission preview or dry run. See Automations for current behavior.
Membership Operations
Only owners and administrators can list memberships:
GET /api/v1/organizations/<organization_id>/members/Direct POST to the members endpoint is unavailable. Membership creation
requires the email invitation and acceptance flow below.
Owners and administrators invite by email through
POST /api/v1/organizations/<organization_id>/invitations/ with email and
role. Owners may invite admin, member, or viewer; administrators may
invite only member or viewer. GET on the same route returns the latest 100
invitations, including their typed pending, accepted, expired, or
revoked status and timestamps. The server sends through that organization's active
default SMTP account, or the installation fallback SMTP if the workspace has
none. It returns an error and saves no invitation if neither is available or
delivery fails. A new invitation to the same email revokes the earlier active
link while preserving its history. Invitation POST and resend requests share a
limit of 20 per user per day.
Owners and administrators may resend or revoke invitations through tenant-scoped actions. Resending creates a fresh seven-day link and revokes every earlier active link for that email. Revoking immediately invalidates the selected link. Administrators cannot resend or revoke an administrator invitation; that remains owner-only.
POST /api/v1/organizations/<organization_id>/invitations/<invitation_id>/resend/
DELETE /api/v1/organizations/<organization_id>/invitations/<invitation_id>/Invitation links contain a random token. Only its SHA-256 hash is stored, and
the link expires after seven days. GET /api/v1/invitations/<token>/ previews
the workspace, invited email, role, and expiry. POST to the corresponding
accept/ route consumes it once. An existing user must sign in with the
invited email; a new user supplies a username and password and receives JWTs.
The invited email cannot be changed at acceptance. No general public signup
endpoint exists. Invited users cannot create additional shared organizations
because workspace creation is reserved for installation administrators.
Set BUBLLIO_APP_URL to the public React origin for correct links in emails.
Local development defaults to http://127.0.0.1:5173.
Change a role:
PATCH /api/v1/organizations/<organization_id>/members/<membership_id>/
Content-Type: application/json
{
"role": "viewer"
}Setting role to owner transfers ownership atomically: the previous owner
becomes an administrator and the selected membership becomes the owner.
Remove a membership:
DELETE /api/v1/organizations/<organization_id>/members/<membership_id>/Superusers
The optional first-run setup flow at GET/POST /api/v1/setup/ can create the
first Django superuser and first organization owner. It is available only when
BUBLLIO_SETUP_TOKEN is configured, the installation has not been completed,
and the user and organization tables are empty. POST requires that server-side token; successful
setup closes the flow permanently for that database. An existing installation
does not gain a public user-creation path. See Getting Started.
The first setup user is an installation administrator. Installation
administrators can invite additional IT administrators through a separate,
email-bound, seven-day invitation. Accepting it grants Django superuser and
staff privileges, but does not create workspace memberships. In the product
API, even a superuser sees CRM workspaces only through OrganizationMembership.
Because Django superusers can access data through /admin/, this role is
reserved for trusted IT operators and must not be used as a workspace role.
Installation administrators create shared organizations and manage installation settings. Workspace creation cannot be delegated to ordinary accounts. Organization owners and administrators can invite people into their own workspace, but cannot provision workspaces or grant installation-admin access.
Important limitation: the REST API enforces membership even for installation
administrators, but Django's /admin/ grants superusers broad database access.
Do not describe the present installation role as technically unable to read
other workspaces. Treat it as highly privileged, reserve it for trusted IT
operators, and protect /admin/ accordingly. See the
research and access-model rationale.
Personal workspace creation is not offered to ordinary users; they see only workspaces reached through accepted invitations. Existing personal workspaces remain isolated and cannot invite members or be deleted through the current API. Disabling the setting blocks new creation but does not remove existing personal workspaces.
Future security work
- a future HttpOnly-cookie refresh transport if the deployment needs stronger browser-side token protection;
- inactive/suspended memberships;
- audit coverage for personal-account profile and sign-in changes;
- rate limiting and production HTTPS/security settings.
Django Admin fallback
The Django admin is an operational fallback for staff/superusers, not an
organization-owner dashboard. Django superusers can inspect organizations,
memberships, organization settings, and email accounts at /admin/, regardless
of REST workspace membership. The email account admin
form accepts a password only when creating or changing an account, encrypts it
before saving, and never displays the encrypted value as an editable field.
The explicit test-email API remains available for Postman or a future frontend:
POST /api/v1/organizations/<organization_id>/email-accounts/<account_id>/test/CRM Domain Model
This document explains the current CRM concepts and the naming decisions.
Privacy and security baseline
Bubllio implements technical safeguards that help an installation operator meet privacy obligations. They are not a legal certification: the operator still needs a privacy notice, lawful-basis decisions, retention rules,
