@atproto/lex-server 0.0.19 → 0.1.0

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.
@@ -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(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'\n// eslint-disable-next-line import/default, import/no-named-as-default-member\nimport httpTerminator from 'http-terminator'\n// eslint-disable-next-line import/no-named-as-default-member\nconst { createHttpTerminator } = httpTerminator\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"]}
@@ -1,14 +1,9 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serviceAuth = serviceAuth;
4
- exports.isPayloadObject = isPayloadObject;
5
- const tslib_1 = require("tslib");
6
- const crypto = tslib_1.__importStar(require("@atproto/crypto"));
7
- const did_1 = require("@atproto/did");
8
- const lex_data_1 = require("@atproto/lex-data");
9
- const lex_schema_1 = require("@atproto/lex-schema");
10
- const did_resolver_1 = require("@atproto-labs/did-resolver");
11
- const errors_js_1 = require("./errors.js");
1
+ import * as crypto from '@atproto/crypto';
2
+ import { matchesIdentifier, } from '@atproto/did';
3
+ import { fromBase64, isPlainObject, utf8FromBase64 } from '@atproto/lex-data';
4
+ import { isDidString } from '@atproto/lex-schema';
5
+ import { createDidResolver, } from '@atproto-labs/did-resolver';
6
+ import { LexServerAuthError } from './errors.js';
12
7
  const BEARER_PREFIX = 'Bearer ';
13
8
  /**
14
9
  * Creates an authentication handler for verifying AT Protocol service auth JWTs.
@@ -53,8 +48,8 @@ const BEARER_PREFIX = 'Bearer ';
53
48
  * })
54
49
  * ```
55
50
  */
56
- function serviceAuth({ audience, maxAge = 5 * 60, unique, ...options }) {
57
- const didResolver = (0, did_resolver_1.createDidResolver)(options);
51
+ export function serviceAuth({ audience, maxAge = 5 * 60, unique, ...options }) {
52
+ const didResolver = createDidResolver(options);
58
53
  return async ({ request, method }) => {
59
54
  const { signal } = request;
60
55
  const jwt = await parseJwtBearer(request, {
@@ -66,7 +61,7 @@ function serviceAuth({ audience, maxAge = 5 * 60, unique, ...options }) {
66
61
  let didDocument = await didResolver
67
62
  .resolve(jwt.payload.iss, { signal })
68
63
  .catch((cause) => {
69
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Could not resolve DID document', { Bearer: { error: 'DidResolutionFailed' } }, { cause });
64
+ throw new LexServerAuthError('AuthenticationRequired', 'Could not resolve DID document', { Bearer: { error: 'DidResolutionFailed' } }, { cause });
70
65
  });
71
66
  const key = getAtprotoSigningKey(didDocument);
72
67
  if (!key || !(await verifyJwt(jwt, key))) {
@@ -75,12 +70,12 @@ function serviceAuth({ audience, maxAge = 5 * 60, unique, ...options }) {
75
70
  didDocument = await didResolver
76
71
  .resolve(jwt.payload.iss, { signal, noCache: true })
77
72
  .catch((cause) => {
78
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Could not resolve DID document', { Bearer: { error: 'DidResolutionFailed' } }, { cause });
73
+ throw new LexServerAuthError('AuthenticationRequired', 'Could not resolve DID document', { Bearer: { error: 'DidResolutionFailed' } }, { cause });
79
74
  });
80
75
  // Verify again with the fresh key (if it changed)
81
76
  const keyFresh = getAtprotoSigningKey(didDocument);
82
77
  if (!keyFresh || key === keyFresh || !(await verifyJwt(jwt, keyFresh))) {
83
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT signature', { Bearer: { error: 'BadJwtSignature' } });
78
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT signature', { Bearer: { error: 'BadJwtSignature' } });
84
79
  }
85
80
  }
86
81
  return {
@@ -98,7 +93,7 @@ async function verifyJwt(jwt, key) {
98
93
  });
99
94
  }
100
95
  catch (cause) {
101
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Could not verify JWT signature', { Bearer: { error: 'BadJwtSignature' } }, { cause });
96
+ throw new LexServerAuthError('AuthenticationRequired', 'Could not verify JWT signature', { Bearer: { error: 'BadJwtSignature' } }, { cause });
102
97
  }
103
98
  }
104
99
  function getAtprotoSigningKey(didDocument) {
@@ -125,12 +120,12 @@ function getAtprotoSigningKey(didDocument) {
125
120
  return null;
126
121
  }
127
122
  function isAtprotoVerificationMethod(vm) {
128
- return typeof vm === 'object' && (0, did_1.matchesIdentifier)(this.id, 'atproto', vm.id);
123
+ return typeof vm === 'object' && matchesIdentifier(this.id, 'atproto', vm.id);
129
124
  }
130
125
  async function parseJwtBearer(request, options) {
131
126
  const authorization = request.headers.get('authorization');
132
127
  if (!authorization?.startsWith(BEARER_PREFIX)) {
133
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Bearer token required', { Bearer: { error: 'MissingBearer' } });
128
+ throw new LexServerAuthError('AuthenticationRequired', 'Bearer token required', { Bearer: { error: 'MissingBearer' } });
134
129
  }
135
130
  const token = authorization.slice(BEARER_PREFIX.length).trim();
136
131
  return parseJwt(token, options);
@@ -138,14 +133,14 @@ async function parseJwtBearer(request, options) {
138
133
  async function parseJwt(token, options) {
139
134
  const { length, 0: headerB64, 1: payloadB64, 2: signatureB64, } = token.split('.');
140
135
  if (length !== 3) {
141
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } });
136
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } });
142
137
  }
143
138
  let header;
144
139
  try {
145
140
  header = jsonFromBase64(headerB64, isHeaderObject);
146
141
  }
147
142
  catch (cause) {
148
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } }, { cause });
143
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } }, { cause });
149
144
  }
150
145
  if (header.alg === 'none' ||
151
146
  // service tokens are not OAuth 2.0 access tokens
@@ -156,53 +151,53 @@ async function parseJwt(token, options) {
156
151
  // "DPoP" proofs are not meant to be used as service tokens
157
152
  // https://datatracker.ietf.org/doc/html/rfc9449
158
153
  header.typ === 'dpop+jwt') {
159
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } });
154
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } });
160
155
  }
161
156
  let payload;
162
157
  try {
163
158
  payload = jsonFromBase64(payloadB64, isPayloadObject);
164
159
  }
165
160
  catch (cause) {
166
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } }, { cause });
161
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT token', { Bearer: { error: 'BadJwt' } }, { cause });
167
162
  }
168
163
  if (options.audience !== null && options.audience !== payload.aud) {
169
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid audience', {
164
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid audience', {
170
165
  Bearer: { error: 'InvalidAudience' },
171
166
  });
172
167
  }
173
168
  const now = Math.floor(Date.now() / 1000);
174
169
  if (payload.nbf != null && now < payload.nbf) {
175
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'JWT token not yet valid', { Bearer: { error: 'JwtNotYetValid' } });
170
+ throw new LexServerAuthError('AuthenticationRequired', 'JWT token not yet valid', { Bearer: { error: 'JwtNotYetValid' } });
176
171
  }
177
172
  if (now > payload.exp) {
178
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'JWT token expired', { Bearer: { error: 'JwtExpired' } });
173
+ throw new LexServerAuthError('AuthenticationRequired', 'JWT token expired', { Bearer: { error: 'JwtExpired' } });
179
174
  }
180
175
  // Prevent issuer from generating very long-lived tokens
181
176
  if (timeDiff(now, payload.exp) > options.maxAge ||
182
177
  timeDiff(now, payload.iat) > options.maxAge) {
183
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'JWT token too old', { Bearer: { error: 'JwtTooOld' } });
178
+ throw new LexServerAuthError('AuthenticationRequired', 'JWT token too old', { Bearer: { error: 'JwtTooOld' } });
184
179
  }
185
180
  if (payload.lxm != null && payload.lxm !== options.lxm) {
186
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Invalid JWT lexicon method ("lxm")', { Bearer: { error: 'BadJwtLexiconMethod' } });
181
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid JWT lexicon method ("lxm")', { Bearer: { error: 'BadJwtLexiconMethod' } });
187
182
  }
188
183
  if (payload.nonce != null && !(await (0, options.unique)(payload.nonce))) {
189
- throw new errors_js_1.LexServerAuthError('AuthenticationRequired', 'Replay attack detected: nonce is not unique', { Bearer: { error: 'NonceNotUnique' } });
184
+ throw new LexServerAuthError('AuthenticationRequired', 'Replay attack detected: nonce is not unique', { Bearer: { error: 'NonceNotUnique' } });
190
185
  }
191
186
  return {
192
187
  header,
193
188
  payload,
194
189
  message: textEncoder.encode(`${headerB64}.${payloadB64}`),
195
- signature: (0, lex_data_1.fromBase64)(signatureB64, 'base64url'),
190
+ signature: fromBase64(signatureB64, 'base64url'),
196
191
  };
197
192
  }
198
193
  const textEncoder = /*#__PURE__*/ new TextEncoder();
199
194
  function isHeaderObject(obj) {
200
- return ((0, lex_data_1.isPlainObject)(obj) &&
195
+ return (isPlainObject(obj) &&
201
196
  typeof obj.alg === 'string' &&
202
197
  (obj.typ === undefined || typeof obj.typ === 'string'));
203
198
  }
204
- function isPayloadObject(obj) {
205
- return ((0, lex_data_1.isPlainObject)(obj) &&
199
+ export function isPayloadObject(obj) {
200
+ return (isPlainObject(obj) &&
206
201
  typeof obj.iss === 'string' &&
207
202
  typeof obj.aud === 'string' &&
208
203
  (obj.lxm === undefined || typeof obj.lxm === 'string') &&
@@ -210,8 +205,8 @@ function isPayloadObject(obj) {
210
205
  (obj.iat === undefined || isPositiveInt(obj.iat)) &&
211
206
  (obj.nbf === undefined || isPositiveInt(obj.nbf)) &&
212
207
  isPositiveInt(obj.exp) &&
213
- (0, lex_schema_1.isDidString)(obj.iss) &&
214
- (0, lex_schema_1.isDidString)(obj.aud));
208
+ isDidString(obj.iss) &&
209
+ isDidString(obj.aud));
215
210
  }
216
211
  function timeDiff(t1, t2) {
217
212
  if (t2 === undefined)
@@ -222,7 +217,7 @@ function isPositiveInt(value) {
222
217
  return typeof value === 'number' && Number.isInteger(value) && value > 0;
223
218
  }
224
219
  function jsonFromBase64(b64, isType) {
225
- const obj = JSON.parse((0, lex_data_1.utf8FromBase64)(b64, 'base64url'));
220
+ const obj = JSON.parse(utf8FromBase64(b64, 'base64url'));
226
221
  if (isType(obj))
227
222
  return obj;
228
223
  throw new Error('Invalid type');
@@ -1 +1 @@
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"]}
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,9 @@
1
1
  {
2
2
  "name": "@atproto/lex-server",
3
- "version": "0.0.19",
3
+ "version": "0.1.0",
4
+ "engines": {
5
+ "node": ">=22"
6
+ },
4
7
  "license": "MIT",
5
8
  "description": "Request router for Atproto Lexicon protocols and schemas",
6
9
  "keywords": [
@@ -21,45 +24,41 @@
21
24
  "./tsconfig.tests.json",
22
25
  "./tsconfig.json",
23
26
  "./dist",
24
- "./nodejs.js",
27
+ "./nodejs.cjs",
25
28
  "./CHANGELOG.md"
26
29
  ],
27
30
  "sideEffects": false,
28
- "type": "commonjs",
29
- "main": "./dist/index.js",
30
- "types": "./dist/index.d.ts",
31
+ "type": "module",
31
32
  "exports": {
32
33
  ".": {
33
34
  "types": "./dist/index.d.ts",
34
- "browser": "./dist/index.js",
35
- "import": "./dist/index.js",
36
35
  "default": "./dist/index.js"
37
36
  },
38
37
  "./nodejs": {
39
38
  "types": "./dist/nodejs.d.ts",
40
- "import": "./dist/nodejs.js",
41
- "require": "./dist/nodejs.js"
39
+ "require": "./dist/nodejs.js",
40
+ "default": "./dist/nodejs.js"
42
41
  }
43
42
  },
44
43
  "dependencies": {
45
44
  "http-terminator": "^3.2.0",
46
45
  "tslib": "^2.8.1",
47
46
  "ws": "^8.18.3",
48
- "@atproto-labs/did-resolver": "^0.2.6",
49
- "@atproto/crypto": "^0.4.5",
50
- "@atproto/did": "^0.3.0",
51
- "@atproto/lex-cbor": "^0.0.16",
52
- "@atproto/lex-client": "^0.0.22",
53
- "@atproto/lex-data": "^0.0.15",
54
- "@atproto/lex-json": "^0.0.16",
55
- "@atproto/lex-schema": "^0.0.20"
47
+ "@atproto-labs/did-resolver": "^0.3.0",
48
+ "@atproto/crypto": "^0.5.0",
49
+ "@atproto/did": "^0.4.0",
50
+ "@atproto/lex-cbor": "^0.1.0",
51
+ "@atproto/lex-client": "^0.1.0",
52
+ "@atproto/lex-data": "^0.1.0",
53
+ "@atproto/lex-json": "^0.1.0",
54
+ "@atproto/lex-schema": "^0.1.0"
56
55
  },
57
56
  "devDependencies": {
58
57
  "@types/ws": "^8.18.1",
59
58
  "@vitest/coverage-v8": "4.0.16",
60
59
  "vitest": "^4.0.16",
61
- "@atproto/lex": "^0.0.27",
62
- "@atproto/lex-client": "^0.0.22"
60
+ "@atproto/lex": "^0.1.0",
61
+ "@atproto/lex-client": "^0.1.0"
63
62
  },
64
63
  "scripts": {
65
64
  "build": "tsc --build tsconfig.build.json",
package/src/lex-router.ts CHANGED
@@ -507,7 +507,7 @@ export type LexRouterOptions = {
507
507
  * ```typescript
508
508
  * import { LexRouter } from '@atproto/lex-server'
509
509
  * import { serve, upgradeWebSocket } from '@atproto/lex-server/nodejs'
510
- * import { getProfile, createPost, subscribeRepos } from './lexicons'
510
+ * import { getProfile, createPost, subscribeRepos } from './lexicons.js'
511
511
  *
512
512
  * const router = new LexRouter({ upgradeWebSocket })
513
513
  *
@@ -1114,14 +1114,17 @@ function onMessage(this: WebSocket, _event: unknown) {
1114
1114
  // Pre-encoded frame header for error frames
1115
1115
  const ERROR_FRAME_HEADER = /*#__PURE__*/ encode({ op: -1 })
1116
1116
 
1117
- function encodeErrorFrame(errorData: LexErrorData): Uint8Array {
1117
+ function encodeErrorFrame(errorData: LexErrorData): Uint8Array<ArrayBuffer> {
1118
1118
  return ui8Concat([ERROR_FRAME_HEADER, encode(errorData)])
1119
1119
  }
1120
1120
 
1121
1121
  // Pre-encoded frame header for message frames with unknown type
1122
1122
  const UNKNOWN_MESSAGE_FRAME_HEADER = /*#__PURE__*/ encode({ op: 1 })
1123
1123
 
1124
- function encodeMessageFrame(method: Subscription, value: LexValue): Uint8Array {
1124
+ function encodeMessageFrame(
1125
+ method: Subscription,
1126
+ value: LexValue,
1127
+ ): Uint8Array<ArrayBuffer> {
1125
1128
  if (isPlainObject(value) && typeof value.$type === 'string') {
1126
1129
  const { $type, ...rest } = value
1127
1130
  return ui8Concat([
package/src/nodejs.ts CHANGED
@@ -12,7 +12,10 @@ import { ListenOptions } from 'node:net'
12
12
  import { Readable } from 'node:stream'
13
13
  import { pipeline } from 'node:stream/promises'
14
14
  import type { ReadableStream as NodeReadableStream } from 'node:stream/web'
15
- import { createHttpTerminator } from 'http-terminator'
15
+ // eslint-disable-next-line import/default, import/no-named-as-default-member
16
+ import httpTerminator from 'http-terminator'
17
+ // eslint-disable-next-line import/no-named-as-default-member
18
+ const { createHttpTerminator } = httpTerminator
16
19
  import { WebSocket as WebSocketPonyfill, WebSocketServer } from 'ws'
17
20
  import type { FetchHandler } from './lex-router.js'
18
21
 
File without changes