pr_01m47d24b0e6n91zwymwxg0vpx/crates/contracts/src/identity.rs

409 lines11,813 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}
246
247#[derive(Clone, Debug, Serialize, Deserialize)]
248pub struct Member {
249 pub username: String,
250 pub role: crate::Role,
251}
252
253/// `create_workspace`. Returns `Outcome<Workspace>`.
254#[derive(Debug, Serialize, Deserialize)]
255pub struct CreateWorkspaceArgs {
256 pub user: User,
257 pub slug: String,
258 #[serde(default)]
259 pub name: String,
260}
261
262/// `get_workspace`: public details, or null. Returns `Option<Workspace>`.
263#[derive(Debug, Serialize, Deserialize)]
264pub struct SlugArgs {
265 pub slug: String,
266}
267
268/// `list_members`: members only. Returns `Outcome<Vec<Member>>`.
269#[derive(Debug, Serialize, Deserialize)]
270pub struct ListMembersArgs {
271 pub slug: String,
272 pub viewer: crate::Viewer,
273}
274
275/// `add_member` and `remove_member`: owners only.
276/// Each returns `Outcome<bool>`.
277#[derive(Debug, Serialize, Deserialize)]
278pub struct MemberArgs {
279 pub actor: User,
280 pub slug: String,
281 pub username: String,
282}
283
284/// `update_workspace`: owners only. An empty name falls back to the slug;
285/// an empty description clears it. Returns `Outcome<Workspace>`.
286#[derive(Debug, Serialize, Deserialize)]
287pub struct UpdateWorkspaceArgs {
288 pub actor: User,
289 pub slug: String,
290 pub name: String,
291 pub description: String,
292}
293
294/// `list_workspace_tokens`: members only. Returns
295/// `Outcome<Vec<AccessToken>>`.
296#[derive(Debug, Serialize, Deserialize)]
297pub struct WorkspaceTokensArgs {
298 pub slug: String,
299 pub viewer: crate::Viewer,
300}
301
302/// `create_workspace_token`: owners only. The token belongs to the
303/// workspace, acts as it, and keeps working when the member who made it
304/// leaves. Returns `Outcome<CreatedAccessToken>`.
305#[derive(Debug, Serialize, Deserialize)]
306pub struct CreateWorkspaceTokenArgs {
307 pub actor: User,
308 pub slug: String,
309 pub name: String,
310}
311
312/// `remove_workspace_token`: owners only. Returns `Outcome<bool>`.
313#[derive(Debug, Serialize, Deserialize)]
314pub struct RemoveWorkspaceTokenArgs {
315 pub actor: User,
316 pub slug: String,
317 pub id: String,
318}
319
320/// `oauth_authorize`: the signed-in person approved an application. The
321/// caller has checked the client and that it may be redirected to
322/// `redirect_uri`. Returns `OAuthCode`.
323#[derive(Debug, Serialize, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct OAuthAuthorizeArgs {
326 pub user: User,
327 pub client_id: String,
328 /// Shown wherever the application's access is listed.
329 pub client_name: String,
330 pub redirect_uri: String,
331 /// PKCE challenge, method S256.
332 pub code_challenge: String,
333}
334
335#[derive(Debug, Serialize, Deserialize)]
336pub struct OAuthCode {
337 pub code: String,
338}
339
340/// `oauth_exchange`: redeems an authorization code.
341/// Returns `Outcome<OAuthTokens>`.
342#[derive(Debug, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct OAuthExchangeArgs {
345 pub code: String,
346 pub code_verifier: String,
347 pub client_id: String,
348 pub redirect_uri: String,
349}
350
351/// `oauth_refresh`: trades a refresh token for new tokens.
352/// Returns `Outcome<OAuthTokens>`.
353#[derive(Debug, Serialize, Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub struct OAuthRefreshArgs {
356 pub refresh_token: String,
357 pub client_id: String,
358}
359
360#[derive(Debug, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct OAuthTokens {
363 pub access_token: String,
364 /// Works once; using it returns the next one.
365 pub refresh_token: String,
366 /// Seconds until the access token stops working.
367 pub expires_in: u64,
368}
369
370/// An application a person has signed in to. Listed by `list_oauth_grants`
371/// and ended by `revoke_oauth_grant`.
372#[derive(Debug, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct OAuthGrant {
375 pub id: String,
376 pub client_name: String,
377 /// RFC 3339.
378 pub created_at: String,
379 /// RFC 3339.
380 pub last_used_at: String,
381}
382
383
384/// What an agent's token may do: these operations, in this repository.
385#[derive(Clone, Debug, Serialize, Deserialize)]
386pub struct AgentScope {
387 pub repo: crate::repos::RepoPath,
388 /// API and MCP operation names, such as `create_issue`.
389 pub operations: Vec<String>,
390}
391
392/// `create_agent_token`: a token for a g1t agent working on someone's
393/// behalf. It acts as `g1t-agent`, a member of the repository's workspace,
394/// and only for the operations in `scope`. Returns `CreatedAccessToken`.
395#[derive(Debug, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct CreateAgentTokenArgs {
398 /// The person the agent works for; the token is recorded as theirs.
399 pub on_behalf_of: User,
400 pub scope: AgentScope,
401 pub ttl_seconds: u64,
402}
403
404// `agent_scope` takes `TokenArgs` and returns `Option<AgentScope>`: what an
405// agent's token may do, or null for any other token.
406
407/// The id and name g1t's agents act under.
408pub const AGENT_ID: &str = "usr_g1t_agent";
409pub const AGENT_NAME: &str = "g1t-agent";