From f86f242fcd18794e661e53835873f4c2bf8c71d8 Mon Sep 17 00:00:00 2001 From: Marcel Overdijk Date: Wed, 11 Sep 2024 07:32:51 +0200 Subject: [PATCH 1/9] Added docs for Context Storage Middleware (#463) --- .vitepress/config.ts | 1 + docs/index.md | 1 + docs/middleware/builtin/context-storage.md | 30 ++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 docs/middleware/builtin/context-storage.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index d6720bc9..d8154878 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -155,6 +155,7 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [ { text: 'Cache', link: '/docs/middleware/builtin/cache' }, { text: 'Combine', link: '/docs/middleware/builtin/combine' }, { text: 'Compress', link: '/docs/middleware/builtin/compress' }, + { text: 'Context Storage', link: '/docs/middleware/builtin/context-storage' }, { text: 'CORS', link: '/docs/middleware/builtin/cors' }, { text: 'CSRF Protection', diff --git a/docs/index.md b/docs/index.md index ee8366bc..0bdafe32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -159,6 +159,7 @@ Out of the box, Hono provides middleware and helpers for: - [Body Limit](/docs/middleware/builtin/body-limit) - [Cache](/docs/middleware/builtin/cache) - [Compress](/docs/middleware/builtin/compress) +- [Context Storage](/docs/middleware/builtin/context-storage) - [Cookie](/docs/helpers/cookie) - [CORS](/docs/middleware/builtin/cors) - [ETag](/docs/middleware/builtin/etag) diff --git a/docs/middleware/builtin/context-storage.md b/docs/middleware/builtin/context-storage.md new file mode 100644 index 00000000..4645bc8c --- /dev/null +++ b/docs/middleware/builtin/context-storage.md @@ -0,0 +1,30 @@ +# Context Storage Middleware + +The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible. + +## Import + +```ts +import { Hono } from 'hono' +import { contextStorage, getContext } from 'hono/context-storage' +``` + +## Usage + +```ts +type Env = { + Variables: { + message: string + } +} + +const app = new Hono() + +app.use(contextStorage()) + +app.get('/', (c) => c.text(getMessage()) + +const getMessage = () => { + return getContext().var.message +} +``` From be4ec911ca57024ef8129c2c2b03731139a4856e Mon Sep 17 00:00:00 2001 From: kbkn3 Date: Wed, 11 Sep 2024 14:33:15 +0900 Subject: [PATCH 2/9] docs(middleware): add permission-policy option on security-header's page (#476) --- docs/middleware/builtin/secure-headers.md | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/middleware/builtin/secure-headers.md b/docs/middleware/builtin/secure-headers.md index 71eeac6a..35dd31a9 100644 --- a/docs/middleware/builtin/secure-headers.md +++ b/docs/middleware/builtin/secure-headers.md @@ -68,6 +68,7 @@ Each option corresponds to the following Header Key-Value pairs. | xFrameOptions | [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True | | xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True | | xXssProtection | [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True | +| permissionPolicy | [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) | Usage: [Setting Permission-Policy](#setting-permission-policy) | No Setting | ## Middleware Conflict @@ -211,3 +212,32 @@ app.get('/', (c) => { ) }) ``` + +## Setting Permission-Policy + +The Permission-Policy header allows you to control which features and APIs can be used in the browser. Here's an example of how to set it: + +```ts +const app = new Hono() +app.use( + '*', + secureHeaders({ + permissionsPolicy: { + fullscreen: ['self'], // fullscreen=(self) + bluetooth: ['none'], // bluetooth=(none) + payment: ['self', 'https://example.com'], // payment=(self "https://example.com") + syncXhr: [], // sync-xhr=() + camera: false, // camera=none + microphone: true, // microphone=* + geolocation: ['*'], // geolocation=* + usb: ['self', 'https://a.example.com', 'https://b.example.com'], // usb=(self "https://a.example.com" "https://b.example.com") + accelerometer: ['https://*.example.com'], // accelerometer=("https://*.example.com") + gyroscope: ['src'], // gyroscope=(src) + magnetometer: [ + 'https://a.example.com', + 'https://b.example.com', + ], // magnetometer=("https://a.example.com" "https://b.example.com") + }, + }) +) +``` From 135693d4430ae9a175ba289e7bf068c270610642 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 14:33:41 +0900 Subject: [PATCH 3/9] docs(jsx-renderer): added `Content-Encoding` for stream option is true (#481) --- docs/middleware/builtin/jsx-renderer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/middleware/builtin/jsx-renderer.md b/docs/middleware/builtin/jsx-renderer.md index adc50344..42ff62b8 100644 --- a/docs/middleware/builtin/jsx-renderer.md +++ b/docs/middleware/builtin/jsx-renderer.md @@ -117,7 +117,8 @@ If `true` is set, the following headers are added: ```ts { 'Transfer-Encoding': 'chunked', - 'Content-Type': 'text/html; charset=UTF-8' + 'Content-Type': 'text/html; charset=UTF-8', + 'Content-Encoding': 'Identity' } ``` From 92a6f0fa46051a53d36c3124cee19e1f8a06c8a3 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 14:33:53 +0900 Subject: [PATCH 4/9] docs(websocket/bun): add `ServerWebSocket` (#480) --- docs/helpers/websocket.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/helpers/websocket.md b/docs/helpers/websocket.md index ec4bc23a..ca084513 100644 --- a/docs/helpers/websocket.md +++ b/docs/helpers/websocket.md @@ -20,8 +20,10 @@ import { upgradeWebSocket } from 'hono/deno' ```ts [Bun] import { Hono } from 'hono' import { createBunWebSocket } from 'hono/bun' +import type { ServerWebSocket } from 'bun' -const { upgradeWebSocket, websocket } = createBunWebSocket() +const { upgradeWebSocket, websocket } = + createBunWebSocket() // ... From 32fd22c631664a6c40eb5ff4027c8f7d914b8a9e Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 14:34:02 +0900 Subject: [PATCH 5/9] docs(cloudflare-pages): Accessing `EventContext` (#479) --- docs/getting-started/cloudflare-pages.md | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/getting-started/cloudflare-pages.md b/docs/getting-started/cloudflare-pages.md index 1dfa1d34..e19cd1be 100644 --- a/docs/getting-started/cloudflare-pages.md +++ b/docs/getting-started/cloudflare-pages.md @@ -361,3 +361,45 @@ export const onRequest = [ handleMiddleware(middleware3), ] ``` + +### Accessing `EventContext` + +You can access [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) object via `c.env` in `handleMiddleware`. + +```ts +// functions/_middleware.ts +import { handleMiddleware } from 'hono/cloudflare-pages' + +export const onRequest = [ + handleMiddleware(async (c, next) => { + c.env.eventContext.data.user = 'Joe' + await next() + }), +] +``` + +Then, you can access the data value in via `c.env.eventContext` in the handler: + +```ts +// functions/api/[[route]].ts +import type { EventContext } from 'hono/cloudflare-pages' +import { handle } from 'hono/cloudflare-pages' + +// ... + +type Env = { + Bindings: { + eventContext: EventContext + } +} + +const app = new Hono() + +app.get('/hello', (c) => { + return c.json({ + message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe' + }) +}) + +export const onRequest = handle(app) +``` From 6dab5809e435c3a0a2725f2c6fa2e9dcfa72c514 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 14:49:54 +0900 Subject: [PATCH 6/9] docs(context-storage): improve (#483) --- docs/middleware/builtin/context-storage.md | 38 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/middleware/builtin/context-storage.md b/docs/middleware/builtin/context-storage.md index 4645bc8c..b36a4d8f 100644 --- a/docs/middleware/builtin/context-storage.md +++ b/docs/middleware/builtin/context-storage.md @@ -1,6 +1,12 @@ # Context Storage Middleware -The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible. +The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible. + +::: info +**Note** This middleware uses `AsyncLocalStorage`. The runtime should support it. + +**Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file. +::: ## Import @@ -11,6 +17,8 @@ import { contextStorage, getContext } from 'hono/context-storage' ## Usage +The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware. + ```ts type Env = { Variables: { @@ -22,9 +30,35 @@ const app = new Hono() app.use(contextStorage()) -app.get('/', (c) => c.text(getMessage()) +app.use(async (c, next) => { + c.set('message', 'Hello!') + await next() +}) +// You can access the variable outside the handler. const getMessage = () => { return getContext().var.message } + +app.get('/', (c) => { + return c.text(getMessage()) +}) +``` + +On Cloudflare Workers, you can access the bindings outside the handler. + +```ts +type Env = { + Bindings: { + KV: KVNamespace + } +} + +const app = new Hono() + +app.use(contextStorage()) + +const setKV = (value: string) => { + return getContext().env.KV.put('key', value) +} ``` From 72c9639dd066dfecd0d9cc464e49f7fb7c48a97f Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 15:04:52 +0900 Subject: [PATCH 7/9] docs(serve-static/bun,deno): add `precompressed` (#484) --- docs/getting-started/bun.md | 13 +++++++++++++ docs/getting-started/deno.md | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/getting-started/bun.md b/docs/getting-started/bun.md index e4efc8d2..9779395b 100644 --- a/docs/getting-started/bun.md +++ b/docs/getting-started/bun.md @@ -150,6 +150,19 @@ app.get( ) ``` +### `precompressed` + +The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file. + +```ts +app.get( + '/static/*', + serveStatic({ + precompressed: true, + }) +) +``` + ## Testing You can use `bun:test` for testing on Bun. diff --git a/docs/getting-started/deno.md b/docs/getting-started/deno.md index a5cf602a..998ece9c 100644 --- a/docs/getting-started/deno.md +++ b/docs/getting-started/deno.md @@ -137,6 +137,19 @@ app.get( ) ``` +### `precompressed` + +The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file. + +```ts +app.get( + '/static/*', + serveStatic({ + precompressed: true, + }) +) +``` + ## Deno Deploy Deno Deploy is an edge runtime platform for Deno. From 8be179c7d5c42cb6a60a7a981eb34e9a3c04a882 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 15:09:52 +0900 Subject: [PATCH 8/9] docs(serve-static/deno,bun): add `onFound` (#485) --- docs/getting-started/bun.md | 16 ++++++++++++++++ docs/getting-started/deno.md | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/docs/getting-started/bun.md b/docs/getting-started/bun.md index 9779395b..760ce7c6 100644 --- a/docs/getting-started/bun.md +++ b/docs/getting-started/bun.md @@ -135,6 +135,22 @@ app.get( ) ``` +### `onFound` + +You can specify handling when the requested file is found with `onFound`: + +```ts +app.get( + '/static/*', + serveStatic({ + // ... + onFound: (_path, c) => { + c.header('Cache-Control', `public, immutable, max-age=31536000`) + }, + }) +) +``` + ### `onNotFound` You can specify handling when the requested file is not found with `onNotFound`: diff --git a/docs/getting-started/deno.md b/docs/getting-started/deno.md index 998ece9c..a098ae0a 100644 --- a/docs/getting-started/deno.md +++ b/docs/getting-started/deno.md @@ -122,6 +122,22 @@ app.get( ) ``` +### `onFound` + +You can specify handling when the requested file is found with `onFound`: + +```ts +app.get( + '/static/*', + serveStatic({ + // ... + onFound: (_path, c) => { + c.header('Cache-Control', `public, immutable, max-age=31536000`) + }, + }) +) +``` + ### `onNotFound` You can specify handling when the requested file is not found with `onNotFound`: From 6934488a5d8879bff10f347f8eb0dd89f1445b47 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Wed, 11 Sep 2024 15:19:18 +0900 Subject: [PATCH 9/9] docs(basic/bearer): add messages options (#486) --- docs/middleware/builtin/basic-auth.md | 4 ++++ docs/middleware/builtin/bearer-auth.md | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/middleware/builtin/basic-auth.md b/docs/middleware/builtin/basic-auth.md index d463c974..b1808060 100644 --- a/docs/middleware/builtin/basic-auth.md +++ b/docs/middleware/builtin/basic-auth.md @@ -87,6 +87,10 @@ A function to handle hashing for safe comparison of passwords. The function to verify the user. +### invalidUserMessage: `string | object | MessageFunction` + +`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if the user is invalid. + ## More Options ### ...users: `{ username: string, password: string }[]` diff --git a/docs/middleware/builtin/bearer-auth.md b/docs/middleware/builtin/bearer-auth.md index 9387c722..c1c82ab8 100644 --- a/docs/middleware/builtin/bearer-auth.md +++ b/docs/middleware/builtin/bearer-auth.md @@ -110,3 +110,15 @@ A function to handle hashing for safe comparison of authentication tokens. ### verifyToken: `(token: string, c: Context) => boolean | Promise` The function to verify the token. + +### noAuthenticationHeaderMessage: `string | object | MessageFunction` + +`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if it does not have an authentication header. + +### invalidAuthenticationHeaderMessage: `string | object | MessageFunction` + +The custom message if the authentication header is invalid. + +### invalidTokenMessage: `string | object | MessageFunction` + +The custom message if the token is invalid.