Public: anyone can read this; the crew can change it.
The OpenAPI document describes the caller's password change and email verification #5056
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5038-the-openapi-document-describes-the-calle → main 1 commit fork
2 files
changed +65
−0
src/contract/openapi.ts +29
| @@ -152,6 +152,35 @@ export const ROUTES: readonly Route[] = [ | ||
| 152 | 152 | body: { source: "object", owner: "string", name: "string", visibility: "string", branch: "string", after: "string", crew: "object" }, |
| 153 | 153 | }, |
| 154 | 154 | { method: "get", path: "/api/v1/migrate/{job}", summary: "Read a migration job's stage", auth: true }, |
| 155 | { | |
| 156 | method: "post", | |
| 157 | path: "/api/v1/user/password", | |
| 158 | summary: "Change the caller's password: current is verified, next replaces it; answers 200 with {}. A person's password or an admin-scope token only", | |
| 159 | auth: true, | |
| 160 | body: { current: "string", next: "string" }, | |
| 161 | refusals: { | |
| 162 | unauthorized: "401: no signed-in account", | |
| 163 | forbidden: "403: a cross-origin browser request, or a token without admin scope", | |
| 164 | bad_credentials: "403: current is not the password", | |
| 165 | throttled: "429: too many wrong passwords from this address or for this name", | |
| 166 | weak_password: "422: next is shorter than the minimum", | |
| 167 | password_too_long: "422: next is longer than the cap", | |
| 168 | }, | |
| 169 | }, | |
| 170 | { | |
| 171 | method: "post", | |
| 172 | path: "/api/v1/user/email/verify", | |
| 173 | summary: "Send the caller another email verification link: {outcome} is sent (202) or already (200, nothing sent)", | |
| 174 | auth: true, | |
| 175 | status: 202, | |
| 176 | refusals: { | |
| 177 | unauthorized: "401: no signed-in account", | |
| 178 | too_soon: "429: a link was sent a moment ago", | |
| 179 | capped: "429: the forge's mail cap is reached", | |
| 180 | no_mail: "503: this forge sends no mail", | |
| 181 | failed: "502: the link could not be sent", | |
| 182 | }, | |
| 183 | }, | |
| 155 | 184 | { |
| 156 | 185 | method: "post", |
| 157 | 186 | path: "/mcp", |
test/openapi-account.test.ts added +36
| @@ -0,0 +1,36 @@ | ||
| 1 | /** The served OpenAPI document describes the caller's password change and verification email. */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 4 | import { tmpdir } from "node:os"; | |
| 5 | import { join } from "node:path"; | |
| 6 | import { createApp, type App } from "../src/app.ts"; | |
| 7 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 8 | ||
| 9 | let dataDir: string; | |
| 10 | let app: App; | |
| 11 | let servers: Servers; | |
| 12 | ||
| 13 | beforeAll(async () => { | |
| 14 | dataDir = await mkdtemp(join(tmpdir(), "forge-openapi-account-")); | |
| 15 | app = createApp({ dataDir, port: 0 }); | |
| 16 | servers = await serve(app); | |
| 17 | }); | |
| 18 | ||
| 19 | afterAll(async () => { | |
| 20 | servers.stop(); | |
| 21 | app.close(); | |
| 22 | await rm(dataDir, { recursive: true, force: true }); | |
| 23 | }); | |
| 24 | ||
| 25 | test("the served document has the password and email verify routes", async () => { | |
| 26 | const res = await fetch(`${servers.url}/contract/openapi.json`); | |
| 27 | expect(res.status).toBe(200); | |
| 28 | const doc = (await res.json()) as any; | |
| 29 | const password = doc.paths["/api/v1/user/password"]?.post; | |
| 30 | const verify = doc.paths["/api/v1/user/email/verify"]?.post; | |
| 31 | expect(password).toBeDefined(); | |
| 32 | expect(verify).toBeDefined(); | |
| 33 | expect(JSON.stringify(password.requestBody)).toContain("current"); | |
| 34 | expect(JSON.stringify(password)).toContain("bad_credentials"); | |
| 35 | expect(JSON.stringify(verify)).toContain("too_soon"); | |
| 36 | }); |
new above
offline