refactor(scripts): auto-resolve PDS from handle, drop BSKY_PDS
Inputs are now just ATPROTO_HANDLE, ATPROTO_APP_PASSWORD, REMANSO_API (or matching --handle, --app-password, --api). The script resolves the handle to a DID via public.api.bsky.app, then resolves the DID to a PDS via plc.directory — same path the server's verifier uses. Works for any AT Protocol PDS (Bluesky, eurosky, self-hosted) without the caller having to know the PDS URL.
This commit is contained in:
@@ -1,21 +1,14 @@
|
||||
// Manage webhook subscriptions on the Remanso API using a Bluesky session
|
||||
// token from your own PDS. Usage:
|
||||
// Manage webhook subscriptions on the Remanso API. Resolves your PDS from
|
||||
// your AT Protocol handle, logs in with an app password, then registers or
|
||||
// deletes webhooks against the Remanso API.
|
||||
//
|
||||
// deno task webhooks register --url https://your-receiver --verb bulk-create
|
||||
// deno task webhooks delete-all
|
||||
//
|
||||
// Required env vars (or matching --flags):
|
||||
// BSKY_IDENTIFIER your handle (e.g. alice.eurosky.social) or email
|
||||
// BSKY_PASSWORD app password (NOT your main password)
|
||||
// BSKY_PDS your PDS base URL (default: https://bsky.social)
|
||||
// REMANSO_API Remanso API base URL (default: https://api.remanso.space)
|
||||
//
|
||||
// Example for an eurosky-hosted account:
|
||||
//
|
||||
// BSKY_IDENTIFIER=alice.eurosky.social \
|
||||
// BSKY_PASSWORD='xxxx-xxxx-xxxx-xxxx' \
|
||||
// BSKY_PDS=https://eurosky.social \
|
||||
// deno task webhooks register --url https://example.com/hook --verb bulk-create
|
||||
// Inputs (env or flag, env preferred):
|
||||
// ATPROTO_HANDLE / --handle e.g. alice.eurosky.social
|
||||
// ATPROTO_APP_PASSWORD / --app-password app password (NOT your account password)
|
||||
// REMANSO_API / --api default: https://api.remanso.space
|
||||
|
||||
const HELP = `
|
||||
Usage:
|
||||
@@ -27,17 +20,19 @@ Usage:
|
||||
|
||||
deno task webhooks delete-all
|
||||
|
||||
Auth (env or flag, env preferred):
|
||||
BSKY_IDENTIFIER / --identifier
|
||||
BSKY_PASSWORD / --password
|
||||
BSKY_PDS / --pds (default: https://bsky.social)
|
||||
REMANSO_API / --api (default: https://api.remanso.space)
|
||||
Inputs (env or flag, env preferred):
|
||||
ATPROTO_HANDLE / --handle your AT Protocol handle
|
||||
ATPROTO_APP_PASSWORD / --app-password app password (NOT your account password)
|
||||
REMANSO_API / --api default: https://api.remanso.space
|
||||
|
||||
Your PDS is resolved automatically from the handle.
|
||||
`;
|
||||
|
||||
type CreateSessionResponse = {
|
||||
did: string;
|
||||
accessJwt: string;
|
||||
type ResolveHandleResponse = { did: string };
|
||||
type DidDocument = {
|
||||
service?: { id: string; serviceEndpoint: string }[];
|
||||
};
|
||||
type CreateSessionResponse = { did: string; accessJwt: string };
|
||||
|
||||
const parseArgs = (args: string[]): Record<string, string> => {
|
||||
const out: Record<string, string> = {};
|
||||
@@ -62,6 +57,35 @@ const die = (msg: string): never => {
|
||||
Deno.exit(1);
|
||||
};
|
||||
|
||||
const resolveHandleToDid = async (handle: string): Promise<string> => {
|
||||
const url =
|
||||
`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${
|
||||
encodeURIComponent(handle)
|
||||
}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`resolveHandle failed for ${handle} (${res.status}): ${await res.text()}`,
|
||||
);
|
||||
}
|
||||
const { did } = await res.json() as ResolveHandleResponse;
|
||||
return did;
|
||||
};
|
||||
|
||||
const resolveDidToPds = async (did: string): Promise<string> => {
|
||||
if (!did.startsWith("did:plc:")) {
|
||||
throw new Error(`Unsupported DID method (server only handles did:plc): ${did}`);
|
||||
}
|
||||
const res = await fetch(`https://plc.directory/${did}`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`plc.directory lookup failed (${res.status})`);
|
||||
}
|
||||
const doc = await res.json() as DidDocument;
|
||||
const pds = doc.service?.find((s) => s.id === "#atproto_pds");
|
||||
if (!pds) throw new Error("No #atproto_pds service in DID document");
|
||||
return pds.serviceEndpoint;
|
||||
};
|
||||
|
||||
const createSession = async (
|
||||
pds: string,
|
||||
identifier: string,
|
||||
@@ -88,17 +112,19 @@ const main = async () => {
|
||||
}
|
||||
|
||||
const flags = parseArgs(rest);
|
||||
const identifier = flags.identifier ?? Deno.env.get("BSKY_IDENTIFIER");
|
||||
const password = flags.password ?? Deno.env.get("BSKY_PASSWORD");
|
||||
const pds = flags.pds ?? Deno.env.get("BSKY_PDS") ?? "https://bsky.social";
|
||||
const handle = flags.handle ?? Deno.env.get("ATPROTO_HANDLE");
|
||||
const password = flags["app-password"] ??
|
||||
Deno.env.get("ATPROTO_APP_PASSWORD");
|
||||
const api = flags.api ?? Deno.env.get("REMANSO_API") ??
|
||||
"https://api.remanso.space";
|
||||
|
||||
if (!identifier) die("BSKY_IDENTIFIER (or --identifier) is required");
|
||||
if (!password) die("BSKY_PASSWORD (or --password) is required");
|
||||
if (!handle) die("ATPROTO_HANDLE (or --handle) is required");
|
||||
if (!password) die("ATPROTO_APP_PASSWORD (or --app-password) is required");
|
||||
|
||||
const session = await createSession(pds, identifier, password);
|
||||
console.log(`[auth] verified DID: ${session.did} via ${pds}`);
|
||||
const did = await resolveHandleToDid(handle);
|
||||
const pds = await resolveDidToPds(did);
|
||||
console.log(`[resolve] ${handle} → ${did} via ${pds}`);
|
||||
const session = await createSession(pds, handle, password);
|
||||
|
||||
if (command === "register") {
|
||||
const url = flags.url ?? die("--url is required for register");
|
||||
|
||||
Reference in New Issue
Block a user