flagon-io/g1t

public

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

g1t/crates/contracts/src/identity.rs

617 lines19,252 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

API and MCP server, Rust identity service, registration, site redesign1//! The identity service: accounts, credentials and sessions.
2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
5
6use serde::{Deserialize, Serialize};
7
8use crate::User;
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct SshKey {
13 pub id: String,
14 pub title: String,
15 pub fingerprint: String,
RFC 3339 timestamps in identity and repos16 /// RFC 3339.
17 pub created_at: String,
API and MCP server, Rust identity service, registration, site redesign18}
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct AccessToken {
23 pub id: String,
24 pub name: String,
RFC 3339 timestamps in identity and repos25 /// RFC 3339.
26 pub created_at: String,
Agents as a team: lifecycle, merge queue, billing and a new shell27 /// RFC 3339, to within a few minutes. Null until it is first used.
28 pub last_used_at: Option<String>,
29 /// For a workspace's token, the username of the member who made it.
30 /// Null once that account is gone, and on personal tokens.
31 pub created_by: Option<String>,
API and MCP server, Rust identity service, registration, site redesign32}
33
34/// `sign_in`: verifies a username and password for website sign-in.
35/// Returns `Outcome<SignedIn>`.
36#[derive(Debug, Serialize, Deserialize)]
37pub struct SignInArgs {
38 pub username: String,
39 pub password: String,
40}
41
42#[derive(Debug, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct SignedIn {
45 pub user: User,
46 pub session_token: String,
47}
48
49/// `sign_out` and `user_for_session`.
50#[derive(Debug, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct SessionArgs {
53 pub session_token: String,
54}
55
56/// `user_for_git_credentials`: the account password or an access token.
57#[derive(Debug, Serialize, Deserialize)]
58pub struct GitCredentialsArgs {
59 pub username: String,
60 pub secret: String,
61}
62
63/// `user_for_access_token`.
64#[derive(Debug, Serialize, Deserialize)]
65pub struct TokenArgs {
66 pub token: String,
67}
68
69/// `user_for_ssh_key`.
70#[derive(Debug, Serialize, Deserialize)]
71pub struct FingerprintArgs {
72 pub fingerprint: String,
73}
74
75/// `user_by_username`.
76#[derive(Debug, Serialize, Deserialize)]
77pub struct UsernameArgs {
78 pub username: String,
79}
80
What happened across an outcome, as a feed beside its graph81/// `usernames`: the names behind account and workspace ids, as events and
82/// other records store them. Returns a map from id to name; ids it does
83/// not know are left out.
84#[derive(Debug, Serialize, Deserialize)]
85pub struct UsernamesArgs {
86 pub ids: Vec<String>,
87}
88
API and MCP server, Rust identity service, registration, site redesign89/// `list_ssh_keys` and `list_access_tokens`.
90#[derive(Debug, Serialize, Deserialize)]
91pub struct UserArgs {
92 pub user: User,
93}
94
95/// `add_ssh_key`: `public_key` is one line in OpenSSH format.
96/// Returns `Outcome<SshKey>`.
97#[derive(Debug, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct AddSshKeyArgs {
100 pub user: User,
101 pub title: String,
102 pub public_key: String,
103}
104
105/// `remove_ssh_key` and `remove_access_token`.
106#[derive(Debug, Serialize, Deserialize)]
107pub struct RemoveArgs {
108 pub user: User,
109 pub id: String,
110}
111
Agents as a team: lifecycle, merge queue, billing and a new shell112/// `create_access_token`: a token that acts as `user`. For a workspace
113/// acting through a token of its own, the new token belongs to that
114/// workspace too.
API and MCP server, Rust identity service, registration, site redesign115#[derive(Debug, Serialize, Deserialize)]
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)116#[serde(rename_all = "camelCase")]
API and MCP server, Rust identity service, registration, site redesign117pub struct CreateAccessTokenArgs {
118 pub user: User,
119 pub name: String,
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)120 /// When set, the token stops working after this many seconds and is
121 /// left out of the user's token list. Used for hosted attempts.
122 #[serde(default)]
123 pub ttl_seconds: Option<u64>,
API and MCP server, Rust identity service, registration, site redesign124}
125
126/// The plaintext token is returned once and never stored.
127#[derive(Debug, Serialize, Deserialize)]
128pub struct CreatedAccessToken {
129 pub token: String,
130 pub info: AccessToken,
131}
132
133/// `register`: creates an account and signs it in.
134/// Returns `Outcome<SignedIn>`.
135#[derive(Debug, Serialize, Deserialize)]
136pub struct RegisterArgs {
137 pub username: String,
138 pub email: String,
139 pub password: String,
140}
Email verification, password reset, and Git for AI scale positioning141
142/// `verify_email`: the token from the emailed link. Returns `Outcome<User>`.
143#[derive(Debug, Serialize, Deserialize)]
144pub struct EmailTokenArgs {
145 pub token: String,
146}
147
148/// `request_password_reset`. Always succeeds, so it cannot be used to find
149/// out which addresses have accounts.
150#[derive(Debug, Serialize, Deserialize)]
151pub struct EmailArgs {
152 pub email: String,
153}
154
155/// `reset_password`: sets a new password and ends every session.
156/// Returns `Outcome<User>`.
157#[derive(Debug, Serialize, Deserialize)]
158pub struct ResetPasswordArgs {
159 pub token: String,
160 pub password: String,
161}
Device sign-in replaces registering and minting tokens over the API162
163/// `device_start`: begins a device sign-in. Returns `DeviceStart`.
164#[derive(Debug, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct DeviceStartArgs {
167 /// What is asking, shown to the person approving, e.g. "Claude Code".
168 pub client_name: String,
169}
170
171#[derive(Debug, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct DeviceStart {
174 /// Secret held by the tool and exchanged for a token once approved.
175 pub device_code: String,
176 /// Short code shown to the person, e.g. `WDJB-MJHT`.
177 pub user_code: String,
178 /// Seconds until both codes stop working.
179 pub expires_in: u32,
180 /// Seconds the tool should wait between polls.
181 pub interval: u32,
182}
183
184/// `device_lookup`: what a user code is asking for, or null if it is not
185/// valid. Returns `Option<DeviceRequest>`.
186#[derive(Debug, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct DeviceLookupArgs {
189 pub user_code: String,
190}
191
192#[derive(Debug, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct DeviceRequest {
195 pub user_code: String,
196 pub client_name: String,
197}
198
199/// `device_resolve`: the signed-in person approves or denies a request.
200/// Returns `Outcome<bool>`.
201#[derive(Debug, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct DeviceResolveArgs {
204 pub user_code: String,
205 pub user: User,
206 pub approve: bool,
207}
208
209/// `device_claim`: the tool asks whether its request was approved.
210#[derive(Debug, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct DeviceClaimArgs {
213 pub device_code: String,
214}
215
216/// The answer to a `device_claim`.
217#[derive(Debug, Serialize, Deserialize)]
218#[serde(tag = "status", rename_all = "snake_case")]
219pub enum DeviceClaim {
220 /// Nobody has approved or denied it yet; ask again after the interval.
221 Pending,
222 Denied,
223 /// The code was never issued, has expired, or was already used.
224 Expired,
225 /// The access token, returned once.
226 Approved {
227 token: String,
228 user: User,
229 },
230}
Workspaces own repositories231
232/// A workspace: the owner of repositories, and the first segment of their
233/// URLs. A person's own space and a team's are the same thing.
234#[derive(Clone, Debug, Serialize, Deserialize)]
235#[serde(rename_all = "camelCase")]
236pub struct Workspace {
237 pub id: String,
238 pub slug: String,
239 pub name: String,
Agents as a team: lifecycle, merge queue, billing and a new shell240 /// One line saying what the workspace is for.
241 pub description: Option<String>,
Workspaces own repositories242 /// RFC 3339.
243 pub created_at: String,
244 pub member_count: u32,
Workspace names and icons, and a component kit for every control245 /// The workspace's uploaded icon: the SHA-256 of its bytes, served at
246 /// `/avatars/<avatar>`. Null means the generated letter avatar.
247 #[serde(default)]
248 pub avatar: Option<String>,
Workspaces own repositories249}
250
251#[derive(Clone, Debug, Serialize, Deserialize)]
252pub struct Member {
253 pub username: String,
254 pub role: crate::Role,
255}
256
257/// `create_workspace`. Returns `Outcome<Workspace>`.
258#[derive(Debug, Serialize, Deserialize)]
259pub struct CreateWorkspaceArgs {
260 pub user: User,
261 pub slug: String,
262 #[serde(default)]
263 pub name: String,
264}
265
266/// `get_workspace`: public details, or null. Returns `Option<Workspace>`.
267#[derive(Debug, Serialize, Deserialize)]
268pub struct SlugArgs {
269 pub slug: String,
270}
271
272/// `list_members`: members only. Returns `Outcome<Vec<Member>>`.
273#[derive(Debug, Serialize, Deserialize)]
274pub struct ListMembersArgs {
275 pub slug: String,
276 pub viewer: crate::Viewer,
277}
278
279/// `add_member` and `remove_member`: owners only.
280/// Each returns `Outcome<bool>`.
281#[derive(Debug, Serialize, Deserialize)]
282pub struct MemberArgs {
283 pub actor: User,
284 pub slug: String,
285 pub username: String,
286}
OAuth 2.1 sign-in for MCP clients and other applications287
Agents as a team: lifecycle, merge queue, billing and a new shell288/// `update_workspace`: owners only. An empty name falls back to the slug;
289/// an empty description clears it. Returns `Outcome<Workspace>`.
290#[derive(Debug, Serialize, Deserialize)]
291pub struct UpdateWorkspaceArgs {
292 pub actor: User,
293 pub slug: String,
294 pub name: String,
295 pub description: String,
296}
297
Agents and memory, checks and conflicts, profiles, slug renames, custom domains298/// `rename_workspace`: owners only. Changes the workspace's slug, the first
299/// segment of its URLs, to `new_slug`; the display name is untouched. The
300/// old slug redirects to the new one, and is held for this workspace, for
301/// [`SLUG_HOLD_DAYS`]. Publishes `workspace.renamed`. Returns
302/// `Outcome<Workspace>`.
303///
304/// `check_workspace_rename` takes the same arguments and answers whether
305/// the rename would be allowed, changing nothing. Returns `Outcome<bool>`.
306#[derive(Debug, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct RenameWorkspaceArgs {
309 pub actor: User,
310 pub slug: String,
311 pub new_slug: String,
312}
313
314/// How long a workspace's old slug keeps redirecting to it, and stays
315/// reserved for it, after a rename.
316pub const SLUG_HOLD_DAYS: u64 = 90;
317
318/// How long a workspace must wait between renames.
319pub const RENAME_COOLDOWN_HOURS: u64 = 24;
320
321// `resolve_slug` takes `SlugArgs` and returns `Option<String>`: the
322// workspace's current slug when `slug` is one it was renamed from within
323// the last `SLUG_HOLD_DAYS`, and null otherwise (including for a slug that
324// is in use).
325
Workspace names and icons, and a component kit for every control326/// `set_workspace_avatar`: owners only. `image` is the file's bytes in
327/// base64: PNG, JPEG, WebP or GIF, at most `MAX_AVATAR_BYTES`. Null removes
328/// the icon. Returns `Outcome<Workspace>`.
329#[derive(Debug, Serialize, Deserialize)]
330pub struct SetWorkspaceAvatarArgs {
331 pub actor: User,
332 pub slug: String,
333 pub image: Option<String>,
334}
335
336/// `set_user_avatar`: a person's own avatar, as `SetWorkspaceAvatarArgs`.
337/// Returns `Outcome<Option<String>>`: the new avatar, or null once removed.
338#[derive(Debug, Serialize, Deserialize)]
339pub struct SetUserAvatarArgs {
340 pub user: User,
341 pub image: Option<String>,
342}
343
344/// The largest avatar that can be uploaded, in bytes.
345pub const MAX_AVATAR_BYTES: usize = 1024 * 1024;
346
Agents as a team: lifecycle, merge queue, billing and a new shell347/// `list_workspace_tokens`: members only. Returns
348/// `Outcome<Vec<AccessToken>>`.
349#[derive(Debug, Serialize, Deserialize)]
350pub struct WorkspaceTokensArgs {
351 pub slug: String,
352 pub viewer: crate::Viewer,
353}
354
355/// `create_workspace_token`: owners only. The token belongs to the
356/// workspace, acts as it, and keeps working when the member who made it
357/// leaves. Returns `Outcome<CreatedAccessToken>`.
358#[derive(Debug, Serialize, Deserialize)]
359pub struct CreateWorkspaceTokenArgs {
360 pub actor: User,
361 pub slug: String,
362 pub name: String,
363}
364
365/// `remove_workspace_token`: owners only. Returns `Outcome<bool>`.
366#[derive(Debug, Serialize, Deserialize)]
367pub struct RemoveWorkspaceTokenArgs {
368 pub actor: User,
369 pub slug: String,
370 pub id: String,
371}
372
OAuth 2.1 sign-in for MCP clients and other applications373/// `oauth_authorize`: the signed-in person approved an application. The
374/// caller has checked the client and that it may be redirected to
375/// `redirect_uri`. Returns `OAuthCode`.
376#[derive(Debug, Serialize, Deserialize)]
377#[serde(rename_all = "camelCase")]
378pub struct OAuthAuthorizeArgs {
379 pub user: User,
380 pub client_id: String,
381 /// Shown wherever the application's access is listed.
382 pub client_name: String,
383 pub redirect_uri: String,
384 /// PKCE challenge, method S256.
385 pub code_challenge: String,
386}
387
388#[derive(Debug, Serialize, Deserialize)]
389pub struct OAuthCode {
390 pub code: String,
391}
392
393/// `oauth_exchange`: redeems an authorization code.
394/// Returns `Outcome<OAuthTokens>`.
395#[derive(Debug, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct OAuthExchangeArgs {
398 pub code: String,
399 pub code_verifier: String,
400 pub client_id: String,
401 pub redirect_uri: String,
402}
403
404/// `oauth_refresh`: trades a refresh token for new tokens.
405/// Returns `Outcome<OAuthTokens>`.
406#[derive(Debug, Serialize, Deserialize)]
407#[serde(rename_all = "camelCase")]
408pub struct OAuthRefreshArgs {
409 pub refresh_token: String,
410 pub client_id: String,
411}
412
413#[derive(Debug, Serialize, Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct OAuthTokens {
416 pub access_token: String,
417 /// Works once; using it returns the next one.
418 pub refresh_token: String,
419 /// Seconds until the access token stops working.
420 pub expires_in: u64,
421}
422
423/// An application a person has signed in to. Listed by `list_oauth_grants`
424/// and ended by `revoke_oauth_grant`.
425#[derive(Debug, Serialize, Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct OAuthGrant {
428 pub id: String,
429 pub client_name: String,
430 /// RFC 3339.
431 pub created_at: String,
432 /// RFC 3339.
433 pub last_used_at: String,
434}
Agents as a team: lifecycle, merge queue, billing and a new shell435
436
437/// What an agent's token may do: these operations, in this repository.
438#[derive(Clone, Debug, Serialize, Deserialize)]
439pub struct AgentScope {
440 pub repo: crate::repos::RepoPath,
441 /// API and MCP operation names, such as `create_issue`.
442 pub operations: Vec<String>,
443}
444
445/// `create_agent_token`: a token for a g1t agent working on someone's
446/// behalf. It acts as `g1t-agent`, a member of the repository's workspace,
447/// and only for the operations in `scope`. Returns `CreatedAccessToken`.
448#[derive(Debug, Serialize, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct CreateAgentTokenArgs {
451 /// The person the agent works for; the token is recorded as theirs.
452 pub on_behalf_of: User,
453 pub scope: AgentScope,
454 pub ttl_seconds: u64,
455}
456
457// `agent_scope` takes `TokenArgs` and returns `Option<AgentScope>`: what an
458// agent's token may do, or null for any other token.
459
460/// The id and name g1t's agents act under.
461pub const AGENT_ID: &str = "usr_g1t_agent";
462pub const AGENT_NAME: &str = "g1t-agent";
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace463
464// --- Staff ---------------------------------------------------------------
465//
466// Staff-only methods, for sudo.g1t.sh. They take no viewer and check no
467// membership: only sudo calls them, over its service binding, after it has
468// verified a Cloudflare Access sign-in and its staff list. Nothing a
469// customer can reach should ever forward to them.
470
471/// `notify_owners`: emails a short notice, with one link, to each owner of
472/// a workspace with a confirmed address. Called by other services (billing
473/// warns owners near their usage limit), never on a person's behalf.
474/// Returns how many were sent.
475#[derive(Clone, Debug, Serialize, Deserialize)]
476pub struct NotifyOwnersArgs {
477 pub workspace: String,
478 pub subject: String,
479 /// One or two sentences: what happened and what it means.
480 pub intro: String,
481 /// The button's words, such as `Open billing`.
482 pub action: String,
483 /// Where the button goes; must be on g1t.sh.
484 pub link: String,
485 /// Small print: why they got it.
486 pub footer: String,
487}
488
489/// `admin_workspaces`: every workspace, newest first, at most
490/// [`ADMIN_WORKSPACES_LIMIT`], optionally only those whose slug, name or
491/// an owner's username or email contains `query`. Returns
492/// `Vec<AdminWorkspace>`. Staff only.
493#[derive(Debug, Default, Serialize, Deserialize)]
494pub struct AdminWorkspacesArgs {
495 #[serde(default)]
496 pub query: Option<String>,
497}
498
499/// The most workspaces one `admin_workspaces` call returns.
500pub const ADMIN_WORKSPACES_LIMIT: usize = 500;
501
502/// An owner of a workspace, as staff see them.
503#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
504pub struct AdminOwner {
505 pub username: String,
506 pub email: Option<String>,
507}
508
509/// A workspace as staff see it: who owns it and how many belong to it.
510#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
511#[serde(rename_all = "camelCase")]
512pub struct AdminWorkspace {
513 pub slug: String,
514 pub name: String,
515 /// RFC 3339.
516 pub created_at: String,
517 pub owners: Vec<AdminOwner>,
518 pub member_count: u32,
519}
520
521/// `admin_workspace`: one workspace with every member, or null. Takes
522/// `SlugArgs`; returns `Option<AdminWorkspaceDetail>`. Staff only.
523#[derive(Clone, Debug, Serialize, Deserialize)]
524#[serde(rename_all = "camelCase")]
525pub struct AdminWorkspaceDetail {
526 pub slug: String,
527 pub name: String,
528 pub description: Option<String>,
529 /// RFC 3339.
530 pub created_at: String,
531 /// Owners first, then by username.
532 pub members: Vec<AdminMember>,
533}
534
535/// A member of a workspace, as staff see them.
536#[derive(Clone, Debug, Serialize, Deserialize)]
537pub struct AdminMember {
538 pub username: String,
539 pub email: Option<String>,
540 pub role: crate::Role,
541 /// When they joined the workspace. RFC 3339.
542 pub joined: String,
543}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains544
545// --- Profiles ------------------------------------------------------------
546//
547// A person's public page at `g1t.sh/u/<username>`. Everything in a
548// `Profile` is shown to anyone, signed in or not; an email address never is.
549
550/// The most characters each profile field takes.
551pub const MAX_PROFILE_NAME: usize = 80;
552pub const MAX_PROFILE_BIO: usize = 160;
553pub const MAX_PROFILE_LOCATION: usize = 80;
554pub const MAX_PROFILE_WEBSITE: usize = 200;
555pub const MAX_PROFILE_PRONOUNS: usize = 40;
556
557/// What anyone may see about a person.
558#[derive(Clone, Debug, Default, Serialize, Deserialize)]
559#[serde(rename_all = "camelCase")]
560pub struct Profile {
561 pub username: String,
562 /// The name they go by, if they gave one.
563 pub name: Option<String>,
564 /// One or two lines about them, at most [`MAX_PROFILE_BIO`] characters.
565 pub bio: Option<String>,
566 pub location: Option<String>,
567 /// An `https://` address.
568 pub website: Option<String>,
569 pub pronouns: Option<String>,
570 /// The uploaded avatar's hash, served at `/avatars/<avatar>`.
571 pub avatar: Option<String>,
572 /// When the account was made. RFC 3339.
573 pub created_at: String,
574}
575
576// `profile` takes `UsernameArgs` and returns `Option<Profile>`: null for
577// an account that does not exist.
578
579/// `update_profile`: a person changes their own profile. Every field is
580/// replaced; an empty one is cleared. Returns `Outcome<Profile>`.
581#[derive(Debug, Default, Serialize, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct UpdateProfileArgs {
584 pub actor: User,
585 #[serde(default)]
586 pub name: String,
587 #[serde(default)]
588 pub bio: String,
589 #[serde(default)]
590 pub location: String,
591 #[serde(default)]
592 pub website: String,
593 #[serde(default)]
594 pub pronouns: String,
595}
596
597/// `profile_workspaces`: the workspaces shown on a person's profile, as
598/// `viewer` may see them. A membership is shown only when it is no secret
599/// from the viewer: a workspace the viewer belongs to as well, or one of
600/// `public`, the workspaces the caller found the person has made a public
601/// project in (whose page shows that already). Returns
602/// `Vec<ProfileWorkspace>`; empty for an account that does not exist.
603#[derive(Debug, Serialize, Deserialize)]
604pub struct ProfileWorkspacesArgs {
605 pub username: String,
606 pub viewer: crate::Viewer,
607 #[serde(default)]
608 pub public: Vec<String>,
609}
610
611/// A workspace on a person's profile.
612#[derive(Clone, Debug, Serialize, Deserialize)]
613pub struct ProfileWorkspace {
614 pub slug: String,
615 pub name: String,
616 pub avatar: Option<String>,
617}