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

515 lines15,539 bytesCodeBlame
1//! 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,
16 /// RFC 3339.
17 pub created_at: String,
18}
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct AccessToken {
23 pub id: String,
24 pub name: String,
25 /// RFC 3339.
26 pub created_at: String,
27 /// 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>,
32}
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
81/// `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
89/// `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
112/// `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.
115#[derive(Debug, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct CreateAccessTokenArgs {
118 pub user: User,
119 pub name: String,
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>,
124}
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}
141
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}
162
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}
231
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,
240 /// One line saying what the workspace is for.
241 pub description: Option<String>,
242 /// RFC 3339.
243 pub created_at: String,
244 pub member_count: u32,
245 /// 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>,
249}
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}
287
288/// `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
298/// `set_workspace_avatar`: owners only. `image` is the file's bytes in
299/// base64: PNG, JPEG, WebP or GIF, at most `MAX_AVATAR_BYTES`. Null removes
300/// the icon. Returns `Outcome<Workspace>`.
301#[derive(Debug, Serialize, Deserialize)]
302pub struct SetWorkspaceAvatarArgs {
303 pub actor: User,
304 pub slug: String,
305 pub image: Option<String>,
306}
307
308/// `set_user_avatar`: a person's own avatar, as `SetWorkspaceAvatarArgs`.
309/// Returns `Outcome<Option<String>>`: the new avatar, or null once removed.
310#[derive(Debug, Serialize, Deserialize)]
311pub struct SetUserAvatarArgs {
312 pub user: User,
313 pub image: Option<String>,
314}
315
316/// The largest avatar that can be uploaded, in bytes.
317pub const MAX_AVATAR_BYTES: usize = 1024 * 1024;
318
319/// `list_workspace_tokens`: members only. Returns
320/// `Outcome<Vec<AccessToken>>`.
321#[derive(Debug, Serialize, Deserialize)]
322pub struct WorkspaceTokensArgs {
323 pub slug: String,
324 pub viewer: crate::Viewer,
325}
326
327/// `create_workspace_token`: owners only. The token belongs to the
328/// workspace, acts as it, and keeps working when the member who made it
329/// leaves. Returns `Outcome<CreatedAccessToken>`.
330#[derive(Debug, Serialize, Deserialize)]
331pub struct CreateWorkspaceTokenArgs {
332 pub actor: User,
333 pub slug: String,
334 pub name: String,
335}
336
337/// `remove_workspace_token`: owners only. Returns `Outcome<bool>`.
338#[derive(Debug, Serialize, Deserialize)]
339pub struct RemoveWorkspaceTokenArgs {
340 pub actor: User,
341 pub slug: String,
342 pub id: String,
343}
344
345/// `oauth_authorize`: the signed-in person approved an application. The
346/// caller has checked the client and that it may be redirected to
347/// `redirect_uri`. Returns `OAuthCode`.
348#[derive(Debug, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct OAuthAuthorizeArgs {
351 pub user: User,
352 pub client_id: String,
353 /// Shown wherever the application's access is listed.
354 pub client_name: String,
355 pub redirect_uri: String,
356 /// PKCE challenge, method S256.
357 pub code_challenge: String,
358}
359
360#[derive(Debug, Serialize, Deserialize)]
361pub struct OAuthCode {
362 pub code: String,
363}
364
365/// `oauth_exchange`: redeems an authorization code.
366/// Returns `Outcome<OAuthTokens>`.
367#[derive(Debug, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct OAuthExchangeArgs {
370 pub code: String,
371 pub code_verifier: String,
372 pub client_id: String,
373 pub redirect_uri: String,
374}
375
376/// `oauth_refresh`: trades a refresh token for new tokens.
377/// Returns `Outcome<OAuthTokens>`.
378#[derive(Debug, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct OAuthRefreshArgs {
381 pub refresh_token: String,
382 pub client_id: String,
383}
384
385#[derive(Debug, Serialize, Deserialize)]
386#[serde(rename_all = "camelCase")]
387pub struct OAuthTokens {
388 pub access_token: String,
389 /// Works once; using it returns the next one.
390 pub refresh_token: String,
391 /// Seconds until the access token stops working.
392 pub expires_in: u64,
393}
394
395/// An application a person has signed in to. Listed by `list_oauth_grants`
396/// and ended by `revoke_oauth_grant`.
397#[derive(Debug, Serialize, Deserialize)]
398#[serde(rename_all = "camelCase")]
399pub struct OAuthGrant {
400 pub id: String,
401 pub client_name: String,
402 /// RFC 3339.
403 pub created_at: String,
404 /// RFC 3339.
405 pub last_used_at: String,
406}
407
408
409/// What an agent's token may do: these operations, in this repository.
410#[derive(Clone, Debug, Serialize, Deserialize)]
411pub struct AgentScope {
412 pub repo: crate::repos::RepoPath,
413 /// API and MCP operation names, such as `create_issue`.
414 pub operations: Vec<String>,
415}
416
417/// `create_agent_token`: a token for a g1t agent working on someone's
418/// behalf. It acts as `g1t-agent`, a member of the repository's workspace,
419/// and only for the operations in `scope`. Returns `CreatedAccessToken`.
420#[derive(Debug, Serialize, Deserialize)]
421#[serde(rename_all = "camelCase")]
422pub struct CreateAgentTokenArgs {
423 /// The person the agent works for; the token is recorded as theirs.
424 pub on_behalf_of: User,
425 pub scope: AgentScope,
426 pub ttl_seconds: u64,
427}
428
429// `agent_scope` takes `TokenArgs` and returns `Option<AgentScope>`: what an
430// agent's token may do, or null for any other token.
431
432/// The id and name g1t's agents act under.
433pub const AGENT_ID: &str = "usr_g1t_agent";
434pub const AGENT_NAME: &str = "g1t-agent";
435
436// --- Staff ---------------------------------------------------------------
437//
438// Staff-only methods, for sudo.g1t.sh. They take no viewer and check no
439// membership: only sudo calls them, over its service binding, after it has
440// verified a Cloudflare Access sign-in and its staff list. Nothing a
441// customer can reach should ever forward to them.
442
443/// `notify_owners`: emails a short notice, with one link, to each owner of
444/// a workspace with a confirmed address. Called by other services (billing
445/// warns owners near their usage limit), never on a person's behalf.
446/// Returns how many were sent.
447#[derive(Clone, Debug, Serialize, Deserialize)]
448pub struct NotifyOwnersArgs {
449 pub workspace: String,
450 pub subject: String,
451 /// One or two sentences: what happened and what it means.
452 pub intro: String,
453 /// The button's words, such as `Open billing`.
454 pub action: String,
455 /// Where the button goes; must be on g1t.sh.
456 pub link: String,
457 /// Small print: why they got it.
458 pub footer: String,
459}
460
461/// `admin_workspaces`: every workspace, newest first, at most
462/// [`ADMIN_WORKSPACES_LIMIT`], optionally only those whose slug, name or
463/// an owner's username or email contains `query`. Returns
464/// `Vec<AdminWorkspace>`. Staff only.
465#[derive(Debug, Default, Serialize, Deserialize)]
466pub struct AdminWorkspacesArgs {
467 #[serde(default)]
468 pub query: Option<String>,
469}
470
471/// The most workspaces one `admin_workspaces` call returns.
472pub const ADMIN_WORKSPACES_LIMIT: usize = 500;
473
474/// An owner of a workspace, as staff see them.
475#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
476pub struct AdminOwner {
477 pub username: String,
478 pub email: Option<String>,
479}
480
481/// A workspace as staff see it: who owns it and how many belong to it.
482#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
483#[serde(rename_all = "camelCase")]
484pub struct AdminWorkspace {
485 pub slug: String,
486 pub name: String,
487 /// RFC 3339.
488 pub created_at: String,
489 pub owners: Vec<AdminOwner>,
490 pub member_count: u32,
491}
492
493/// `admin_workspace`: one workspace with every member, or null. Takes
494/// `SlugArgs`; returns `Option<AdminWorkspaceDetail>`. Staff only.
495#[derive(Clone, Debug, Serialize, Deserialize)]
496#[serde(rename_all = "camelCase")]
497pub struct AdminWorkspaceDetail {
498 pub slug: String,
499 pub name: String,
500 pub description: Option<String>,
501 /// RFC 3339.
502 pub created_at: String,
503 /// Owners first, then by username.
504 pub members: Vec<AdminMember>,
505}
506
507/// A member of a workspace, as staff see them.
508#[derive(Clone, Debug, Serialize, Deserialize)]
509pub struct AdminMember {
510 pub username: String,
511 pub email: Option<String>,
512 pub role: crate::Role,
513 /// When they joined the workspace. RFC 3339.
514 pub joined: String,
515}