Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355import { AtpAgent } from "@atproto/api";import { shell } from "./shell.mjs";import { userEmailFromID } from "./authorization.mjs";import crypto from "crypto";
const DEFAULT_PDS_URL = "https://at.aesthetic.computer";
const FALLBACK_ERROR_MESSAGES = [ "Reserved handle", "Invalid handle", "must be a valid handle", "Handle too short", "Handle already taken",];
function getPdsUrl() { return process.env.PDS_URL || DEFAULT_PDS_URL;}
function getAdminPassword() { return process.env.PDS_ADMIN_PASSWORD;}
export function sanitizeHandleForPds(handle) { if (!handle) return handle; return handle.replace(/[._]/g, "-");}
function isRetryableHandleError(error) { if (!error?.message) return false; return FALLBACK_ERROR_MESSAGES.some((msg) => error.message.includes(msg));}
export async function updateAtprotoHandle(database, sub, handle) { const users = database.db.collection("users"); const userRecord = await users.findOne({ _id: sub });
if (!userRecord) { shell.log("🪪 No user record for ATProto sync:", sub); return { updated: false, reason: "user-missing" }; }
const atproto = userRecord.atproto; if (!atproto?.password) { shell.log("🪪 No ATProto credentials stored for:", sub); return { updated: false, reason: "missing-credentials" }; }
const sanitizedHandle = sanitizeHandleForPds(handle); const desiredHandle = `${sanitizedHandle}.at.aesthetic.computer`; const currentHandle = atproto.handle; const identifier = currentHandle || atproto.did;
if (!identifier) { shell.log("🪪 No ATProto identifier available for:", sub); return { updated: false, reason: "missing-identifier" }; }
if (desiredHandle === currentHandle) { return { updated: false, reason: "already-synced", handle: desiredHandle, sanitized: sanitizedHandle, }; }
const agent = new AtpAgent({ service: getPdsUrl() });
try { await agent.login({ identifier, password: atproto.password }); } catch (error) { shell.log("🪪 Failed to login to PDS for handle sync:", error); return { updated: false, reason: "login-failed", error: error.message }; }
const performUpdate = async (targetHandle) => { await agent.com.atproto.identity.updateHandle({ handle: targetHandle }); await users.updateOne( { _id: sub }, { $set: { "atproto.handle": targetHandle, "atproto.syncedAt": new Date().toISOString(), }, }, ); shell.log("🪪 Updated PDS handle to:", targetHandle); return targetHandle; };
try { const finalHandle = await performUpdate(desiredHandle); return { updated: true, handle: finalHandle, sanitized: sanitizedHandle, }; } catch (error) { const fallbackHandle = userRecord.code ? `${userRecord.code}.at.aesthetic.computer` : null;
if (fallbackHandle && isRetryableHandleError(error)) { shell.log( "🪪 Falling back to user code handle for PDS:", fallbackHandle, "Reason:", error.message, );
try { const finalHandle = await performUpdate(fallbackHandle); return { updated: true, handle: finalHandle, sanitized: fallbackHandle.replace(/\.at\.aesthetic\.computer$/, ""), fallback: true, error: error.message, }; } catch (fallbackError) { shell.log("🪪 Fallback PDS handle update failed:", fallbackError); return { updated: false, reason: "fallback-failed", error: fallbackError.message, }; } }
shell.log("🪪 PDS handle update failed:", error); return { updated: false, reason: "update-failed", error: error.message }; }}
export async function deleteAtprotoAccount(database, sub) { const adminPassword = getAdminPassword(); if (!adminPassword) { shell.log("🪦 Skipping PDS deletion, missing admin password env."); return { deleted: false, reason: "missing-admin-password" }; }
const users = database.db.collection("users"); const userRecord = await users.findOne({ _id: sub });
if (!userRecord?.atproto?.did) { shell.log("🪦 No ATProto DID on record for:", sub); return { deleted: false, reason: "missing-did" }; }
const did = userRecord.atproto.did; const auth = Buffer.from(`admin:${adminPassword}`).toString("base64");
try { const response = await fetch( `${getPdsUrl()}/xrpc/com.atproto.admin.deleteAccount`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${auth}`, }, body: JSON.stringify({ did }), }, );
if (!response.ok) { const errorText = await response.text(); throw new Error(`status ${response.status}: ${errorText}`); }
await users.updateOne({ _id: sub }, { $unset: { atproto: "" } }); shell.log("🪦 Deleted ATProto account for:", sub); return { deleted: true }; } catch (error) { shell.log("🪦 Failed to delete ATProto account:", error.message); return { deleted: false, reason: "request-failed", error: error.message }; }}
async function generateInviteCode() { const adminPassword = getAdminPassword(); if (!adminPassword) { throw new Error("PDS_ADMIN_PASSWORD environment variable is required"); }
const auth = Buffer.from(`admin:${adminPassword}`).toString("base64");
const response = await fetch( `${getPdsUrl()}/xrpc/com.atproto.server.createInviteCode`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${auth}`, }, body: JSON.stringify({ useCount: 1 }), }, );
if (!response.ok) { throw new Error(`Failed to create invite code: ${response.statusText}`); }
const data = await response.json(); return data.code;}
function generatePassword() { const chars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%&*"; let password = ""; for (let i = 0; i < 32; i++) { const randomIndex = crypto.randomInt(0, chars.length); password += chars[randomIndex]; } return password;}
export async function createAtprotoAccount(database, sub, email) { try { shell.log(`🦋 Creating ATProto account for: ${sub}`);
const users = database.db.collection("users"); const handles = database.db.collection("@handles");
// Check if account already exists const existingUser = await users.findOne({ _id: sub }); if (existingUser?.atproto?.did) { shell.log(`🦋 ATProto account already exists for: ${sub}`); return { created: false, reason: "already-exists", did: existingUser.atproto.did, handle: existingUser.atproto.handle, }; }
// Get email if not provided if (!email) { const tenant = sub.startsWith("sotce-") ? "sotce" : "aesthetic"; const result = await userEmailFromID(sub, tenant); if (!result?.email) { throw new Error(`No email found for user: ${sub}`); } email = result.email; }
shell.log(`📧 Email: ${email}`);
// Generate password const password = generatePassword();
// Determine handle from AC handle or user code const handleRecord = await handles.findOne({ _id: sub }); let pdsHandle; if (handleRecord?.handle) { const sanitizedHandle = sanitizeHandleForPds(handleRecord.handle); pdsHandle = `${sanitizedHandle}.at.aesthetic.computer`; shell.log(`🏷️ Using AC handle: ${pdsHandle}`); } else if (existingUser?.code) { pdsHandle = `${existingUser.code}.at.aesthetic.computer`; shell.log(`🏷️ Using user code: ${pdsHandle}`); } else { throw new Error(`No handle or user code found for: ${sub}`); }
// Generate invite code shell.log(`🎫 Generating invite code...`); const inviteCode = await generateInviteCode();
// Create account on PDS const agent = new AtpAgent({ service: getPdsUrl() }); const tenant = sub.startsWith("sotce-") ? "sotce" : "aesthetic";
let accountCreated = false; let finalDid, finalHandle; let currentEmail = email; let attempts = 0;
while (!accountCreated && attempts < 3) { attempts++;
try { const response = await agent.createAccount({ email: currentEmail, handle: pdsHandle, password, inviteCode, });
finalDid = response.data.did; finalHandle = response.data.handle; accountCreated = true; } catch (error) { // Handle duplicate email by appending tenant if ( error.message.includes("Email already taken") && attempts === 1 && tenant === "sotce" ) { shell.log(`⚠️ Email "${currentEmail}" already taken, adding +sotce`); const [localPart, domain] = currentEmail.split("@"); currentEmail = `${localPart}+sotce@${domain}`; } // Try fallback to user code if handle fails else if ( isRetryableHandleError(error) && attempts <= 2 && existingUser?.code ) { shell.log(`⚠️ Handle failed, falling back to user code`); pdsHandle = `${existingUser.code}.at.aesthetic.computer`; } else { throw error; } } }
if (!accountCreated) { throw new Error("Failed to create account after all attempts"); }
shell.log(`✅ Created ATProto account: ${finalDid}`);
// Store atproto data in MongoDB const atprotoData = { did: finalDid, handle: finalHandle, password: password, created: new Date().toISOString(), };
await users.updateOne( { _id: sub }, { $set: { atproto: atprotoData } }, { upsert: true }, );
return { created: true, did: finalDid, handle: finalHandle, email: currentEmail, }; } catch (error) { shell.log(`❌ Failed to create ATProto account: ${error.message}`); return { created: false, reason: "creation-failed", error: error.message, }; }}