1 Users Teams and Access
mitosis edited this page 2026-07-29 08:11:35 -05:00

How to bring people onto the platform and scope exactly what they can touch — org roles, teams as permission sets, the operator/admin split, and the restricted machine accounts you should copy when you need a bot.

Registration

Registration is open. New users sign up at https://git.espoautos.com/user/sign_up (image CAPTCHA, internal accounts only — no external auth). Every new user can create organizations, and everything they create is private by default. If you want an invite-only instance, that is a Forgejo config switch (DISABLE_REGISTRATION) — the platform doesn't depend on either mode.

Add a person to an org

Membership flows through teams — a user is in an org because they're on one of its teams.

UI: open the org page → Teams in the sidebar → pick a team → Add Team Member. URL shape: https://git.espoautos.com/org/<org>/teams/<team-name> — e.g. https://git.espoautos.com/org/homestead/teams/owners.

API (exactly what the platform itself uses):

# find the team id
curl -s https://git.espoautos.com/api/v1/orgs/<org>/teams?limit=50 -H "Authorization: token $TOKEN"
# add the member (422 = already a member; retry on 5xx only)
curl -X PUT https://git.espoautos.com/api/v1/teams/<team_id>/members/<username> -H "Authorization: token $TOKEN"

For a single repo instead of a whole org: repo Settings → Collaborators, or PUT /api/v1/repos/{org}/{repo}/collaborators/{username} with {"permission": "read"|"write"|"admin"}.

One thing to internalize: org membership is not repo permission. Authorization checks run against repo-level permission (owner | admin | write | read | none), which teams and collaborator grants confer. That's the keystone of the access model.

Teams are permission sets

Every org has a built-in Owners team (full control). Other teams carry a level — read, write, or admin — optionally narrowed to specific repo units (repo.code, repo.issues, repo.packages, ...) via per-unit overrides.

The platform's own pattern is the sharpest example: each org gets a registry-writers team that can push container images and Helm charts but touch nothing else —

{
  "permission": "read",
  "units": ["repo.packages"],
  "units_map": { "repo.packages": "write" },
  "includes_all_repositories": true,
  "can_create_org_repo": false
}

The units_map is what keeps it from carrying any repo permission. Package-write is only delegatable through an org team at all — which is why every app is org-owned. Copy this shape whenever you need a narrowly scoped grant: pick the unit, map only that unit to write, leave the base permission at read.

Operator vs admin

Two hats, enforced by construction, never worn at once. The operator is a normal user — not site admin, zero access to any plat/* system repo — who is Owner of every product org and steers the fleet via labels (agent-work, ultracode), the /flow/:org API, and mission-control issues on their own <login>/fleet repo. The admin is plat: Forgejo site admin, owner of the platform-altering repos (plat/agents, plat/mcp, plat/gitops, plat/mitosis, plat/_app-template, plat/ci-runner); the dispatcher runs as plat, and you log into it only for admin duties. To make someone an operator: add their login to the dispatcher's PLAT_OPERATORS env, add them to the Owners team of the product orgs, and create their <login>/fleet repo — never site-admin them. Product work never needs admin, and platform changes never ride an operator account. See Steering the Fleet for what the operator seat does day to day.

Machine users: the restricted-bot model

When automation needs a credential, mint a restricted user (Forgejo restricted: true — sees only what it is explicitly granted) with the narrowest token scope that works. Two live examples to copy:

Bot Access Token scope
ci-bot write on plat/mitosis; read on gitops/agents/mcp/_app-template and a few others write:repository,write:issue
registry-bot-<org> member of that org's registry-writers team only write:package only

Registry bot passwords are derived (HMAC(SESSION_KEY, "registry-bot:"+org)), never stored. Agents themselves don't even get a user: each run pushes with a per-run, single-repo SSH deploy key that's deleted on exit — an agent physically cannot write outside its assigned repo. The Security Model page covers why this holds under adversarial pressure.

What a new member sees

Private-first, so: their own orgs and repos, the public seed repos on /explore, and nothing else. Every org and app repo is born private (DEFAULT_ORG_VISIBILITY=private, DEFAULT_PRIVATE=private); other tenants' work is invisible unless a team or collaborator grant says otherwise. From there, their first stop is Build Your First App — and if they need credentials for an app, Secrets and Config shows the self-serve channel (secrets go in repo settings, never in issues or comments).