Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

99 linesCodeBlameRaw
1---
2title: Identity and access
3description: How every kind of credential becomes one viewer, what the viewer carries, and how each service decides access with the same compiled-in rules instead of asking identity.
4---
5
6import Aside from '../../../components/Aside.astro';
7import Diagram from '../../../components/Diagram.astro';
8
9`identity` (`services/identity`, Rust, D1 `g1t`) is the only service that
10knows about credentials. It holds accounts, sessions, workspaces and their
11members, teams, repository roles and invitations, access tokens, OAuth,
12device sign-in and two-factor authentication. Every other service learns
13who is asking from the viewer it is handed.
14
15<Diagram name="identity-flow" />
16
17## Credentials
18
19| Credential | Looks like | Used by | Resolved with |
20| --- | --- | --- | --- |
21| Session cookie | `g1t_session`, 64 hex characters, 30 days | The site | `user_for_session` |
22| Access token | `g1t_…`, personal or a workspace's, with scopes | The API, MCP, git, the CLI | `user_for_access_token` |
23| OAuth access token | Issued by `POST /oauth/token`, refreshed with `g1r_…` | MCP clients, the phone app, apps you authorise | `user_for_access_token` |
24| Job token | `G1T_TOKEN` in a workflow job | Actions, for that job alone | `user_for_access_token` |
25| Run credential | Made per sandbox run, up to 6 hours | Sandboxes | `user_for_access_token` |
26| git password | Your account's password | git, only without two-factor authentication | `user_for_git_credentials` |
27
28The CLI signs in with the device flow of RFC 8628 (`/device/code`, then
29`/device/token`) and keeps its token in the system keychain. Session
30cookies and access tokens are stored as their SHA-256, so neither can be
31read back.
32
33## The viewer
34
35`identity` answers with a `User` (`crates/contracts/src/lib.rs`), and the
36edge hands it to every service it calls, as `viewer` in the call's
37arguments. It is `null` for someone signed out.
38
39| Field | Holds |
40| --- | --- |
41| `kind` | `User`, `Workspace`, `Agent` or `System`: what kind of principal this is |
42| `workspaces` | Each membership: the role, the workspace's base permission, the roles that add to a member, such as billing or security manager, and privileges |
43| `grants` | Roles on repositories, directly or through teams |
44| `acting` | For a job or run token: the narrower scope it acts within |
45
46A service never asks `identity` whether someone may do something. Each
47calls `can` in `g1t_contracts::access`, the same rules compiled into every
48Rust service, with `packages/contracts/src/access.ts` as the TypeScript
49mirror, which a test keeps identical. That takes `identity` out of every
50request after the first call, and means access is decided by the service
51that owns the data, next to it.
52
53<Aside title="Why the viewer can be trusted">
54 The viewer is passed as plain JSON, not signed. That is safe because
55 services can be reached only through service bindings: none is on
56 `workers.dev`, and the only routes in the core are `telemetry`'s and
57 `og`'s public endpoints, which take no viewer. A Worker can only be
58 called by the Workers bound to it, and those are the edge Workers and
59 other services, which resolved the viewer themselves.
60</Aside>
61
62## Roles
63
64| Level | Roles |
65| --- | --- |
66| Workspace | Owner and member, plus the roles that add to a member (billing manager, security manager) and member privileges |
67| Repository | Read, Triage, Write, Maintain and Admin, given to a person, a team, or everyone in the workspace as its base permission |
68| Outside collaborators | A repository role for someone who is not a member |
69
70The rules live in `crates/contracts/src/access.rs`; the grants live in
71`identity`. [Access and roles](/guides/access-and-roles/) explains each
72role from a person's side.
73
74## Scopes
75
76A token can do no more than its owner, and often less. Scopes are written
77`resource:level`, where `admin` includes `write` and `write` includes
78`read` (`crates/contracts/src/scopes.rs`). A token's reach is every
79workspace its owner belongs to, one of them, or none. The API checks the
80scopes before it calls any service, and the MCP server lists only the
81tools they allow ([How a request is served](/architecture/requests/#scopes)).
82
83## Sign-in hardening
84
85| | |
86| --- | --- |
87| Two-factor | TOTP (RFC 6238), with ten recovery codes stored as SHA-256. A workspace can require it. |
88| Sudo mode | Sensitive changes ask you to prove it is you again (`Identity::proof`) |
89| Lockouts | Wrong passwords are throttled per account and per address |
90| Unconfirmed email | Can sign in, but cannot push |
91| GitHub sign-in | A GitHub App's OAuth web flow with PKCE, keyed on your GitHub account's numeric id |
92| Staff | `sudo.g1t.sh` checks the Cloudflare Access token against a staff list, and is the only caller of `identity`'s admin methods |
93
94## Events
95
96`identity` publishes `user.*`, `workspace.*`, `team.*` and `invite.*`
97events. Lifecycle events, such as a workspace being
98deleted or renamed, go to every subscriber, so each service cleans up or
99renames its own data ([Events](/architecture/events/#subscriber-queues)).