-
Notifications
You must be signed in to change notification settings - Fork 0
fix: docs search, and allow scalar to send requests to api.dw #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
0c72ade
35cfc0a
7606da8
6022849
ead0ac3
bcc8356
6dea9a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,8 @@ | |
| "private": true, | ||
| "scripts": { | ||
| "dev": "next dev", | ||
| "prebuild": "node scripts/build-search-index.mjs", | ||
| "build": "next build", | ||
| "build:search-index": "node scripts/build-search-index.mjs", | ||
| "build": "node scripts/build-search-index.mjs && next build", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: Consider using Why it matters: If the search index takes significant time to generate and Suggested fix: No change needed — this is fine as-is. Just documenting the trade-off: failing fast on index errors is correct, and Next.js handles its own failures appropriately. |
||
| "start": "next start", | ||
| "lint": "eslint", | ||
| "test": "vitest", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,9 @@ import { withCspNonce } from "@/lib/scalar-api-reference"; | |
| export const GET = withCspNonce( | ||
| ApiReference({ | ||
| url: "/api/control-layer-openapi", | ||
| // See inference-api/api-reference: avoid the Google Fonts load blocked by | ||
| // our `font-src 'self' data:` CSP. | ||
| withDefaultFonts: false, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: The comment references Why it matters: A future maintainer might assume Control Layer doesn't need Suggested fix: Expand the comment slightly: // Scalar loads its default web fonts from Google Fonts for both Inference and
// Control Layer API references, which our `font-src 'self' data:` CSP blocks.
// Use the system font stack instead of allowlisting an external font host.
withDefaultFonts: false, |
||
| metaData: { | ||
| title: "API Reference | Control Layer | Doubleword Docs", | ||
| description: "Complete API reference for the Doubleword Control Layer API", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { NextRequest } from "next/server"; | ||
| import { middleware } from "./middleware"; | ||
|
|
||
| // Pull the CSP off the response middleware emits for a normal document request. | ||
| function cspFor( | ||
| url = "https://docs.doubleword.ai/inference-api/api-reference", | ||
| ): string { | ||
| const res = middleware(new NextRequest(url)); | ||
| return res.headers.get("content-security-policy") ?? ""; | ||
| } | ||
|
|
||
| // Return the directive (e.g. "connect-src ...") as a single trimmed string. | ||
| function directive(csp: string, name: string): string { | ||
| return ( | ||
| csp | ||
| .split(";") | ||
| .map((d) => d.trim()) | ||
| .find((d) => d === name || d.startsWith(`${name} `)) ?? "" | ||
| ); | ||
| } | ||
|
|
||
| describe("CSP middleware", () => { | ||
| it("allows the inference API host so Scalar's Test Request works", () => { | ||
| // The OpenAPI spec's server is https://api.doubleword.ai/v1, so Scalar fires | ||
| // its try-it fetch at that host from the browser. Without this entry the | ||
| // request is blocked and the user sees "Failed to fetch". | ||
| expect(directive(cspFor(), "connect-src")).toContain( | ||
| "https://api.doubleword.ai", | ||
| ); | ||
| }); | ||
|
|
||
| it("keeps the other connect-src allowances intact", () => { | ||
| const connectSrc = directive(cspFor(), "connect-src"); | ||
| expect(connectSrc).toContain("'self'"); // PostHog via /ingest rewrite | ||
| expect(connectSrc).toContain("https://app.doubleword.ai"); // SSO session check | ||
| expect(connectSrc).toContain("https://status.doubleword.ai"); // StatusWidget | ||
| }); | ||
|
|
||
| it("does not allowlist a font host — fonts stay self/data only", () => { | ||
| // We fixed the Scalar font violation by disabling its default fonts, not by | ||
| // opening font-src. Guard against a future regression that re-opens it. | ||
| expect(directive(cspFor(), "font-src")).toBe("font-src 'self' data:"); | ||
| }); | ||
|
|
||
| it("emits a per-request nonce in script-src", () => { | ||
| expect(cspFor()).toMatch(/script-src[^;]*'nonce-[^']+'/); | ||
| }); | ||
|
|
||
| it("uses a unique nonce per response", () => { | ||
| const first = cspFor().match(/'nonce-([^']+)'/)?.[1]; | ||
| const second = cspFor().match(/'nonce-([^']+)'/)?.[1]; | ||
| expect(first).toBeTruthy(); | ||
| expect(first).not.toBe(second); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,12 @@ import {NextRequest, NextResponse} from 'next/server' | |
| // `credentials: 'include'` to verify the SSO session. CORS is already | ||
| // allowed on the control-layer side (see | ||
| // internal/values/control-layer.yaml `allowed_origins`). | ||
| // - `https://api.doubleword.ai` for the Scalar API-reference "Test Request" | ||
| // feature at /inference-api/api-reference. The OpenAPI spec's server is | ||
| // `https://api.doubleword.ai/v1`, so Scalar fires the try-it `fetch` at that | ||
| // host from the browser; without it CSP blocks the request ("Failed to | ||
| // fetch"). CORS is already allowed on the api side — docs.doubleword.ai is | ||
| // in the same `allowed_origins` list (which covers the api proxy too). | ||
| // - `https://status.doubleword.ai` for the StatusWidget component, which | ||
| // fetches `/api/v1/summary` from the public status page to render | ||
| // live incident status inline in docs pages. | ||
|
|
@@ -35,7 +41,7 @@ function buildCsp(nonce: string): string { | |
| "style-src 'self' 'unsafe-inline'", | ||
| "img-src 'self' data: blob: https://cdn.sanity.io", | ||
| "font-src 'self' data:", | ||
| "connect-src 'self' https://app.doubleword.ai https://status.doubleword.ai", | ||
| "connect-src 'self' https://app.doubleword.ai https://api.doubleword.ai https://status.doubleword.ai", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: Duplicate Why it matters: The previous version already had Suggested fix: Deduplicate the list: "connect-src 'self' https://app.doubleword.ai https://api.doubleword.ai https://status.doubleword.ai",(Keep only one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: Consider adding a comment inline here (similar to lines 27–32) explaining why Why it matters: Six months from now, someone auditing the CSP might wonder why this external host is allowlisted and whether it can be removed. An inline comment prevents accidental regression. Suggested fix: Add a brief comment near line 32 or directly above line 44: // - `https://api.doubleword.ai` for Scalar's Test Request feature (see line 44)or expand the existing comment block. |
||
| 'frame-src https://www.youtube.com https://www.youtube-nocookie.com https://player.vimeo.com', | ||
| "worker-src 'self' blob:", | ||
| "frame-ancestors 'none'", | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.