Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/latest/advanced/opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ with Docker:
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
cr.jaegertracing.io/jaegertracing/jaeger:latest
```

Then start your Fresh app pointing at the Jaeger collector:
Expand Down
54 changes: 33 additions & 21 deletions packages/fresh/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ function applyCommandsInner<State>(
basePath: string,
onError?: (err: unknown) => void,
) {
const routePhase: (() => void)[] = [];

for (let i = 0; i < commands.length; i++) {
const cmd = commands[i];

Expand Down Expand Up @@ -274,13 +276,14 @@ function applyCommandsInner<State>(
break;
}
case CommandType.Route: {
const { pattern, route, config } = cmd;
const segment = getOrCreateSegment(
root,
pattern,
cmd.pattern,
cmd.includeLastSegment,
);
const fns = segmentToMiddlewares(segment);
const { pattern, route, config } = cmd;
routePhase.push(() => {
const fns = segmentToMiddlewares(segment);

if (isLazy(route)) {
const routePath = mergePath(
Expand Down Expand Up @@ -337,33 +340,35 @@ function applyCommandsInner<State>(
}
}
}
});
break;
}
case CommandType.Handler: {
const { pattern, fns, method } = cmd;
const segment = getOrCreateSegment(
root,
pattern,
cmd.pattern,
cmd.includeLastSegment,
);
const result = segmentToMiddlewares(segment);

result.push(...fns);
const { pattern, fns, method } = cmd;
routePhase.push(() => {
const result = segmentToMiddlewares(segment);

const compiled = compileMiddlewares(result, onError);
const resPath = toRoutePath(mergePath(basePath, pattern, false));
if (method === "ALL") {
router.add("GET", resPath, compiled);
router.add("DELETE", resPath, compiled);
router.add("HEAD", resPath, compiled);
router.add("OPTIONS", resPath, compiled);
router.add("PATCH", resPath, compiled);
router.add("POST", resPath, compiled);
router.add("PUT", resPath, compiled);
} else {
router.add(method, resPath, compiled);
}
result.push(...fns);

const compiled = compileMiddlewares(result, onError);
const resPath = toRoutePath(mergePath(basePath, pattern, false));
if (method === "ALL") {
router.add("GET", resPath, compiled);
router.add("DELETE", resPath, compiled);
router.add("HEAD", resPath, compiled);
router.add("OPTIONS", resPath, compiled);
router.add("PATCH", resPath, compiled);
router.add("POST", resPath, compiled);
router.add("PUT", resPath, compiled);
} else {
router.add(method, resPath, compiled);
}
});
break;
}
case CommandType.FsRoute: {
Expand All @@ -376,4 +381,11 @@ function applyCommandsInner<State>(
throw new Error(`Unknown command: ${JSON.stringify(cmd)}`);
}
}

// Phase 2: Register routes after all middlewares are in place.
// This ensures that middleware declared after routes still wraps
// those routes correctly.
for (const fn of routePhase) {
fn();
}
}
47 changes: 47 additions & 0 deletions packages/fresh/src/middlewares/csp_order_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { expect } from "@std/expect/expect";
import { App } from "../app.ts";
import { csp } from "./csp.ts";
import { FakeServer } from "../test_utils.ts";

Deno.test("CSP - useNonce after route still sets header", async () => {
// Reproduces #3844: middleware registered AFTER route
const app = new App()
.get("/", (ctx) => {
return ctx.render(
<html>
<head><style>{"body { color: red; }"}</style></head>
<body><h1>hello</h1></body>
</html>,
);
})
.use(csp({ useNonce: true }));

const server = new FakeServer(app.handler());
const res = await server.get("/");
const cspHeader = res.headers.get("Content-Security-Policy");

// The header should be set and should contain the nonce
expect(cspHeader).toBeDefined();
expect(cspHeader).not.toBeNull();
expect(cspHeader!).toMatch(/'nonce-[a-f0-9]+'/);
});

Deno.test("CSP - useNonce before route still sets header", async () => {
// Control: middleware registered BEFORE route (the normal case)
const app = new App()
.use(csp({ useNonce: true }))
.get("/", (ctx) => {
return ctx.render(
<html>
<head><style>{"body { color: red; }"}</style></head>
<body><h1>hello</h1></body>
</html>,
);
});

const server = new FakeServer(app.handler());
const res = await server.get("/");
const cspHeader = res.headers.get("Content-Security-Policy")!;

expect(cspHeader).toMatch(/'nonce-[a-f0-9]+'/);
});
Loading