Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations.

wip

Changed files
+84 -3
web-ui
src
lib
routes
+70
web-ui/src/lib/components/RotationKeyDisplay.svelte
··· 1 + <script lang="ts"> 2 + let {handle, multiBasePrivateKey} = $props(); 3 + import {handleAndPDSResolver} from '@pds-moover/moover' 4 + 5 + const copyToClipboard = async (text: string) => { 6 + try { 7 + await navigator.clipboard.writeText(text); 8 + alert('Copied to clipboard'); 9 + } catch (e) { 10 + console.error(e); 11 + alert('Failed to copy to clipboard'); 12 + } 13 + } 14 + 15 + const downloadNewRotationKey = async (rotationKey: string, handle: string) => { 16 + if (!rotationKey) return; 17 + 18 + 19 + //try and find the did to add to the file as well 20 + let didText = ''; 21 + try { 22 + let {usersDid} = await handleAndPDSResolver(handle); 23 + didText = `DID: ${usersDid}\n`; 24 + } catch (e) { 25 + //sliently log. Rather the user have their rotation key than not. a did can always be found other ways if needed 26 + console.error(e); 27 + } 28 + 29 + 30 + const content = `You can use these to recover your account if it's ever necessary via https://pdsmoover.com/restore. The restore process will ask for the Private key\n\nKEEP IN A SECURE LOCATION\n\n${didText}PublicKey: ${rotationKey.publicKey}\nPrivateKey: ${rotationKey.privateKey}\n`; 31 + const blob = new Blob([content], {type: 'text/plain'}); 32 + const url = URL.createObjectURL(blob); 33 + const a = document.createElement('a'); 34 + a.href = url; 35 + 36 + 37 + a.download = `${handle}-rotation-key.txt`; 38 + document.body.appendChild(a); 39 + a.click(); 40 + document.body.removeChild(a); 41 + URL.revokeObjectURL(url); 42 + } 43 + </script> 44 + 45 + 46 + <div class="section" style="margin-top: 16px; border: 2px solid #f39c12; padding: 16px;"> 47 + <h3 style="color: #d35400;">Important: Save Your New Rotation Key Now</h3> 48 + <p style="color: #c0392b; font-weight: bold;"> 49 + Warning: This is the only time we will show you your private rotation key. Save it in a secure place. 50 + If you lose it, you may not be able to recover your account in the event of a PDS failure or hijack. 51 + </p> 52 + <div class="form-group"> 53 + <label>New Rotation Key (Private - keep secret)</label> 54 + <div style="display:flex; gap:8px; align-items:center;"> 55 + {#if multiBasePrivateKey} 56 + <code 57 + style="overflow-wrap:anywhere;">{multiBasePrivateKey}</code> 58 + {/if} 59 + 60 + <button type="button" 61 + onclick={async () => await copyToClipboard(multiBasePrivateKey)}>Copy 62 + </button> 63 + </div> 64 + </div> 65 + <div class="form-group"> 66 + <button type="button" onclick={async () => await downloadNewRotationKey(multiBasePrivateKey, handle)}>Download 67 + Key File 68 + </button> 69 + </div> 70 + </div>
+11 -3
web-ui/src/routes/moover/+page.svelte
··· 29 29 let showTwoFactorCodeInput = $state(false); 30 30 let showAdvance = $state(false); 31 31 let showStatusMessage = $state(false); 32 - let askForPlcToken = $state(true); 32 + let askForPlcToken = $state(false); 33 + let disableSubmit = $state(false); 33 34 34 35 let errorMessage: null | string = $state(null); 35 36 ··· 43 44 44 45 async function submitMoove(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }) { 45 46 event.preventDefault(); 47 + disableSubmit = true; 46 48 errorMessage = null; 47 49 showStatusMessage = false; 48 50 49 51 if (!formData.confirmation) { 50 52 errorMessage = 'Please confirm that you understand the risks of doing an account migration'; 53 + disableSubmit = false; 51 54 return; 52 55 } 53 56 ··· 56 59 if (showTwoFactorCodeInput) { 57 60 if (showTwoFactorCodeInput === null) { 58 61 errorMessage = 'Please enter the 2FA that was sent to your email.' 62 + disableSubmit = false; 63 + return; 59 64 } 60 65 } 61 66 ··· 83 88 formData.twoFactorCode, 84 89 ); 85 90 if (migrator.migratePlcRecord) { 91 + //I don't think disable submit is needed, but you never know. 92 + disableSubmit = false; 86 93 askForPlcToken = true; 87 94 } else { 88 95 updateStatusHandler('Migration of your repo is complete! But the PLC operation was not done so your old account is still the valid one.'); 89 96 } 90 97 } catch (error) { 98 + disableSubmit = false; 91 99 console.error(error); 92 100 //@ts-expect-error: JS being js. doesn't like not having the type' 93 101 if (error.error === 'AuthFactorTokenRequired') { ··· 107 115 108 116 <div class="container"> 109 117 <MooHeader title="PDS MOOver"/> 110 - <button onclick={() => askForPlcToken = !askForPlcToken}>I'm not a cow</button> 118 + <!-- <button onclick={() => askForPlcToken = !askForPlcToken}>I'm not a cow</button>--> 111 119 112 120 {#if !askForPlcToken} 113 121 <a href={resolve('/info')}>Idk if I trust a cow to move my atproto account to a new PDS</a> ··· 255 263 {/if} 256 264 257 265 <div> 258 - <button type="submit">MOOve</button> 266 + <button disabled={disableSubmit} type="submit">MOOve</button> 259 267 </div> 260 268 </form> 261 269 {:else}
+3
web-ui/src/routes/moover/SignThePapers.svelte
··· 31 31 // Generate a new rotation key if requested 32 32 if (createANewRotationKey) { 33 33 34 + //TODO write the new component and should be about ready to test 35 + 36 + 34 37 //TODO this will become a prop to the new component 35 38 // window.handle = this.newHandle; 36 39 let plcOps = new PlcOps();
web-ui/src/routes/rotation-key/+page.svelte

This is a binary file and will not be displayed.