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/packages/contracts/src/identity.ts

290 lines11,095 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.

Agents as a team: lifecycle, merge queue, billing and a new shell1import type { RepoPath } from "./repos";
Initial g1t: services, event bus, intents and attempts2import type { Result } from "./result";
3
Email verification, password reset, and Git for AI scale positioning4export type User = {
5 id: string;
6 username: string;
7 /**
Agents as a team: lifecycle, merge queue, billing and a new shell8 * `workspace` when a workspace is acting through one of its own access
9 * tokens: `id` is then the workspace's and `username` its slug. Absent
10 * means `user`.
11 */
12 kind?: "user" | "workspace" | "agent";
13 /**
Email verification, password reset, and Git for AI scale positioning14 * Whether the account's email address is confirmed. Only set on users
15 * resolved from credentials; unverified accounts cannot change anything.
16 */
17 verified?: boolean;
Workspaces own repositories18 /**
19 * The workspaces this user belongs to. Set on users resolved from
20 * credentials, so any service can authorize from it.
21 */
22 workspaces?: Membership[];
Workspace names and icons, and a component kit for every control23 /**
24 * The person's uploaded avatar: the SHA-256 of its bytes, served at
25 * `/avatars/<avatar>`. Only set on the signed-in person; absent means
26 * the generated letter avatar.
27 */
28 avatar?: string;
Email verification, password reset, and Git for AI scale positioning29};
Initial g1t: services, event bus, intents and attempts30
Workspaces own repositories31/** What a member may do: an owner also manages the workspace's members. */
32export type Role = "owner" | "member";
33
Workspace names and icons, and a component kit for every control34export type Membership = {
35 /** The workspace's name in URLs: `g1t.sh/<slug>`. */
36 slug: string;
37 role: Role;
38 /** Its display name. Set on users resolved from credentials. */
39 name?: string;
40 /** Its uploaded icon, as `Workspace.avatar`. */
41 avatar?: string;
42};
Workspaces own repositories43
Workspace names and icons, and a component kit for every control44/** The largest avatar that can be uploaded, in bytes. */
45export const MAX_AVATAR_BYTES = 1024 * 1024;
46
Workspaces own repositories47/**
48 * A workspace: the owner of repositories, and the first segment of their
49 * URLs. A person's own space and a team's are the same thing.
50 */
51export type Workspace = {
52 id: string;
53 slug: string;
54 name: string;
Agents as a team: lifecycle, merge queue, billing and a new shell55 /** One line saying what the workspace is for. */
56 description: string | null;
Workspaces own repositories57 /** RFC 3339. */
58 createdAt: string;
59 memberCount: number;
Workspace names and icons, and a component kit for every control60 /**
61 * The workspace's uploaded icon: the SHA-256 of its bytes, served at
62 * `/avatars/<avatar>`. Null means the generated letter avatar.
63 */
64 avatar: string | null;
Workspaces own repositories65};
66
67export type Member = { username: string; role: Role };
68
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace69/** An owner of a workspace, as staff see them. */
70export type AdminOwner = { username: string; email: string | null };
71
72/** A workspace as staff see it. Mirrors `AdminWorkspace` in `crates/contracts/src/identity.rs`. */
73export type AdminWorkspace = {
74 slug: string;
75 name: string;
76 /** RFC 3339. */
77 createdAt: string;
78 owners: AdminOwner[];
79 memberCount: number;
80};
81
82/** A member of a workspace, as staff see them. */
83export type AdminMember = { username: string; email: string | null; role: Role; /** RFC 3339. */ joined: string };
84
85export type AdminWorkspaceDetail = {
86 slug: string;
87 name: string;
88 description: string | null;
89 /** RFC 3339. */
90 createdAt: string;
91 /** Owners first, then by username. */
92 members: AdminMember[];
93};
94
95/** The most workspaces one `workspaces` call returns. */
96export const ADMIN_WORKSPACES_LIMIT = 500;
97
98/**
99 * Staff-only identity, for sudo.g1t.sh. It takes no viewer and checks no
100 * membership: only sudo calls it, over its service binding, once Cloudflare
101 * Access and its staff list have let someone in. Never call it on behalf of
102 * a customer.
103 */
104export interface IdentityAdminApi {
105 /** Every workspace, newest first, at most 500; `query` matches slug, name, or an owner's username or email. */
106 workspaces(query?: string): Promise<AdminWorkspace[]>;
107 /** One workspace with all its members, or null. */
108 workspace(slug: string): Promise<AdminWorkspaceDetail | null>;
109}
110
Initial g1t: services, event bus, intents and attempts111/** Who is asking. Every read and write in every service takes one. */
112export type Viewer = User | null;
113
114export type SshKey = {
115 id: string;
116 title: string;
117 fingerprint: string;
RFC 3339 timestamps in identity and repos118 /** RFC 3339. */
119 createdAt: string;
Initial g1t: services, event bus, intents and attempts120};
121
Agents as a team: lifecycle, merge queue, billing and a new shell122export type AccessToken = {
123 id: string;
124 name: string;
125 /** RFC 3339. */
126 createdAt: string;
127 /** RFC 3339, to within a few minutes. Null until it is first used. */
128 lastUsedAt: string | null;
129 /**
130 * For a workspace's token, the username of the member who made it. Null
131 * once that account is gone, and on personal tokens.
132 */
133 createdBy: string | null;
134};
Initial g1t: services, event bus, intents and attempts135
Device sign-in replaces registering and minting tokens over the API136export type DeviceStart = {
137 /** Secret held by the tool and exchanged for a token once approved. */
138 deviceCode: string;
139 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
140 userCode: string;
141 /** Seconds until both codes stop working. */
142 expiresIn: number;
143 /** Seconds the tool should wait between polls. */
144 interval: number;
145};
146
147export type DeviceRequest = { userCode: string; clientName: string };
148
149export type DeviceClaim =
150 | { status: "pending" | "denied" | "expired" }
151 | { status: "approved"; token: string; user: User };
152
OAuth 2.1 sign-in for MCP clients and other applications153/** What the site passes on once a person has approved an application. */
154export type OAuthApproval = {
155 clientId: string;
156 /** Shown wherever the application's access is listed. */
157 clientName: string;
158 redirectUri: string;
159 /** PKCE challenge, method S256. */
160 codeChallenge: string;
161};
162
163export type OAuthTokens = {
164 accessToken: string;
165 /** Works once; using it returns the next one. */
166 refreshToken: string;
167 /** Seconds until the access token stops working. */
168 expiresIn: number;
169};
170
171/** An application a person has signed in to. */
172export type OAuthGrant = {
173 id: string;
174 clientName: string;
175 /** RFC 3339. */
176 createdAt: string;
177 /** RFC 3339. */
178 lastUsedAt: string;
179};
180
Initial g1t: services, event bus, intents and attempts181/** Accounts, credentials and sessions. */
182export interface IdentityApi {
API and MCP server, Rust identity service, registration, site redesign183 /** Creates an account and signs it in. */
184 register(username: string, email: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
Initial g1t: services, event bus, intents and attempts185 /** Verifies a username and password for website sign-in. */
186 signIn(username: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
187 signOut(sessionToken: string): Promise<void>;
Email verification, password reset, and Git for AI scale positioning188
189 /** Sends the confirmation email again. */
190 resendVerification(user: User): Promise<Result<boolean>>;
191 /** Confirms the address the emailed token was sent to. */
192 verifyEmail(token: string): Promise<Result<User>>;
193 /** Emails a reset link if the address has an account. Always resolves. */
194 requestPasswordReset(email: string): Promise<boolean>;
195 /** Sets a new password from an emailed token and ends every session. */
196 resetPassword(token: string, password: string): Promise<Result<User>>;
197
Device sign-in replaces registering and minting tokens over the API198 /**
199 * Device sign-in (RFC 8628). A tool starts a request, a person approves
200 * its short code in a browser, and the tool claims an access token.
201 */
202 deviceStart(clientName: string): Promise<DeviceStart>;
203 /** What a user code is asking for, or null if it is not valid. */
204 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
205 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
206 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
207
OAuth 2.1 sign-in for MCP clients and other applications208 /**
209 * OAuth 2.1 for applications that sign a person in through the browser.
210 * The caller has checked the client and its redirect address; this
211 * returns the one-time code the application exchanges for tokens.
212 */
213 oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
214 /** Redeems a code. It works once, for that client, with the PKCE verifier. */
215 oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
216 /** Trades a refresh token for new tokens; the old ones stop working. */
217 oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
218 /** Applications the user has signed in to, most recently used first. */
219 listOAuthGrants(user: User): Promise<OAuthGrant[]>;
220 /** Signs an application out. */
221 revokeOAuthGrant(user: User, id: string): Promise<void>;
222
Workspaces own repositories223 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
224 /** Public details of a workspace, or null. */
225 getWorkspace(slug: string): Promise<Workspace | null>;
226 /** Members only. */
227 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
228 /** Owners only. */
229 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
230 /** Owners only. */
231 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
Agents as a team: lifecycle, merge queue, billing and a new shell232 /** Owners only. An empty name falls back to the slug. */
233 updateWorkspace(actor: User, slug: string, details: { name: string; description: string }): Promise<Result<Workspace>>;
Workspace names and icons, and a component kit for every control234 /**
235 * Owners only. `image` is the file in base64: PNG, JPEG, WebP or GIF, at
236 * most `MAX_AVATAR_BYTES`, checked by its bytes. Null removes the icon.
237 */
238 setWorkspaceAvatar(actor: User, slug: string, image: string | null): Promise<Result<Workspace>>;
239 /** A person's own avatar, as `setWorkspaceAvatar`: the new one, or null. */
240 setUserAvatar(user: User, image: string | null): Promise<Result<string | null>>;
Workspaces own repositories241
Agents as a team: lifecycle, merge queue, billing and a new shell242 /**
243 * A workspace's own access tokens. They belong to the workspace, act as
244 * it, and keep working when the member who made one leaves. Members only.
245 */
246 listWorkspaceTokens(slug: string, viewer: Viewer): Promise<Result<AccessToken[]>>;
247 /** Owners only. The plaintext token is returned once and never stored. */
248 createWorkspaceToken(actor: User, slug: string, name: string): Promise<Result<{ token: string; info: AccessToken }>>;
249 /** Owners only. */
250 removeWorkspaceToken(actor: User, slug: string, id: string): Promise<Result<boolean>>;
251
Initial g1t: services, event bus, intents and attempts252 userForSession(sessionToken: string): Promise<Viewer>;
253
254 /** Verifies git credentials: the account password or an access token. */
255 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
API and MCP server, Rust identity service, registration, site redesign256 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
257 userForAccessToken(token: string): Promise<Viewer>;
Initial g1t: services, event bus, intents and attempts258 userForSshKey(fingerprint: string): Promise<Viewer>;
259 userByUsername(username: string): Promise<Viewer>;
What happened across an outcome, as a feed beside its graph260 /** The names behind account and workspace ids; unknown ids are left out. */
261 usernames(ids: string[]): Promise<Record<string, string>>;
Initial g1t: services, event bus, intents and attempts262
263 listSshKeys(user: User): Promise<SshKey[]>;
264 /** Takes one line in OpenSSH public key format. */
265 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
266 removeSshKey(user: User, id: string): Promise<void>;
267
268 listAccessTokens(user: User): Promise<AccessToken[]>;
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)269 /**
Agents as a team: lifecycle, merge queue, billing and a new shell270 * The plaintext token is returned once and never stored. With
271 * `ttlSeconds` the token expires and is left out of token lists; that
272 * form is used for hosted agents. A token made for a workspace acting
273 * through a token of its own belongs to that workspace too.
Account dropdown, llms.txt onboarding, hosted agent runner (not yet deployed)274 */
275 createAccessToken(user: User, name: string, ttlSeconds?: number): Promise<{ token: string; info: AccessToken }>;
Agents as a team: lifecycle, merge queue, billing and a new shell276 /**
277 * A token for a g1t agent working for `onBehalfOf`: it acts as
278 * `g1t-agent`, in `scope.repo` only, and only for `scope.operations`.
279 */
280 createAgentToken(
281 onBehalfOf: User,
282 scope: AgentScope,
283 ttlSeconds: number,
284 ): Promise<{ token: string; info: AccessToken }>;
Initial g1t: services, event bus, intents and attempts285 removeAccessToken(user: User, id: string): Promise<void>;
286}
Agents as a team: lifecycle, merge queue, billing and a new shell287
288
289/** What an agent's token may do: these operations, in this repository. */
290export type AgentScope = { repo: RepoPath; operations: string[] };