Graphical PDS migrator for AT Protocol
at main 3.8 kB view raw
1import { getSessionAgent } from "../../../lib/sessions.ts"; 2import { setCredentialSession } from "../../../lib/cred/sessions.ts"; 3import { Agent } from "@atproto/api"; 4import { define } from "../../../utils.ts"; 5import { assertMigrationAllowed } from "../../../lib/migration-state.ts"; 6 7/** 8 * Handle account creation 9 * First step of the migration process 10 * Body must contain: 11 * - service: The service URL of the new account 12 * - handle: The handle of the new account 13 * - password: The password of the new account 14 * - email: The email of the new account 15 * - invite: The invite code of the new account (optional depending on the PDS) 16 * @param ctx - The context object containing the request and response 17 * @returns A response object with the creation result 18 */ 19export const handler = define.handlers({ 20 async POST(ctx) { 21 const res = new Response(); 22 try { 23 // Check if migrations are currently allowed 24 assertMigrationAllowed(); 25 26 const body = await ctx.req.json(); 27 const serviceUrl = body.service; 28 const newHandle = body.handle; 29 const newPassword = body.password; 30 const email = body.email; 31 const inviteCode = body.invite; 32 33 if (!serviceUrl || !newHandle || !newPassword || !email) { 34 return new Response( 35 "Missing params service, handle, password, or email", 36 { status: 400 }, 37 ); 38 } 39 40 const oldAgent = await getSessionAgent(ctx.req, res); 41 const newAgent = new Agent({ service: serviceUrl }); 42 43 if (!oldAgent) return new Response("Unauthorized", { status: 401 }); 44 if (!newAgent) { 45 return new Response("Could not create new agent", { status: 400 }); 46 } 47 48 console.log("getting did"); 49 const session = await oldAgent.com.atproto.server.getSession(); 50 const accountDid = session.data.did; 51 console.log("got did"); 52 const describeRes = await newAgent.com.atproto.server.describeServer(); 53 const newServerDid = describeRes.data.did; 54 const inviteRequired = describeRes.data.inviteCodeRequired ?? false; 55 56 if (inviteRequired && !inviteCode) { 57 return new Response("Missing param invite code", { status: 400 }); 58 } 59 60 const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({ 61 aud: newServerDid, 62 lxm: "com.atproto.server.createAccount", 63 }); 64 const serviceJwt = serviceJwtRes.data.token; 65 66 await newAgent.com.atproto.server.createAccount( 67 { 68 handle: newHandle, 69 email: email, 70 password: newPassword, 71 did: accountDid, 72 inviteCode: inviteCode ?? undefined, 73 }, 74 { 75 headers: { authorization: `Bearer ${serviceJwt}` }, 76 encoding: "application/json", 77 }, 78 ); 79 80 // Store the migration session 81 await setCredentialSession(ctx.req, res, { 82 did: accountDid, 83 handle: newHandle, 84 service: serviceUrl, 85 password: newPassword, 86 }, true); 87 88 return new Response( 89 JSON.stringify({ 90 success: true, 91 message: "Account created successfully", 92 did: accountDid, 93 handle: newHandle, 94 }), 95 { 96 status: 200, 97 headers: { 98 "Content-Type": "application/json", 99 ...Object.fromEntries(res.headers), // Include session cookie headers 100 }, 101 }, 102 ); 103 } catch (error) { 104 console.error("Create account error:", error); 105 return new Response( 106 JSON.stringify({ 107 success: false, 108 message: error instanceof Error 109 ? error.message 110 : "Failed to create account", 111 }), 112 { 113 status: 400, 114 headers: { "Content-Type": "application/json" }, 115 }, 116 ); 117 } 118 }, 119});