pr_01m47d15m3e54sn21z27rpy5n9/crates/contracts/src/identity.rs

401 lines11,541 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/// `list_ssh_keys` and `list_access_tokens`.
82#[derive(Debug, Serialize, Deserialize)]
83pub struct UserArgs {
84 pub user: User,
85}
86
87/// `add_ssh_key`: `public_key` is one line in OpenSSH format.
88/// Returns `Outcome<SshKey>`.
89#[derive(Debug, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct AddSshKeyArgs {
92 pub user: User,
93 pub title: String,
94 pub public_key: String,
95}
96
97/// `remove_ssh_key` and `remove_access_token`.
98#[derive(Debug, Serialize, Deserialize)]
99pub struct RemoveArgs {
100 pub user: User,
101 pub id: String,
102}
103
104/// `create_access_token`: a token that acts as `user`. For a workspace
105/// acting through a token of its own, the new token belongs to that
106/// workspace too.
107#[derive(Debug, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct CreateAccessTokenArgs {
110 pub user: User,
111 pub name: String,
112 /// When set, the token stops working after this many seconds and is
113 /// left out of the user's token list. Used for hosted attempts.
114 #[serde(default)]
115 pub ttl_seconds: Option<u64>,
116}
117
118/// The plaintext token is returned once and never stored.
119#[derive(Debug, Serialize, Deserialize)]
120pub struct CreatedAccessToken {
121 pub token: String,
122 pub info: AccessToken,
123}
124
125/// `register`: creates an account and signs it in.
126/// Returns `Outcome<SignedIn>`.
127#[derive(Debug, Serialize, Deserialize)]
128pub struct RegisterArgs {
129 pub username: String,
130 pub email: String,
131 pub password: String,
132}
133
134/// `verify_email`: the token from the emailed link. Returns `Outcome<User>`.
135#[derive(Debug, Serialize, Deserialize)]
136pub struct EmailTokenArgs {
137 pub token: String,
138}
139
140/// `request_password_reset`. Always succeeds, so it cannot be used to find
141/// out which addresses have accounts.
142#[derive(Debug, Serialize, Deserialize)]
143pub struct EmailArgs {
144 pub email: String,
145}
146
147/// `reset_password`: sets a new password and ends every session.
148/// Returns `Outcome<User>`.
149#[derive(Debug, Serialize, Deserialize)]
150pub struct ResetPasswordArgs {
151 pub token: String,
152 pub password: String,
153}
154
155/// `device_start`: begins a device sign-in. Returns `DeviceStart`.
156#[derive(Debug, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct DeviceStartArgs {
159 /// What is asking, shown to the person approving, e.g. "Claude Code".
160 pub client_name: String,
161}
162
163#[derive(Debug, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct DeviceStart {
166 /// Secret held by the tool and exchanged for a token once approved.
167 pub device_code: String,
168 /// Short code shown to the person, e.g. `WDJB-MJHT`.
169 pub user_code: String,
170 /// Seconds until both codes stop working.
171 pub expires_in: u32,
172 /// Seconds the tool should wait between polls.
173 pub interval: u32,
174}
175
176/// `device_lookup`: what a user code is asking for, or null if it is not
177/// valid. Returns `Option<DeviceRequest>`.
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct DeviceLookupArgs {
181 pub user_code: String,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct DeviceRequest {
187 pub user_code: String,
188 pub client_name: String,
189}
190
191/// `device_resolve`: the signed-in person approves or denies a request.
192/// Returns `Outcome<bool>`.
193#[derive(Debug, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct DeviceResolveArgs {
196 pub user_code: String,
197 pub user: User,
198 pub approve: bool,
199}
200
201/// `device_claim`: the tool asks whether its request was approved.
202#[derive(Debug, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct DeviceClaimArgs {
205 pub device_code: String,
206}
207
208/// The answer to a `device_claim`.
209#[derive(Debug, Serialize, Deserialize)]
210#[serde(tag = "status", rename_all = "snake_case")]
211pub enum DeviceClaim {
212 /// Nobody has approved or denied it yet; ask again after the interval.
213 Pending,
214 Denied,
215 /// The code was never issued, has expired, or was already used.
216 Expired,
217 /// The access token, returned once.
218 Approved {
219 token: String,
220 user: User,
221 },
222}
223
224/// A workspace: the owner of repositories, and the first segment of their
225/// URLs. A person's own space and a team's are the same thing.
226#[derive(Clone, Debug, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct Workspace {
229 pub id: String,
230 pub slug: String,
231 pub name: String,
232 /// One line saying what the workspace is for.
233 pub description: Option<String>,
234 /// RFC 3339.
235 pub created_at: String,
236 pub member_count: u32,
237}
238
239#[derive(Clone, Debug, Serialize, Deserialize)]
240pub struct Member {
241 pub username: String,
242 pub role: crate::Role,
243}
244
245/// `create_workspace`. Returns `Outcome<Workspace>`.
246#[derive(Debug, Serialize, Deserialize)]
247pub struct CreateWorkspaceArgs {
248 pub user: User,
249 pub slug: String,
250 #[serde(default)]
251 pub name: String,
252}
253
254/// `get_workspace`: public details, or null. Returns `Option<Workspace>`.
255#[derive(Debug, Serialize, Deserialize)]
256pub struct SlugArgs {
257 pub slug: String,
258}
259
260/// `list_members`: members only. Returns `Outcome<Vec<Member>>`.
261#[derive(Debug, Serialize, Deserialize)]
262pub struct ListMembersArgs {
263 pub slug: String,
264 pub viewer: crate::Viewer,
265}
266
267/// `add_member` and `remove_member`: owners only.
268/// Each returns `Outcome<bool>`.
269#[derive(Debug, Serialize, Deserialize)]
270pub struct MemberArgs {
271 pub actor: User,
272 pub slug: String,
273 pub username: String,
274}
275
276/// `update_workspace`: owners only. An empty name falls back to the slug;
277/// an empty description clears it. Returns `Outcome<Workspace>`.
278#[derive(Debug, Serialize, Deserialize)]
279pub struct UpdateWorkspaceArgs {
280 pub actor: User,
281 pub slug: String,
282 pub name: String,
283 pub description: String,
284}
285
286/// `list_workspace_tokens`: members only. Returns
287/// `Outcome<Vec<AccessToken>>`.
288#[derive(Debug, Serialize, Deserialize)]
289pub struct WorkspaceTokensArgs {
290 pub slug: String,
291 pub viewer: crate::Viewer,
292}
293
294/// `create_workspace_token`: owners only. The token belongs to the
295/// workspace, acts as it, and keeps working when the member who made it
296/// leaves. Returns `Outcome<CreatedAccessToken>`.
297#[derive(Debug, Serialize, Deserialize)]
298pub struct CreateWorkspaceTokenArgs {
299 pub actor: User,
300 pub slug: String,
301 pub name: String,
302}
303
304/// `remove_workspace_token`: owners only. Returns `Outcome<bool>`.
305#[derive(Debug, Serialize, Deserialize)]
306pub struct RemoveWorkspaceTokenArgs {
307 pub actor: User,
308 pub slug: String,
309 pub id: String,
310}
311
312/// `oauth_authorize`: the signed-in person approved an application. The
313/// caller has checked the client and that it may be redirected to
314/// `redirect_uri`. Returns `OAuthCode`.
315#[derive(Debug, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub struct OAuthAuthorizeArgs {
318 pub user: User,
319 pub client_id: String,
320 /// Shown wherever the application's access is listed.
321 pub client_name: String,
322 pub redirect_uri: String,
323 /// PKCE challenge, method S256.
324 pub code_challenge: String,
325}
326
327#[derive(Debug, Serialize, Deserialize)]
328pub struct OAuthCode {
329 pub code: String,
330}
331
332/// `oauth_exchange`: redeems an authorization code.
333/// Returns `Outcome<OAuthTokens>`.
334#[derive(Debug, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct OAuthExchangeArgs {
337 pub code: String,
338 pub code_verifier: String,
339 pub client_id: String,
340 pub redirect_uri: String,
341}
342
343/// `oauth_refresh`: trades a refresh token for new tokens.
344/// Returns `Outcome<OAuthTokens>`.
345#[derive(Debug, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct OAuthRefreshArgs {
348 pub refresh_token: String,
349 pub client_id: String,
350}
351
352#[derive(Debug, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct OAuthTokens {
355 pub access_token: String,
356 /// Works once; using it returns the next one.
357 pub refresh_token: String,
358 /// Seconds until the access token stops working.
359 pub expires_in: u64,
360}
361
362/// An application a person has signed in to. Listed by `list_oauth_grants`
363/// and ended by `revoke_oauth_grant`.
364#[derive(Debug, Serialize, Deserialize)]
365#[serde(rename_all = "camelCase")]
366pub struct OAuthGrant {
367 pub id: String,
368 pub client_name: String,
369 /// RFC 3339.
370 pub created_at: String,
371 /// RFC 3339.
372 pub last_used_at: String,
373}
374
375
376/// What an agent's token may do: these operations, in this repository.
377#[derive(Clone, Debug, Serialize, Deserialize)]
378pub struct AgentScope {
379 pub repo: crate::repos::RepoPath,
380 /// API and MCP operation names, such as `create_issue`.
381 pub operations: Vec<String>,
382}
383
384/// `create_agent_token`: a token for a g1t agent working on someone's
385/// behalf. It acts as `g1t-agent`, a member of the repository's workspace,
386/// and only for the operations in `scope`. Returns `CreatedAccessToken`.
387#[derive(Debug, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct CreateAgentTokenArgs {
390 /// The person the agent works for; the token is recorded as theirs.
391 pub on_behalf_of: User,
392 pub scope: AgentScope,
393 pub ttl_seconds: u64,
394}
395
396// `agent_scope` takes `TokenArgs` and returns `Option<AgentScope>`: what an
397// agent's token may do, or null for any other token.
398
399/// The id and name g1t's agents act under.
400pub const AGENT_ID: &str = "usr_g1t_agent";
401pub const AGENT_NAME: &str = "g1t-agent";