feat: Add account export functionality for identity backup
Some checks failed
Build & Test / build (20.x) (push) Has been cancelled
Build & Test / build (22.x) (push) Has been cancelled

This commit is contained in:
Shibo Lyu 2025-05-09 01:28:37 +08:00
parent e1d025470b
commit 4b51f2ebeb
4 changed files with 96 additions and 2 deletions

View file

@ -5,7 +5,7 @@ import {
type BlahProfile
} from '@blah-im/core/identity';
import { type IdentityDB, openIdentityDB } from './identityDB';
import { BlahKeyPair } from '@blah-im/core/crypto';
import { BlahKeyPair, type EncodedBlahKeyPair } from '@blah-im/core/crypto';
import { browser } from '$app/environment';
export type Account = BlahIdentityDescription & {
@ -106,6 +106,17 @@ class AccountManager {
await this.loadAccounts();
}
async exportAccountIDKeyPair(accountOrIdKeyId: Account | string): Promise<EncodedBlahKeyPair> {
if (!this.keyDB) throw new Error('Account manager not initialized');
const idKeyId =
typeof accountOrIdKeyId === 'string' ? accountOrIdKeyId : accountOrIdKeyId.id_key;
const accountCreds = await this.keyDB.fetchAccount(idKeyId);
const encodedIdKeyPair = accountCreds?.encodedIdKeyPair;
if (!encodedIdKeyPair) throw new Error('No encoded ID key pair found');
return encodedIdKeyPair;
}
async changePassword(
accountOrIdKeyId: Account | string,
oldPassword: string,

View file

@ -5,8 +5,10 @@
import PageHeader from '$lib/components/PageHeader.svelte';
import { DocumentDuplicate, Key } from 'svelte-hero-icons';
import ChangePasswordDialog from './ChangePasswordDialog.svelte';
import ExportIdentityKeyDialog from './ExportIdentityKeyDialog.svelte';
let showChangePasswordDialog = $state(false);
let showExportIdentityDialog = $state(false);
</script>
<PageHeader>
@ -21,8 +23,16 @@
</GroupedListSection>
<GroupedListSection>
<GroupedListItem icon={DocumentDuplicate}>Backup Account</GroupedListItem>
<GroupedListItem icon={DocumentDuplicate} onclick={() => (showExportIdentityDialog = true)}>Generate Identity Backup File</GroupedListItem>
{#snippet footer()}
<p>
In case you don't have any other <em>full-access-enabled</em> devices, this file, combining
with your <em>current password</em>, can be imported on Blah apps to regain
<em>full access</em>.
</p>
{/snippet}
</GroupedListSection>
</GroupedListContainer>
<ChangePasswordDialog bind:open={showChangePasswordDialog} />
<ExportIdentityKeyDialog bind:open={showExportIdentityDialog} />

View file

@ -190,6 +190,10 @@
<p>
Remember to change it on all other devices to which you granted <em>full access</em>.
</p>
<p>
If you keeps backups of your identity, please update your backup file so it is encrypted
with the new password.
</p>
</div>
{/if}
</GroupedListContainer>

View file

@ -0,0 +1,69 @@
<script lang="ts">
import Button from '$lib/components/Button.svelte';
import Dialog from '$lib/components/Dialog.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import { accountManager } from '$lib/accounts';
interface Props {
open: boolean;
}
let { open = $bindable() }: Props = $props();
async function exportIdentityBackup() {
try {
if (!accountManager.currentAccountId || !accountManager.currentAccount) {
throw new Error('No current account selected');
}
const encodedKeyPair = await accountManager.exportAccountIDKeyPair(
accountManager.currentAccountId
);
// Create a JSON blob
const jsonData = JSON.stringify(encodedKeyPair, null, 2);
const blob = new Blob([jsonData], { type: 'application/json' });
// Generate download link
const url = URL.createObjectURL(blob);
const downloadLink = document.createElement('a');
downloadLink.href = url;
// Set the filename with the account name
const accountName =
accountManager.currentAccount.profile?.signee.payload.name ||
accountManager.currentAccountId.slice(-6);
downloadLink.download = `Blah ID Backup - ${accountName}.json`;
// Trigger download and clean up
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
URL.revokeObjectURL(url);
open = false;
} catch (err) {
console.error('Failed to export identity backup:', err);
}
}
</script>
<Dialog bind:open class="h-1/3">
<PageHeader>
<h3 class="grow">Export Identity Backup</h3>
</PageHeader>
<div class="flex flex-col items-center space-y-6 overflow-y-auto p-6">
<p class="text-center">
This will generate a backup file containing your encrypted identity keys. You'll need your
current password to use this backup later.
</p>
<Button variant="primary" onclick={exportIdentityBackup}>Save Backup File</Button>
<p class="text-center text-sm text-gray-600">
Keep this file in a secure location. Anyone with this file and your password will have
<em>full access</em> to your account.
</p>
</div>
</Dialog>