Public: anyone can read this; the crew can change it.
The OpenAPI document describes a person's sign-ins and ending one #5192
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5150-the-openapi-document-describes-a-person → main 5 commits fork 1 thread · 1 unresolved
3 files
changed +37
−2
src/contract/openapi.ts +23
| @@ -280,6 +280,29 @@ export const ROUTES: readonly Route[] = [ | ||
| 280 | 280 | status: 204, |
| 281 | 281 | refusals: { forbidden: "the caller holds a bearer token, which cannot manage tokens" }, |
| 282 | 282 | }, |
| 283 | { | |
| 284 | method: "get", | |
| 285 | path: "/api/v1/users/{username}/signins", | |
| 286 | summary: "List where a person is signed in: answers { tokens, sessions }, each token { id, name, scope, createdAt, lastUsedAt, expiresAt } and each live browser session { id, createdAt, lastSeenAt, expiresAt, current }; a session's secret and a token's value are never answered; the person themself or the forge's owner, with a browser session, a password or an admin-scope token", | |
| 287 | auth: true, | |
| 288 | refusals: { | |
| 289 | unauthorized: "no credentials were sent", | |
| 290 | forbidden: "the caller holds a token below admin scope", | |
| 291 | not_found: "no such account, or it is not one the caller may see", | |
| 292 | }, | |
| 293 | }, | |
| 294 | { | |
| 295 | method: "delete", | |
| 296 | path: "/api/v1/users/{username}/sessions/{id}", | |
| 297 | summary: "End one browser session of a person, as when a device is lost; the person themself or the forge's owner, with a browser session, a password or an admin-scope token; answers 204 and never a session secret", | |
| 298 | auth: true, | |
| 299 | status: 204, | |
| 300 | refusals: { | |
| 301 | unauthorized: "no credentials were sent", | |
| 302 | forbidden: "the caller holds a token below admin scope", | |
| 303 | not_found: "no such account or session, or it is not one the caller may end", | |
| 304 | }, | |
| 305 | }, | |
| 283 | 306 | { method: "get", path: "/api/v1/telemetry/system", summary: "Read the forge's system view; the owner or an agent holding telemetry:read, with a ro token; every agent read is logged", auth: true }, |
| 284 | 307 | { method: "get", path: "/api/v1/telemetry/flow", summary: "Read the forge's flow view; the owner or an agent holding telemetry:read, with a ro token; every agent read is logged", auth: true }, |
| 285 | 308 | { method: "get", path: "/api/v1/telemetry/sources", summary: "List telemetry sources; answers { sources }; the owner or an agent holding telemetry:read", auth: true }, |
test/openapi-coverage.test.ts −2
| @@ -20,7 +20,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [ | ||
| 20 | 20 | "DELETE /api/v1/user/origin-apps/{}", |
| 21 | 21 | "DELETE /api/v1/user/origins/{}", |
| 22 | 22 | "DELETE /api/v1/user/pins/{}/{}", |
| 23 | "DELETE /api/v1/users/{}/sessions/{}", | |
| 24 | 23 | "GET /api/v1/agents", |
| 25 | 24 | "GET /api/v1/asks", |
| 26 | 25 | "GET /api/v1/boards", |
| @@ -80,7 +79,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [ | ||
| 80 | 79 | "GET /api/v1/user/pins", |
| 81 | 80 | "GET /api/v1/users/{}", |
| 82 | 81 | "GET /api/v1/users/{}/repos", |
| 83 | "GET /api/v1/users/{}/signins", | |
| 84 | 82 | "PATCH /api/v1/repos/{}/{}/pulls/{}", |
| 85 | 83 | "PATCH /api/v1/user", |
| 86 | 84 | "POST /api/v1/asks/bulk", |
test/openapi.test.ts +14
| @@ -202,6 +202,20 @@ test("an app's routes are described, and env values are never read back", () => | ||
| 202 | 202 | expect(doc.paths[`${A}/{env}/redeploy`].post.responses["202"]).toBeDefined(); |
| 203 | 203 | }); |
| 204 | 204 | |
| 205 | test("a person's sign-ins and ending one are described, with no session secret", () => { | |
| 206 | const doc = openapi("https://f.test", "1") as any; | |
| 207 | const list = doc.paths["/api/v1/users/{username}/signins"]?.get; | |
| 208 | const end = doc.paths["/api/v1/users/{username}/sessions/{id}"]?.delete; | |
| 209 | expect(list).toBeDefined(); | |
| 210 | expect(end).toBeDefined(); | |
| 211 | expect(list.security).toBeDefined(); | |
| 212 | expect(list.summary).toContain("admin-scope"); | |
| 213 | expect(end.responses["204"]).toBeDefined(); | |
| 214 | expect(end.responses.default.description).toContain("not_found"); | |
| 215 | expect(list.responses.default.description).toContain("unauthorized"); | |
| 216 | expect(list.responses.default.description).toContain("forbidden"); | |
| 217 | }); | |
| 218 | ||
| 205 | 219 | test("a repository's code writes are described: branches and contents", () => { |
| 206 | 220 | const doc = openapi("https://f.test", "1") as any; |
| 207 | 221 | const R = "/api/v1/repos/{owner}/{name}"; |
new above
offline
This test covers the rows, but the coverage list in
test/openapi-coverage.test.ts(lines 23 and 83) still exempts both routes. Delete those two entries so the coverage test enforces these rows from now on.