Graphical PDS migrator for AT Protocol
1import { setCredentialSession } from "../../../lib/cred/sessions.ts";
2import { resolver } from "../../../lib/id-resolver.ts";
3import { define } from "../../../utils.ts";
4import { Agent } from "npm:@atproto/api";
5
6/**
7 * Handle credential login
8 * Save iron session to cookies
9 * Save credential session state to database
10 * @param ctx - The context object containing the request and response
11 * @returns A response object with the login result
12 */
13export const handler = define.handlers({
14 async POST(ctx) {
15 try {
16 const body = await ctx.req.json();
17 const { handle, password } = body;
18
19 if (!handle || !password) {
20 return new Response(
21 JSON.stringify({
22 success: false,
23 message: "Handle and password are required",
24 }),
25 {
26 status: 400,
27 headers: { "Content-Type": "application/json" },
28 },
29 );
30 }
31
32 console.log("Resolving handle:", handle);
33 const did = typeof handle == "string" && handle.startsWith("did:")
34 ? handle
35 : await resolver.resolveHandleToDid(handle);
36 const service = await resolver.resolveDidToPdsUrl(did);
37 console.log("Resolved service:", service);
38
39 if (!service) {
40 return new Response(
41 JSON.stringify({
42 success: false,
43 message: "Invalid handle",
44 }),
45 {
46 status: 400,
47 },
48 );
49 }
50
51 try {
52 // Create agent and get session
53 console.log("Creating agent with service:", service);
54 const agent = new Agent({ service });
55 const sessionRes = await agent.com.atproto.server.createSession({
56 identifier: handle,
57 password: password,
58 });
59 console.log("Created ATProto session:", {
60 did: sessionRes.data.did,
61 handle: sessionRes.data.handle,
62 hasAccessJwt: !!sessionRes.data.accessJwt,
63 });
64
65 // Create response for setting cookies
66 const response = new Response(
67 JSON.stringify({
68 success: true,
69 did,
70 handle,
71 }),
72 {
73 status: 200,
74 headers: { "Content-Type": "application/json" },
75 },
76 );
77
78 // Create and save our client session with tokens
79 await setCredentialSession(ctx.req, response, {
80 did,
81 service,
82 password,
83 handle,
84 accessJwt: sessionRes.data.accessJwt,
85 });
86
87 // Log the response headers
88 console.log("Response headers:", {
89 cookies: response.headers.get("Set-Cookie"),
90 allHeaders: Object.fromEntries(response.headers.entries()),
91 });
92
93 return response;
94 } catch (err) {
95 const message = err instanceof Error ? err.message : String(err);
96 console.error("Login failed:", message);
97 return new Response(
98 JSON.stringify({
99 success: false,
100 message: "Invalid credentials",
101 }),
102 {
103 status: 401,
104 headers: { "Content-Type": "application/json" },
105 },
106 );
107 }
108 } catch (error) {
109 const message = error instanceof Error ? error.message : String(error);
110 console.error("Login error:", message);
111 return new Response(
112 JSON.stringify({
113 success: false,
114 message: error instanceof Error ? error.message : "An error occurred",
115 }),
116 {
117 status: 500,
118 headers: { "Content-Type": "application/json" },
119 },
120 );
121 }
122 },
123});