chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// @pdsjs/cloudflare/cf-api-proxy - a passthrough to api.cloudflare.com for
// the setup wizard, which runs as a static site and cannot call the API from
// the browser: api.cloudflare.com answers no CORS preflight. The proxy
// forwards the caller's Authorization header, allows only the paths the
// wizard uses, and keeps nothing. Enabled by PDS_EXPERIMENTAL_CF_API_PROXY;
// the prefix answers on every hostname the worker serves, so a site page
// reaches it same-origin.
const CF_API = 'https://api.cloudflare.com';
// Everything the wizard touches hangs off the visitor's own account: their
// account paths, their zone list, and DNS records in a zone they name (the
// handle-verification TXT record). Zone and record ids are 32 hex chars.
const PATH_ALLOWED =
/^\/client\/v4\/(accounts(\/|$)|oauth\/scopes$|zones$|zones\/[a-f0-9]{32}\/dns_records(\/[a-f0-9]{32})?$)/;
export const CF_API_PROXY_PREFIX = '/cf-api/';
const CORS_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
'Access-Control-Max-Age': '86400',
};
/**
* @param {{PDS_EXPERIMENTAL_CF_API_PROXY?: string}} env
*/
export function cfApiProxyEnabled(env) {
return env.PDS_EXPERIMENTAL_CF_API_PROXY === 'true';
}
/**
* @param {unknown} body
* @param {number} status
*/
function json(body, status) {
return Response.json(body, { status, headers: CORS_HEADERS });
}
/**
* Handle a request whose path starts with the proxy prefix.
* @param {Request} request
* @returns {Promise<Response>}
*/
export async function handleCfApiProxy(request) {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
const url = new URL(request.url);
const apiPath = url.pathname.slice(CF_API_PROXY_PREFIX.length - 1);
if (!PATH_ALLOWED.test(apiPath)) {
return json({ error: 'PathNotAllowed', message: 'Path not proxied' }, 403);
}
const auth = request.headers.get('Authorization');
if (!auth) {
return json(
{ error: 'AuthenticationRequired', message: 'Missing token' },
401,
);
}
/** @type {Record<string, string>} */
const headers = { Authorization: auth };
const contentType = request.headers.get('Content-Type');
if (contentType) headers['Content-Type'] = contentType;
const response = await fetch(`${CF_API}${apiPath}${url.search}`, {
method: request.method,
headers,
body:
request.method === 'GET' || request.method === 'HEAD'
? undefined
: request.body,
});
// A fresh Response strips Set-Cookie and lets the body stream through.
return new Response(response.body, {
status: response.status,
headers: {
...CORS_HEADERS,
'Content-Type': response.headers.get('Content-Type') ?? 'text/plain',
},
});
}