~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0

Revert "A Cloudflare PDS reaches Workers on its own account through service bindings"

Merged chadtmiller.com asked to merge into main from chadtmiller.com/pds.js:revert/3mzj4z5cv33wo
.changeset/fetch-bindings.mddeleted+0−8
@@ -1,8 +0,0 @@
---
'@pdsjs/cloudflare': patch
---
`PDS_FETCH_BINDINGS` names hosts that the lexicon and DID resolvers reach
through a service binding, as `host=BINDING` pairs. A Worker cannot fetch
another Worker's `workers.dev` address on the same account, so a PDS whose
lexicons or DIDs are published on such a Worker read them as not published.
docs/deploy-cloudflare.md+0−17
@@ -70,23 +70,6 @@ Your `wrangler.toml` needs the same bindings as
The Durable Object needs the `new_sqlite_classes` migration. Keep `BACKUPS` in a
separate bucket from `BLOBS`. The retention policy deletes freely inside it.
## Other Workers on your account
A Worker cannot fetch another Worker's `workers.dev` address on the same
Cloudflare account. When a lexicon or a DID the PDS resolves is on a PDS at
such an address, bind that Worker as a service and name its host in
`PDS_FETCH_BINDINGS`, as `host=BINDING` pairs separated by commas. The lexicon
and DID resolvers then reach that host through the binding.
```toml
services = [{ binding = "LEXICONS", service = "lexicons-pds" }]
[vars]
PDS_FETCH_BINDINGS = "lexicons-pds.example.workers.dev=LEXICONS"
```
Without the binding, every lexicon on that PDS reads as not published.
## Email
Account verification mail (confirming an address, authorizing a PLC operation)
packages/cloudflare/src/index.js+6−38
@@ -1243,40 +1243,9 @@ export function createEmailer(binding, from) {
* @property {string} [PDS_EXPERIMENTAL_DRIVE] - "true" backs the account page's Drive section over dev.pdsjs.drive.file records
* @property {string} [PDS_EMAIL_FROM] - The From address account mail is sent as; its domain must be onboarded for Email Sending. Absent, no-reply@HOSTNAME is used. Neither set, mail cannot be sent and the account page reports email as unavailable.
* @property {CloudflareEmailer} [EMAIL] - Email Service send_email binding. Present only when the binding is configured; without it mail cannot be sent.
* @property {string} [PDS_FETCH_BINDINGS] - Hosts the lexicon and DID resolvers reach through a service binding, as comma-separated host=BINDING pairs. A Worker cannot fetch another workers.dev Worker on its own account, so a PDS on that account needs one.
* @property {DurableObjectNamespace} PDS - Durable Object namespace
*/
/**
* A fetch that sends each host named in `PDS_FETCH_BINDINGS` to its service
* binding, and every other request to the network.
* @param {Record<string, unknown>} env - The worker's env
* @returns {typeof fetch}
*/
export function createBoundFetch(env) {
/** @type {Map<string, { fetch: typeof fetch }>} */
const bound = new Map();
for (const pair of String(env.PDS_FETCH_BINDINGS ?? '').split(',')) {
const [host, name] = pair.split('=').map((part) => part.trim());
const binding = /** @type {{ fetch?: unknown } | undefined} */ (
name ? env[name] : undefined
);
if (host && typeof binding?.fetch === 'function') {
bound.set(
host.toLowerCase(),
/** @type {{ fetch: typeof fetch }} */ (binding),
);
}
}
/** @type {typeof fetch} */
const boundFetch = (input, init) => {
const url = input instanceof Request ? input.url : input;
const binding = bound.get(new URL(url).hostname);
return binding ? binding.fetch(input, init) : fetch(input, init);
};
return boundFetch;
}
/**
* Durable Object class for per-user PDS instances
*/
@@ -1324,12 +1293,11 @@ export class PDSDurableObject {
// Initialize WebSocket adapter with hibernation support
const webSocket = createWebSocket(state);
// The resolvers reach a sibling Worker on this account through its
// service binding. A wrapped fetch keeps the global's `this` binding.
const boundFetch = createBoundFetch(
/** @type {Record<string, unknown>} */ (/** @type {unknown} */ (env)),
);
const lexiconResolver = new LexiconResolver({ fetch: boundFetch });
// Initialize lexicon resolver for record validation
// Wrap fetch to preserve `this` binding in Cloudflare Workers
const lexiconResolver = new LexiconResolver({
fetch: (url, init) => fetch(url, init),
});
// Signature verification, shared by createAccount (migration service
// tokens) and spaces (credentials). P-256 is built into core; secp256k1 —
@@ -1342,7 +1310,7 @@ export class PDSDurableObject {
// which reads its issuer's key.
const didResolver = createCachedDidResolver({
plcUrl: env.PLC_URL,
fetch: boundFetch,
fetch: (url, init) => fetch(url, init),
});
// Permissioned data (proposal 0016), off by default. The storage is this
packages/cloudflare/test/fetch-bindings.test.jsdeleted+0−50
@@ -1,50 +0,0 @@
// A Worker cannot fetch another workers.dev Worker on its own account, so the
// resolvers reach such a host through a service binding named for it.
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createBoundFetch } from '../src/index.js';
const LEXICONS = 'https://lexicons-pds.example.workers.dev';
afterEach(() => vi.unstubAllGlobals());
describe('createBoundFetch', () => {
it('sends a bound host to its binding and the rest to the network', async () => {
const binding = { fetch: vi.fn(async () => new Response('bound')) };
const network = vi.fn(async () => new Response('network'));
vi.stubGlobal('fetch', network);
const boundFetch = createBoundFetch({
PDS_FETCH_BINDINGS: ' Lexicons-PDS.example.workers.dev = LEXICONS ,',
LEXICONS: binding,
});
const url = `${LEXICONS}/xrpc/com.atproto.repo.getRecord`;
expect(await (await boundFetch(url)).text()).toBe('bound');
expect(binding.fetch).toHaveBeenCalledWith(url, undefined);
const request = new Request(`${LEXICONS}/xrpc/_health`);
await boundFetch(request);
expect(binding.fetch).toHaveBeenLastCalledWith(request, undefined);
const elsewhere = 'https://plc.directory/did:plc:abc';
const init = { method: 'GET' };
expect(await (await boundFetch(new URL(elsewhere), init)).text()).toBe(
'network',
);
expect(network).toHaveBeenCalledWith(new URL(elsewhere), init);
});
it('fetches over the network when a pair names no binding', async () => {
const network = vi.fn(async () => new Response('network'));
vi.stubGlobal('fetch', network);
const boundFetch = createBoundFetch({
PDS_FETCH_BINDINGS: 'lexicons-pds.example.workers.dev=MISSING,=X,bare',
X: 'not a binding',
});
await boundFetch(`${LEXICONS}/xrpc/_health`);
expect(network).toHaveBeenCalledTimes(1);
await createBoundFetch({})(`${LEXICONS}/xrpc/_health`);
expect(network).toHaveBeenCalledTimes(2);
});
});