Skip to content

Commit a71a607

Browse files
Fix HttpApiBuilder security middleware cache reuse (#1837)
Co-authored-by: Tim Smart <hello@timsmart.co>
1 parent 3a5b0ca commit a71a607

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
Fix `HttpApiBuilder` security middleware caching so separate handler builds do not reuse the first provided middleware implementation.

packages/effect/src/unstable/httpapi/HttpApiBuilder.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -652,21 +652,22 @@ const applyMiddleware = <A extends Effect.Effect<any, any, any>>(
652652
}
653653

654654
const securityMiddlewareCache = new WeakMap<
655-
any,
655+
object,
656656
(effect: Effect.Effect<any, any, any>, options: any) => Effect.Effect<any, any, any>
657657
>()
658658

659659
const makeSecurityMiddleware = (
660660
key: HttpApiMiddleware.AnyServiceSecurity,
661661
service: HttpApiMiddleware.HttpApiMiddlewareSecurity<any, any, any, any>
662662
): (effect: Effect.Effect<any, any, any>, options: any) => Effect.Effect<any, any, any> => {
663-
if (securityMiddlewareCache.has(key)) {
664-
return securityMiddlewareCache.get(key)!
663+
const cached = securityMiddlewareCache.get(service)
664+
if (cached !== undefined) {
665+
return cached
665666
}
666667

667-
const entries = Object.entries(key.security).map(([key, security]) => ({
668+
const entries = Object.entries(key.security).map(([securityKey, security]) => ({
668669
decode: securityDecode(security),
669-
middleware: service[key]
670+
middleware: service[securityKey]
670671
}))
671672
if (entries.length === 0) {
672673
return identity
@@ -694,7 +695,7 @@ const makeSecurityMiddleware = (
694695
return yield* lastResult!.asEffect()
695696
})
696697

697-
securityMiddlewareCache.set(key, middleware)
698+
securityMiddlewareCache.set(service, middleware)
698699
return middleware
699700
}
700701

packages/platform-node/test/HttpApi.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,81 @@ describe("HttpApi", () => {
484484
}).pipe(Effect.provide(ApiLive))
485485
})
486486

487+
it("security middleware cache does not reuse the first service impl", async () => {
488+
class CurrentMarker extends ServiceMap.Service<CurrentMarker, string>()("CurrentMarker") {}
489+
490+
class M extends HttpApiMiddleware.Service<M, {
491+
provides: CurrentMarker
492+
}>()("Server/Security/Repro", {
493+
security: {
494+
apiKey: HttpApiSecurity.apiKey({
495+
in: "header",
496+
key: "authorization"
497+
})
498+
}
499+
}) {}
500+
501+
const Api = HttpApi.make("api").add(
502+
HttpApiGroup.make("group")
503+
.add(HttpApiEndpoint.get("a", "/a", {
504+
success: Schema.Struct({
505+
marker: Schema.String
506+
})
507+
}))
508+
.middleware(M)
509+
)
510+
511+
const makeWebHandler = (marker: string) => {
512+
const GroupLive = HttpApiBuilder.group(
513+
Api,
514+
"group",
515+
(handlers) => handlers.handle("a", () => Effect.map(CurrentMarker.asEffect(), (marker) => ({ marker })))
516+
)
517+
const MLive = Layer.succeed(M)({
518+
apiKey: (effect) => Effect.provideService(effect, CurrentMarker, marker)
519+
})
520+
521+
return HttpRouter.toWebHandler(
522+
Layer.mergeAll(
523+
HttpApiBuilder.layer(Api).pipe(
524+
Layer.provide(GroupLive),
525+
Layer.provide(MLive),
526+
Layer.provide(HttpServer.layerServices)
527+
)
528+
),
529+
{ disableLogger: true }
530+
)
531+
}
532+
533+
const first = makeWebHandler("first")
534+
const second = makeWebHandler("second")
535+
536+
try {
537+
const firstResponse = await first.handler(
538+
new Request("http://localhost/a", {
539+
headers: {
540+
authorization: "token"
541+
}
542+
})
543+
)
544+
assert.strictEqual(firstResponse.status, 200)
545+
assert.deepStrictEqual(await firstResponse.json(), { marker: "first" })
546+
547+
const secondResponse = await second.handler(
548+
new Request("http://localhost/a", {
549+
headers: {
550+
authorization: "token"
551+
}
552+
})
553+
)
554+
assert.strictEqual(secondResponse.status, 200)
555+
assert.deepStrictEqual(await secondResponse.json(), { marker: "second" })
556+
} finally {
557+
await first.dispose()
558+
await second.dispose()
559+
}
560+
})
561+
487562
it.effect("addHttpApi + middleware works across merged groups", () => {
488563
class M1 extends HttpApiMiddleware.Service<M1>()("Http/M1") {}
489564
class M2 extends HttpApiMiddleware.Service<M2>()("Http/M2") {}

0 commit comments

Comments
 (0)