Accounts and organisations
People log in, belong to organisations, and reach projects through a role.
Two ways to turn it on
--signup lets anyone create an account. This is how the hosted
mcpd at mcpd.sh runs. --accounts turns on the same accounts,
organisations, roles, single sign-on and SCIM without public sign-up, so a
deployment on your own cluster lets people in by invitation, by single
sign-on or by SCIM. In both, configured keys keep working beside accounts,
and a request with no credential is refused.
your own cluster, with single sign-on from the config file
mcpd serve --accounts --config-file mcpd.json \
--public-url https://mcpd.example.com \
--smtp-host smtp.example.com --smtp-from no-reply@example.com Organisations and roles
Every account project belongs to an organisation. An account made by sign-up gets an organisation of its own with one project in it. An organisation can own up to 100 projects, and members join it by invitation. Each member has one role, and the role maps onto the six key scopes on every project the organisation owns:
| Role | Scopes | Manages the organisation |
|---|---|---|
viewer | read | no |
member | ingest, read, write, execute, worker | no |
admin | all six | members below owner, projects, keys, SSO, SCIM |
owner | all six | everything, including owners, quotas and deleting it |
A project role replaces a viewer's or member's role on one project:
none, viewer, member or
admin. Admins and owners keep their reach on every project.
A session carries the scopes the account's role allows on each project of its organisations. An API key carries one scope on one project, and works only while the role of the account that made it still allows that scope, so demoting or removing a member disarms their keys at once. Nothing an account holds reaches a project of an organisation it does not belong to. An organisation id that is not yours answers 404, so the answer does not say which organisations exist.
$ curl -s -X POST $MCPD/v1/api/orgs/org_7c1e08b53a9f/invites -H "Authorization: Bearer $SESSION" -d '{"email":"kim@example.com","role":"viewer"}'{"id":"inv_3f9c2e1b","email":"kim@example.com","role":"viewer",...}$ curl -s -X POST $MCPD/v1/api/account/keys -H "Authorization: Bearer $KIM" -d '{"scope":"ingest","project":"prj_a3f9c2e1b7d4"}'{"error":"forbidden","message":"your role on the project \"prj_a3f9c2e1b7d4\" does not allow the ingest scope"} The account lifecycle
Sign-up answers the same way whether or not the address is in use, and mails a link: for a new account it confirms the address and logs in; for an address in use it says so and offers a reset. Password reset works the same way. An address is confirmed before the account can log in. An account can change its password, which ends its other sessions, and its address, which changes when a link sent to the new address is opened. It can list its sessions and end any of them, and delete itself: every organisation it is the only member of goes with it, and the stored signals and runs of those organisations' projects are erased.
Two-step log-in uses an authenticator app. Setting it up returns a secret and a QR code, confirming a code turns it on and returns ten single-use recovery codes, and from then on log-in, password reset and account deletion need a code. A code used once is refused if sent again.
Mail goes through the SMTP server in --smtp-host. With none,
links are written to the daemon's log at info level, which is what a laptop
or a closed cluster needs. With --signup on a public address
and no SMTP server, the doors that need mail answer 503 and the daemon
warns at start, because a link in a log is one the operator could use.
Where SMTP ports are closed, post mail over HTTPS instead:
--mail-http-url names an endpoint and
--mail-http-token its bearer token. Each mail is one JSON
POST of to, subject and
text, and the endpoint picks the sender. A Cloudflare Email
Worker is a few lines:
export default {
async fetch(request, env) {
if (request.headers.get('authorization') !== `Bearer ${env.MAIL_TOKEN}`)
return new Response('unauthorized', { status: 401 })
const { to, subject, text } = await request.json()
await env.EMAIL.send({ from: 'no-reply@example.com', to, subject, text })
return Response.json({ sent: true })
},
} Single sign-on
An organisation can log its members in with any OpenID Connect provider: Google, Microsoft Entra ID, Okta, Auth0 or your own. GitHub works through an OAuth app. An admin sets the issuer, client id and client secret under the organisation's SSO settings, and registers the redirect URL shown there with the provider. On your own cluster the same settings go in the config file, with the secret read from an environment variable.
The flow is the authorization code flow with PKCE, a one-time
state and a nonce, and a secret the starting
browser keeps, so a login link cannot be handed to somebody else. The ID
token's signature is checked against the provider's published keys, with
its issuer, audience, expiry and nonce.
An organisation claims email domains. "Log in with SSO" finds the organisation by the address's domain, and a new account is created only for an address in a claimed domain. A person is found by their identity at the provider, and an existing account is never taken over: an account that is not a member must be invited and accept first. A single sign-on session reaches that organisation's projects only, and lasts 24 hours. With enforcement on, members whose address is in a claimed domain cannot log in with a password, and turning it on ends their password sessions.
On the hosted mcpd an admin can claim the domain of their own confirmed address. A domain belongs to one organisation.
Groups and SCIM
Group roles map identity provider groups onto roles. With a groups claim
set, single sign-on gives a member the highest role among their groups,
or the default role when none maps. A group never grants
owner, and an owner keeps their role.
SCIM 2.0 lets your identity provider create, change and remove users and
groups. Create a SCIM token under the organisation's SSO settings and give
your provider the base URL shown with it. The service answers
/scim/v2/Users, /scim/v2/Groups,
/scim/v2/ServiceProviderConfig, /scim/v2/Schemas
and /scim/v2/ResourceTypes, with filtering on
userName, externalId, emails.value
and displayName, and PATCH.
- A user in a claimed domain with no account gets one, which the organisation manages.
- A user whose address already has an account is sent an invitation, and becomes a member when the account accepts it.
active: falseends the membership, the account's keys on the organisation's projects and its sessions. An account the organisation manages is deactivated.- Deleting a user deprovisions it the same way, and deletes an account the organisation manages.
Quotas and usage
An organisation's projects share one ceiling on signals per minute, so one
tenant cannot starve the others and adding projects does not add
allowance. --org-quota-per-minute sets the ceiling an
organisation gets and the most it may set; with --signup it is
60000. --org-retention-days sets how long its projects keep
data, and an owner may choose a shorter window. The usage panel shows the
ceiling, what was stored in the current minute, and what was stored and
refused since the daemon started.
The audit log
Every change to membership, roles, projects, keys, single sign-on and SCIM
is written to the audit log against the organisation's id, with the acting
account's email, whether it was allowed or refused. Admins and owners read
it in the interface or with GET /v1/api/orgs/{org_id}/audit.
See security for every action.