Public: anyone can read this; the crew can change it.
Secret routes leave api.ts for src/http/api/secrets.ts #6895
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/6330-secret-routes-leave-api-ts-for-src-http → main 1 commit fork
2 files
changed +65
−50
src/http/api.ts +2 −50
| @@ -93,7 +93,6 @@ import { forgeState, PARENT_HEADER } from "../spawn/clients.ts"; | ||
| 93 | 93 | import { familyView } from "../spawn/family.ts"; |
| 94 | 94 | import { receiveBeat, SIGNATURE_HEADER } from "../spawn/heartbeat.ts"; |
| 95 | 95 | import { reissueDoor, spawnDoor } from "../spawn/door.ts"; |
| 96 | import { listSecrets, putSecret, removeSecret } from "../actions/secrets.ts"; | |
| 97 | 96 | import { forgeProposals, proposeToParentForge, proposeUpstream } from "../actions/upstream.ts"; |
| 98 | 97 | import { FOLLOW_MODE_REFUSALS, setFollowMode, severForge, takeForgeCode, takeForgeWords, takeOrTell } from "../actions/follow.ts"; |
| 99 | 98 | import { relationOf } from "../actions/forks.ts"; |
| @@ -455,6 +454,7 @@ import { frontInput, frontJson, setFront } from "../actions/front.ts"; | ||
| 455 | 454 | import { COPIES_PER_DAY, makeItYours } from "../actions/make_it_yours.ts"; |
| 456 | 455 | import { passportRoutes } from "./passport_edge.ts"; |
| 457 | 456 | import { egressGrantRoutes } from "./api/egress_grants.ts"; |
| 457 | import { secretRoutes } from "./api/secrets.ts"; | |
| 458 | 458 | import type { Router } from "./router.ts"; |
| 459 | 459 | import { clientAddress, clientBucket } from "./peer.ts"; |
| 460 | 460 | import { manifestFor } from "../actions/here.ts"; |
| @@ -5941,55 +5941,7 @@ export function apiRoutes(router: Router<Handler>): void { | ||
| 5941 | 5941 | return json({}, 200); |
| 5942 | 5942 | }); |
| 5943 | 5943 | |
| 5944 | // Forge-level secrets: a sealed, write-only slot for the operator's own | |
| 5945 | // credentials. Admins only; the list is names and timestamps, never a | |
| 5946 | // value. `ref` names the owner: an org's name, or a repository's | |
| 5947 | // `owner/name`; the forge scope takes none. | |
| 5948 | router.add("GET", "/api/v1/secrets/:scope", (ctx) => { | |
| 5949 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 5950 | const denied = requireScope(ctx, "admin"); | |
| 5951 | if (denied) return denied; | |
| 5952 | const listed = timePhase(ctx.request, "secrets", () => | |
| 5953 | listSecrets( | |
| 5954 | ctx.app, | |
| 5955 | ctx.auth.user, | |
| 5956 | ctx.params.scope ?? "", | |
| 5957 | ctx.url.searchParams.get("ref") ?? "", | |
| 5958 | ), | |
| 5959 | ); | |
| 5960 | if (!listed.ok) return respondAccess(listed.error, 422); | |
| 5961 | return timePhase(ctx.request, "render", () => json({ items: listed.value, next_cursor: null })); | |
| 5962 | }); | |
| 5963 | ||
| 5964 | router.add("PUT", "/api/v1/secrets/:scope/:name", async (ctx) => { | |
| 5965 | const denied = requireScope(ctx, "admin"); | |
| 5966 | if (denied) return denied; | |
| 5967 | const input = await body(ctx.request); | |
| 5968 | const set = putSecret( | |
| 5969 | ctx.app, | |
| 5970 | ctx.auth.user, | |
| 5971 | ctx.params.scope ?? "", | |
| 5972 | str(input.ref), | |
| 5973 | ctx.params.name ?? "", | |
| 5974 | str(input.value), | |
| 5975 | ); | |
| 5976 | if (!set.ok) return respondAccess(set.error, 422); | |
| 5977 | return json(set.value); | |
| 5978 | }); | |
| 5979 | ||
| 5980 | router.add("DELETE", "/api/v1/secrets/:scope/:name", (ctx) => { | |
| 5981 | const denied = requireScope(ctx, "admin"); | |
| 5982 | if (denied) return denied; | |
| 5983 | const removed = removeSecret( | |
| 5984 | ctx.app, | |
| 5985 | ctx.auth.user, | |
| 5986 | ctx.params.scope ?? "", | |
| 5987 | ctx.url.searchParams.get("ref") ?? "", | |
| 5988 | ctx.params.name ?? "", | |
| 5989 | ); | |
| 5990 | if (!removed.ok) return respondAccess(removed.error, 422); | |
| 5991 | return json({}, 200); | |
| 5992 | }); | |
| 5944 | secretRoutes(router); | |
| 5993 | 5945 | |
| 5994 | 5946 | // The app's environment: names in, names out, and never a value back. |
| 5995 | 5947 | // `:var`, not `:name` — `:name` is the repository's. |
src/http/api/secrets.ts added +63
| @@ -0,0 +1,63 @@ | ||
| 1 | /** | |
| 2 | * Forge-level secrets over the API: `GET /api/v1/secrets/:scope`, and `PUT` | |
| 3 | * and `DELETE` `/api/v1/secrets/:scope/:name`. Moved out of `api.ts` (#6330); | |
| 4 | * the rules live in `actions/secrets.ts`. | |
| 5 | */ | |
| 6 | ||
| 7 | import { listSecrets, putSecret, removeSecret } from "../../actions/secrets.ts"; | |
| 8 | import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts"; | |
| 9 | import type { Handler } from "../context.ts"; | |
| 10 | import { body, json, requireScope, respondAccess, str } from "../respond.ts"; | |
| 11 | import type { Router } from "../router.ts"; | |
| 12 | ||
| 13 | export function secretRoutes(router: Router<Handler>): void { | |
| 14 | // Forge-level secrets: a sealed, write-only slot for the operator's own | |
| 15 | // credentials. Admins only; the list is names and timestamps, never a | |
| 16 | // value. `ref` names the owner: an org's name, or a repository's | |
| 17 | // `owner/name`; the forge scope takes none. | |
| 18 | router.add("GET", "/api/v1/secrets/:scope", (ctx) => { | |
| 19 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 20 | const denied = requireScope(ctx, "admin"); | |
| 21 | if (denied) return denied; | |
| 22 | const listed = timePhase(ctx.request, "secrets", () => | |
| 23 | listSecrets( | |
| 24 | ctx.app, | |
| 25 | ctx.auth.user, | |
| 26 | ctx.params.scope ?? "", | |
| 27 | ctx.url.searchParams.get("ref") ?? "", | |
| 28 | ), | |
| 29 | ); | |
| 30 | if (!listed.ok) return respondAccess(listed.error, 422); | |
| 31 | return timePhase(ctx.request, "render", () => json({ items: listed.value, next_cursor: null })); | |
| 32 | }); | |
| 33 | ||
| 34 | router.add("PUT", "/api/v1/secrets/:scope/:name", async (ctx) => { | |
| 35 | const denied = requireScope(ctx, "admin"); | |
| 36 | if (denied) return denied; | |
| 37 | const input = await body(ctx.request); | |
| 38 | const set = putSecret( | |
| 39 | ctx.app, | |
| 40 | ctx.auth.user, | |
| 41 | ctx.params.scope ?? "", | |
| 42 | str(input.ref), | |
| 43 | ctx.params.name ?? "", | |
| 44 | str(input.value), | |
| 45 | ); | |
| 46 | if (!set.ok) return respondAccess(set.error, 422); | |
| 47 | return json(set.value); | |
| 48 | }); | |
| 49 | ||
| 50 | router.add("DELETE", "/api/v1/secrets/:scope/:name", (ctx) => { | |
| 51 | const denied = requireScope(ctx, "admin"); | |
| 52 | if (denied) return denied; | |
| 53 | const removed = removeSecret( | |
| 54 | ctx.app, | |
| 55 | ctx.auth.user, | |
| 56 | ctx.params.scope ?? "", | |
| 57 | ctx.url.searchParams.get("ref") ?? "", | |
| 58 | ctx.params.name ?? "", | |
| 59 | ); | |
| 60 | if (!removed.ok) return respondAccess(removed.error, 422); | |
| 61 | return json({}, 200); | |
| 62 | }); | |
| 63 | } |
new above
offline