@getcirrus/pds 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/cli.js +127 -14
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +256 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,6 +177,24 @@ pds migrate --clean # Reset and re-import
|
|
|
177
177
|
pds activate # Go live again
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
### `pds migrate-token`
|
|
181
|
+
|
|
182
|
+
Generates a migration token for migrating away from this PDS to another one.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pds migrate-token # Generate token for production PDS
|
|
186
|
+
pds migrate-token --dev # Generate token for local development PDS
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
When migrating to a new PDS, the destination will ask for a confirmation token. This command generates a stateless HMAC-based token that:
|
|
190
|
+
|
|
191
|
+
- Is valid for 15 minutes
|
|
192
|
+
- Contains your DID and expiry time
|
|
193
|
+
- Is cryptographically signed with your JWT secret
|
|
194
|
+
- Requires no database storage
|
|
195
|
+
|
|
196
|
+
The token is copied to your clipboard and displayed in the terminal. After migration completes, run `pds deactivate` on this PDS.
|
|
197
|
+
|
|
180
198
|
### `pds passkey`
|
|
181
199
|
|
|
182
200
|
Manage passkeys for passwordless authentication.
|
package/dist/cli.js
CHANGED
|
@@ -977,6 +977,27 @@ var PDSClient = class PDSClient {
|
|
|
977
977
|
}
|
|
978
978
|
return res.json();
|
|
979
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Get a migration token for outbound migration.
|
|
982
|
+
* This token can be used to migrate to another PDS.
|
|
983
|
+
*/
|
|
984
|
+
async getMigrationToken() {
|
|
985
|
+
const url = new URL("/xrpc/gg.mk.experimental.getMigrationToken", this.baseUrl);
|
|
986
|
+
const headers = {};
|
|
987
|
+
if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
988
|
+
const res = await fetch(url.toString(), {
|
|
989
|
+
method: "GET",
|
|
990
|
+
headers
|
|
991
|
+
});
|
|
992
|
+
if (!res.ok) return {
|
|
993
|
+
success: false,
|
|
994
|
+
error: (await res.json().catch(() => ({}))).message ?? `Request failed: ${res.status}`
|
|
995
|
+
};
|
|
996
|
+
return {
|
|
997
|
+
success: true,
|
|
998
|
+
token: (await res.json()).token
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
980
1001
|
static RELAY_URLS = ["https://relay1.us-west.bsky.network", "https://relay1.us-east.bsky.network"];
|
|
981
1002
|
/**
|
|
982
1003
|
* Get relay's view of this PDS host status from a single relay.
|
|
@@ -1192,20 +1213,22 @@ async function saveTo1Password(key, handle) {
|
|
|
1192
1213
|
* Captures output and throws on non-zero exit code.
|
|
1193
1214
|
* Use this for running npm/pnpm/yarn scripts etc.
|
|
1194
1215
|
*/
|
|
1195
|
-
function runCommand(cmd, args) {
|
|
1216
|
+
function runCommand(cmd, args, options = {}) {
|
|
1196
1217
|
return new Promise((resolve$1, reject) => {
|
|
1197
|
-
const child = spawn(cmd, args, { stdio: "pipe" });
|
|
1218
|
+
const child = spawn(cmd, args, { stdio: options.stream ? "inherit" : "pipe" });
|
|
1198
1219
|
let output = "";
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1220
|
+
if (!options.stream) {
|
|
1221
|
+
child.stdout?.on("data", (data) => {
|
|
1222
|
+
output += data.toString();
|
|
1223
|
+
});
|
|
1224
|
+
child.stderr?.on("data", (data) => {
|
|
1225
|
+
output += data.toString();
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1205
1228
|
child.on("close", (code) => {
|
|
1206
1229
|
if (code === 0) resolve$1();
|
|
1207
1230
|
else {
|
|
1208
|
-
if (output) console.error(output);
|
|
1231
|
+
if (output && !options.stream) console.error(output);
|
|
1209
1232
|
reject(/* @__PURE__ */ new Error(`${cmd} ${args.join(" ")} failed with code ${code}`));
|
|
1210
1233
|
}
|
|
1211
1234
|
});
|
|
@@ -2098,14 +2121,12 @@ const initCommand = defineCommand({
|
|
|
2098
2121
|
initialValue: true
|
|
2099
2122
|
});
|
|
2100
2123
|
if (!p.isCancel(deployWorker) && deployWorker) {
|
|
2101
|
-
|
|
2124
|
+
p.log.step("Deploying to Cloudflare...");
|
|
2102
2125
|
try {
|
|
2103
|
-
await runCommand(pm
|
|
2104
|
-
|
|
2105
|
-
spinner.stop("Deployed to Cloudflare! 🚀");
|
|
2126
|
+
await runCommand(pm, ["run", "deploy"], { stream: true });
|
|
2127
|
+
p.log.success("Deployed to Cloudflare! 🚀");
|
|
2106
2128
|
deployed = true;
|
|
2107
2129
|
} catch (error) {
|
|
2108
|
-
spinner.stop("Deployment failed");
|
|
2109
2130
|
p.log.error(`Failed to deploy: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2110
2131
|
p.log.info(`You can deploy manually with: ${formatCommand(pm, "deploy")}`);
|
|
2111
2132
|
}
|
|
@@ -2495,6 +2516,97 @@ function showNextSteps(pm, sourceDomain) {
|
|
|
2495
2516
|
]), "Almost there!");
|
|
2496
2517
|
}
|
|
2497
2518
|
|
|
2519
|
+
//#endregion
|
|
2520
|
+
//#region src/cli/commands/migrate-token.ts
|
|
2521
|
+
/**
|
|
2522
|
+
* Generate a migration token for outbound migration
|
|
2523
|
+
*
|
|
2524
|
+
* Calls the PDS API to generate a stateless HMAC token that another PDS
|
|
2525
|
+
* can use to request a signed PLC operation. The token is valid for 15 minutes.
|
|
2526
|
+
*/
|
|
2527
|
+
const migrateTokenCommand = defineCommand({
|
|
2528
|
+
meta: {
|
|
2529
|
+
name: "migrate-token",
|
|
2530
|
+
description: "Generate a migration token for moving to another PDS"
|
|
2531
|
+
},
|
|
2532
|
+
args: { dev: {
|
|
2533
|
+
type: "boolean",
|
|
2534
|
+
description: "Target local development server instead of production",
|
|
2535
|
+
default: false
|
|
2536
|
+
} },
|
|
2537
|
+
async run({ args }) {
|
|
2538
|
+
const isDev = args.dev;
|
|
2539
|
+
const pm = detectPackageManager();
|
|
2540
|
+
p.intro("Generate Migration Token");
|
|
2541
|
+
const spinner = p.spinner();
|
|
2542
|
+
spinner.start("Loading configuration...");
|
|
2543
|
+
const wranglerVars = getVars();
|
|
2544
|
+
const config = {
|
|
2545
|
+
...readDevVars(),
|
|
2546
|
+
...wranglerVars
|
|
2547
|
+
};
|
|
2548
|
+
const pdsHostname = config.PDS_HOSTNAME;
|
|
2549
|
+
const authToken = config.AUTH_TOKEN;
|
|
2550
|
+
if (!pdsHostname && !isDev) {
|
|
2551
|
+
spinner.stop("No PDS_HOSTNAME configured");
|
|
2552
|
+
p.log.error("Run 'pds init' first to set up your PDS.");
|
|
2553
|
+
p.outro("Token generation cancelled.");
|
|
2554
|
+
process.exit(1);
|
|
2555
|
+
}
|
|
2556
|
+
if (!authToken) {
|
|
2557
|
+
spinner.stop("No AUTH_TOKEN found");
|
|
2558
|
+
p.log.error("AUTH_TOKEN is required to authenticate with your PDS.");
|
|
2559
|
+
p.outro("Token generation cancelled.");
|
|
2560
|
+
process.exit(1);
|
|
2561
|
+
}
|
|
2562
|
+
let targetUrl;
|
|
2563
|
+
try {
|
|
2564
|
+
targetUrl = getTargetUrl(isDev, pdsHostname);
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
spinner.stop("Configuration error");
|
|
2567
|
+
p.log.error(err instanceof Error ? err.message : "Configuration error");
|
|
2568
|
+
p.outro("Token generation cancelled.");
|
|
2569
|
+
process.exit(1);
|
|
2570
|
+
}
|
|
2571
|
+
spinner.stop("Configuration loaded");
|
|
2572
|
+
spinner.start("Connecting to PDS...");
|
|
2573
|
+
const pdsClient = new PDSClient(targetUrl, authToken);
|
|
2574
|
+
if (!await pdsClient.healthCheck()) {
|
|
2575
|
+
spinner.stop("PDS not responding");
|
|
2576
|
+
p.log.error(`Your PDS isn't responding at ${targetUrl}`);
|
|
2577
|
+
if (isDev) p.log.info(`Start it with: ${formatCommand(pm, "dev")}`);
|
|
2578
|
+
else p.log.info(`Make sure your worker is deployed: ${formatCommand(pm, "deploy")}`);
|
|
2579
|
+
p.outro("Token generation cancelled.");
|
|
2580
|
+
process.exit(1);
|
|
2581
|
+
}
|
|
2582
|
+
spinner.stop("Connected to PDS");
|
|
2583
|
+
spinner.start("Generating migration token...");
|
|
2584
|
+
const result = await pdsClient.getMigrationToken();
|
|
2585
|
+
if (!result.success || !result.token) {
|
|
2586
|
+
spinner.stop("Failed to generate token");
|
|
2587
|
+
p.log.error(result.error ?? "Could not generate migration token");
|
|
2588
|
+
p.outro("Token generation cancelled.");
|
|
2589
|
+
process.exit(1);
|
|
2590
|
+
}
|
|
2591
|
+
spinner.stop("Token generated");
|
|
2592
|
+
if (await copyToClipboard(result.token)) p.log.success("Migration token copied to clipboard!");
|
|
2593
|
+
else p.log.info("Could not copy to clipboard. Token:");
|
|
2594
|
+
console.log("");
|
|
2595
|
+
console.log(pc.bold(result.token));
|
|
2596
|
+
console.log("");
|
|
2597
|
+
p.note([
|
|
2598
|
+
"This token is valid for 15 minutes.",
|
|
2599
|
+
"",
|
|
2600
|
+
"Paste it into the new PDS when prompted for",
|
|
2601
|
+
"a migration/confirmation code.",
|
|
2602
|
+
"",
|
|
2603
|
+
"Once migration is complete, you can deactivate",
|
|
2604
|
+
"this PDS with: pnpm pds deactivate"
|
|
2605
|
+
].join("\n"), "Next Steps");
|
|
2606
|
+
p.outro("Good luck with the migration!");
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2498
2610
|
//#endregion
|
|
2499
2611
|
//#region src/cli/utils/plc-client.ts
|
|
2500
2612
|
/**
|
|
@@ -3724,6 +3836,7 @@ runMain(defineCommand({
|
|
|
3724
3836
|
secret: secretCommand,
|
|
3725
3837
|
passkey: passkeyCommand,
|
|
3726
3838
|
migrate: migrateCommand,
|
|
3839
|
+
"migrate-token": migrateTokenCommand,
|
|
3727
3840
|
identity: identityCommand,
|
|
3728
3841
|
activate: activateCommand,
|
|
3729
3842
|
deactivate: deactivateCommand,
|
package/dist/index.d.ts
CHANGED
|
@@ -439,6 +439,13 @@ declare class AccountDurableObject extends DurableObject<PDSEnv> {
|
|
|
439
439
|
* Used for partial sync and migration.
|
|
440
440
|
*/
|
|
441
441
|
rpcGetBlocks(cids: string[]): Promise<Uint8Array>;
|
|
442
|
+
/**
|
|
443
|
+
* RPC method: Get record with proof as CAR file.
|
|
444
|
+
* Returns the commit block and all MST blocks needed to verify
|
|
445
|
+
* the existence (or non-existence) of a record.
|
|
446
|
+
* Used by com.atproto.sync.getRecord for record verification.
|
|
447
|
+
*/
|
|
448
|
+
rpcGetRecordProof(collection: string, rkey: string): Promise<Uint8Array>;
|
|
442
449
|
/**
|
|
443
450
|
* RPC method: Import repo from CAR file
|
|
444
451
|
* This is used for account migration - importing an existing repository
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/storage.ts","../src/oauth-storage.ts","../src/blobs.ts","../src/types.ts","../src/account-do.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;cAUa,iBAAA,SACJ,kBAAA,YACG;EAFC,QAAA,GAAA;EAIa,WAAA,CAAA,GAAA,EAAA,UAAA;EA2FA;;;;EAiCR,UAAA,CAAA,aAAA,CAAA,EAAA,OAAA,CAAA,EAAA,IAAA;EAQG;;;EAcL,OAAA,CAAA,CAAA,EAvDE,OAuDF,CAvDU,GAuDV,GAAA,IAAA,CAAA;EAAM;;;EAU8C,MAAA,CAAA,CAAA,EApDnD,OAoDmD,CAAA,MAAA,GAAA,IAAA,CAAA;EAArC;;;EAmB4B,MAAA,CAAA,CAAA,EA7D1C,OA6D0C,CAAA,MAAA,CAAA;EAYpC;;;EAoBmB,OAAA,CAAA,CAAA,EAnFxB,OAmFwB,CAAA,MAAA,CAAA;EAWf;;;EA6CT,QAAA,CAAA,GAAA,EAnIG,GAmIH,CAAA,EAnIS,OAmIT,CAnIiB,UAmIjB,GAAA,IAAA,CAAA;EAUI;;;EAoCF,GAAA,CAAA,GAAA,EAnKJ,GAmKI,CAAA,EAnKE,OAmKF,CAAA,OAAA,CAAA;EAUe;;;EA2JtB,SAAA,CAAA,IAAA,EA9TU,GA8TV,EAAA,CAAA,EA9TkB,OA8TlB,CAAA;IA8BI,MAAA,EA5VgC,QA4VhC;IA3fR,OAAA,EA+J2D,GA/J3D,EAAA;EACG,CAAA,CAAA;EAAW;;;gBAiLF,YAAY,0BAA0B;EC/K9C;;;EAuG0C,OAAA,CAAA,MAAA,EDoFhC,QCpFgC,EAAA,GAAA,EAAA,MAAA,CAAA,EDoFR,OCpFQ,CAAA,IAAA,CAAA;EAgBb;;;EAsClB,UAAA,CAAA,GAAA,EDkDD,GClDC,EAAA,GAAA,EAAA,MAAA,CAAA,EDkDkB,OClDlB,CAAA,IAAA,CAAA;EAAY;;;EAiDoB,WAAA,CAAA,MAAA,EDY7B,UCZ6B,CAAA,EDYhB,OCZgB,CAAA,IAAA,CAAA;EAAR;;;EA4CF,WAAA,CAAA,CAAA,EDGxB,OCHwB,CAAA,MAAA,CAAA;EAAiB;;;EAwCtB,OAAA,CAAA,CAAA,ED3BvB,OC2BuB,CAAA,IAAA,CAAA;EAAU;;;EAuCb,WAAA,CAAA,CAAA,EDxDhB,OCwDgB,CAAA,MAAA,CAAA;EAWG;;;oBDzDhB;;;AEvSzB;0CFyT+C;;;AGpT/C;EAkBiC,SAAA,CAAA,CAAA,EH0Sb,OG1Sa,CAAA,OAAA,CAAA;EAAvB;;;8BHoTyB;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/storage.ts","../src/oauth-storage.ts","../src/blobs.ts","../src/types.ts","../src/account-do.ts","../src/index.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurableObject, env, waitUntil } from "cloudflare:workers";
|
|
2
|
-
import { BlockMap, ReadableBlockstore, Repo, WriteOpAction, blocksToCarFile, readCarWithRoot } from "@atproto/repo";
|
|
2
|
+
import { BlockMap, ReadableBlockstore, Repo, WriteOpAction, blocksToCarFile, getRecords, readCarWithRoot } from "@atproto/repo";
|
|
3
3
|
import { Secp256k1Keypair, randomStr, verifySignature } from "@atproto/crypto";
|
|
4
4
|
import { CID, asCid, isBlobRef } from "@atproto/lex-data";
|
|
5
5
|
import { now } from "@atcute/tid";
|
|
@@ -7,8 +7,8 @@ import { decode, encode, fromBytes, isBytes, toBytes, toCidLink } from "@atcute/
|
|
|
7
7
|
import { CODEC_RAW, create, fromString, toString } from "@atcute/cid";
|
|
8
8
|
import { Hono } from "hono";
|
|
9
9
|
import { cors } from "hono/cors";
|
|
10
|
-
import { isDid, isHandle } from "@atcute/lexicons/syntax";
|
|
11
|
-
import { SignJWT, jwtVerify } from "jose";
|
|
10
|
+
import { isDid, isHandle, isNsid, isRecordKey } from "@atcute/lexicons/syntax";
|
|
11
|
+
import { SignJWT, base64url, jwtVerify } from "jose";
|
|
12
12
|
import { compare, compare as compare$1 } from "bcryptjs";
|
|
13
13
|
import { ATProtoOAuthProvider } from "@getcirrus/oauth-provider";
|
|
14
14
|
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
|
|
@@ -1365,6 +1365,30 @@ var AccountDurableObject = class extends DurableObject {
|
|
|
1365
1365
|
return blocksToCarFile(root, blocks);
|
|
1366
1366
|
}
|
|
1367
1367
|
/**
|
|
1368
|
+
* RPC method: Get record with proof as CAR file.
|
|
1369
|
+
* Returns the commit block and all MST blocks needed to verify
|
|
1370
|
+
* the existence (or non-existence) of a record.
|
|
1371
|
+
* Used by com.atproto.sync.getRecord for record verification.
|
|
1372
|
+
*/
|
|
1373
|
+
async rpcGetRecordProof(collection, rkey) {
|
|
1374
|
+
const storage = await this.getStorage();
|
|
1375
|
+
const root = await storage.getRoot();
|
|
1376
|
+
if (!root) throw new Error("No repository root found");
|
|
1377
|
+
const carChunks = [];
|
|
1378
|
+
for await (const chunk of getRecords(storage, root, [{
|
|
1379
|
+
collection,
|
|
1380
|
+
rkey
|
|
1381
|
+
}])) carChunks.push(chunk);
|
|
1382
|
+
const totalLength = carChunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
1383
|
+
const result = new Uint8Array(totalLength);
|
|
1384
|
+
let offset = 0;
|
|
1385
|
+
for (const chunk of carChunks) {
|
|
1386
|
+
result.set(chunk, offset);
|
|
1387
|
+
offset += chunk.length;
|
|
1388
|
+
}
|
|
1389
|
+
return result;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1368
1392
|
* RPC method: Import repo from CAR file
|
|
1369
1393
|
* This is used for account migration - importing an existing repository
|
|
1370
1394
|
* from another PDS.
|
|
@@ -1816,7 +1840,7 @@ function extractBlobCids(obj) {
|
|
|
1816
1840
|
|
|
1817
1841
|
//#endregion
|
|
1818
1842
|
//#region src/service-auth.ts
|
|
1819
|
-
const
|
|
1843
|
+
const SERVICE_JWT_EXPIRY_SECONDS = 300;
|
|
1820
1844
|
/**
|
|
1821
1845
|
* Shared keypair cache for signing and verification.
|
|
1822
1846
|
*/
|
|
@@ -1847,7 +1871,7 @@ function noUndefinedVals(obj) {
|
|
|
1847
1871
|
async function createServiceJwt(params) {
|
|
1848
1872
|
const { iss, aud, keypair } = params;
|
|
1849
1873
|
const iat = Math.floor(Date.now() / 1e3);
|
|
1850
|
-
const exp = iat +
|
|
1874
|
+
const exp = iat + SERVICE_JWT_EXPIRY_SECONDS;
|
|
1851
1875
|
const lxm = params.lxm ?? void 0;
|
|
1852
1876
|
const jti = randomStr(16, "hex");
|
|
1853
1877
|
const header = {
|
|
@@ -1893,7 +1917,7 @@ async function verifyServiceJwt(token, signingKey, expectedAudience, expectedIss
|
|
|
1893
1917
|
|
|
1894
1918
|
//#endregion
|
|
1895
1919
|
//#region src/session.ts
|
|
1896
|
-
const ACCESS_TOKEN_LIFETIME = "
|
|
1920
|
+
const ACCESS_TOKEN_LIFETIME = "15m";
|
|
1897
1921
|
const REFRESH_TOKEN_LIFETIME = "90d";
|
|
1898
1922
|
/**
|
|
1899
1923
|
* Create a secret key from string for HS256 signing
|
|
@@ -2387,7 +2411,7 @@ async function requireAuth(c, next) {
|
|
|
2387
2411
|
* Uses @atcute/identity-resolver which is already Workers-compatible
|
|
2388
2412
|
* (uses redirect: "manual" internally).
|
|
2389
2413
|
*/
|
|
2390
|
-
const PLC_DIRECTORY = "https://plc.directory";
|
|
2414
|
+
const PLC_DIRECTORY$1 = "https://plc.directory";
|
|
2391
2415
|
const TIMEOUT_MS = 3e3;
|
|
2392
2416
|
/**
|
|
2393
2417
|
* Wrapper that always uses globalThis.fetch so it can be mocked in tests.
|
|
@@ -2404,7 +2428,7 @@ var DidResolver = class {
|
|
|
2404
2428
|
this.cache = opts.didCache;
|
|
2405
2429
|
this.resolver = new CompositeDidDocumentResolver({ methods: {
|
|
2406
2430
|
plc: new PlcDidDocumentResolver({
|
|
2407
|
-
apiUrl: opts.plcUrl ?? PLC_DIRECTORY,
|
|
2431
|
+
apiUrl: opts.plcUrl ?? PLC_DIRECTORY$1,
|
|
2408
2432
|
fetch: stubbableFetch
|
|
2409
2433
|
}),
|
|
2410
2434
|
web: new WebDidDocumentResolver({ fetch: stubbableFetch })
|
|
@@ -2753,6 +2777,55 @@ async function getBlob(c, _accountDO) {
|
|
|
2753
2777
|
}
|
|
2754
2778
|
});
|
|
2755
2779
|
}
|
|
2780
|
+
async function getRecord$1(c, accountDO) {
|
|
2781
|
+
const did = c.req.query("did");
|
|
2782
|
+
const collection = c.req.query("collection");
|
|
2783
|
+
const rkey = c.req.query("rkey");
|
|
2784
|
+
if (!did) return c.json({
|
|
2785
|
+
error: "InvalidRequest",
|
|
2786
|
+
message: "Missing required parameter: did"
|
|
2787
|
+
}, 400);
|
|
2788
|
+
if (!collection) return c.json({
|
|
2789
|
+
error: "InvalidRequest",
|
|
2790
|
+
message: "Missing required parameter: collection"
|
|
2791
|
+
}, 400);
|
|
2792
|
+
if (!rkey) return c.json({
|
|
2793
|
+
error: "InvalidRequest",
|
|
2794
|
+
message: "Missing required parameter: rkey"
|
|
2795
|
+
}, 400);
|
|
2796
|
+
if (!isDid(did)) return c.json({
|
|
2797
|
+
error: "InvalidRequest",
|
|
2798
|
+
message: "Invalid DID format"
|
|
2799
|
+
}, 400);
|
|
2800
|
+
if (!isNsid(collection)) return c.json({
|
|
2801
|
+
error: "InvalidRequest",
|
|
2802
|
+
message: "Invalid collection format (must be NSID)"
|
|
2803
|
+
}, 400);
|
|
2804
|
+
if (!isRecordKey(rkey)) return c.json({
|
|
2805
|
+
error: "InvalidRequest",
|
|
2806
|
+
message: "Invalid rkey format"
|
|
2807
|
+
}, 400);
|
|
2808
|
+
if (did !== c.env.DID) return c.json({
|
|
2809
|
+
error: "RepoNotFound",
|
|
2810
|
+
message: `Repository not found for DID: ${did}`
|
|
2811
|
+
}, 404);
|
|
2812
|
+
try {
|
|
2813
|
+
const carBytes = await accountDO.rpcGetRecordProof(collection, rkey);
|
|
2814
|
+
return new Response(carBytes, {
|
|
2815
|
+
status: 200,
|
|
2816
|
+
headers: {
|
|
2817
|
+
"Content-Type": "application/vnd.ipld.car",
|
|
2818
|
+
"Content-Length": carBytes.length.toString()
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
} catch (err) {
|
|
2822
|
+
console.error("Error getting record proof:", err);
|
|
2823
|
+
return c.json({
|
|
2824
|
+
error: "InternalServerError",
|
|
2825
|
+
message: "Failed to get record proof"
|
|
2826
|
+
}, 500);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2756
2829
|
|
|
2757
2830
|
//#endregion
|
|
2758
2831
|
//#region src/validation.ts
|
|
@@ -3334,6 +3407,176 @@ async function resetMigration(c, accountDO) {
|
|
|
3334
3407
|
}
|
|
3335
3408
|
}
|
|
3336
3409
|
|
|
3410
|
+
//#endregion
|
|
3411
|
+
//#region src/migration-token.ts
|
|
3412
|
+
/**
|
|
3413
|
+
* Stateless migration tokens for outbound account migration
|
|
3414
|
+
*
|
|
3415
|
+
* Uses HMAC-SHA256 to create tokens that encode the DID and expiry time.
|
|
3416
|
+
* No database storage required - validity is verified by the signature.
|
|
3417
|
+
*
|
|
3418
|
+
* Token format: base64url(payload).base64url(signature)
|
|
3419
|
+
* Payload: {"did":"did:plc:xxx","exp":1736600000}
|
|
3420
|
+
*
|
|
3421
|
+
* Tokens expire after 15 minutes - enough time to complete the migration
|
|
3422
|
+
* process but short enough to limit exposure if the token is leaked.
|
|
3423
|
+
*/
|
|
3424
|
+
const TOKEN_EXPIRY = 15 * (60 * 1e3);
|
|
3425
|
+
/**
|
|
3426
|
+
* Create an HMAC-SHA256 signature
|
|
3427
|
+
*/
|
|
3428
|
+
async function hmacSign(data, secret) {
|
|
3429
|
+
const encoder = new TextEncoder();
|
|
3430
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
3431
|
+
name: "HMAC",
|
|
3432
|
+
hash: "SHA-256"
|
|
3433
|
+
}, false, ["sign"]);
|
|
3434
|
+
return crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
3435
|
+
}
|
|
3436
|
+
/**
|
|
3437
|
+
* Verify an HMAC-SHA256 signature
|
|
3438
|
+
*/
|
|
3439
|
+
async function hmacVerify(data, signature, secret) {
|
|
3440
|
+
const encoder = new TextEncoder();
|
|
3441
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
3442
|
+
name: "HMAC",
|
|
3443
|
+
hash: "SHA-256"
|
|
3444
|
+
}, false, ["verify"]);
|
|
3445
|
+
return crypto.subtle.verify("HMAC", key, signature, encoder.encode(data));
|
|
3446
|
+
}
|
|
3447
|
+
/**
|
|
3448
|
+
* Create a migration token for outbound migration
|
|
3449
|
+
*
|
|
3450
|
+
* @param did - The user's DID
|
|
3451
|
+
* @param jwtSecret - The JWT_SECRET used for signing
|
|
3452
|
+
* @returns A stateless, signed token
|
|
3453
|
+
*/
|
|
3454
|
+
async function createMigrationToken(did, jwtSecret) {
|
|
3455
|
+
const payload = {
|
|
3456
|
+
did,
|
|
3457
|
+
exp: Math.floor((Date.now() + TOKEN_EXPIRY) / 1e3)
|
|
3458
|
+
};
|
|
3459
|
+
const payloadStr = JSON.stringify(payload);
|
|
3460
|
+
const payloadB64 = base64url.encode(new TextEncoder().encode(payloadStr));
|
|
3461
|
+
const signature = await hmacSign(payloadB64, jwtSecret);
|
|
3462
|
+
return `${payloadB64}.${base64url.encode(new Uint8Array(signature))}`;
|
|
3463
|
+
}
|
|
3464
|
+
/**
|
|
3465
|
+
* Validate a migration token
|
|
3466
|
+
*
|
|
3467
|
+
* @param token - The token to validate
|
|
3468
|
+
* @param expectedDid - The expected DID (must match token payload)
|
|
3469
|
+
* @param jwtSecret - The JWT_SECRET used for verification
|
|
3470
|
+
* @returns The payload if valid, null if invalid/expired
|
|
3471
|
+
*/
|
|
3472
|
+
async function validateMigrationToken(token, expectedDid, jwtSecret) {
|
|
3473
|
+
const parts = token.split(".");
|
|
3474
|
+
if (parts.length !== 2) return null;
|
|
3475
|
+
const [payloadB64, signatureB64] = parts;
|
|
3476
|
+
try {
|
|
3477
|
+
if (!await hmacVerify(payloadB64, base64url.decode(signatureB64).buffer, jwtSecret)) return null;
|
|
3478
|
+
const payloadStr = new TextDecoder().decode(base64url.decode(payloadB64));
|
|
3479
|
+
const payload = JSON.parse(payloadStr);
|
|
3480
|
+
if (payload.did !== expectedDid) return null;
|
|
3481
|
+
const now$1 = Math.floor(Date.now() / 1e3);
|
|
3482
|
+
if (payload.exp < now$1) return null;
|
|
3483
|
+
return payload;
|
|
3484
|
+
} catch {
|
|
3485
|
+
return null;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
//#endregion
|
|
3490
|
+
//#region src/xrpc/identity.ts
|
|
3491
|
+
const PLC_DIRECTORY = "https://plc.directory";
|
|
3492
|
+
/**
|
|
3493
|
+
* Request a PLC operation signature for outbound migration.
|
|
3494
|
+
*
|
|
3495
|
+
* In Bluesky's implementation, this sends an email with a token.
|
|
3496
|
+
* In Cirrus, we're single-user with no email, so we just return success.
|
|
3497
|
+
* The user gets the token via `pds migrate-token` CLI.
|
|
3498
|
+
*
|
|
3499
|
+
* Endpoint: POST com.atproto.identity.requestPlcOperationSignature
|
|
3500
|
+
*/
|
|
3501
|
+
async function requestPlcOperationSignature(c) {
|
|
3502
|
+
return new Response(null, { status: 200 });
|
|
3503
|
+
}
|
|
3504
|
+
/**
|
|
3505
|
+
* Sign a PLC operation for migrating to a new PDS.
|
|
3506
|
+
*
|
|
3507
|
+
* Validates the migration token and returns a signed PLC operation
|
|
3508
|
+
* that updates the DID document to point to the new PDS.
|
|
3509
|
+
*
|
|
3510
|
+
* Endpoint: POST com.atproto.identity.signPlcOperation
|
|
3511
|
+
*/
|
|
3512
|
+
async function signPlcOperation(c) {
|
|
3513
|
+
const body = await c.req.json();
|
|
3514
|
+
const { token } = body;
|
|
3515
|
+
if (!token) return c.json({
|
|
3516
|
+
error: "InvalidRequest",
|
|
3517
|
+
message: "Missing required parameter: token"
|
|
3518
|
+
}, 400);
|
|
3519
|
+
if (!await validateMigrationToken(token, c.env.DID, c.env.JWT_SECRET)) return c.json({
|
|
3520
|
+
error: "InvalidToken",
|
|
3521
|
+
message: "Invalid or expired migration token"
|
|
3522
|
+
}, 400);
|
|
3523
|
+
const currentOp = await getLatestPlcOperation(c.env.DID);
|
|
3524
|
+
if (!currentOp) return c.json({
|
|
3525
|
+
error: "InternalServerError",
|
|
3526
|
+
message: "Could not fetch current PLC state"
|
|
3527
|
+
}, 500);
|
|
3528
|
+
const signedOp = await signOperation({
|
|
3529
|
+
type: "plc_operation",
|
|
3530
|
+
prev: currentOp.cid,
|
|
3531
|
+
rotationKeys: body.rotationKeys ?? currentOp.operation.rotationKeys,
|
|
3532
|
+
alsoKnownAs: body.alsoKnownAs ?? currentOp.operation.alsoKnownAs,
|
|
3533
|
+
verificationMethods: body.verificationMethods ?? currentOp.operation.verificationMethods,
|
|
3534
|
+
services: body.services ?? currentOp.operation.services
|
|
3535
|
+
}, await Secp256k1Keypair.import(c.env.SIGNING_KEY));
|
|
3536
|
+
return c.json({ operation: signedOp });
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Get the latest PLC operation for a DID
|
|
3540
|
+
*/
|
|
3541
|
+
async function getLatestPlcOperation(did) {
|
|
3542
|
+
try {
|
|
3543
|
+
const res = await fetch(`${PLC_DIRECTORY}/${did}/log/audit`);
|
|
3544
|
+
if (!res.ok) return null;
|
|
3545
|
+
return (await res.json()).filter((op) => !op.nullified).pop() ?? null;
|
|
3546
|
+
} catch {
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
/**
|
|
3551
|
+
* Sign a PLC operation with the given keypair
|
|
3552
|
+
*
|
|
3553
|
+
* PLC operations are signed by:
|
|
3554
|
+
* 1. CBOR-encoding the unsigned operation
|
|
3555
|
+
* 2. Signing the bytes with secp256k1
|
|
3556
|
+
* 3. Adding the signature as base64url
|
|
3557
|
+
*/
|
|
3558
|
+
async function signOperation(op, keypair) {
|
|
3559
|
+
const bytes = encode(op);
|
|
3560
|
+
const sig = await keypair.sign(bytes);
|
|
3561
|
+
return {
|
|
3562
|
+
...op,
|
|
3563
|
+
sig: base64url.encode(sig)
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* Generate a migration token for the CLI.
|
|
3568
|
+
*
|
|
3569
|
+
* This endpoint allows the CLI to generate a token that can be used
|
|
3570
|
+
* to complete an outbound migration without requiring the secret
|
|
3571
|
+
* to be available client-side.
|
|
3572
|
+
*
|
|
3573
|
+
* Endpoint: GET gg.mk.experimental.getMigrationToken
|
|
3574
|
+
*/
|
|
3575
|
+
async function getMigrationToken(c) {
|
|
3576
|
+
const token = await createMigrationToken(c.env.DID, c.env.JWT_SECRET);
|
|
3577
|
+
return c.json({ token });
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3337
3580
|
//#endregion
|
|
3338
3581
|
//#region src/passkey-ui.ts
|
|
3339
3582
|
/**
|
|
@@ -3755,7 +3998,7 @@ function renderPasskeyErrorPage(error, description) {
|
|
|
3755
3998
|
|
|
3756
3999
|
//#endregion
|
|
3757
4000
|
//#region package.json
|
|
3758
|
-
var version = "0.
|
|
4001
|
+
var version = "0.9.0";
|
|
3759
4002
|
|
|
3760
4003
|
//#endregion
|
|
3761
4004
|
//#region src/index.ts
|
|
@@ -3886,6 +4129,7 @@ app.get("/xrpc/com.atproto.sync.getBlocks", (c) => getBlocks(c, getAccountDO(c.e
|
|
|
3886
4129
|
app.get("/xrpc/com.atproto.sync.getBlob", (c) => getBlob(c, getAccountDO(c.env)));
|
|
3887
4130
|
app.get("/xrpc/com.atproto.sync.listRepos", (c) => listRepos(c, getAccountDO(c.env)));
|
|
3888
4131
|
app.get("/xrpc/com.atproto.sync.listBlobs", (c) => listBlobs(c, getAccountDO(c.env)));
|
|
4132
|
+
app.get("/xrpc/com.atproto.sync.getRecord", (c) => getRecord$1(c, getAccountDO(c.env)));
|
|
3889
4133
|
app.get("/xrpc/com.atproto.sync.subscribeRepos", async (c) => {
|
|
3890
4134
|
if (c.req.header("Upgrade") !== "websocket") return c.json({
|
|
3891
4135
|
error: "InvalidRequest",
|
|
@@ -3920,6 +4164,9 @@ app.use("/xrpc/com.atproto.identity.resolveHandle", async (c, next) => {
|
|
|
3920
4164
|
if (c.req.query("handle") === c.env.HANDLE) return c.json({ did: c.env.DID });
|
|
3921
4165
|
await next();
|
|
3922
4166
|
});
|
|
4167
|
+
app.post("/xrpc/com.atproto.identity.requestPlcOperationSignature", requireAuth, requestPlcOperationSignature);
|
|
4168
|
+
app.post("/xrpc/com.atproto.identity.signPlcOperation", requireAuth, signPlcOperation);
|
|
4169
|
+
app.get("/xrpc/gg.mk.experimental.getMigrationToken", requireAuth, getMigrationToken);
|
|
3923
4170
|
app.post("/xrpc/com.atproto.server.createSession", createSession);
|
|
3924
4171
|
app.post("/xrpc/com.atproto.server.refreshSession", refreshSession);
|
|
3925
4172
|
app.get("/xrpc/com.atproto.server.getSession", getSession);
|