diff options
author | 2023-06-06 23:50:43 -0700 | |
---|---|---|
committer | 2023-06-06 23:50:43 -0700 | |
commit | d265ed80d20267bc5252c31022af36d139bc283e (patch) | |
tree | 479830e709995033bb9911005890029c37ec970e | |
parent | fa3cfd34cb1195f7068ff45576202accb74ff52e (diff) | |
download | bun-d265ed80d20267bc5252c31022af36d139bc283e.tar.gz bun-d265ed80d20267bc5252c31022af36d139bc283e.tar.zst bun-d265ed80d20267bc5252c31022af36d139bc283e.zip |
Docs for `Bun.password` and ws publish (#3227)
* Update websocket docs & jsdoc
* Document Bun.password
* Update hash encoding docs
* Fix typos
* Add info about user-specific data in ws
* Update outdated websocket jsdoc
* Replace usages of req.url
* Remove log
-rw-r--r-- | .github/workflows/bun-release-types-canary.yml | 2 | ||||
-rw-r--r-- | docs/api/hashing.md | 65 | ||||
-rw-r--r-- | docs/api/websockets.md | 51 | ||||
-rw-r--r-- | examples/openInEditor.js | 3 | ||||
-rw-r--r-- | packages/bun-types/bun.d.ts | 27 | ||||
-rw-r--r-- | packages/bun-types/tests/serve.test-d.ts | 3 |
6 files changed, 120 insertions, 31 deletions
diff --git a/.github/workflows/bun-release-types-canary.yml b/.github/workflows/bun-release-types-canary.yml index 861c8d454..d4bfca08c 100644 --- a/.github/workflows/bun-release-types-canary.yml +++ b/.github/workflows/bun-release-types-canary.yml @@ -1,4 +1,4 @@ -name: bun-release-canary +name: bun-release-types-canary concurrency: release-canary on: push: diff --git a/docs/api/hashing.md b/docs/api/hashing.md index 58bb05034..3c15e1346 100644 --- a/docs/api/hashing.md +++ b/docs/api/hashing.md @@ -4,6 +4,71 @@ Bun implements the `createHash` and `createHmac` functions from [`node:crypto`]( {% /callout %} +## `Bun.password` + +{% callout %} +**Note** — Added in Bun 0.6.8. +{% /callout %} + +`Bun.password` is a collection of utility functions for hashing and verifying passwords with various cryptographically secure algorithms. + +```ts +const password = "super-secure-pa$$word"; + +const hash = await Bun.password.hash(password); +// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4 + +const isMatch = await Bun.password.verify(password, hash); +// => true +``` + +The second argument to `Bun.password.hash` accepts a params object that lets you pick and configure the hashing algorithm. + +```ts +const password = "super-secure-pa$$word"; + +// use argon2 (default) +const argonHash = await Bun.password.hash(password, { + algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d" + memoryCost: 4, // memory usage in kibibytes + timeCost: 3, // the number of iterations +}); + +// use bcrypt +const bcryptHash = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 4, // number between 4-31 +}); +``` + +The algorithm used to create the hash is stored in the hash itself. When using `bcrypt`, the returned hash is encoded in [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) for compatibility with most existing `bcrypt` implementations; with `argon2` the result is encoded in the newer [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md). + +The `verify` function automatically detects the algorithm based on the input hash and use the correct verification method. It can correctly infer the algorithm from both PHC- or MCF-encoded hashes. + +```ts +const password = "super-secure-pa$$word"; + +const hash = await Bun.password.hash(password, { + /* config */ +}); + +const isMatch = await Bun.password.verify(password, hash); +// => true +``` + +Synchronous versions of all functions are also available. Keep in mind that these functions are computationally expensive, so using a blocking API may degrade application performance. + +```ts +const password = "super-secure-pa$$word"; + +const hash = Bun.password.hashSync(password, { + /* config */ +}); + +const isMatch = Bun.password.verifySync(password, hash); +// => true +``` + ## `Bun.hash` `Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security. diff --git a/docs/api/websockets.md b/docs/api/websockets.md index 0c40a05c0..d1e1d3831 100644 --- a/docs/api/websockets.md +++ b/docs/api/websockets.md @@ -20,7 +20,7 @@ To connect to an external socket server, create an instance of `WebSocket` with const socket = new WebSocket("ws://localhost:3000"); ``` -Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. +Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._ ```ts const socket = new WebSocket("ws://localhost:3000", { @@ -150,11 +150,13 @@ type WebSocketData = { // TypeScript: specify the type of `data` Bun.serve<WebSocketData>({ fetch(req, server) { + const cookies = parseCookies(req.headers.get("Cookie")); server.upgrade(req, { // TS: this object must conform to WebSocketData data: { createdAt: Date.now(), channelId: new URL(req.url).searchParams.get("channelId"), + authToken: cookies["X-Token"], }, }); @@ -173,42 +175,59 @@ Bun.serve<WebSocketData>({ }); ``` +To connect to this server from the browser, create a new `WebSocket`. + +```ts#browser.js +const socket = new WebSocket("ws://localhost:3000/chat"); + +socket.addEventListener("message", event => { + console.log(event.data); +}) +``` + +The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly. + ## Pub/Sub Bun's `ServerWebSocket` implementation implements a native publish-subscribe API for topic-based broadcasting. Individual sockets can `.subscribe()` to a topic (specified with a string identifier) and `.publish()` messages to all other subscribers to that topic. This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub). ```ts -const pubsubserver = Bun.serve<{username: string}>({ +const server = Bun.serve<{ username: string }>({ fetch(req, server) { - if (req.url === '/chat') { - const cookies = getCookieFromRequest(req); - const success = server.upgrade(req, { - data: {username: cookies.username}, - }); - return success - ? undefined - : new Response('WebSocket upgrade error', {status: 400}); + const url = new URL(req.url); + if (url.pathname === "/chat") { + console.log(`upgrade!`); + const username = getUsernameFromReq(req); + const success = server.upgrade(req, { data: { username } }); + return success ? undefined : new Response("WebSocket upgrade error", { status: 400 }); } - return new Response('Hello world'); + return new Response("Hello world"); }, websocket: { open(ws) { - ws.subscribe('the-group-chat'); - ws.publish('the-group-chat', `${ws.data.username} has entered the chat`); + const msg = `${ws.data.username} has entered the chat`; + ws.subscribe("the-group-chat"); + ws.publish("the-group-chat", msg); }, message(ws, message) { // this is a group chat // so the server re-broadcasts incoming message to everyone - ws.publish('the-group-chat', `${ws.data.username}: ${message}`); + ws.publish("the-group-chat", `${ws.data.username}: ${message}`); }, close(ws) { - ws.unsubscribe('the-group-chat'); - ws.publish('the-group-chat', `${ws.data.username} has left the chat`); + const msg = `${ws.data.username} has left the chat`; + ws.unsubscribe("the-group-chat"); + ws.publish("the-group-chat", msg); }, + }, }); + +console.log(`Listening on ${server.hostname}:${server.port}`); ``` +Calling `.publish(data)` will send the message to all subscribers of a topic (excluding the socket that called `.publish()`). + ## Compression Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter. diff --git a/examples/openInEditor.js b/examples/openInEditor.js index 3c67f02c2..30992d958 100644 --- a/examples/openInEditor.js +++ b/examples/openInEditor.js @@ -3,7 +3,8 @@ import { parse } from "querystring"; export default { fetch(req) { - if (req.url === "/favicon.ico") return new Response("nooo dont open favicon in editor", { status: 404 }); + const url = new URL(req.url); + if (url.pathname === "/favicon.ico") return new Response("nooo dont open favicon in editor", { status: 404 }); var pathname = req.url.substring(1); const q = pathname.indexOf("?"); diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 8e6664aed..d7d9ffd09 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1599,9 +1599,9 @@ declare module "bun" { * ```ts * import { websocket, serve } from "bun"; * - * serve({ + * serve<{name: string}>({ * port: 3000, - * websocket: websocket<{name: string}>({ + * websocket: { * open: (ws) => { * console.log("Client connected"); * }, @@ -1611,10 +1611,11 @@ declare module "bun" { * close: (ws) => { * console.log("Client disconnected"); * }, - * }), + * }, * * fetch(req, server) { - * if (req.url === "/chat") { + * const url = new URL(req.url); + * if (url.pathname === "/chat") { * const upgraded = server.upgrade(req, { * data: { * name: new URL(req.url).searchParams.get("name"), @@ -1829,9 +1830,9 @@ declare module "bun" { * * @example * ```js - *import { serve, websocket } from "bun"; + *import { serve } from "bun"; *serve({ - * websocket: websocket({ + * websocket: { * open: (ws) => { * console.log("Client connected"); * }, @@ -1841,9 +1842,10 @@ declare module "bun" { * close: (ws) => { * console.log("Client disconnected"); * }, - * }), + * }, * fetch(req, server) { - * if (req.url === "/chat") { + * const url = new URL(req.url); + * if (url.pathname === "/chat") { * const upgraded = server.upgrade(req); * if (!upgraded) { * return new Response("Upgrade failed", { status: 400 }); @@ -2059,9 +2061,9 @@ declare module "bun" { * * @example * ```js - * import { serve, websocket } from "bun"; + * import { serve } from "bun"; * serve({ - * websocket: websocket({ + * websocket: { * open: (ws) => { * console.log("Client connected"); * }, @@ -2071,9 +2073,10 @@ declare module "bun" { * close: (ws) => { * console.log("Client disconnected"); * }, - * }), + * }, * fetch(req, server) { - * if (req.url === "/chat") { + * const url = new URL(req.url); + * if (url.pathname === "/chat") { * const upgraded = server.upgrade(req); * if (!upgraded) { * return new Response("Upgrade failed", { status: 400 }); diff --git a/packages/bun-types/tests/serve.test-d.ts b/packages/bun-types/tests/serve.test-d.ts index 67a450f7e..2477433dc 100644 --- a/packages/bun-types/tests/serve.test-d.ts +++ b/packages/bun-types/tests/serve.test-d.ts @@ -39,7 +39,8 @@ type User = { Bun.serve<User>({ fetch(req, server) { - if (req.url === "/chat") { + const url = new URL(req.url); + if (url.pathname === "/chat") { if ( server.upgrade(req, { data: { |