Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations. pdsmoover.com
pds atproto migrations moo cow
at main 355 lines 18 kB view raw
1{%- import "partials/cow-header.askama.html" as cow -%} 2 3{% extends "layout.askama.html" %} 4 5{% block meta %} 6 <meta property="og:description" content="ATProto account migration tool"/> 7 <meta property="og:image" content="/halloween_moover.webp"> 8{% endblock %} 9 10{% block content %} 11 12<script> 13 document.addEventListener('alpine:init', () => { 14 window.Alpine.data('moover', () => ({ 15 handle: '', 16 password: '', 17 newPds: '', 18 newEmail: '', 19 newHandle: '', 20 inviteCode: null, 21 twoFactorCode: null, 22 confirmation: false, 23 showTwoFactorCodeInput: false, 24 error: null, 25 showStatusMessage: false, 26 askForPlcToken: false, 27 28 done: false, 29 //advance 30 showAdvance: false, 31 showAdvancedPlcOptions: false, 32 createNewAccount: true, 33 migrateRepo: true, 34 migrateBlobs: true, 35 migrateMissingBlobs: true, 36 migratePrefs: true, 37 migratePlcRecord: true, 38 //Plc signing 39 backupOptions: { 40 createANewRotationKey: false, 41 signupForBackups: false, 42 }, 43 backupSignupMessage: null, 44 backupSignupError: null, 45 rotationKeys: ['', '', '', ''], 46 plcToken: null, 47 plcStatus: null, 48 newlyCreatedRotationKey: null, // { publicKey, privateKey } when generated 49 toggleAdvanceMenu() { 50 this.showAdvance = !this.showAdvance; 51 }, 52 updateStatusHandler(status) { 53 console.log("Status update:", status); 54 document.getElementById("status-message").innerText = status; 55 56 }, 57 async handleSubmit() { 58 this.error = null; 59 this.showStatusMessage = false; 60 console.log("Form data:", { 61 handle: this.handle, 62 password: this.password, 63 newPds: this.newPds, 64 newEmail: this.newEmail, 65 newHandle: this.newHandle, 66 inviteCode: this.inviteCode 67 }) 68 try { 69 70 if (this.showTwoFactorCodeInput) { 71 if (this.twoFactorCode === null) { 72 this.error = 'Please enter the 2FA that was sent to your email.' 73 } 74 } 75 if (!this.confirmation) { 76 this.error = 'Please confirm that you understand the risks.' 77 } 78 79 window.Migrator.createNewAccount = this.createNewAccount; 80 window.Migrator.migrateRepo = this.migrateRepo; 81 window.Migrator.migrateBlobs = this.migrateBlobs; 82 window.Migrator.migrateMissingBlobs = this.migrateMissingBlobs; 83 window.Migrator.migratePrefs = this.migratePrefs; 84 window.Migrator.migratePlcRecord = this.migratePlcRecord; 85 this.updateStatusHandler('Starting migration...'); 86 this.showStatusMessage = true; 87 await window.Migrator.migrate( 88 this.handle, 89 this.password, 90 this.newPds, 91 this.newEmail, 92 this.newHandle, 93 this.inviteCode, 94 this.updateStatusHandler, 95 this.twoFactorCode 96 ); 97 if (this.migratePlcRecord) { 98 this.askForPlcToken = true; 99 } else { 100 this.updateStatusHandler('Migration of your repo is complete! But the PLC operation was not done so your old account is still the valid one.'); 101 } 102 } catch (error) { 103 console.error(error.error, error.message); 104 if (error.error === 'AuthFactorTokenRequired') { 105 this.showTwoFactorCodeInput = true; 106 } 107 this.error = error.message; 108 } 109 }, 110 async signPlcOperation() { 111 try { 112 this.plcStatus = 'Signing PLC operation...'; 113 this.backupSignupMessage = null; 114 this.backupSignupError = null; 115 // Build additional rotation keys list (user-provided and/or newly created) 116 const additionalRotationKeysToAdd = []; 117 // Generate a new rotation key if requested 118 if (this.backupOptions.createANewRotationKey) { 119 window.handle = this.newHandle; 120 const created = await window.PlcOps.createANewSecp256k1(); 121 this.newlyCreatedRotationKey = created; // { publicKey, privateKey } 122 // Reserve first slot for the newly created key (will appear above the PDS rotation key) 123 additionalRotationKeysToAdd.push(created.publicKey); 124 } 125 // Append any manually entered rotation keys (non-empty) 126 for (let i = 0; i < this.rotationKeys.length; i++) { 127 const k = (this.rotationKeys[i] || '').trim(); 128 if (k) { 129 additionalRotationKeysToAdd.push(k); 130 } 131 } 132 133 await window.Migrator.signPlcOperation(this.plcToken, additionalRotationKeysToAdd); 134 this.plcStatus = 'PLC operation signed successfully! Your account has been MOOved to the new PDS.'; 135 this.done = true; 136 137 if (this.backupOptions.signupForBackups) { 138 try { 139 this.backupSignupMessage = 'Signing you up for automated backups...'; 140 let _ = await window.Migrator.signUpForBackupsFromMigration(); 141 this.backupSignupMessage = 'Signed up for automated backups successfully.'; 142 } catch (e) { 143 console.error(e); 144 this.backupSignupError = e?.message || 'Failed to sign you up for automated backups.'; 145 } 146 } 147 } catch (error) { 148 this.error = error.message; 149 console.error(error); 150 } 151 } 152 })) 153 }) 154</script> 155 156<div class="container" x-data="moover"> 157 {% call cow::cow_header("PDS MOOver") %} 158 159 <a href="/info">Idk if I trust a cow to move my atproto account to a new PDS</a> 160 <br/> 161 <a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lw3hcuojck2u">Video guide for 162 joining blacksky.app</a> 163 <form x-show="!askForPlcToken" id="moover-form" @submit.prevent="await handleSubmit()"> 164 <!-- First section: Login credentials --> 165 <div class="section"> 166 <h2>Login for your current PDS</h2> 167 <div class="form-group"> 168 <label for="handle">Old Handle:</label> 169 <input type="text" id="handle" name="handle" placeholder="alice.bsky.social" x-model="handle" required> 170 </div> 171 172 <div class="form-group"> 173 <label for="password">Old Password (Will also be your new password):</label> 174 <input type="password" id="password" name="password" x-model="password" required> 175 </div> 176 177 <div x-show="showTwoFactorCodeInput" class="form-group"> 178 <label for="two-factor-code">2FA from the email sent</label> 179 <input type="text" id="two-factor-code" name="two-factor-code" x-model="twoFactorCode"> 180 <div class="error-message">Enter your 2fa code here</div> 181 182 </div> 183 </div> 184 185 <!-- Second section: New account details --> 186 <div class="section"> 187 <h2>Setup for the new PDS</h2> 188 <div class="form-group"> 189 <label for="new-pds">New PDS (URL):</label> 190 <input type="url" id="new-pds" name="new-pds" placeholder="https://coolnewpds.com" x-model="newPds" 191 required> 192 </div> 193 194 <div class="form-group"> 195 <label for="new-email">New Email:</label> 196 <input type="email" id="new-email" name="new-email" placeholder="CanBeSameEmailAsTheOldPds@email.com" 197 x-model="newEmail" required> 198 </div> 199 200 <div class="form-group"> 201 <label for="new-handle">New Handle:</label> 202 <input type="text" id="new-handle" name="new-handle" 203 placeholder="username.newpds.com or mycooldomain.com" x-model="newHandle" required> 204 </div> 205 206 <div class="form-group"> 207 <label for="invite-code">Invite Code:</label> 208 <input type="text" id="invite-code" name="invite-code" placeholder="Invite code from your new PDS (Leave blank if you don't have one)" 209 x-model="inviteCode"> 210 </div> 211 </div> 212 <div class="form-group"> 213 <button type="button" @click="toggleAdvanceMenu()" id="advance" name="advance">Advance Options 214 </button> 215 </div> 216 <div x-show="showAdvance" class="section" style="padding-bottom: 10px; text-align: left"> 217 <h3>Pick and choose which actions to run</h3> 218 <p>Useful if a migration failed and you want to have a bit more manual control</p> 219 <div class="form-control"> 220 <label class="moove-checkbox-label"> 221 <input type="checkbox" id="createNewAccount" name="createNewAccount" x-model="createNewAccount"> 222 Create a New Account on the New PDS 223 </label> 224 </div> 225 <div class="form-control"> 226 <label class="moove-checkbox-label"> 227 <input type="checkbox" id="migrateRepo" name="migrateRepo" x-model="migrateRepo"> 228 Migrate Repo 229 </label> 230 </div> 231 <div class="form-control"> 232 <label class="moove-checkbox-label"> 233 <input type="checkbox" id="migrateBlobs" name="migrateBlobs" x-model="migrateBlobs"> 234 Migrate Blobs 235 </label> 236 </div> 237 <div class="form-control"> 238 <label class="moove-checkbox-label"> 239 <input type="checkbox" id="migrateMissingBlobs" name="migrateMissingBlobs" 240 x-model="migrateMissingBlobs"> 241 Migrate Missing Blobs 242 </label> 243 </div> 244 <div class="form-control"> 245 <label class="moove-checkbox-label"> 246 <input type="checkbox" id="migratePrefs" name="migratePrefs" x-model="migratePrefs"> 247 Migrate Prefs 248 </label> 249 </div> 250 <div class="form-control"> 251 <label class="moove-checkbox-label"> 252 <input type="checkbox" id="migratePlcRecord" name="migratePlcRecord" x-model="migratePlcRecord"> 253 Migrate PLC Record 254 </label> 255 </div> 256 257 </div> 258 259 <p style="text-align: left">There are some risks that come with doing an account migration. 260 (Can view them 261 <a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md#%EF%B8%8F-warning-%EF%B8%8F-%EF%B8%8F">here</a>) 262 and that the creator or host of this migration tool is not liable and will not be able to help you in the 263 event something goes wrong. I also have read over the <a href="/info">extended information from PDS MOOver about account 264 migrations.</a> 265 </p> 266 <div class="form-group"> 267 <label for="confirmation" class="moove-checkbox-label"> 268 <input x-model="confirmation" type="checkbox" id="confirmation" name="confirmation" required> 269 <span>I understand</span> 270 </label> 271 </div> 272 <div x-show="error" x-text="error" class="error-message"></div> 273 <div x-show="showStatusMessage" id="warning">*Please make sure to stay on this page during the MOOve for the 274 best result 275 </div> 276 <div x-show="showStatusMessage" id="status-message" class="status-message"></div> 277 <div> 278 <button type="submit">MOOve</button> 279 </div> 280 </form> 281 282 <div x-show="askForPlcToken" class="section"> 283 <form @submit.prevent="await signPlcOperation()"> 284 <div x-show="!done"> 285 <h2>Please enter your PLC Token you received in an email</h2> 286 <div class="form-group"> 287 <label for="plc-token">PLC Token:</label> 288 <input type="text" id="plc-token" name="plc-token" x-model="plcToken" required> 289 </div> 290 <p style="text-align: left">You can now select to add a new Rotation Key during migration and sign up for PDS MOOver's free backup service. With a Rotation Key and backups if your new PDS ever goes down you can recover your account and it's data.</p> 291 <div class="form-group"> 292 <label for="rotation-key" class="moove-checkbox-label"> 293 <input x-model="backupOptions.createANewRotationKey" type="checkbox" id="rotation-key" name="rotation-key"> 294 <span>Create and add a new Rotation Key</span> 295 </label> 296 </div> 297 <div class="form-group"> 298 <label for="backups-signup" class="moove-checkbox-label"> 299 <input x-model="backupOptions.signupForBackups" type="checkbox" id="backups-signup" name="backups-signup" > 300 <span>Signup for automated account backups</span> 301 </label> 302 </div> 303 <div class="form-group"> 304 <button type="button" id="plc-advance" @click="showAdvancedPlcOptions = !showAdvancedPlcOptions">Add Additional Rotation Keys</button> 305 </div> 306 <div x-show="showAdvancedPlcOptions" class="section" style="padding-bottom: 10px;"> 307 <div style="text-align: left;"> 308 You can pick up to 4 rotation keys to your PLC document. These will appear above your new PDS rotation key so you can recover your account in the event of an adversarial take over from a rogue PDS 309 </div> 310 <div class="form-group" style="margin-top: 10px;"> 311 <label for="rotation-key-1">Rotation key 1</label> 312 <input type="text" id="rotation-key-1" name="rotation-key-1" 313 x-model="rotationKeys[0]" 314 :disabled="backupOptions.createANewRotationKey" 315 :placeholder="backupOptions.createANewRotationKey ? 'reserved for the newly created rotation key' : ''"> 316 </div> 317 <div class="form-group"> 318 <label for="rotation-key-2">Rotation key 2</label> 319 <input type="text" id="rotation-key-2" name="rotation-key-2" x-model="rotationKeys[1]"> 320 </div> 321 <div class="form-group"> 322 <label for="rotation-key-3">Rotation key 3</label> 323 <input type="text" id="rotation-key-3" name="rotation-key-3" x-model="rotationKeys[2]"> 324 </div> 325 <div class="form-group"> 326 <label for="rotation-key-4">Rotation key 4</label> 327 <input type="text" id="rotation-key-4" name="rotation-key-4" x-model="rotationKeys[3]"> 328 </div> 329 </div> 330 </div> 331 <div x-show="error" x-text="error" class="error-message"></div> 332 <template x-if="done && backupOptions.createANewRotationKey && newlyCreatedRotationKey"> 333 <div x-data="{newlyCreatedRotationKey: newlyCreatedRotationKey}"> 334 {% include "partials/rotation_key_display.askama.html" %} 335 </div> 336 </template> 337 <div x-show="!done && plcStatus" x-text="plcStatus" class="status-message"></div> 338 <template x-if="backupOptions.signupForBackups"> 339 <div> 340 <div x-show="backupSignupMessage" x-text="backupSignupMessage" class="status-message"></div> 341 <div x-show="backupSignupError" x-text="backupSignupError" class="error-message"></div> 342 </div> 343 </template> 344 <div x-show="done" class="status-message">Congratulations! You have MOOved to a new PDS! Remember to use 345 your new PDS URL under "Hosting provider" when logging in on Bluesky. Can find more detail information 346 <a href="/info#cant-login">here.</a></div> 347 348 <div> 349 <button x-show="!done" type="submit">Sign the papers</button> 350 </div> 351 </form> 352 </div> 353</div> 354 355{% endblock %}