Graphical PDS migrator for AT Protocol
1import { getSessionAgent } from "../../../lib/sessions.ts";
2import { define } from "../../../utils.ts";
3import * as plc from "@did-plc/lib";
4
5/**
6 * Handle PLC update operation
7 * Body must contain:
8 * - key: The new rotation key to add
9 * - token: The email token received from requestPlcOperationSignature
10 * @param ctx - The context object containing the request and response
11 * @returns A response object with the update result
12 */
13export const handler = define.handlers({
14 async POST(ctx) {
15 const res = new Response();
16 try {
17 console.log("=== PLC Update Debug ===");
18 const body = await ctx.req.json();
19 const { key: newKey, token } = body;
20 console.log("Request body:", { newKey, hasToken: !!token });
21
22 if (!newKey) {
23 console.log("Missing key in request");
24 return new Response("Missing param key in request body", {
25 status: 400,
26 });
27 }
28
29 if (!token) {
30 console.log("Missing token in request");
31 return new Response("Missing param token in request body", {
32 status: 400,
33 });
34 }
35
36 const agent = await getSessionAgent(ctx.req, res);
37 if (!agent) {
38 console.log("No agent found");
39 return new Response("Unauthorized", { status: 401 });
40 }
41
42 const session = await agent.com.atproto.server.getSession();
43 const did = session.data.did;
44 if (!did) {
45 console.log("No DID found in session");
46 return new Response(
47 JSON.stringify({
48 success: false,
49 message: "No DID found in your session",
50 }),
51 {
52 status: 400,
53 headers: { "Content-Type": "application/json" },
54 },
55 );
56 }
57 console.log("Using agent DID:", did);
58
59 // Get recommended credentials first
60 console.log("Getting did:plc document...");
61 const plcClient = new plc.Client("https://plc.directory");
62 const didDoc = await plcClient.getDocumentData(did);
63 if (!didDoc) {
64 console.log("No DID document found for agent DID");
65 return new Response(
66 JSON.stringify({
67 success: false,
68 message: "No DID document found for your account",
69 }),
70 {
71 status: 400,
72 headers: { "Content-Type": "application/json" },
73 },
74 );
75 }
76 console.log("Got DID document:", didDoc);
77
78 const rotationKeys = didDoc.rotationKeys ?? [];
79 if (!rotationKeys.length) {
80 console.log("No existing rotation keys found");
81 throw new Error("No rotation keys provided in recommended credentials");
82 }
83
84 // Check if the key is already in rotation keys
85 if (rotationKeys.includes(newKey)) {
86 console.log("Key already exists in rotation keys");
87 return new Response(
88 JSON.stringify({
89 success: false,
90 message: "This key is already in your rotation keys",
91 }),
92 {
93 status: 400,
94 headers: { "Content-Type": "application/json" },
95 },
96 );
97 }
98
99 // Perform the actual PLC update with the provided token
100 console.log("Signing PLC operation...");
101 const plcOp = await agent.com.atproto.identity.signPlcOperation({
102 token,
103 rotationKeys: [newKey, ...rotationKeys],
104 });
105 console.log("PLC operation signed successfully:", plcOp.data);
106
107 console.log("Submitting PLC operation...");
108 const plcSubmit = await agent.com.atproto.identity.submitPlcOperation({
109 operation: plcOp.data.operation,
110 });
111 console.log("PLC operation submitted successfully:", plcSubmit);
112
113 return new Response(
114 JSON.stringify({
115 success: true,
116 message: "PLC update completed successfully",
117 did: plcOp.data,
118 newKey,
119 rotationKeys: [newKey, ...rotationKeys],
120 }),
121 {
122 status: 200,
123 headers: {
124 "Content-Type": "application/json",
125 ...Object.fromEntries(res.headers), // Include session cookie headers
126 },
127 },
128 );
129 } catch (error) {
130 console.error("PLC update error:", error);
131 const errorMessage = error instanceof Error
132 ? error.message
133 : "Failed to update your PLC";
134 console.log("Sending error response:", errorMessage);
135
136 return new Response(
137 JSON.stringify({
138 success: false,
139 message: errorMessage,
140 error: error instanceof Error
141 ? {
142 name: error.name,
143 message: error.message,
144 stack: error.stack,
145 }
146 : String(error),
147 }),
148 {
149 status: 400,
150 headers: { "Content-Type": "application/json" },
151 },
152 );
153 }
154 },
155});