Graphical PDS migrator for AT Protocol
at main 6.7 kB view raw
1import { Agent } from "npm:@atproto/api"; 2import { getIronSession, SessionOptions } from "npm:iron-session"; 3import { createSessionOptions, CredentialSession } from "../types.ts"; 4 5let migrationSessionOptions: SessionOptions; 6let credentialSessionOptions: SessionOptions; 7 8/** 9 * Get the session options for the given request. 10 * @param isMigration - Whether to get the migration session options 11 * @returns The session options 12 */ 13async function getOptions(isMigration: boolean) { 14 if (isMigration) { 15 if (!migrationSessionOptions) { 16 migrationSessionOptions = await createSessionOptions("migration_sid"); 17 } 18 return migrationSessionOptions; 19 } 20 21 if (!credentialSessionOptions) { 22 credentialSessionOptions = await createSessionOptions("cred_sid"); 23 } 24 return credentialSessionOptions; 25} 26 27/** 28 * Get the credential session for the given request. 29 * @param req - The request object 30 * @param res - The response object 31 * @param isMigration - Whether to get the migration session 32 * @returns The credential session 33 */ 34export async function getCredentialSession( 35 req: Request, 36 res: Response = new Response(), 37 isMigration: boolean = false, 38) { 39 const options = await getOptions(isMigration); 40 return getIronSession<CredentialSession>(req, res, options); 41} 42 43/** 44 * Get the credential agent for the given request. 45 * @param req - The request object 46 * @param res - The response object 47 * @param isMigration - Whether to get the migration session 48 * @returns The credential agent 49 */ 50export async function getCredentialAgent( 51 req: Request, 52 res: Response = new Response(), 53 isMigration: boolean = false, 54) { 55 const session = await getCredentialSession(req, res, isMigration); 56 if ( 57 !session.did || 58 !session.service || 59 !session.handle || 60 !session.password 61 ) { 62 return null; 63 } 64 65 try { 66 console.log("Creating agent with service:", session.service); 67 const agent = new Agent({ service: session.service }); 68 69 // Attempt to restore session by creating a new one 70 try { 71 console.log("Attempting to create session for:", session.handle); 72 const sessionRes = await agent.com.atproto.server.createSession({ 73 identifier: session.handle, 74 password: session.password, 75 }); 76 console.log("Session created successfully:", !!sessionRes); 77 78 // Set the auth tokens in the agent 79 agent.setHeader("Authorization", `Bearer ${sessionRes.data.accessJwt}`); 80 81 return agent; 82 } catch (err) { 83 // Session creation failed, clear the session 84 console.error("Failed to create session:", err); 85 session.destroy(); 86 return null; 87 } 88 } catch (err) { 89 console.warn("Failed to create migration agent:", err); 90 session.destroy(); 91 return null; 92 } 93} 94 95/** 96 * Set the credential session for the given request. 97 * @param req - The request object 98 * @param res - The response object 99 * @param data - The credential session data 100 * @param isMigration - Whether to set the migration session 101 * @returns The credential session 102 */ 103export async function setCredentialSession( 104 req: Request, 105 res: Response, 106 data: CredentialSession, 107 isMigration: boolean = false, 108) { 109 const session = await getCredentialSession(req, res, isMigration); 110 session.did = data.did; 111 session.handle = data.handle; 112 session.service = data.service; 113 session.password = data.password; 114 await session.save(); 115 return session; 116} 117 118/** 119 * Get the credential session agent for the given request. 120 * @param req - The request object 121 * @param res - The response object 122 * @param isMigration - Whether to get the migration session 123 * @returns The credential session agent 124 */ 125export async function getCredentialSessionAgent( 126 req: Request, 127 res: Response = new Response(), 128 isMigration: boolean = false, 129) { 130 const session = await getCredentialSession(req, res, isMigration); 131 132 console.log("Session state:", { 133 hasDid: !!session.did, 134 hasService: !!session.service, 135 hasHandle: !!session.handle, 136 hasPassword: !!session.password, 137 hasAccessJwt: !!session.accessJwt, 138 service: session.service, 139 handle: session.handle, 140 }); 141 142 if ( 143 !session.did || 144 !session.service || 145 !session.handle || 146 !session.password 147 ) { 148 console.log("Missing required session fields"); 149 return null; 150 } 151 152 try { 153 console.log("Creating agent with service:", session.service); 154 const agent = new Agent({ service: session.service }); 155 156 // If we have a stored JWT, try to use it first 157 if (session.accessJwt) { 158 console.log("Found stored JWT, attempting to use it"); 159 agent.setHeader("Authorization", `Bearer ${session.accessJwt}`); 160 try { 161 // Verify the token is still valid 162 const sessionInfo = await agent.com.atproto.server.getSession(); 163 console.log("Stored JWT is valid, session info:", { 164 did: sessionInfo.data.did, 165 handle: sessionInfo.data.handle, 166 }); 167 return agent; 168 } catch (err) { 169 // Token expired or invalid, continue to create new session 170 const message = err instanceof Error ? err.message : String(err); 171 console.log("Stored token invalid or expired:", message); 172 } 173 } 174 175 // Create new session if no token or token expired 176 try { 177 console.log("Attempting to create session for:", session.handle); 178 const sessionRes = await agent.com.atproto.server.createSession({ 179 identifier: session.handle, 180 password: session.password, 181 }); 182 console.log("Session created successfully:", { 183 did: sessionRes.data.did, 184 handle: sessionRes.data.handle, 185 hasAccessJwt: !!sessionRes.data.accessJwt, 186 }); 187 188 // Store the new token 189 session.accessJwt = sessionRes.data.accessJwt; 190 await session.save(); 191 console.log("Saved new access token to session"); 192 193 // Set the auth tokens in the agent 194 agent.setHeader("Authorization", `Bearer ${sessionRes.data.accessJwt}`); 195 196 // Verify the agent is properly authenticated 197 try { 198 await agent.com.atproto.server.getSession(); 199 return agent; 200 } catch (err) { 201 console.error("Failed to verify agent authentication:", err); 202 session.destroy(); 203 return null; 204 } 205 } catch (err) { 206 // Session creation failed, clear the session 207 const message = err instanceof Error ? err.message : String(err); 208 console.error("Failed to create session:", message); 209 session.destroy(); 210 return null; 211 } 212 } catch (err) { 213 const message = err instanceof Error ? err.message : String(err); 214 console.warn("Failed to create migration agent:", message); 215 session.destroy(); 216 return null; 217 } 218}