···66[dependencies]
77atrium-api = { version = "0.25.0", default-features = false, features = ["agent", "bluesky"] }
88atrium-common = "0.1.0"
99+atrium-crypto = "0.1.2"
910atrium-identity = { version = "0.1.1", features = ["doh-handle-resolver"] }
1011atrium-xrpc-client = "0.5.11"
1212+hex = "0.4.3"
1113hickory-resolver = "0.25.1"
1414+rand = "0.8.5"
1215tokio = { version = "1.44.0", features = ["macros", "rt-multi-thread"]}
+142-3
src/main.rs
···55 },
66 app::bsky::actor::{get_preferences, put_preferences},
77 com::atproto::{
88- server::{create_account, get_service_auth},
88+ identity::sign_plc_operation,
99+ server::{create_account, deactivate_account, get_service_auth},
910 sync::{get_blob, get_repo, list_blobs},
1011 },
1112 types::string::{Did, Handle, Nsid},
1213};
1314use atrium_common::resolver::Resolver;
1515+use atrium_crypto::keypair::{Did as _, Export, Secp256k1Keypair};
1416use atrium_identity::{
1517 did::{CommonDidResolver, CommonDidResolverConfig},
1618 handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
···327329 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
328330 Ok(_) => {
329331 println!("Blob with CID {:?} migrated", cid)
330330- },
332332+ }
331333 Err(err) => {
332334 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
333335 return;
···385387 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
386388 Ok(_) => {
387389 println!("Blob with CID {:?} migrated", cid)
388388- },
390390+ }
389391 Err(err) => {
390392 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
391393 return;
···431433 }
432434 }
433435 println!("Preferences successfully migrated!");
436436+437437+ // Update identity
438438+ println!("Migrating you identity (DID document) ...");
439439+440440+ let pds_credentials = match new_agent
441441+ .api
442442+ .com
443443+ .atproto
444444+ .identity
445445+ .get_recommended_did_credentials()
446446+ .await
447447+ {
448448+ Ok(response) => response,
449449+ Err(err) => {
450450+ println!("com.atproto.identity.getRecommendedDidCredentials at new PDS failed due to error: {err}");
451451+ return;
452452+ }
453453+ };
454454+455455+ match Did::new(identity.did.clone()).unwrap().method() {
456456+ "plc" => {
457457+ println!(
458458+ "did:plc detected! Creating a recovery key and updating your DID document ..."
459459+ );
460460+ let recovery_keypair = Secp256k1Keypair::create(&mut rand::thread_rng());
461461+ let private_key = hex::encode(recovery_keypair.export());
462462+ let mut recovery_keys = vec![recovery_keypair.did()];
463463+ if let Some(keys) = pds_credentials.rotation_keys.clone() {
464464+ recovery_keys.extend(keys);
465465+ }
466466+467467+ println!("PLC operations are potentially destructive therefore you will need to complete an email challenge with your current PDS");
468468+ if let Err(err) = current_agent
469469+ .api
470470+ .com
471471+ .atproto
472472+ .identity
473473+ .request_plc_operation_signature()
474474+ .await
475475+ {
476476+ println!("com.atproto.identity.requestPlcOperationSignature at current PDS failed due to error: {err}")
477477+ };
478478+ let challenge_token = match readln(Some(
479479+ "Challenge email sent. Please provide the token you where sent over email here",
480480+ )) {
481481+ Ok(token) => token,
482482+ Err(err) => {
483483+ println!("Could not read token due to error: {err}");
484484+ return;
485485+ }
486486+ };
487487+ println!("Your private recovery key is {private_key}. Please store this in a secure location!!");
488488+ if let Err(err) = readln(Some("Press enter once you've saved the key securely")) {
489489+ println!("Could not handle enter due to error: {err}");
490490+ return;
491491+ }
492492+493493+ match current_agent
494494+ .api
495495+ .com
496496+ .atproto
497497+ .identity
498498+ .sign_plc_operation(
499499+ sign_plc_operation::InputData {
500500+ also_known_as: pds_credentials.also_known_as.clone(),
501501+ rotation_keys: Some(recovery_keys),
502502+ services: pds_credentials.services.clone(),
503503+ token: Some(challenge_token.to_string()),
504504+ verification_methods: pds_credentials.verification_methods.clone(),
505505+ }
506506+ .into(),
507507+ )
508508+ .await
509509+ {
510510+ Ok(response) => response,
511511+ Err(err) => {
512512+ println!("com.atproto.identity.signPlcOperation at current PDS failed due to error: {err}");
513513+ return;
514514+ }
515515+ };
516516+ println!("DID document successfully updated!");
517517+ }
518518+ "web" => {
519519+ let did = identity.did;
520520+ println!("did:web detected! Please manually update your DID document to match these values: {pds_credentials:#?}");
521521+ if let Err(err) = readln(Some("Press enter once you've updated your DID document")) {
522522+ println!("Could not handle enter due to error: {err}");
523523+ return;
524524+ }
525525+ let mut valid_document = match identity_resolver.resolve(did.as_str()).await {
526526+ Ok(response) => response.pds == new_pds_url.to_string(),
527527+ Err(err) => {
528528+ println!("Couldn't resolve DID {did} due to error: {err}");
529529+ return;
530530+ }
531531+ };
532532+533533+ while !valid_document {
534534+ println!("DID document not updated or updated incorretly! Needed PDS configuration: {new_pds_url}");
535535+ if let Err(err) = readln(Some("Press enter once you've updated your DID document"))
536536+ {
537537+ println!("Could not handle enter due to error: {err}");
538538+ return;
539539+ }
540540+ valid_document = match identity_resolver.resolve(did.as_str()).await {
541541+ Ok(response) => response.pds == new_pds_url.to_string(),
542542+ Err(err) => {
543543+ println!("Couldn't resolve DID {did} due to error: {err}");
544544+ return;
545545+ }
546546+ };
547547+ }
548548+ }
549549+ _ => {
550550+ println!("Unknown and invalid DID method found. This should not be possible!");
551551+ return;
552552+ }
553553+ }
554554+ println!("Identity migrated successfully!");
555555+556556+ // Finalise migration
557557+ if let Err(err) = new_agent.api.com.atproto.server.activate_account().await {
558558+ println!("com.atproto.server.activateAccount at new PDS failed due to error: {err}")
559559+ };
560560+ if let Err(err) = current_agent
561561+ .api
562562+ .com
563563+ .atproto
564564+ .server
565565+ .deactivate_account(deactivate_account::InputData { delete_after: None }.into())
566566+ .await
567567+ {
568568+ println!("com.atproto.server.activateAccount at current PDS failed due to error: {err}")
569569+ };
570570+571571+ println!("The account migration was successful!");
572572+ println!("The account on your old PDS has been deactivated. Please make sure everything works before fully deleting it in case you need to go back");
434573}