g1t

syntaqx/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/packages/contracts/src/identity.ts

170 lines6,487 bytes
import type { Result } from "./result";

export type User = {
  id: string;
  username: string;
  /**
   * Whether the account's email address is confirmed. Only set on users
   * resolved from credentials; unverified accounts cannot change anything.
   */
  verified?: boolean;
  /**
   * The workspaces this user belongs to. Set on users resolved from
   * credentials, so any service can authorize from it.
   */
  workspaces?: Membership[];
};

/** What a member may do: an owner also manages the workspace's members. */
export type Role = "owner" | "member";

export type Membership = { slug: string; role: Role };

/**
 * A workspace: the owner of repositories, and the first segment of their
 * URLs. A person's own space and a team's are the same thing.
 */
export type Workspace = {
  id: string;
  slug: string;
  name: string;
  /** RFC 3339. */
  createdAt: string;
  memberCount: number;
};

export type Member = { username: string; role: Role };

/** Who is asking. Every read and write in every service takes one. */
export type Viewer = User | null;

export type SshKey = {
  id: string;
  title: string;
  fingerprint: string;
  /** RFC 3339. */
  createdAt: string;
};

export type AccessToken = { id: string; name: string; createdAt: string };

export type DeviceStart = {
  /** Secret held by the tool and exchanged for a token once approved. */
  deviceCode: string;
  /** Short code shown to the person, e.g. `WDJB-MJHT`. */
  userCode: string;
  /** Seconds until both codes stop working. */
  expiresIn: number;
  /** Seconds the tool should wait between polls. */
  interval: number;
};

export type DeviceRequest = { userCode: string; clientName: string };

export type DeviceClaim =
  | { status: "pending" | "denied" | "expired" }
  | { status: "approved"; token: string; user: User };

/** What the site passes on once a person has approved an application. */
export type OAuthApproval = {
  clientId: string;
  /** Shown wherever the application's access is listed. */
  clientName: string;
  redirectUri: string;
  /** PKCE challenge, method S256. */
  codeChallenge: string;
};

export type OAuthTokens = {
  accessToken: string;
  /** Works once; using it returns the next one. */
  refreshToken: string;
  /** Seconds until the access token stops working. */
  expiresIn: number;
};

/** An application a person has signed in to. */
export type OAuthGrant = {
  id: string;
  clientName: string;
  /** RFC 3339. */
  createdAt: string;
  /** RFC 3339. */
  lastUsedAt: string;
};

/** Accounts, credentials and sessions. */
export interface IdentityApi {
  /** Creates an account and signs it in. */
  register(username: string, email: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
  /** Verifies a username and password for website sign-in. */
  signIn(username: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
  signOut(sessionToken: string): Promise<void>;

  /** Sends the confirmation email again. */
  resendVerification(user: User): Promise<Result<boolean>>;
  /** Confirms the address the emailed token was sent to. */
  verifyEmail(token: string): Promise<Result<User>>;
  /** Emails a reset link if the address has an account. Always resolves. */
  requestPasswordReset(email: string): Promise<boolean>;
  /** Sets a new password from an emailed token and ends every session. */
  resetPassword(token: string, password: string): Promise<Result<User>>;

  /**
   * Device sign-in (RFC 8628). A tool starts a request, a person approves
   * its short code in a browser, and the tool claims an access token.
   */
  deviceStart(clientName: string): Promise<DeviceStart>;
  /** What a user code is asking for, or null if it is not valid. */
  deviceLookup(userCode: string): Promise<DeviceRequest | null>;
  deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
  deviceClaim(deviceCode: string): Promise<DeviceClaim>;

  /**
   * OAuth 2.1 for applications that sign a person in through the browser.
   * The caller has checked the client and its redirect address; this
   * returns the one-time code the application exchanges for tokens.
   */
  oauthAuthorize(user: User, approval: OAuthApproval): Promise<{ code: string }>;
  /** Redeems a code. It works once, for that client, with the PKCE verifier. */
  oauthExchange(code: string, codeVerifier: string, clientId: string, redirectUri: string): Promise<Result<OAuthTokens>>;
  /** Trades a refresh token for new tokens; the old ones stop working. */
  oauthRefresh(refreshToken: string, clientId: string): Promise<Result<OAuthTokens>>;
  /** Applications the user has signed in to, most recently used first. */
  listOAuthGrants(user: User): Promise<OAuthGrant[]>;
  /** Signs an application out. */
  revokeOAuthGrant(user: User, id: string): Promise<void>;

  createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
  /** Public details of a workspace, or null. */
  getWorkspace(slug: string): Promise<Workspace | null>;
  /** Members only. */
  listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
  /** Owners only. */
  addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
  /** Owners only. */
  removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;

  userForSession(sessionToken: string): Promise<Viewer>;

  /** Verifies git credentials: the account password or an access token. */
  userForGitCredentials(username: string, secret: string): Promise<Viewer>;
  /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
  userForAccessToken(token: string): Promise<Viewer>;
  userForSshKey(fingerprint: string): Promise<Viewer>;
  userByUsername(username: string): Promise<Viewer>;

  listSshKeys(user: User): Promise<SshKey[]>;
  /** Takes one line in OpenSSH public key format. */
  addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
  removeSshKey(user: User, id: string): Promise<void>;

  listAccessTokens(user: User): Promise<AccessToken[]>;
  /** The plaintext token is returned once and never stored. */
  /**
   * With `ttlSeconds` the token expires and is left out of the user's list;
   * that form is used for hosted attempts.
   */
  createAccessToken(user: User, name: string, ttlSeconds?: number): Promise<{ token: string; info: AccessToken }>;
  removeAccessToken(user: User, id: string): Promise<void>;
}