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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
63 lines · 2.3 KB Raw History
#!/usr/bin/env node
// A throwaway OAuth client for driving the authorize flow by hand, so the
// consent screen can be seen and verified. Runs a loopback client through PAR,
// then fetches (and saves) the consent page and prints the authorize URL to
// open in a browser.
//
//   node scripts/oauth-probe.mjs [--scope "..."] [--login-hint <handle|did>]
//
// Env: BASE (default http://localhost:2471).

import { writeFileSync } from 'node:fs';
import { DpopClient, seedDpopNonce } from '../test/helpers/dpop.js';

const BASE = process.env.BASE || 'http://localhost:2471';
const args = process.argv.slice(2);
const arg = (name, fallback) => {
  const i = args.indexOf(name);
  return i >= 0 ? args[i + 1] : fallback;
};

const clientId = 'http://localhost:3000';
const redirectUri = 'http://localhost:3000/callback';
const scope = arg('--scope', 'atproto transition:generic');
const loginHint = arg('--login-hint', 'test.host.docker.internal');

const dpop = await DpopClient.create();
await seedDpopNonce(BASE);
const parProof = await dpop.createProof('POST', `${BASE}/oauth/par`);
const parRes = await fetch(`${BASE}/oauth/par`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    DPoP: parProof,
  },
  body: new URLSearchParams({
    client_id: clientId,
    redirect_uri: redirectUri,
    response_type: 'code',
    scope,
    code_challenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM',
    code_challenge_method: 'S256',
    login_hint: loginHint,
  }).toString(),
});
if (!parRes.ok) {
  console.error(`PAR failed: ${parRes.status} ${await parRes.text()}`);
  process.exit(1);
}
const { request_uri } = await parRes.json();

const authorizeUrl = `${BASE}/oauth/authorize?client_id=${encodeURIComponent(clientId)}&request_uri=${encodeURIComponent(request_uri)}`;
const consent = await fetch(authorizeUrl, { headers: { Accept: 'text/html' } });
const html = await consent.text();
writeFileSync('/tmp/consent.html', html);

console.log(`scope:        ${scope}`);
console.log(
  `consent GET:  ${consent.status} (${html.length} bytes -> /tmp/consent.html)`,
);
console.log(
  `react mode:   ${/window\.__PDS__=JSON\.parse/.test(html) ? 'yes (SPA consent)' : 'no (server-rendered)'}`,
);
console.log(`\nOpen in a browser to click through:\n${authorizeUrl}`);