g1t/packages/contracts/src/identity.ts

127 lines4,891 bytesCodeBlame
1import type { Result } from "./result";
2
3export type User = {
4 id: string;
5 username: string;
6 /**
7 * Whether the account's email address is confirmed. Only set on users
8 * resolved from credentials; unverified accounts cannot change anything.
9 */
10 verified?: boolean;
11 /**
12 * The workspaces this user belongs to. Set on users resolved from
13 * credentials, so any service can authorize from it.
14 */
15 workspaces?: Membership[];
16};
17
18/** What a member may do: an owner also manages the workspace's members. */
19export type Role = "owner" | "member";
20
21export type Membership = { slug: string; role: Role };
22
23/**
24 * A workspace: the owner of repositories, and the first segment of their
25 * URLs. A person's own space and a team's are the same thing.
26 */
27export type Workspace = {
28 id: string;
29 slug: string;
30 name: string;
31 /** RFC 3339. */
32 createdAt: string;
33 memberCount: number;
34};
35
36export type Member = { username: string; role: Role };
37
38/** Who is asking. Every read and write in every service takes one. */
39export type Viewer = User | null;
40
41export type SshKey = {
42 id: string;
43 title: string;
44 fingerprint: string;
45 /** RFC 3339. */
46 createdAt: string;
47};
48
49export type AccessToken = { id: string; name: string; createdAt: string };
50
51export type DeviceStart = {
52 /** Secret held by the tool and exchanged for a token once approved. */
53 deviceCode: string;
54 /** Short code shown to the person, e.g. `WDJB-MJHT`. */
55 userCode: string;
56 /** Seconds until both codes stop working. */
57 expiresIn: number;
58 /** Seconds the tool should wait between polls. */
59 interval: number;
60};
61
62export type DeviceRequest = { userCode: string; clientName: string };
63
64export type DeviceClaim =
65 | { status: "pending" | "denied" | "expired" }
66 | { status: "approved"; token: string; user: User };
67
68/** Accounts, credentials and sessions. */
69export interface IdentityApi {
70 /** Creates an account and signs it in. */
71 register(username: string, email: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
72 /** Verifies a username and password for website sign-in. */
73 signIn(username: string, password: string): Promise<Result<{ user: User; sessionToken: string }>>;
74 signOut(sessionToken: string): Promise<void>;
75
76 /** Sends the confirmation email again. */
77 resendVerification(user: User): Promise<Result<boolean>>;
78 /** Confirms the address the emailed token was sent to. */
79 verifyEmail(token: string): Promise<Result<User>>;
80 /** Emails a reset link if the address has an account. Always resolves. */
81 requestPasswordReset(email: string): Promise<boolean>;
82 /** Sets a new password from an emailed token and ends every session. */
83 resetPassword(token: string, password: string): Promise<Result<User>>;
84
85 /**
86 * Device sign-in (RFC 8628). A tool starts a request, a person approves
87 * its short code in a browser, and the tool claims an access token.
88 */
89 deviceStart(clientName: string): Promise<DeviceStart>;
90 /** What a user code is asking for, or null if it is not valid. */
91 deviceLookup(userCode: string): Promise<DeviceRequest | null>;
92 deviceResolve(userCode: string, user: User, approve: boolean): Promise<Result<boolean>>;
93 deviceClaim(deviceCode: string): Promise<DeviceClaim>;
94
95 createWorkspace(user: User, slug: string, name: string): Promise<Result<Workspace>>;
96 /** Public details of a workspace, or null. */
97 getWorkspace(slug: string): Promise<Workspace | null>;
98 /** Members only. */
99 listMembers(slug: string, viewer: Viewer): Promise<Result<Member[]>>;
100 /** Owners only. */
101 addMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
102 /** Owners only. */
103 removeMember(actor: User, slug: string, username: string): Promise<Result<boolean>>;
104
105 userForSession(sessionToken: string): Promise<Viewer>;
106
107 /** Verifies git credentials: the account password or an access token. */
108 userForGitCredentials(username: string, secret: string): Promise<Viewer>;
109 /** Resolves a `g1t_…` access token, as sent to the API and MCP server. */
110 userForAccessToken(token: string): Promise<Viewer>;
111 userForSshKey(fingerprint: string): Promise<Viewer>;
112 userByUsername(username: string): Promise<Viewer>;
113
114 listSshKeys(user: User): Promise<SshKey[]>;
115 /** Takes one line in OpenSSH public key format. */
116 addSshKey(user: User, title: string, publicKey: string): Promise<Result<SshKey>>;
117 removeSshKey(user: User, id: string): Promise<void>;
118
119 listAccessTokens(user: User): Promise<AccessToken[]>;
120 /** The plaintext token is returned once and never stored. */
121 /**
122 * With `ttlSeconds` the token expires and is left out of the user's list;
123 * that form is used for hosted attempts.
124 */
125 createAccessToken(user: User, name: string, ttlSeconds?: number): Promise<{ token: string; info: AccessToken }>;
126 removeAccessToken(user: User, id: string): Promise<void>;
127}