A zero-dependency AT Protocol Personal Data Server written in JavaScript

add com.atproto.server.getServiceAuth endpoint

enables server-side JWT signing so clients don't need private keys

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+46
+46
src/pds.js
··· 1579 1579 method: 'POST', 1580 1580 handler: (pds, req, _url) => pds.handleRefreshSession(req), 1581 1581 }, 1582 + '/xrpc/com.atproto.server.getServiceAuth': { 1583 + handler: (pds, req, _url) => pds.handleGetServiceAuth(req), 1584 + }, 1582 1585 '/xrpc/app.bsky.actor.getPreferences': { 1583 1586 handler: (pds, req, _url) => pds.handleGetPreferences(req), 1584 1587 }, ··· 2541 2544 lxm, 2542 2545 signingKey, 2543 2546 }); 2547 + } 2548 + 2549 + /** 2550 + * Get a service auth token (com.atproto.server.getServiceAuth) 2551 + * Called with session auth, returns JWT signed by the user's key 2552 + * @param {Request} request 2553 + */ 2554 + async handleGetServiceAuth(request) { 2555 + const url = new URL(request.url); 2556 + const aud = url.searchParams.get('aud'); 2557 + const lxm = url.searchParams.get('lxm'); 2558 + 2559 + if (!aud) { 2560 + return errorResponse('InvalidRequest', 'missing aud param', 400); 2561 + } 2562 + 2563 + const did = await this.state.storage.get('did'); 2564 + if (!did) { 2565 + return errorResponse('InvalidRequest', 'account not found', 400); 2566 + } 2567 + 2568 + const signingKey = await this.getSigningKey(); 2569 + if (!signingKey) { 2570 + return errorResponse('InvalidRequest', 'signing key not available', 400); 2571 + } 2572 + 2573 + const jwt = await createServiceJwt({ 2574 + iss: /** @type {string} */ (did), 2575 + aud, 2576 + lxm: lxm || undefined, 2577 + signingKey, 2578 + }); 2579 + 2580 + return Response.json({ token: jwt }); 2544 2581 } 2545 2582 2546 2583 /** ··· 3711 3748 const defaultId = env.PDS.idFromName('default'); 3712 3749 const defaultPds = env.PDS.get(defaultId); 3713 3750 return defaultPds.fetch(request); 3751 + } 3752 + 3753 + // getServiceAuth - authenticate and route to user's DO for server-side JWT signing 3754 + if (url.pathname === '/xrpc/com.atproto.server.getServiceAuth') { 3755 + const auth = await requireAuth(request, env); 3756 + if ('error' in auth) return auth.error; 3757 + const id = env.PDS.idFromName(auth.did); 3758 + const pds = env.PDS.get(id); 3759 + return pds.fetch(request); 3714 3760 } 3715 3761 3716 3762 // Proxy app.bsky.* endpoints to Bluesky AppView