feat: add OpenAPI spec and generate TS types from it - #251
feat: add OpenAPI spec and generate TS types from it#251ezekielcyclops-ux wants to merge 1 commit into
Conversation
- Add docs/openapi.yaml (OpenAPI 3.1.0) covering all 6 API routes: POST /api/issue, POST /api/witness, GET /api/health, GET /api/ready, GET /api/issuers, GET /api/plaid-balance - Generate frontend/types/api.d.ts via openapi-typescript@7 - Add frontend/types/index.ts with ergonomic re-exports - Wire generated types into route handlers (ready, issuers, plaid-balance, issue) replacing inline interface declarations - Add pnpm generate-types script to keep types in sync - Serve Redoc UI at GET /api/docs and raw YAML at GET /api/docs/spec - Spec validates with @redocly/cli (0 errors, 0 warnings) - pnpm tsc --noEmit passes; all tests pass
|
@ezekielcyclops-ux Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
| let specYaml: string; | ||
| try { | ||
| specYaml = readFileSync(specPath, "utf-8"); | ||
| } catch { | ||
| return NextResponse.json({ error: "OpenAPI spec not found" }, { status: 404 }); | ||
| } |
There was a problem hiding this comment.
Spec file read is unused — dead I/O on every request
specYaml holds the parsed YAML but is never referenced again; the HTML response hard-codes spec-url="/api/docs/spec" and fetches it from the sibling endpoint. The read serves only as a file-existence guard, meaning the entire YAML (~858 lines) is loaded into memory and discarded on every Redoc page load. A stat check (fs.existsSync or statSync) would achieve the same guard without reading the file contents.
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/docs/route.ts
Line: 8-13
Comment:
**Spec file read is unused — dead I/O on every request**
`specYaml` holds the parsed YAML but is never referenced again; the HTML response hard-codes `spec-url="/api/docs/spec"` and fetches it from the sibling endpoint. The read serves only as a file-existence guard, meaning the entire YAML (~858 lines) is loaded into memory and discarded on every Redoc page load. A stat check (`fs.existsSync` or `statSync`) would achieve the same guard without reading the file contents.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <title>StellarCred API Reference</title> | ||
| <!-- Redoc standalone bundle (no React dependency needed) --> | ||
| <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script> |
There was a problem hiding this comment.
Unpinned CDN script without subresource integrity
The Redoc bundle is fetched from cdn.redoc.ly/redoc/latest with no integrity= attribute. Two problems compound here: (1) latest is a moving pointer — a breaking release silently breaks the docs UI; (2) without SRI the browser will execute whatever the CDN serves, so a compromised or hijacked CDN delivers arbitrary JavaScript in your app's origin. Because /api/docs is served from the same origin as the API, any injected script can read document.cookie, localStorage, and make credentialed same-origin requests. Pin to a specific version and add a crossorigin="anonymous" integrity="sha384-..." attribute.
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/docs/route.ts
Line: 23
Comment:
**Unpinned CDN script without subresource integrity**
The Redoc bundle is fetched from `cdn.redoc.ly/redoc/latest` with no `integrity=` attribute. Two problems compound here: (1) `latest` is a moving pointer — a breaking release silently breaks the docs UI; (2) without SRI the browser will execute whatever the CDN serves, so a compromised or hijacked CDN delivers arbitrary JavaScript in your app's origin. Because `/api/docs` is served from the same origin as the API, any injected script can read `document.cookie`, `localStorage`, and make credentialed same-origin requests. Pin to a specific version and add a `crossorigin="anonymous" integrity="sha384-..."` attribute.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| import { IssuerClient, CREDENTIAL_TYPES, type CredentialType, type ClaimParams } from "@stellarcred/issuer"; | ||
| import { fetchIssuerPubkey } from "@/lib/issuer-registry"; | ||
| import { logger, stripSensitiveFields, resolveRequestId } from "../../../lib/logger"; | ||
| import type { IssueRequest, IssueResponse200, IssueResponse202 } from "../../../types/index.js"; |
There was a problem hiding this comment.
IssueResponse200 and IssueResponse202 are imported but not used anywhere in the file — no function return type is annotated with them. They were likely intended to type the NextResponse.json(...) call sites, but that wiring was not completed.
| import type { IssueRequest, IssueResponse200, IssueResponse202 } from "../../../types/index.js"; | |
| import type { IssueRequest } from "../../../types/index.js"; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/app/api/issue/route.ts
Line: 6
Comment:
**Unused type imports**
`IssueResponse200` and `IssueResponse202` are imported but not used anywhere in the file — no function return type is annotated with them. They were likely intended to type the `NextResponse.json(...)` call sites, but that wiring was not completed.
```suggestion
import type { IssueRequest } from "../../../types/index.js";
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
@ezekielcyclops-ux resolve conversations and fix conflicts |
What does this PR do?
Closes #
Type of change
Checklist
cargo testpasses (contracts)pnpm tsc --noEmitpasses (frontend)pnpm buildpasses (frontend)fixtures/<type>/artifacts updatedNEXT_PUBLIC_prefix on server-only env varsprehash:falsepreserved on any issuer signing path touched✅ Merge requirements
Notes for reviewers
closes #138
Greptile Summary
This PR introduces an OpenAPI 3.1.0 spec covering all six API routes, uses
openapi-typescript@7to generatefrontend/types/api.d.ts, and wires the generated types into the route handlers — replacing the inline interface declarations inready,issuers,plaid-balance, andissue. A Redoc UI is served atGET /api/docsand the raw YAML atGET /api/docs/spec.DependencyStatus,SignerStatus, andReadyResponseinterfaces removed fromready/route.ts;issue/route.tsnow uses the generatedIssueRequesttype, thoughIssueResponse200andIssueResponse202are imported but not applied as return-type annotations.api/docs/route.tsreads the full YAML file on every request purely to guard against a missing file, but never uses the content — the Redoc HTML points at the sibling/api/docs/specendpoint instead.cdn.redoc.ly/redoc/latestwith no subresource integrity hash and no pinned version, exposing the app's origin to CDN-supply-chain risk.Confidence Score: 3/5
Safe to merge after addressing the CDN script loading issue in the Redoc handler; the type wiring across other routes is clean and non-breaking.
The core type-generation plumbing is solid — generated types correctly replace inline interfaces and TypeScript passes. The Redoc page at api/docs/route.ts loads a JavaScript bundle from an external CDN without an integrity hash and without pinning a version. Because that page is served from the app's own origin, a supply-chain compromise of the CDN would give attacker code full same-origin access. The unused IssueResponse200/IssueResponse202 imports and the wasteful full-file read in the docs handler are minor quality issues.
Files Needing Attention: frontend/app/api/docs/route.ts — the CDN script loading and unnecessary file read both need attention before this ships.
Security Review
frontend/app/api/docs/route.ts: The Redoc UI bundle is loaded fromcdn.redoc.ly/redoc/latestwith nointegrity=(SRI) attribute and no version pin. Because the page is served from the app's own origin, a compromised CDN script executes with full same-origin access — cookies,localStorage, and credentialed API calls are all reachable. Pin to a specific version and add acrossorigin=\"anonymous\" integrity=\"sha384-...\"attribute.Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[docs/openapi.yaml] -->|pnpm generate-types| B[frontend/types/api.d.ts] B --> C[frontend/types/index.ts\nErgonomic re-exports] C -->|IssueRequest| D[api/issue/route.ts] C -->|RegisteredIssuer| E[api/issuers/route.ts] C -->|PlaidBalanceResponse| F[api/plaid-balance/route.ts] C -->|ReadyResponse, SignerStatus, DependencyStatus| G[api/ready/route.ts] A -->|read from disk| H[api/docs/spec/route.ts\nGET /api/docs/spec] H -->|YAML response| I[Redoc / Postman / tooling] J[api/docs/route.ts\nGET /api/docs] -->|existence check only\nspec content unused| A J -->|serves HTML with\nspec-url pointing to H| K[Browser: Redoc UI] L[cdn.redoc.ly/redoc/latest] -->|no SRI, unpinned| KPrompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat: add OpenAPI spec and generate TS t..." | Re-trigger Greptile