From cea7b7b993af682fbf10bb29937d028a55b8ab7e Mon Sep 17 00:00:00 2001 From: Kai Gritun Date: Fri, 6 Feb 2026 01:47:46 -0500 Subject: [PATCH 1/3] fix(jsx): preserve context when using await before html helper (#4662) * fix(jsx): preserve context when using await before html helper When an async component used await before returning html with children, the context was being popped synchronously in the finally block before the Promise resolved. This caused children to lose access to the context. The fix delays the pop() until the Promise resolves (or rejects), ensuring context remains available throughout the async component's execution. Fixes #4582 * refactor: use finally() for context cleanup per review * chore: remove package-lock.json --------- Co-authored-by: Kai Gritun Co-authored-by: Taku Amano --- src/jsx/context.ts | 8 ++++++-- src/jsx/index.test.tsx | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/jsx/context.ts b/src/jsx/context.ts index 882beb7970..eed1388c87 100644 --- a/src/jsx/context.ts +++ b/src/jsx/context.ts @@ -24,13 +24,17 @@ export const createContext = (defaultValue: T): Context => { : props.children ).toString() : '' - } finally { + } catch (e) { values.pop() + throw e } if (string instanceof Promise) { - return string.then((resString) => raw(resString, (resString as HtmlEscapedString).callbacks)) + return string + .finally(() => values.pop()) + .then((resString) => raw(resString, (resString as HtmlEscapedString).callbacks)) } else { + values.pop() return raw(string) } }) as Context diff --git a/src/jsx/index.test.tsx b/src/jsx/index.test.tsx index bb72e8bb9b..a830cd1827 100644 --- a/src/jsx/index.test.tsx +++ b/src/jsx/index.test.tsx @@ -1022,6 +1022,44 @@ d.replaceWith(c.content) expect(nextRequest.toString()).toBe('light') }) }) + + describe('async with html helper', () => { + it('should preserve context when using await before html helper', async () => { + // Regression test for https://github.com/honojs/hono/issues/4582 + // Context was being popped before async children resolved + const AsyncParentWithHtml = async (props: { children?: any }) => { + await new Promise((r) => setTimeout(r, 10)) + return html`
${props.children}
` + } + + const template = ( + + + + + + ) + expect((await template.toString()).toString()).toBe('
dark
') + }) + + it('should preserve nested context when using await before html helper', async () => { + const AsyncParentWithHtml = async (props: { children?: any }) => { + await new Promise((r) => setTimeout(r, 10)) + return html`
${props.children}
` + } + + const template = ( + + + + + + + + ) + expect((await template.toString()).toString()).toBe('
black
') + }) + }) }) describe('version', () => { From 3aa2f9ae0957b4466a6d84978aba36f1e5677f25 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:50:31 +0900 Subject: [PATCH 2/3] fix(bearer-auth): make auth-scheme case-insensitive (#4659) * fix(bearer-auth): make auth-scheme case-insensitive * refactor: adjust test case order --- src/middleware/bearer-auth/index.test.ts | 21 +++++++++++++++++++++ src/middleware/bearer-auth/index.ts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/middleware/bearer-auth/index.test.ts b/src/middleware/bearer-auth/index.test.ts index c9f579c999..2dfa97e9c0 100644 --- a/src/middleware/bearer-auth/index.test.ts +++ b/src/middleware/bearer-auth/index.test.ts @@ -450,6 +450,18 @@ describe('Bearer Auth by Middleware', () => { expect(res.headers.get('x-custom')).toBe('foo') }) + it.each([['bearer'], ['BEARER'], ['BeArEr']])( + 'Should authorize - prefix is case-insensitive: %s', + async (prefix) => { + const req = new Request('http://localhost/auth/a') + req.headers.set('Authorization', `${prefix} ${token}`) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(handlerExecuted).toBeTruthy() + } + ) + it('Should not authorize - no authorization header', async () => { const req = new Request('http://localhost/auth/a') const res = await app.request(req) @@ -481,6 +493,15 @@ describe('Bearer Auth by Middleware', () => { expect(res.headers.get('x-custom')).toBeNull() }) + it('Should not authorize - token is case-sensitive', async () => { + const req = new Request('http://localhost/auth/a') + req.headers.set('Authorization', `Bearer ${token.toUpperCase()}`) + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(401) + expect(await res.text()).toBe('Unauthorized') + }) + it('Should authorize', async () => { const req = new Request('http://localhost/authBot/a') req.headers.set('Authorization', 'Bot abcdefg12345-._~+/=') diff --git a/src/middleware/bearer-auth/index.ts b/src/middleware/bearer-auth/index.ts index 3ecd2e3be2..122ccc55ef 100644 --- a/src/middleware/bearer-auth/index.ts +++ b/src/middleware/bearer-auth/index.ts @@ -113,7 +113,7 @@ export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { const realm = options.realm?.replace(/"/g, '\\"') const prefixRegexStr = options.prefix === '' ? '' : `${options.prefix} +` - const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`) + const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`, 'i') const wwwAuthenticatePrefix = options.prefix === '' ? '' : `${options.prefix} ` const throwHTTPException = async ( From 5ca5c3e9764486b31ad7db4c0c19b2c926753ae3 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Fri, 6 Feb 2026 15:53:11 +0900 Subject: [PATCH 3/3] 4.11.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a02feccaf3..24c3039fe2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hono", - "version": "4.11.7", + "version": "4.11.8", "description": "Web framework built on Web Standards", "main": "dist/cjs/index.js", "type": "module",