@atproto/lex-server 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atproto/lex-server
2
2
 
3
+ ## 0.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`d8b2374`](https://github.com/bluesky-social/atproto/commit/d8b2374e1592d1dec65a33439791bc141f02397a)]:
8
+ - @atproto/lex-client@0.0.22
9
+
10
+ ## 0.0.18
11
+
12
+ ### Patch Changes
13
+
14
+ - [#4892](https://github.com/bluesky-social/atproto/pull/4892) [`6ccfa49`](https://github.com/bluesky-social/atproto/commit/6ccfa49cdf3f995dd6f30f594d284aa0fe395e61) Thanks [@essential-randomness](https://github.com/essential-randomness)! - Fix `lxm` service token validation
15
+
16
+ - Updated dependencies [[`2fd8d62`](https://github.com/bluesky-social/atproto/commit/2fd8d62708dc23de6ed21cbcccfebab68b19f588), [`907edfa`](https://github.com/bluesky-social/atproto/commit/907edfa1d16b1074bab4dc617d0bd1a810f3da02)]:
17
+ - @atproto/lex-schema@0.0.20
18
+ - @atproto/lex-client@0.0.21
19
+
3
20
  ## 0.0.17
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -524,7 +524,7 @@ import { toRequestListener } from '@atproto/lex-server/nodejs'
524
524
  const app = express()
525
525
 
526
526
  // Mount the XRPC router
527
- app.use('/xrpc', toRequestListener(router.fetch))
527
+ app.use(toRequestListener(router.fetch))
528
528
 
529
529
  app.listen(3000)
530
530
  ```
package/dist/nodejs.d.ts CHANGED
@@ -130,7 +130,7 @@ export interface HandlerObject {
130
130
  * const app = express()
131
131
  *
132
132
  * // Mount the XRPC router
133
- * app.use('/xrpc', toRequestListener(router.fetch))
133
+ * app.use(toRequestListener(router.fetch))
134
134
  * ```
135
135
  */
136
136
  export declare function toRequestListener<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof ServerResponse<InstanceType<Request>> = typeof ServerResponse>(fetchHandler: FetchHandler): (req: InstanceType<Request>, res: InstanceType<Response> & {
package/dist/nodejs.js CHANGED
@@ -291,7 +291,7 @@ function toConnectionInfo(req) {
291
291
  * const app = express()
292
292
  *
293
293
  * // Mount the XRPC router
294
- * app.use('/xrpc', toRequestListener(router.fetch))
294
+ * app.use(toRequestListener(router.fetch))
295
295
  * ```
296
296
  */
297
297
  function toRequestListener(fetchHandler) {
@@ -1 +1 @@
1
- {"version":3,"file":"nodejs.js","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events'\nimport {\n IncomingHttpHeaders,\n IncomingMessage,\n RequestListener,\n Server as HttpServer,\n ServerOptions,\n ServerResponse,\n createServer as createHttpServer,\n} from 'node:http'\nimport { ListenOptions } from 'node:net'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport type { ReadableStream as NodeReadableStream } from 'node:stream/web'\nimport { createHttpTerminator } from 'http-terminator'\nimport { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'\nimport type { FetchHandler } from './lex-router.js'\n\n// @ts-expect-error\nSymbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose')\n\nconst kResponseWs = Symbol.for('@atproto/lex-server:WebSocket')\n\nfunction isUpgradeRequest(request: Request, upgrade: string): boolean {\n return (\n request.method === 'GET' &&\n request.headers.get('connection')?.toLowerCase() === 'upgrade' &&\n request.headers.get('upgrade')?.toLowerCase() === upgrade\n )\n}\n\n/**\n * Upgrades an HTTP request to a WebSocket connection for Node.js.\n *\n * This function must be passed to the {@link LexRouter} constructor to enable\n * subscription (WebSocket) support on Node.js. It creates a WebSocket instance\n * and a placeholder response that signals the need for protocol upgrade.\n *\n * The actual upgrade is handled internally when the response is sent through\n * {@link sendResponse}.\n *\n * @param request - The incoming HTTP request to upgrade\n * @returns An object containing the WebSocket and upgrade response\n * @throws {TypeError} If the request is not a valid WebSocket upgrade request\n *\n * @example\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * // Pass to router for subscription support\n * const router = new LexRouter({ upgradeWebSocket })\n *\n * // Now you can add subscription handlers\n * router.add(subscribeRepos, async function* (ctx) {\n * for await (const event of eventStream) {\n * yield event\n * }\n * })\n * ```\n */\nexport function upgradeWebSocket(request: Request): {\n response: Response\n socket: WebSocket\n} {\n if (!isUpgradeRequest(request, 'websocket')) {\n throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade')\n }\n\n // Placeholder response for WebSocket upgrade. The actual handling will happen\n // through the handleWebSocketUpgrade function. Headers set on the response\n // will be applied during the upgrade.\n const response = new Response(null, { status: 200 })\n\n // The Response constructor does not allow setting status 101, so we\n // define it directly. The purpose of this response is just to signal\n // that an upgrade is needed, and to carry any headers.\n Object.defineProperty(response, 'status', {\n value: 101,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n // @ts-expect-error\n const socket: WebSocket = new WebSocketPonyfill(null, undefined, {\n autoPong: true,\n })\n\n // Attach the WebSocket to the response for later retrieval\n Object.defineProperty(response, kResponseWs, {\n value: socket,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return { response, socket }\n}\n\nconst kUpgradeEvent = Symbol.for('@atproto/lex-server:upgrade')\n\nfunction handleWebSocketUpgrade(\n req: IncomingMessage,\n response: Response,\n): void {\n const ws = (response as { [kResponseWs]?: WebSocketPonyfill })[kResponseWs]\n if (!ws) throw new TypeError('Response not created by upgradeWebSocket()')\n\n // Create a one time use WebSocketServer to handle the upgrade\n const wss = new WebSocketServer({\n autoPong: true,\n noServer: true,\n clientTracking: false,\n perMessageDeflate: true,\n // @ts-expect-error\n WebSocket: function () {\n // Return the websocket that was created earlier instead of a new instance\n return ws\n },\n })\n\n // Apply headers that might have been set on the response object during\n // handling. This will be called during wss.handleUpgrade().\n wss.on('headers', (headers) => {\n for (const [name, value] of response.headers) {\n headers.push(`${name}: ${value}`)\n }\n })\n\n wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {\n // @TODO find a way to properly \"close\" the _socket when the server is\n // shutting down (might require replacing http-terminator with a local\n // implementation)\n\n req.emit(kUpgradeEvent, ws)\n })\n}\n\n/**\n * Sends a fetch API Response through a Node.js ServerResponse.\n *\n * Handles both regular HTTP responses and WebSocket upgrades. For WebSocket\n * upgrades (status 101), delegates to the WebSocket upgrade handler.\n *\n * This function is used internally by {@link toRequestListener} and\n * {@link createServer}, but can be used directly for custom integrations.\n *\n * @param req - The Node.js IncomingMessage\n * @param res - The Node.js ServerResponse to write to\n * @param response - The fetch API Response to send\n * @throws {TypeError} If headers have already been sent\n *\n * @example\n * ```typescript\n * import http from 'node:http'\n * import { sendResponse } from '@atproto/lex-server/nodejs'\n *\n * const server = http.createServer(async (req, res) => {\n * const response = new Response('Hello, World!', {\n * headers: { 'Content-Type': 'text/plain' }\n * })\n * await sendResponse(req, res, response)\n * })\n * ```\n */\nexport async function sendResponse(\n req: IncomingMessage,\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n // Invalid usage\n if (res.headersSent) {\n throw new TypeError('Response has already been sent')\n }\n\n if (response.status === 101) {\n return handleWebSocketUpgrade(req, response)\n }\n\n res.statusCode = response.status\n res.statusMessage = response.statusText\n\n for (const [key, value] of response.headers) {\n res.setHeader(key, value)\n }\n\n if (response.body != null && req.method !== 'HEAD') {\n const stream = Readable.fromWeb(response.body as NodeReadableStream)\n await pipeline(stream, res)\n } else {\n await response.body?.cancel()\n res.end()\n }\n}\n\nfunction toRequest(req: IncomingMessage): Request {\n const host = req.headers.host ?? req.socket.localAddress ?? 'localhost'\n const isEncrypted = (req.socket as any).encrypted === true\n const protocol = isEncrypted ? 'https' : 'http'\n const url = new URL(req.url ?? '/', `${protocol}://${host}`)\n const headers = toHeaders(req.headers)\n const body = toBody(req)\n const signal = requestSignal(req)\n\n return new Request(url, {\n signal,\n method: req.method,\n headers,\n body,\n referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,\n redirect: 'manual',\n // @ts-expect-error\n duplex: body ? 'half' : undefined,\n })\n}\n\nfunction requestSignal(req: IncomingMessage): AbortSignal {\n if (req.destroyed) return AbortSignal.abort()\n\n const abortController = new AbortController()\n\n const abort = (err?: Error | WebSocket) => {\n abortController.abort(err instanceof Error ? err : undefined)\n\n req.off('close', abort)\n req.off('error', abort)\n req.off('end', abort)\n req.off(kUpgradeEvent, abort)\n }\n\n req.on('close', abort)\n req.on('error', abort)\n req.on('end', abort)\n req.on(kUpgradeEvent, abort)\n\n return abortController.signal\n}\n\nfunction requestCompletion(req: IncomingMessage): Promise<void> {\n if (req.destroyed) return Promise.resolve()\n\n // Unlike the abort signal, we complete the promise only when the request\n // is fully done, accounting for websocket upgrade.\n return new Promise((resolve) => {\n const cleanup = () => {\n req.off('close', done)\n req.off('error', done)\n req.off('end', done)\n req.off(kUpgradeEvent, onUpgrade)\n }\n\n const onUpgrade = (ws: WebSocket) => {\n cleanup()\n ws.addEventListener('close', () => resolve())\n }\n\n const done = () => {\n resolve()\n cleanup()\n }\n\n req.on('close', done)\n req.on('error', done)\n req.on('end', done)\n req.on(kUpgradeEvent, onUpgrade)\n })\n}\n\nfunction toHeaders(headers: IncomingHttpHeaders): Headers {\n const result = new Headers()\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n for (const v of value) result.append(key, v)\n } else {\n result.set(key, value)\n }\n }\n return result\n}\n\nfunction toBody(req: IncomingMessage): null | ReadableStream {\n if (\n req.method === 'GET' ||\n req.method === 'HEAD' ||\n req.method === 'OPTIONS'\n ) {\n return null\n }\n\n if (\n req.headers['content-type'] == null &&\n req.headers['transfer-encoding'] == null &&\n req.headers['content-length'] == null\n ) {\n return null\n }\n\n return Readable.toWeb(req) as ReadableStream\n}\n\n/**\n * Network address type for Node.js TCP connections.\n *\n * @example\n * ```typescript\n * const addr: NetAddr = {\n * transport: 'tcp',\n * hostname: '192.168.1.100',\n * port: 54321\n * }\n * ```\n */\nexport type NetAddr = {\n /** Always 'tcp' for Node.js HTTP connections. */\n transport: 'tcp'\n /** The IP address of the remote client. */\n hostname: string\n /** The port number of the remote client. */\n port: number\n}\n\n/**\n * Connection metadata for Node.js HTTP requests.\n *\n * Provides information about the client connection, including the remote\n * address and a promise that resolves when the connection is fully closed\n * (including WebSocket connections).\n */\nexport type NodeConnectionInfo = {\n /** Promise that resolves when the connection is fully closed. */\n completed: Promise<void>\n /** The remote address of the client, if available. */\n remoteAddr: NetAddr | undefined\n}\n\n/**\n * Interface for objects that can handle fetch-style requests.\n *\n * Used by {@link createServer} and {@link serve} to accept either\n * a fetch handler function or an object with a `fetch` method\n * (like {@link LexRouter}).\n */\nexport interface HandlerObject {\n /** The fetch handler method. */\n fetch: FetchHandler\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n fetchHandler: FetchHandler,\n) {\n const request = toRequest(req)\n const info = toConnectionInfo(req)\n const response = await fetchHandler(request, info)\n await sendResponse(req, res, response)\n}\n\nfunction toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {\n const { socket } = req\n\n return {\n completed: requestCompletion(req),\n remoteAddr:\n socket.remoteAddress != null\n ? {\n transport: 'tcp',\n hostname: socket.remoteAddress,\n port: socket.remotePort!,\n }\n : undefined,\n }\n}\n\n/**\n * Converts a fetch-style handler to a Node.js request listener.\n *\n * The returned listener can be used with Node.js HTTP servers directly,\n * or as middleware in frameworks like Express (supports the `next` callback).\n *\n * @typeParam Request - The request class type (default: IncomingMessage)\n * @typeParam Response - The response class type (default: ServerResponse)\n * @param fetchHandler - The fetch-style handler function\n * @returns A Node.js RequestListener compatible with http.createServer\n *\n * @example Using as Express middleware\n * ```typescript\n * import express from 'express'\n * import { toRequestListener } from '@atproto/lex-server/nodejs'\n * import { LexRouter } from '@atproto/lex-server'\n *\n * const router = new LexRouter()\n * // Register handlers...\n *\n * const app = express()\n *\n * // Mount the XRPC router\n * app.use('/xrpc', toRequestListener(router.fetch))\n * ```\n */\nexport function toRequestListener<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(fetchHandler: FetchHandler) {\n return ((\n req: InstanceType<Request>,\n res: InstanceType<Response> & { req: InstanceType<Request> },\n next?: (err?: unknown) => void,\n ): void => {\n handleRequest(req, res, fetchHandler).catch((err) => {\n if (next) next(err)\n else {\n if (!res.headersSent) {\n res.statusCode = 500\n res.setHeader('content-type', 'text/plain; charset=utf-8')\n res.end('Internal Server Error')\n } else if (!res.writableEnded) {\n res.destroy()\n }\n }\n })\n }) satisfies RequestListener<Request, Response>\n}\n\n/**\n * Options for creating an XRPC server.\n *\n * Extends Node.js {@link ServerOptions} with additional options for graceful shutdown.\n */\nexport type CreateServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ServerOptions<Request, Response> & {\n /**\n * Timeout in milliseconds for graceful termination.\n *\n * When `terminate()` is called, the server will wait up to this duration\n * for active connections to complete before forcibly closing them.\n */\n gracefulTerminationTimeout?: number\n}\n\n/**\n * Extended HTTP server with graceful shutdown support.\n *\n * Extends the standard Node.js HttpServer with a `terminate()` method\n * for graceful shutdown and implements `AsyncDisposable` for use with\n * `await using`.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n *\n * @example Graceful shutdown\n * ```typescript\n * const server = createServer(router)\n * server.listen(3000)\n *\n * process.on('SIGTERM', async () => {\n * console.log('Shutting down...')\n * await server.terminate()\n * console.log('Server stopped')\n * })\n * ```\n *\n * @example Using with await using\n * ```typescript\n * await using server = await serve(router, { port: 3000 })\n * // Server will be automatically terminated when scope exits\n * ```\n */\nexport interface Server<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> extends HttpServer<Request, Response>,\n AsyncDisposable {\n /**\n * Gracefully terminates the server.\n *\n * Stops accepting new connections and waits for active connections\n * to complete (up to `gracefulTerminationTimeout`).\n *\n * @returns Promise that resolves when the server is fully stopped\n */\n terminate(): Promise<void>\n [Symbol.asyncDispose](): Promise<void>\n}\n\n/**\n * Creates an HTTP server configured for XRPC request handling.\n *\n * The server includes graceful shutdown support and can be used with\n * either a fetch handler function or an object with a `fetch` method\n * (like {@link LexRouter}).\n *\n * Note: This creates the server but does not start listening. Call\n * `server.listen()` to start the server, or use {@link serve} for\n * a combined create-and-listen operation.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n * @param handler - A fetch handler or object with fetch method\n * @param options - Server configuration options\n * @returns An HTTP server with graceful shutdown support\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { createServer, upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n * router.add(myMethod, myHandler)\n *\n * const server = createServer(router)\n * server.listen(3000, () => {\n * console.log('Server listening on port 3000')\n * })\n * ```\n *\n * @example With graceful termination timeout\n * ```typescript\n * const server = createServer(router, {\n * gracefulTerminationTimeout: 10000 // 10 seconds\n * })\n * ```\n */\nexport function createServer<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: FetchHandler | HandlerObject,\n options: CreateServerOptions<Request, Response> = {},\n): Server<Request, Response> {\n const fetchHandler =\n typeof handler === 'function' ? handler : handler.fetch.bind(handler)\n\n const listener = toRequestListener(fetchHandler)\n const server = createHttpServer(options, listener)\n\n const terminator = createHttpTerminator({\n server: server as HttpServer,\n gracefulTerminationTimeout: options?.gracefulTerminationTimeout,\n })\n\n const terminate = async function terminate(this: Server<Request, Response>) {\n if (this !== server) {\n throw new TypeError('Server.terminate called with incorrect context')\n }\n // @TODO properly close all active WebSocket connections\n return terminator.terminate()\n }\n\n Object.defineProperty(server, 'terminate', {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n Object.defineProperty(server, Symbol.asyncDispose, {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return server as Server<Request, Response>\n}\n\n/**\n * Combined options for creating and starting an XRPC server.\n *\n * Includes both server creation options and network listen options.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n *\n * @example\n * ```typescript\n * const options: StartServerOptions = {\n * port: 3000,\n * host: '0.0.0.0',\n * gracefulTerminationTimeout: 10000\n * }\n * ```\n */\nexport type StartServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ListenOptions & CreateServerOptions<Request, Response>\n\n/**\n * Creates and starts an HTTP server, returning when it's ready to accept connections.\n *\n * This is a convenience function that combines {@link createServer} and `server.listen()`\n * into a single async operation. The returned promise resolves once the server\n * is actively listening.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n * @param handler - A fetch handler or object with fetch method (like {@link LexRouter})\n * @param options - Combined server and listen options\n * @returns Promise resolving to the running server\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n *\n * // Register handlers\n * router.add(getProfile, async (ctx) => {\n * return { body: await db.getProfile(ctx.params.actor) }\n * })\n *\n * // Start server on port 3000\n * const server = await serve(router, { port: 3000 })\n * console.log('Server listening on port 3000')\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => server.terminate())\n * process.on('SIGINT', () => server.terminate())\n * ```\n *\n * @example With all options\n * ```typescript\n * const server = await serve(router, {\n * port: 3000,\n * host: '0.0.0.0',\n * gracefulTerminationTimeout: 15000,\n * })\n * ```\n *\n * @example Using with await using (auto-cleanup)\n * ```typescript\n * async function main() {\n * await using server = await serve(router, { port: 3000 })\n *\n * // Server is running...\n * console.log('Server listening on port 3000')\n *\n * // Wait for termination signal\n * await Promise.race([\n * once(process, 'SIGINT'),\n * once(process, 'SIGTERM'),\n * ])\n *\n * // Server will be automatically terminated when scope exits\n * }\n * ```\n */\nexport async function serve<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: FetchHandler | HandlerObject,\n options?: StartServerOptions<Request, Response>,\n): Promise<Server<Request, Response>> {\n const server = createServer(handler, options)\n server.listen(options)\n await once(server, 'listening')\n return server\n}\n"]}
1
+ {"version":3,"file":"nodejs.js","sourceRoot":"","sources":["../src/nodejs.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events'\nimport {\n IncomingHttpHeaders,\n IncomingMessage,\n RequestListener,\n Server as HttpServer,\n ServerOptions,\n ServerResponse,\n createServer as createHttpServer,\n} from 'node:http'\nimport { ListenOptions } from 'node:net'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport type { ReadableStream as NodeReadableStream } from 'node:stream/web'\nimport { createHttpTerminator } from 'http-terminator'\nimport { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'\nimport type { FetchHandler } from './lex-router.js'\n\n// @ts-expect-error\nSymbol.asyncDispose ??= Symbol.for('Symbol.asyncDispose')\n\nconst kResponseWs = Symbol.for('@atproto/lex-server:WebSocket')\n\nfunction isUpgradeRequest(request: Request, upgrade: string): boolean {\n return (\n request.method === 'GET' &&\n request.headers.get('connection')?.toLowerCase() === 'upgrade' &&\n request.headers.get('upgrade')?.toLowerCase() === upgrade\n )\n}\n\n/**\n * Upgrades an HTTP request to a WebSocket connection for Node.js.\n *\n * This function must be passed to the {@link LexRouter} constructor to enable\n * subscription (WebSocket) support on Node.js. It creates a WebSocket instance\n * and a placeholder response that signals the need for protocol upgrade.\n *\n * The actual upgrade is handled internally when the response is sent through\n * {@link sendResponse}.\n *\n * @param request - The incoming HTTP request to upgrade\n * @returns An object containing the WebSocket and upgrade response\n * @throws {TypeError} If the request is not a valid WebSocket upgrade request\n *\n * @example\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * // Pass to router for subscription support\n * const router = new LexRouter({ upgradeWebSocket })\n *\n * // Now you can add subscription handlers\n * router.add(subscribeRepos, async function* (ctx) {\n * for await (const event of eventStream) {\n * yield event\n * }\n * })\n * ```\n */\nexport function upgradeWebSocket(request: Request): {\n response: Response\n socket: WebSocket\n} {\n if (!isUpgradeRequest(request, 'websocket')) {\n throw new TypeError('upgradeWebSocket() expects a WebSocket upgrade')\n }\n\n // Placeholder response for WebSocket upgrade. The actual handling will happen\n // through the handleWebSocketUpgrade function. Headers set on the response\n // will be applied during the upgrade.\n const response = new Response(null, { status: 200 })\n\n // The Response constructor does not allow setting status 101, so we\n // define it directly. The purpose of this response is just to signal\n // that an upgrade is needed, and to carry any headers.\n Object.defineProperty(response, 'status', {\n value: 101,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n // @ts-expect-error\n const socket: WebSocket = new WebSocketPonyfill(null, undefined, {\n autoPong: true,\n })\n\n // Attach the WebSocket to the response for later retrieval\n Object.defineProperty(response, kResponseWs, {\n value: socket,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return { response, socket }\n}\n\nconst kUpgradeEvent = Symbol.for('@atproto/lex-server:upgrade')\n\nfunction handleWebSocketUpgrade(\n req: IncomingMessage,\n response: Response,\n): void {\n const ws = (response as { [kResponseWs]?: WebSocketPonyfill })[kResponseWs]\n if (!ws) throw new TypeError('Response not created by upgradeWebSocket()')\n\n // Create a one time use WebSocketServer to handle the upgrade\n const wss = new WebSocketServer({\n autoPong: true,\n noServer: true,\n clientTracking: false,\n perMessageDeflate: true,\n // @ts-expect-error\n WebSocket: function () {\n // Return the websocket that was created earlier instead of a new instance\n return ws\n },\n })\n\n // Apply headers that might have been set on the response object during\n // handling. This will be called during wss.handleUpgrade().\n wss.on('headers', (headers) => {\n for (const [name, value] of response.headers) {\n headers.push(`${name}: ${value}`)\n }\n })\n\n wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (_socket) => {\n // @TODO find a way to properly \"close\" the _socket when the server is\n // shutting down (might require replacing http-terminator with a local\n // implementation)\n\n req.emit(kUpgradeEvent, ws)\n })\n}\n\n/**\n * Sends a fetch API Response through a Node.js ServerResponse.\n *\n * Handles both regular HTTP responses and WebSocket upgrades. For WebSocket\n * upgrades (status 101), delegates to the WebSocket upgrade handler.\n *\n * This function is used internally by {@link toRequestListener} and\n * {@link createServer}, but can be used directly for custom integrations.\n *\n * @param req - The Node.js IncomingMessage\n * @param res - The Node.js ServerResponse to write to\n * @param response - The fetch API Response to send\n * @throws {TypeError} If headers have already been sent\n *\n * @example\n * ```typescript\n * import http from 'node:http'\n * import { sendResponse } from '@atproto/lex-server/nodejs'\n *\n * const server = http.createServer(async (req, res) => {\n * const response = new Response('Hello, World!', {\n * headers: { 'Content-Type': 'text/plain' }\n * })\n * await sendResponse(req, res, response)\n * })\n * ```\n */\nexport async function sendResponse(\n req: IncomingMessage,\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n // Invalid usage\n if (res.headersSent) {\n throw new TypeError('Response has already been sent')\n }\n\n if (response.status === 101) {\n return handleWebSocketUpgrade(req, response)\n }\n\n res.statusCode = response.status\n res.statusMessage = response.statusText\n\n for (const [key, value] of response.headers) {\n res.setHeader(key, value)\n }\n\n if (response.body != null && req.method !== 'HEAD') {\n const stream = Readable.fromWeb(response.body as NodeReadableStream)\n await pipeline(stream, res)\n } else {\n await response.body?.cancel()\n res.end()\n }\n}\n\nfunction toRequest(req: IncomingMessage): Request {\n const host = req.headers.host ?? req.socket.localAddress ?? 'localhost'\n const isEncrypted = (req.socket as any).encrypted === true\n const protocol = isEncrypted ? 'https' : 'http'\n const url = new URL(req.url ?? '/', `${protocol}://${host}`)\n const headers = toHeaders(req.headers)\n const body = toBody(req)\n const signal = requestSignal(req)\n\n return new Request(url, {\n signal,\n method: req.method,\n headers,\n body,\n referrer: headers.get('referrer') ?? headers.get('referer') ?? undefined,\n redirect: 'manual',\n // @ts-expect-error\n duplex: body ? 'half' : undefined,\n })\n}\n\nfunction requestSignal(req: IncomingMessage): AbortSignal {\n if (req.destroyed) return AbortSignal.abort()\n\n const abortController = new AbortController()\n\n const abort = (err?: Error | WebSocket) => {\n abortController.abort(err instanceof Error ? err : undefined)\n\n req.off('close', abort)\n req.off('error', abort)\n req.off('end', abort)\n req.off(kUpgradeEvent, abort)\n }\n\n req.on('close', abort)\n req.on('error', abort)\n req.on('end', abort)\n req.on(kUpgradeEvent, abort)\n\n return abortController.signal\n}\n\nfunction requestCompletion(req: IncomingMessage): Promise<void> {\n if (req.destroyed) return Promise.resolve()\n\n // Unlike the abort signal, we complete the promise only when the request\n // is fully done, accounting for websocket upgrade.\n return new Promise((resolve) => {\n const cleanup = () => {\n req.off('close', done)\n req.off('error', done)\n req.off('end', done)\n req.off(kUpgradeEvent, onUpgrade)\n }\n\n const onUpgrade = (ws: WebSocket) => {\n cleanup()\n ws.addEventListener('close', () => resolve())\n }\n\n const done = () => {\n resolve()\n cleanup()\n }\n\n req.on('close', done)\n req.on('error', done)\n req.on('end', done)\n req.on(kUpgradeEvent, onUpgrade)\n })\n}\n\nfunction toHeaders(headers: IncomingHttpHeaders): Headers {\n const result = new Headers()\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n for (const v of value) result.append(key, v)\n } else {\n result.set(key, value)\n }\n }\n return result\n}\n\nfunction toBody(req: IncomingMessage): null | ReadableStream {\n if (\n req.method === 'GET' ||\n req.method === 'HEAD' ||\n req.method === 'OPTIONS'\n ) {\n return null\n }\n\n if (\n req.headers['content-type'] == null &&\n req.headers['transfer-encoding'] == null &&\n req.headers['content-length'] == null\n ) {\n return null\n }\n\n return Readable.toWeb(req) as ReadableStream\n}\n\n/**\n * Network address type for Node.js TCP connections.\n *\n * @example\n * ```typescript\n * const addr: NetAddr = {\n * transport: 'tcp',\n * hostname: '192.168.1.100',\n * port: 54321\n * }\n * ```\n */\nexport type NetAddr = {\n /** Always 'tcp' for Node.js HTTP connections. */\n transport: 'tcp'\n /** The IP address of the remote client. */\n hostname: string\n /** The port number of the remote client. */\n port: number\n}\n\n/**\n * Connection metadata for Node.js HTTP requests.\n *\n * Provides information about the client connection, including the remote\n * address and a promise that resolves when the connection is fully closed\n * (including WebSocket connections).\n */\nexport type NodeConnectionInfo = {\n /** Promise that resolves when the connection is fully closed. */\n completed: Promise<void>\n /** The remote address of the client, if available. */\n remoteAddr: NetAddr | undefined\n}\n\n/**\n * Interface for objects that can handle fetch-style requests.\n *\n * Used by {@link createServer} and {@link serve} to accept either\n * a fetch handler function or an object with a `fetch` method\n * (like {@link LexRouter}).\n */\nexport interface HandlerObject {\n /** The fetch handler method. */\n fetch: FetchHandler\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n fetchHandler: FetchHandler,\n) {\n const request = toRequest(req)\n const info = toConnectionInfo(req)\n const response = await fetchHandler(request, info)\n await sendResponse(req, res, response)\n}\n\nfunction toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {\n const { socket } = req\n\n return {\n completed: requestCompletion(req),\n remoteAddr:\n socket.remoteAddress != null\n ? {\n transport: 'tcp',\n hostname: socket.remoteAddress,\n port: socket.remotePort!,\n }\n : undefined,\n }\n}\n\n/**\n * Converts a fetch-style handler to a Node.js request listener.\n *\n * The returned listener can be used with Node.js HTTP servers directly,\n * or as middleware in frameworks like Express (supports the `next` callback).\n *\n * @typeParam Request - The request class type (default: IncomingMessage)\n * @typeParam Response - The response class type (default: ServerResponse)\n * @param fetchHandler - The fetch-style handler function\n * @returns A Node.js RequestListener compatible with http.createServer\n *\n * @example Using as Express middleware\n * ```typescript\n * import express from 'express'\n * import { toRequestListener } from '@atproto/lex-server/nodejs'\n * import { LexRouter } from '@atproto/lex-server'\n *\n * const router = new LexRouter()\n * // Register handlers...\n *\n * const app = express()\n *\n * // Mount the XRPC router\n * app.use(toRequestListener(router.fetch))\n * ```\n */\nexport function toRequestListener<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(fetchHandler: FetchHandler) {\n return ((\n req: InstanceType<Request>,\n res: InstanceType<Response> & { req: InstanceType<Request> },\n next?: (err?: unknown) => void,\n ): void => {\n handleRequest(req, res, fetchHandler).catch((err) => {\n if (next) next(err)\n else {\n if (!res.headersSent) {\n res.statusCode = 500\n res.setHeader('content-type', 'text/plain; charset=utf-8')\n res.end('Internal Server Error')\n } else if (!res.writableEnded) {\n res.destroy()\n }\n }\n })\n }) satisfies RequestListener<Request, Response>\n}\n\n/**\n * Options for creating an XRPC server.\n *\n * Extends Node.js {@link ServerOptions} with additional options for graceful shutdown.\n */\nexport type CreateServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ServerOptions<Request, Response> & {\n /**\n * Timeout in milliseconds for graceful termination.\n *\n * When `terminate()` is called, the server will wait up to this duration\n * for active connections to complete before forcibly closing them.\n */\n gracefulTerminationTimeout?: number\n}\n\n/**\n * Extended HTTP server with graceful shutdown support.\n *\n * Extends the standard Node.js HttpServer with a `terminate()` method\n * for graceful shutdown and implements `AsyncDisposable` for use with\n * `await using`.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n *\n * @example Graceful shutdown\n * ```typescript\n * const server = createServer(router)\n * server.listen(3000)\n *\n * process.on('SIGTERM', async () => {\n * console.log('Shutting down...')\n * await server.terminate()\n * console.log('Server stopped')\n * })\n * ```\n *\n * @example Using with await using\n * ```typescript\n * await using server = await serve(router, { port: 3000 })\n * // Server will be automatically terminated when scope exits\n * ```\n */\nexport interface Server<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> extends HttpServer<Request, Response>,\n AsyncDisposable {\n /**\n * Gracefully terminates the server.\n *\n * Stops accepting new connections and waits for active connections\n * to complete (up to `gracefulTerminationTimeout`).\n *\n * @returns Promise that resolves when the server is fully stopped\n */\n terminate(): Promise<void>\n [Symbol.asyncDispose](): Promise<void>\n}\n\n/**\n * Creates an HTTP server configured for XRPC request handling.\n *\n * The server includes graceful shutdown support and can be used with\n * either a fetch handler function or an object with a `fetch` method\n * (like {@link LexRouter}).\n *\n * Note: This creates the server but does not start listening. Call\n * `server.listen()` to start the server, or use {@link serve} for\n * a combined create-and-listen operation.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n * @param handler - A fetch handler or object with fetch method\n * @param options - Server configuration options\n * @returns An HTTP server with graceful shutdown support\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { createServer, upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n * router.add(myMethod, myHandler)\n *\n * const server = createServer(router)\n * server.listen(3000, () => {\n * console.log('Server listening on port 3000')\n * })\n * ```\n *\n * @example With graceful termination timeout\n * ```typescript\n * const server = createServer(router, {\n * gracefulTerminationTimeout: 10000 // 10 seconds\n * })\n * ```\n */\nexport function createServer<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: FetchHandler | HandlerObject,\n options: CreateServerOptions<Request, Response> = {},\n): Server<Request, Response> {\n const fetchHandler =\n typeof handler === 'function' ? handler : handler.fetch.bind(handler)\n\n const listener = toRequestListener(fetchHandler)\n const server = createHttpServer(options, listener)\n\n const terminator = createHttpTerminator({\n server: server as HttpServer,\n gracefulTerminationTimeout: options?.gracefulTerminationTimeout,\n })\n\n const terminate = async function terminate(this: Server<Request, Response>) {\n if (this !== server) {\n throw new TypeError('Server.terminate called with incorrect context')\n }\n // @TODO properly close all active WebSocket connections\n return terminator.terminate()\n }\n\n Object.defineProperty(server, 'terminate', {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n Object.defineProperty(server, Symbol.asyncDispose, {\n value: terminate,\n enumerable: false,\n configurable: false,\n writable: false,\n })\n\n return server as Server<Request, Response>\n}\n\n/**\n * Combined options for creating and starting an XRPC server.\n *\n * Includes both server creation options and network listen options.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n *\n * @example\n * ```typescript\n * const options: StartServerOptions = {\n * port: 3000,\n * host: '0.0.0.0',\n * gracefulTerminationTimeout: 10000\n * }\n * ```\n */\nexport type StartServerOptions<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n> = ListenOptions & CreateServerOptions<Request, Response>\n\n/**\n * Creates and starts an HTTP server, returning when it's ready to accept connections.\n *\n * This is a convenience function that combines {@link createServer} and `server.listen()`\n * into a single async operation. The returned promise resolves once the server\n * is actively listening.\n *\n * @typeParam Request - The request class type\n * @typeParam Response - The response class type\n * @param handler - A fetch handler or object with fetch method (like {@link LexRouter})\n * @param options - Combined server and listen options\n * @returns Promise resolving to the running server\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter } from '@atproto/lex-server'\n * import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs'\n *\n * const router = new LexRouter({ upgradeWebSocket })\n *\n * // Register handlers\n * router.add(getProfile, async (ctx) => {\n * return { body: await db.getProfile(ctx.params.actor) }\n * })\n *\n * // Start server on port 3000\n * const server = await serve(router, { port: 3000 })\n * console.log('Server listening on port 3000')\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => server.terminate())\n * process.on('SIGINT', () => server.terminate())\n * ```\n *\n * @example With all options\n * ```typescript\n * const server = await serve(router, {\n * port: 3000,\n * host: '0.0.0.0',\n * gracefulTerminationTimeout: 15000,\n * })\n * ```\n *\n * @example Using with await using (auto-cleanup)\n * ```typescript\n * async function main() {\n * await using server = await serve(router, { port: 3000 })\n *\n * // Server is running...\n * console.log('Server listening on port 3000')\n *\n * // Wait for termination signal\n * await Promise.race([\n * once(process, 'SIGINT'),\n * once(process, 'SIGTERM'),\n * ])\n *\n * // Server will be automatically terminated when scope exits\n * }\n * ```\n */\nexport async function serve<\n Request extends typeof IncomingMessage = typeof IncomingMessage,\n Response extends typeof ServerResponse<\n InstanceType<Request>\n > = typeof ServerResponse,\n>(\n handler: FetchHandler | HandlerObject,\n options?: StartServerOptions<Request, Response>,\n): Promise<Server<Request, Response>> {\n const server = createServer(handler, options)\n server.listen(options)\n await once(server, 'listening')\n return server\n}\n"]}
@@ -182,7 +182,7 @@ async function parseJwt(token, options) {
182
182
  timeDiff(now, payload.iat) > options.maxAge) {
183
183
  throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'JWT token too old', { Bearer: { error: 'JwtTooOld' } });
184
184
  }
185
- if (payload.lxm != null && typeof payload.lxm !== options.lxm) {
185
+ if (payload.lxm != null && payload.lxm !== options.lxm) {
186
186
  throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT lexicon method ("lxm")', { Bearer: { error: 'BadJwtLexiconMethod' } });
187
187
  }
188
188
  if (payload.nonce != null && !(await (0, options.unique)(payload.nonce))) {
@@ -1 +1 @@
1
- {"version":3,"file":"service-auth.js","sourceRoot":"","sources":["../src/service-auth.ts"],"names":[],"mappings":";;AAoKA,kCA8DC;AAsQD,0CAaC;;AArfD,gEAAyC;AACzC,sCAKqB;AACrB,gDAA6E;AAC7E,oDAA4D;AAC5D,6DAGmC;AACnC,2CAAgD;AAGhD,MAAM,aAAa,GAAG,SAAS,CAAA;AAyG/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,SAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,MAAM,GAAG,CAAC,GAAG,EAAE,EACf,MAAM,EACN,GAAG,OAAO,EACS;IACnB,MAAM,WAAW,GAAG,IAAA,gCAAiB,EAAC,OAAO,CAAC,CAAA;IAE9C,OAAO,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QACnC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;QAC1B,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE;YACxC,GAAG,EAAE,MAAM,CAAC,IAAI;YAChB,MAAM;YACN,QAAQ;YACR,MAAM;SACP,CAAC,CAAA;QAEF,IAAI,WAAW,GAAuB,MAAM,WAAW;aACpD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC;aACpC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,gCAAgC,EAChC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,EAC5C,EAAE,KAAK,EAAE,CACV,CAAA;QACH,CAAC,CAAC,CAAA;QAEJ,MAAM,GAAG,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAA;QAE7C,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,cAAc,EAAE,CAAA;YAEvB,yDAAyD;YACzD,WAAW,GAAG,MAAM,WAAW;iBAC5B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;iBACnD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,gCAAgC,EAChC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,EAC5C,EAAE,KAAK,EAAE,CACV,CAAA;YACH,CAAC,CAAC,CAAA;YAEJ,kDAAkD;YAClD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAA;YAClD,IAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;gBACvE,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,uBAAuB,EACvB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,CACzC,CAAA;YACH,CAAC;QACH,CAAC;QAED,OAAO;YACL,GAAG,EAAE,WAAW,CAAC,EAAE;YACnB,WAAW;YACX,GAAG;SACJ,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,GAAc,EAAE,GAAe;IACtD,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,SAAS,EAAE;YACnE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG;YACtB,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,gCAAgC,EAChC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,EACxC,EAAE,KAAK,EAAE,CACV,CAAA;IACH,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAC3B,WAA+B;IAE/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,CAAC,kBAAkB,EAAE,IAAI,CAC9C,2BAA2B,EAC3B,WAAW,CACZ,CAAA;QAED,IAAI,GAAG,EAAE,kBAAkB,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,mCAAmC,EAAE,CAAC;gBACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBAChE,OAAO,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;YAC3D,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,mCAAmC,EAAE,CAAC;gBAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBAChE,OAAO,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAA;YAChE,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACnC,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBAC3D,OAAO,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,2BAA2B,CAIlC,EAAK;IAIL,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,IAAA,uBAAiB,EAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,CAAA;AAC/E,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAAgB,EAChB,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAC1D,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,uBAAuB,EACvB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,CACvC,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAE9D,OAAO,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAiDD,KAAK,UAAU,QAAQ,CACrB,KAAa,EACb,OAAwB;IAExB,MAAM,EACJ,MAAM,EACN,CAAC,EAAE,SAAS,EACZ,CAAC,EAAE,UAAU,EACb,CAAC,EAAE,YAAY,GAChB,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACpB,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAChC,CAAA;IACH,CAAC;IAED,IAAI,MAAoB,CAAA;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAC/B,EAAE,KAAK,EAAE,CACV,CAAA;IACH,CAAC;IAED,IACE,MAAM,CAAC,GAAG,KAAK,MAAM;QACrB,iDAAiD;QACjD,gDAAgD;QAChD,MAAM,CAAC,GAAG,KAAK,QAAQ;QACvB,qEAAqE;QACrE,MAAM,CAAC,GAAG,KAAK,aAAa;QAC5B,2DAA2D;QAC3D,gDAAgD;QAChD,MAAM,CAAC,GAAG,KAAK,UAAU,EACzB,CAAC;QACD,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAChC,CAAA;IACH,CAAC;IAED,IAAI,OAAsB,CAAA;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,cAAc,CAAC,UAAU,EAAE,eAAe,CAAC,CAAA;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAC/B,EAAE,KAAK,EAAE,CACV,CAAA;IACH,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;QAClE,MAAM,IAAI,8BAAkB,CAAC,wBAAwB,EAAE,kBAAkB,EAAE;YACzE,MAAM,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE;SACrC,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAEzC,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC7C,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,yBAAyB,EACzB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CACxC,CAAA;IACH,CAAC;IAED,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,CACpC,CAAA;IACH,CAAC;IAED,wDAAwD;IACxD,IACE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM;QAC3C,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAC3C,CAAC;QACD,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CACnC,CAAA;IACH,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;QAC9D,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,oCAAoC,EACpC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,CAC7C,CAAA;IACH,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,8BAAkB,CAC1B,wBAAwB,EACxB,6CAA6C,EAC7C,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CACxC,CAAA;IACH,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO;QACP,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,UAAU,EAAE,CAAC;QACzD,SAAS,EAAE,IAAA,qBAAU,EAAC,YAAY,EAAE,WAAW,CAAC;KACjD,CAAA;AACH,CAAC;AAED,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,WAAW,EAAE,CAAA;AAGnD,SAAS,cAAc,CAAC,GAAY;IAClC,OAAO,CACL,IAAA,wBAAa,EAAC,GAAG,CAAC;QAClB,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;QAC3B,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,CACvD,CAAA;AACH,CAAC;AAWD,SAAgB,eAAe,CAAC,GAAY;IAC1C,OAAO,CACL,IAAA,wBAAa,EAAC,GAAG,CAAC;QAClB,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;QAC3B,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;QAC3B,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC;QACtD,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC;QAC1D,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjD,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;QACtB,IAAA,wBAAW,EAAC,GAAG,CAAC,GAAG,CAAC;QACpB,IAAA,wBAAW,EAAC,GAAG,CAAC,GAAG,CAAC,CACrB,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,EAAU,EAAE,EAAW;IACvC,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;AAC1B,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAA;AAC1E,CAAC;AAED,SAAS,cAAc,CAAI,GAAW,EAAE,MAAkC;IACxE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,yBAAc,EAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAA;IACxD,IAAI,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC3B,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import * as crypto from '@atproto/crypto'\nimport {\n AtprotoDid,\n AtprotoDidDocument,\n Did,\n matchesIdentifier,\n} from '@atproto/did'\nimport { fromBase64, isPlainObject, utf8FromBase64 } from '@atproto/lex-data'\nimport { DidString, isDidString } from '@atproto/lex-schema'\nimport {\n CreateDidResolverOptions,\n createDidResolver,\n} from '@atproto-labs/did-resolver'\nimport { LexServerAuthError } from './errors.js'\nimport type { LexRouterAuth } from './lex-router.js'\n\nconst BEARER_PREFIX = 'Bearer '\n\n/**\n * Callback function to check and record nonce uniqueness.\n *\n * Used to prevent replay attacks by ensuring each nonce is only used once.\n * The implementation must track nonces for at least the `maxAge` duration\n * (default 5 minutes before and after the current time).\n *\n * @param nonce - The nonce string from the JWT token\n * @returns Promise resolving to `true` if the nonce is unique (first time seen),\n * `false` if it has been seen before\n *\n * @example\n * ```typescript\n * // Using Redis for nonce tracking\n * const checkNonce: UniqueNonceChecker = async (nonce) => {\n * const key = `nonce:${nonce}`\n * const result = await redis.setnx(key, '1')\n * if (result === 1) {\n * await redis.expire(key, 600) // 10 minutes TTL\n * return true\n * }\n * return false\n * }\n * ```\n */\nexport type UniqueNonceChecker = (nonce: string) => Promise<boolean>\n\n/**\n * Configuration options for AT Protocol service authentication.\n *\n * Service auth is used for server-to-server communication in the AT Protocol,\n * where one service authenticates to another using signed JWT tokens tied to\n * the caller's DID.\n *\n * @example\n * ```typescript\n * const options: ServiceAuthOptions = {\n * audience: 'did:web:api.example.com',\n * unique: async (nonce) => nonceStore.checkAndAdd(nonce),\n * maxAge: 300, // 5 minutes\n * // Optional DID resolver options\n * plcDirectoryUrl: 'https://plc.directory'\n * }\n * ```\n */\nexport type ServiceAuthOptions = CreateDidResolverOptions & {\n /**\n * Expected audience (\"aud\") claim in the JWT token.\n *\n * This should be the DID of your service. The token must include this\n * value in its `aud` claim to be accepted. Set to `null` to skip\n * audience verification (not recommended for production).\n */\n audience: null | DidString\n /**\n * Function to check and record nonce uniqueness.\n *\n * This is critical for preventing replay attacks. The value checked here\n * must be unique within `maxAge` seconds before and after the current time.\n *\n * @param nonce - The nonce to check\n * @returns Promise resolving to `true` if unique, `false` if seen before\n */\n unique: UniqueNonceChecker\n /**\n * Maximum age of the JWT token in seconds.\n *\n * Tokens with `iat` (issued at) or `exp` (expiry) timestamps outside\n * this window from the current time will be rejected.\n *\n * @default 300 (5 minutes)\n */\n maxAge?: number\n}\n\n/**\n * Credentials returned after successful service authentication.\n *\n * Contains the verified DID, resolved DID document, and parsed JWT token.\n * These are available in handler context as `ctx.credentials`.\n *\n * @example\n * ```typescript\n * router.add(protectedMethod, {\n * handler: async (ctx) => {\n * const { did, didDocument, jwt } = ctx.credentials\n * console.log('Request from:', did)\n * console.log('Token expires:', new Date(jwt.payload.exp * 1000))\n * return { body: { callerDid: did } }\n * },\n * auth: serviceAuth({ audience: myDid, unique: checkNonce })\n * })\n * ```\n */\nexport type ServiceAuthCredentials = {\n /** The verified AT Protocol DID of the caller. */\n did: AtprotoDid\n /** The resolved DID document of the caller. */\n didDocument: AtprotoDidDocument\n /** The parsed and validated JWT token. */\n jwt: ParsedJwt\n}\n\n/**\n * Creates an authentication handler for verifying AT Protocol service auth JWTs.\n *\n * Service auth is the standard authentication mechanism for server-to-server\n * communication in the AT Protocol. It uses JWT bearer tokens signed by the\n * caller's DID signing key, with the signature verified against the public\n * key in the caller's DID document.\n *\n * The handler performs the following validations:\n * - Extracts and parses the Bearer token from the Authorization header\n * - Validates JWT structure and claims (aud, exp, iat, lxm, nonce)\n * - Resolves the issuer's DID document\n * - Verifies the JWT signature against the `#atproto` verification method\n * - Checks nonce uniqueness to prevent replay attacks\n *\n * @param options - Configuration options for service auth\n * @returns An auth handler function for use with {@link LexRouter.add}\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter, serviceAuth } from '@atproto/lex-server'\n *\n * const router = new LexRouter()\n *\n * const auth = serviceAuth({\n * audience: 'did:web:api.example.com',\n * unique: async (nonce) => {\n * // Check if nonce has been seen, return true if unique\n * const isNew = await redis.setnx(`nonce:${nonce}`, '1')\n * if (isNew) await redis.expire(`nonce:${nonce}`, 600)\n * return isNew\n * }\n * })\n *\n * router.add(myMethod, {\n * handler: async (ctx) => {\n * console.log('Authenticated as:', ctx.credentials.did)\n * return { body: { success: true } }\n * },\n * auth\n * })\n * ```\n */\nexport function serviceAuth({\n audience,\n maxAge = 5 * 60,\n unique,\n ...options\n}: ServiceAuthOptions): LexRouterAuth<ServiceAuthCredentials> {\n const didResolver = createDidResolver(options)\n\n return async ({ request, method }) => {\n const { signal } = request\n const jwt = await parseJwtBearer(request, {\n lxm: method.nsid,\n maxAge,\n audience,\n unique,\n })\n\n let didDocument: AtprotoDidDocument = await didResolver\n .resolve(jwt.payload.iss, { signal })\n .catch((cause) => {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not resolve DID document',\n { Bearer: { error: 'DidResolutionFailed' } },\n { cause },\n )\n })\n\n const key = getAtprotoSigningKey(didDocument)\n\n if (!key || !(await verifyJwt(jwt, key))) {\n signal.throwIfAborted()\n\n // Try refreshing the DID document in case it was updated\n didDocument = await didResolver\n .resolve(jwt.payload.iss, { signal, noCache: true })\n .catch((cause) => {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not resolve DID document',\n { Bearer: { error: 'DidResolutionFailed' } },\n { cause },\n )\n })\n\n // Verify again with the fresh key (if it changed)\n const keyFresh = getAtprotoSigningKey(didDocument)\n if (!keyFresh || key === keyFresh || !(await verifyJwt(jwt, keyFresh))) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT signature',\n { Bearer: { error: 'BadJwtSignature' } },\n )\n }\n }\n\n return {\n did: didDocument.id,\n didDocument,\n jwt,\n }\n }\n}\n\nasync function verifyJwt(jwt: ParsedJwt, key: Did<'key'>) {\n try {\n return await crypto.verifySignature(key, jwt.message, jwt.signature, {\n jwtAlg: jwt.header.alg,\n allowMalleableSig: true,\n })\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not verify JWT signature',\n { Bearer: { error: 'BadJwtSignature' } },\n { cause },\n )\n }\n}\n\nfunction getAtprotoSigningKey(\n didDocument: AtprotoDidDocument,\n): null | Did<'key'> {\n try {\n const key = didDocument.verificationMethod?.find(\n isAtprotoVerificationMethod,\n didDocument,\n )\n\n if (key?.publicKeyMultibase) {\n if (key.type === 'EcdsaSecp256r1VerificationKey2019') {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)\n } else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)\n } else if (key.type === 'Multikey') {\n const parsed = crypto.parseMultikey(key.publicKeyMultibase)\n return crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)\n }\n }\n } catch {\n // Invalid key, ignore\n }\n\n return null\n}\n\nfunction isAtprotoVerificationMethod<\n V extends string | { id: string; type: string; publicKeyMultibase?: string },\n>(\n this: AtprotoDidDocument,\n vm: V,\n): vm is Exclude<V, string> & {\n id: `${string}#atproto`\n} {\n return typeof vm === 'object' && matchesIdentifier(this.id, 'atproto', vm.id)\n}\n\nasync function parseJwtBearer(\n request: Request,\n options: ParseJwtOptions,\n): Promise<ParsedJwt> {\n const authorization = request.headers.get('authorization')\n if (!authorization?.startsWith(BEARER_PREFIX)) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Bearer token required',\n { Bearer: { error: 'MissingBearer' } },\n )\n }\n\n const token = authorization.slice(BEARER_PREFIX.length).trim()\n\n return parseJwt(token, options)\n}\n\n/**\n * Options for parsing and validating a JWT token.\n */\nexport type ParseJwtOptions = {\n /** Maximum age in seconds for token validity window. */\n maxAge: number\n /** Expected audience claim, or null to skip audience verification. */\n audience: null | DidString\n /** Function to check nonce uniqueness. */\n unique: UniqueNonceChecker\n /** Expected lexicon method NSID for the `lxm` claim. */\n lxm: string\n}\n\n/**\n * A parsed and partially validated JWT token.\n *\n * Contains the decoded header and payload, along with the raw bytes\n * needed for signature verification.\n *\n * @example\n * ```typescript\n * const jwt: ParsedJwt = {\n * header: { alg: 'ES256K', typ: 'JWT' },\n * payload: {\n * iss: 'did:plc:abc123',\n * aud: 'did:web:api.example.com',\n * exp: 1704067200,\n * iat: 1704066900,\n * lxm: 'com.atproto.sync.getBlob'\n * },\n * message: new Uint8Array([...]),\n * signature: new Uint8Array([...])\n * }\n * ```\n */\nexport type ParsedJwt = {\n /** The decoded JWT header containing algorithm and type. */\n header: HeaderObject\n /** The decoded JWT payload containing claims. */\n payload: PayloadObject\n /** The raw header.payload bytes for signature verification. */\n message: Uint8Array\n /** The decoded signature bytes. */\n signature: Uint8Array\n}\n\nasync function parseJwt(\n token: string,\n options: ParseJwtOptions,\n): Promise<ParsedJwt> {\n const {\n length,\n 0: headerB64,\n 1: payloadB64,\n 2: signatureB64,\n } = token.split('.')\n if (length !== 3) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n )\n }\n\n let header: HeaderObject\n try {\n header = jsonFromBase64(headerB64, isHeaderObject)\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n { cause },\n )\n }\n\n if (\n header.alg === 'none' ||\n // service tokens are not OAuth 2.0 access tokens\n // https://datatracker.ietf.org/doc/html/rfc9068\n header.typ === 'at+jwt' ||\n // \"refresh+jwt\" is a non-standard type used by the @atproto packages\n header.typ === 'refresh+jwt' ||\n // \"DPoP\" proofs are not meant to be used as service tokens\n // https://datatracker.ietf.org/doc/html/rfc9449\n header.typ === 'dpop+jwt'\n ) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n )\n }\n\n let payload: PayloadObject\n try {\n payload = jsonFromBase64(payloadB64, isPayloadObject)\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n { cause },\n )\n }\n\n if (options.audience !== null && options.audience !== payload.aud) {\n throw new LexServerAuthError('AuthenticationRequired', 'Invalid audience', {\n Bearer: { error: 'InvalidAudience' },\n })\n }\n\n const now = Math.floor(Date.now() / 1000)\n\n if (payload.nbf != null && now < payload.nbf) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token not yet valid',\n { Bearer: { error: 'JwtNotYetValid' } },\n )\n }\n\n if (now > payload.exp) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token expired',\n { Bearer: { error: 'JwtExpired' } },\n )\n }\n\n // Prevent issuer from generating very long-lived tokens\n if (\n timeDiff(now, payload.exp) > options.maxAge ||\n timeDiff(now, payload.iat) > options.maxAge\n ) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token too old',\n { Bearer: { error: 'JwtTooOld' } },\n )\n }\n\n if (payload.lxm != null && typeof payload.lxm !== options.lxm) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT lexicon method (\"lxm\")',\n { Bearer: { error: 'BadJwtLexiconMethod' } },\n )\n }\n\n if (payload.nonce != null && !(await (0, options.unique)(payload.nonce))) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Replay attack detected: nonce is not unique',\n { Bearer: { error: 'NonceNotUnique' } },\n )\n }\n\n return {\n header,\n payload,\n message: textEncoder.encode(`${headerB64}.${payloadB64}`),\n signature: fromBase64(signatureB64, 'base64url'),\n }\n}\n\nconst textEncoder = /*#__PURE__*/ new TextEncoder()\n\ntype HeaderObject = { alg: string; typ?: string }\nfunction isHeaderObject(obj: unknown): obj is HeaderObject {\n return (\n isPlainObject(obj) &&\n typeof obj.alg === 'string' &&\n (obj.typ === undefined || typeof obj.typ === 'string')\n )\n}\n\ntype PayloadObject = {\n iss: DidString\n aud: DidString\n exp: number\n iat?: number\n nbf?: number\n lxm?: string\n nonce?: string\n}\nexport function isPayloadObject(obj: unknown): obj is PayloadObject {\n return (\n isPlainObject(obj) &&\n typeof obj.iss === 'string' &&\n typeof obj.aud === 'string' &&\n (obj.lxm === undefined || typeof obj.lxm === 'string') &&\n (obj.nonce === undefined || typeof obj.nonce === 'string') &&\n (obj.iat === undefined || isPositiveInt(obj.iat)) &&\n (obj.nbf === undefined || isPositiveInt(obj.nbf)) &&\n isPositiveInt(obj.exp) &&\n isDidString(obj.iss) &&\n isDidString(obj.aud)\n )\n}\n\nfunction timeDiff(t1: number, t2?: number): number {\n if (t2 === undefined) return 0\n return Math.abs(t1 - t2)\n}\n\nfunction isPositiveInt(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value > 0\n}\n\nfunction jsonFromBase64<T>(b64: string, isType: (obj: unknown) => obj is T): T {\n const obj = JSON.parse(utf8FromBase64(b64, 'base64url'))\n if (isType(obj)) return obj\n throw new Error('Invalid type')\n}\n"]}
1
+ {"version":3,"file":"service-auth.js","sourceRoot":"","sources":["../src/service-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import * as crypto from '@atproto/crypto'\nimport {\n AtprotoDid,\n AtprotoDidDocument,\n Did,\n matchesIdentifier,\n} from '@atproto/did'\nimport { fromBase64, isPlainObject, utf8FromBase64 } from '@atproto/lex-data'\nimport { DidString, isDidString } from '@atproto/lex-schema'\nimport {\n CreateDidResolverOptions,\n createDidResolver,\n} from '@atproto-labs/did-resolver'\nimport { LexServerAuthError } from './errors.js'\nimport type { LexRouterAuth } from './lex-router.js'\n\nconst BEARER_PREFIX = 'Bearer '\n\n/**\n * Callback function to check and record nonce uniqueness.\n *\n * Used to prevent replay attacks by ensuring each nonce is only used once.\n * The implementation must track nonces for at least the `maxAge` duration\n * (default 5 minutes before and after the current time).\n *\n * @param nonce - The nonce string from the JWT token\n * @returns Promise resolving to `true` if the nonce is unique (first time seen),\n * `false` if it has been seen before\n *\n * @example\n * ```typescript\n * // Using Redis for nonce tracking\n * const checkNonce: UniqueNonceChecker = async (nonce) => {\n * const key = `nonce:${nonce}`\n * const result = await redis.setnx(key, '1')\n * if (result === 1) {\n * await redis.expire(key, 600) // 10 minutes TTL\n * return true\n * }\n * return false\n * }\n * ```\n */\nexport type UniqueNonceChecker = (nonce: string) => Promise<boolean>\n\n/**\n * Configuration options for AT Protocol service authentication.\n *\n * Service auth is used for server-to-server communication in the AT Protocol,\n * where one service authenticates to another using signed JWT tokens tied to\n * the caller's DID.\n *\n * @example\n * ```typescript\n * const options: ServiceAuthOptions = {\n * audience: 'did:web:api.example.com',\n * unique: async (nonce) => nonceStore.checkAndAdd(nonce),\n * maxAge: 300, // 5 minutes\n * // Optional DID resolver options\n * plcDirectoryUrl: 'https://plc.directory'\n * }\n * ```\n */\nexport type ServiceAuthOptions = CreateDidResolverOptions & {\n /**\n * Expected audience (\"aud\") claim in the JWT token.\n *\n * This should be the DID of your service. The token must include this\n * value in its `aud` claim to be accepted. Set to `null` to skip\n * audience verification (not recommended for production).\n */\n audience: null | DidString\n /**\n * Function to check and record nonce uniqueness.\n *\n * This is critical for preventing replay attacks. The value checked here\n * must be unique within `maxAge` seconds before and after the current time.\n *\n * @param nonce - The nonce to check\n * @returns Promise resolving to `true` if unique, `false` if seen before\n */\n unique: UniqueNonceChecker\n /**\n * Maximum age of the JWT token in seconds.\n *\n * Tokens with `iat` (issued at) or `exp` (expiry) timestamps outside\n * this window from the current time will be rejected.\n *\n * @default 300 (5 minutes)\n */\n maxAge?: number\n}\n\n/**\n * Credentials returned after successful service authentication.\n *\n * Contains the verified DID, resolved DID document, and parsed JWT token.\n * These are available in handler context as `ctx.credentials`.\n *\n * @example\n * ```typescript\n * router.add(protectedMethod, {\n * handler: async (ctx) => {\n * const { did, didDocument, jwt } = ctx.credentials\n * console.log('Request from:', did)\n * console.log('Token expires:', new Date(jwt.payload.exp * 1000))\n * return { body: { callerDid: did } }\n * },\n * auth: serviceAuth({ audience: myDid, unique: checkNonce })\n * })\n * ```\n */\nexport type ServiceAuthCredentials = {\n /** The verified AT Protocol DID of the caller. */\n did: AtprotoDid\n /** The resolved DID document of the caller. */\n didDocument: AtprotoDidDocument\n /** The parsed and validated JWT token. */\n jwt: ParsedJwt\n}\n\n/**\n * Creates an authentication handler for verifying AT Protocol service auth JWTs.\n *\n * Service auth is the standard authentication mechanism for server-to-server\n * communication in the AT Protocol. It uses JWT bearer tokens signed by the\n * caller's DID signing key, with the signature verified against the public\n * key in the caller's DID document.\n *\n * The handler performs the following validations:\n * - Extracts and parses the Bearer token from the Authorization header\n * - Validates JWT structure and claims (aud, exp, iat, lxm, nonce)\n * - Resolves the issuer's DID document\n * - Verifies the JWT signature against the `#atproto` verification method\n * - Checks nonce uniqueness to prevent replay attacks\n *\n * @param options - Configuration options for service auth\n * @returns An auth handler function for use with {@link LexRouter.add}\n *\n * @example Basic usage\n * ```typescript\n * import { LexRouter, serviceAuth } from '@atproto/lex-server'\n *\n * const router = new LexRouter()\n *\n * const auth = serviceAuth({\n * audience: 'did:web:api.example.com',\n * unique: async (nonce) => {\n * // Check if nonce has been seen, return true if unique\n * const isNew = await redis.setnx(`nonce:${nonce}`, '1')\n * if (isNew) await redis.expire(`nonce:${nonce}`, 600)\n * return isNew\n * }\n * })\n *\n * router.add(myMethod, {\n * handler: async (ctx) => {\n * console.log('Authenticated as:', ctx.credentials.did)\n * return { body: { success: true } }\n * },\n * auth\n * })\n * ```\n */\nexport function serviceAuth({\n audience,\n maxAge = 5 * 60,\n unique,\n ...options\n}: ServiceAuthOptions): LexRouterAuth<ServiceAuthCredentials> {\n const didResolver = createDidResolver(options)\n\n return async ({ request, method }) => {\n const { signal } = request\n const jwt = await parseJwtBearer(request, {\n lxm: method.nsid,\n maxAge,\n audience,\n unique,\n })\n\n let didDocument: AtprotoDidDocument = await didResolver\n .resolve(jwt.payload.iss, { signal })\n .catch((cause) => {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not resolve DID document',\n { Bearer: { error: 'DidResolutionFailed' } },\n { cause },\n )\n })\n\n const key = getAtprotoSigningKey(didDocument)\n\n if (!key || !(await verifyJwt(jwt, key))) {\n signal.throwIfAborted()\n\n // Try refreshing the DID document in case it was updated\n didDocument = await didResolver\n .resolve(jwt.payload.iss, { signal, noCache: true })\n .catch((cause) => {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not resolve DID document',\n { Bearer: { error: 'DidResolutionFailed' } },\n { cause },\n )\n })\n\n // Verify again with the fresh key (if it changed)\n const keyFresh = getAtprotoSigningKey(didDocument)\n if (!keyFresh || key === keyFresh || !(await verifyJwt(jwt, keyFresh))) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT signature',\n { Bearer: { error: 'BadJwtSignature' } },\n )\n }\n }\n\n return {\n did: didDocument.id,\n didDocument,\n jwt,\n }\n }\n}\n\nasync function verifyJwt(jwt: ParsedJwt, key: Did<'key'>) {\n try {\n return await crypto.verifySignature(key, jwt.message, jwt.signature, {\n jwtAlg: jwt.header.alg,\n allowMalleableSig: true,\n })\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Could not verify JWT signature',\n { Bearer: { error: 'BadJwtSignature' } },\n { cause },\n )\n }\n}\n\nfunction getAtprotoSigningKey(\n didDocument: AtprotoDidDocument,\n): null | Did<'key'> {\n try {\n const key = didDocument.verificationMethod?.find(\n isAtprotoVerificationMethod,\n didDocument,\n )\n\n if (key?.publicKeyMultibase) {\n if (key.type === 'EcdsaSecp256r1VerificationKey2019') {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)\n } else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {\n const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)\n return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)\n } else if (key.type === 'Multikey') {\n const parsed = crypto.parseMultikey(key.publicKeyMultibase)\n return crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)\n }\n }\n } catch {\n // Invalid key, ignore\n }\n\n return null\n}\n\nfunction isAtprotoVerificationMethod<\n V extends string | { id: string; type: string; publicKeyMultibase?: string },\n>(\n this: AtprotoDidDocument,\n vm: V,\n): vm is Exclude<V, string> & {\n id: `${string}#atproto`\n} {\n return typeof vm === 'object' && matchesIdentifier(this.id, 'atproto', vm.id)\n}\n\nasync function parseJwtBearer(\n request: Request,\n options: ParseJwtOptions,\n): Promise<ParsedJwt> {\n const authorization = request.headers.get('authorization')\n if (!authorization?.startsWith(BEARER_PREFIX)) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Bearer token required',\n { Bearer: { error: 'MissingBearer' } },\n )\n }\n\n const token = authorization.slice(BEARER_PREFIX.length).trim()\n\n return parseJwt(token, options)\n}\n\n/**\n * Options for parsing and validating a JWT token.\n */\nexport type ParseJwtOptions = {\n /** Maximum age in seconds for token validity window. */\n maxAge: number\n /** Expected audience claim, or null to skip audience verification. */\n audience: null | DidString\n /** Function to check nonce uniqueness. */\n unique: UniqueNonceChecker\n /** Expected lexicon method NSID for the `lxm` claim. */\n lxm: string\n}\n\n/**\n * A parsed and partially validated JWT token.\n *\n * Contains the decoded header and payload, along with the raw bytes\n * needed for signature verification.\n *\n * @example\n * ```typescript\n * const jwt: ParsedJwt = {\n * header: { alg: 'ES256K', typ: 'JWT' },\n * payload: {\n * iss: 'did:plc:abc123',\n * aud: 'did:web:api.example.com',\n * exp: 1704067200,\n * iat: 1704066900,\n * lxm: 'com.atproto.sync.getBlob'\n * },\n * message: new Uint8Array([...]),\n * signature: new Uint8Array([...])\n * }\n * ```\n */\nexport type ParsedJwt = {\n /** The decoded JWT header containing algorithm and type. */\n header: HeaderObject\n /** The decoded JWT payload containing claims. */\n payload: PayloadObject\n /** The raw header.payload bytes for signature verification. */\n message: Uint8Array\n /** The decoded signature bytes. */\n signature: Uint8Array\n}\n\nasync function parseJwt(\n token: string,\n options: ParseJwtOptions,\n): Promise<ParsedJwt> {\n const {\n length,\n 0: headerB64,\n 1: payloadB64,\n 2: signatureB64,\n } = token.split('.')\n if (length !== 3) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n )\n }\n\n let header: HeaderObject\n try {\n header = jsonFromBase64(headerB64, isHeaderObject)\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n { cause },\n )\n }\n\n if (\n header.alg === 'none' ||\n // service tokens are not OAuth 2.0 access tokens\n // https://datatracker.ietf.org/doc/html/rfc9068\n header.typ === 'at+jwt' ||\n // \"refresh+jwt\" is a non-standard type used by the @atproto packages\n header.typ === 'refresh+jwt' ||\n // \"DPoP\" proofs are not meant to be used as service tokens\n // https://datatracker.ietf.org/doc/html/rfc9449\n header.typ === 'dpop+jwt'\n ) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n )\n }\n\n let payload: PayloadObject\n try {\n payload = jsonFromBase64(payloadB64, isPayloadObject)\n } catch (cause) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT token',\n { Bearer: { error: 'BadJwt' } },\n { cause },\n )\n }\n\n if (options.audience !== null && options.audience !== payload.aud) {\n throw new LexServerAuthError('AuthenticationRequired', 'Invalid audience', {\n Bearer: { error: 'InvalidAudience' },\n })\n }\n\n const now = Math.floor(Date.now() / 1000)\n\n if (payload.nbf != null && now < payload.nbf) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token not yet valid',\n { Bearer: { error: 'JwtNotYetValid' } },\n )\n }\n\n if (now > payload.exp) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token expired',\n { Bearer: { error: 'JwtExpired' } },\n )\n }\n\n // Prevent issuer from generating very long-lived tokens\n if (\n timeDiff(now, payload.exp) > options.maxAge ||\n timeDiff(now, payload.iat) > options.maxAge\n ) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'JWT token too old',\n { Bearer: { error: 'JwtTooOld' } },\n )\n }\n\n if (payload.lxm != null && payload.lxm !== options.lxm) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Invalid JWT lexicon method (\"lxm\")',\n { Bearer: { error: 'BadJwtLexiconMethod' } },\n )\n }\n\n if (payload.nonce != null && !(await (0, options.unique)(payload.nonce))) {\n throw new LexServerAuthError(\n 'AuthenticationRequired',\n 'Replay attack detected: nonce is not unique',\n { Bearer: { error: 'NonceNotUnique' } },\n )\n }\n\n return {\n header,\n payload,\n message: textEncoder.encode(`${headerB64}.${payloadB64}`),\n signature: fromBase64(signatureB64, 'base64url'),\n }\n}\n\nconst textEncoder = /*#__PURE__*/ new TextEncoder()\n\ntype HeaderObject = { alg: string; typ?: string }\nfunction isHeaderObject(obj: unknown): obj is HeaderObject {\n return (\n isPlainObject(obj) &&\n typeof obj.alg === 'string' &&\n (obj.typ === undefined || typeof obj.typ === 'string')\n )\n}\n\ntype PayloadObject = {\n iss: DidString\n aud: DidString\n exp: number\n iat?: number\n nbf?: number\n lxm?: string\n nonce?: string\n}\nexport function isPayloadObject(obj: unknown): obj is PayloadObject {\n return (\n isPlainObject(obj) &&\n typeof obj.iss === 'string' &&\n typeof obj.aud === 'string' &&\n (obj.lxm === undefined || typeof obj.lxm === 'string') &&\n (obj.nonce === undefined || typeof obj.nonce === 'string') &&\n (obj.iat === undefined || isPositiveInt(obj.iat)) &&\n (obj.nbf === undefined || isPositiveInt(obj.nbf)) &&\n isPositiveInt(obj.exp) &&\n isDidString(obj.iss) &&\n isDidString(obj.aud)\n )\n}\n\nfunction timeDiff(t1: number, t2?: number): number {\n if (t2 === undefined) return 0\n return Math.abs(t1 - t2)\n}\n\nfunction isPositiveInt(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value > 0\n}\n\nfunction jsonFromBase64<T>(b64: string, isType: (obj: unknown) => obj is T): T {\n const obj = JSON.parse(utf8FromBase64(b64, 'base64url'))\n if (isType(obj)) return obj\n throw new Error('Invalid type')\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-server",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "license": "MIT",
5
5
  "description": "Request router for Atproto Lexicon protocols and schemas",
6
6
  "keywords": [
@@ -49,17 +49,17 @@
49
49
  "@atproto/crypto": "^0.4.5",
50
50
  "@atproto/did": "^0.3.0",
51
51
  "@atproto/lex-cbor": "^0.0.16",
52
- "@atproto/lex-client": "^0.0.20",
52
+ "@atproto/lex-client": "^0.0.22",
53
53
  "@atproto/lex-data": "^0.0.15",
54
54
  "@atproto/lex-json": "^0.0.16",
55
- "@atproto/lex-schema": "^0.0.19"
55
+ "@atproto/lex-schema": "^0.0.20"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/ws": "^8.18.1",
59
59
  "@vitest/coverage-v8": "4.0.16",
60
60
  "vitest": "^4.0.16",
61
- "@atproto/lex": "^0.0.25",
62
- "@atproto/lex-client": "^0.0.20"
61
+ "@atproto/lex": "^0.0.27",
62
+ "@atproto/lex-client": "^0.0.22"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsc --build tsconfig.build.json",
package/src/nodejs.ts CHANGED
@@ -397,7 +397,7 @@ function toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {
397
397
  * const app = express()
398
398
  *
399
399
  * // Mount the XRPC router
400
- * app.use('/xrpc', toRequestListener(router.fetch))
400
+ * app.use(toRequestListener(router.fetch))
401
401
  * ```
402
402
  */
403
403
  export function toRequestListener<
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { serviceAuth } from './service-auth.js'
3
+
4
+ describe('serviceAuth - lxm validation', () => {
5
+ const audience = 'did:web:api.example.com'
6
+ const issuer = 'did:web:caller.example.com'
7
+ const nsid = 'io.example.test'
8
+
9
+ function makeJwt(payload: Record<string, unknown>): string {
10
+ const header = Buffer.from(
11
+ JSON.stringify({ alg: 'ES256K', typ: 'JWT' }),
12
+ ).toString('base64url')
13
+ const body = Buffer.from(JSON.stringify(payload)).toString('base64url')
14
+ const sig = Buffer.from([0]).toString('base64url')
15
+ return `${header}.${body}.${sig}`
16
+ }
17
+
18
+ function basePayload(overrides: Record<string, unknown> = {}) {
19
+ const now = Math.floor(Date.now() / 1000)
20
+ return { iss: issuer, aud: audience, iat: now, exp: now + 60, ...overrides }
21
+ }
22
+
23
+ function setup() {
24
+ const resolve = vi.fn(async () => {
25
+ throw new Error('stop after lxm check')
26
+ })
27
+ const auth = serviceAuth({
28
+ audience,
29
+ unique: async () => true,
30
+ didResolver: { resolve },
31
+ })
32
+ return { auth, resolve }
33
+ }
34
+
35
+ it('rejects with BadJwtLexiconMethod when lxm does not match method.nsid', async () => {
36
+ const { auth, resolve } = setup()
37
+ const jwt = makeJwt(basePayload({ lxm: 'io.example.different' }))
38
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
39
+ headers: { authorization: `Bearer ${jwt}` },
40
+ })
41
+
42
+ await expect(
43
+ auth({ request, method: { nsid } as any, params: {} }),
44
+ ).rejects.toThrow('Invalid JWT lexicon method ("lxm")')
45
+ expect(resolve).not.toHaveBeenCalled()
46
+ })
47
+
48
+ it('passes lxm check when payload.lxm matches method.nsid', async () => {
49
+ const { auth, resolve } = setup()
50
+ const jwt = makeJwt(basePayload({ lxm: nsid }))
51
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
52
+ headers: { authorization: `Bearer ${jwt}` },
53
+ })
54
+
55
+ await expect(
56
+ auth({ request, method: { nsid } as any, params: {} }),
57
+ ).rejects.toThrow()
58
+ // The DID resolver isn't called unless "lxm" validation succeeded
59
+ expect(resolve).toHaveBeenCalled()
60
+ })
61
+
62
+ it('skips lxm check when payload has no lxm claim', async () => {
63
+ const { auth, resolve } = setup()
64
+ const jwt = makeJwt(basePayload())
65
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
66
+ headers: { authorization: `Bearer ${jwt}` },
67
+ })
68
+
69
+ await expect(
70
+ auth({ request, method: { nsid } as any, params: {} }),
71
+ ).rejects.toThrow()
72
+ expect(resolve).toHaveBeenCalled()
73
+ })
74
+
75
+ it('rejects an empty-string lxm claim against a real NSID', async () => {
76
+ const { auth, resolve } = setup()
77
+ const jwt = makeJwt(basePayload({ lxm: '' }))
78
+ const request = new Request(`https://api.example.com/xrpc/${nsid}`, {
79
+ headers: { authorization: `Bearer ${jwt}` },
80
+ })
81
+
82
+ await expect(
83
+ auth({ request, method: { nsid } as any, params: {} }),
84
+ ).rejects.toThrow('Invalid JWT lexicon method ("lxm")')
85
+ expect(resolve).not.toHaveBeenCalled()
86
+ })
87
+ })
@@ -442,7 +442,7 @@ async function parseJwt(
442
442
  )
443
443
  }
444
444
 
445
- if (payload.lxm != null && typeof payload.lxm !== options.lxm) {
445
+ if (payload.lxm != null && payload.lxm !== options.lxm) {
446
446
  throw new LexServerAuthError(
447
447
  'AuthenticationRequired',
448
448
  'Invalid JWT lexicon method ("lxm")',