You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

233 lines
6.8 KiB

<template>
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<button
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.go(-1)"
>
<font-awesome icon="chevron-left"></font-awesome>
</button>
Import Existing Identifier
</h1>
</div>
<!-- Import Account Form -->
<p class="text-center text-xl mb-4 font-light">
Enter your seed phrase below to import your identifier on this device.
</p>
<!-- id used by puppeteer test script -->
<textarea
id="seed-input"
v-model="mnemonic"
type="text"
placeholder="Seed Phrase"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
/>
<h3
class="text-blue-500 text-sm font-semibold mb-3"
@click="showAdvanced = !showAdvanced"
>
Advanced
</h3>
<div v-if="showAdvanced">
Enter a custom derivation path
<input
v-model="derivationPath"
type="text"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/>
<span class="ml-4">
For previous uPort or Endorser users,
<a
class="text-blue-500"
@click="derivationPath = UPORT_DERIVATION_PATH"
>
click here to use that value.
</a>
</span>
<div v-if="numAccounts == 1" class="mt-4">
<input v-model="shouldErase" type="checkbox" class="mr-2" />
<label>Erase previous identifiers.</label>
</div>
<div v-if="isNotProdServer()" class="mt-4 text-blue-500">
<!-- if they click this, fill in the mnemonic seed-input with the test mnemonic -->
<button @click="mnemonic = TEST_USER_0_MNEMONIC">
Use mnemonic for Test User #0
</button>
</div>
</div>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
@click="onImportClick()"
>
Import
</button>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
@click="onCancelClick()"
>
Cancel
</button>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { AppString, NotificationIface } from "../constants/app";
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
import { retrieveAccountCount, importFromMnemonic } from "../libs/util";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
/**
* Import Account View Component
*
* Allows users to import existing identifiers using seed phrases:
* - Secure mnemonic phrase input with validation
* - Advanced options for custom derivation paths
* - Legacy uPort compatibility support
* - Test environment utilities for development
*
* Features:
* - Secure seed phrase import functionality
* - Custom derivation path configuration
* - Account erasure options for fresh imports
* - Development mode test utilities
* - Comprehensive error handling and validation
*
* Security Considerations:
* - Seed phrases are handled securely and not logged
* - Import process includes validation and error recovery
* - Advanced options are hidden by default
*
* @author Matthew Raymer
*/
@Component({
components: {},
mixins: [PlatformServiceMixin],
})
export default class ImportAccountView extends Vue {
TEST_USER_0_MNEMONIC =
"rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage";
UPORT_DERIVATION_PATH = "m/7696500'/0'/0'/0'"; // for legacy imports, likely never used
AppString = AppString;
$notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router;
notify!: ReturnType<typeof createNotifyHelpers>;
apiServer = "";
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
mnemonic = "";
numAccounts = 0;
showAdvanced = false;
shouldErase = false;
/**
* Initializes notification helpers
*/
created() {
this.notify = createNotifyHelpers(this.$notify);
}
/**
* Component initialization
*
* Loads account count and server settings for import configuration
* Uses PlatformServiceMixin for secure database access
*/
async mounted() {
await this.initializeSettings();
}
/**
* Initializes component settings and account information
*/
private async initializeSettings() {
this.numAccounts = await retrieveAccountCount();
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
}
/**
* Handles cancel button click
*
* Navigates back to previous view
*/
public onCancelClick() {
this.$router.back();
}
/**
* Checks if running on production server
*
* @returns True if not on production server (enables test utilities)
*/
public isNotProdServer() {
return this.apiServer !== AppString.PROD_ENDORSER_API_SERVER;
}
/**
* Handles import button click
*
* Validates input and initiates account import process
* Uses importFromMnemonic utility for secure import
*/
public async onImportClick() {
if (!this.mnemonic?.trim()) {
this.notify.warning(
"Seed phrase is required to import an account.",
TIMEOUTS.LONG,
);
return;
}
try {
await importFromMnemonic(
this.mnemonic,
this.derivationPath,
this.shouldErase,
);
// Check what was actually imported
const settings = await this.$accountSettings();
// Check account-specific settings
if (settings?.activeDid) {
try {
await this.$query("SELECT * FROM settings WHERE accountDid = ?", [
settings.activeDid,
]);
} catch (error) {
// Log error but don't interrupt import flow
this.$logError("Error checking post-import settings: " + error);
}
}
this.notify.success("Account imported successfully!", TIMEOUTS.STANDARD);
this.$router.push({ name: "account" });
} catch (error: any) {
this.$logError("Import failed: " + error);
this.notify.error(
error.message || "Failed to import account.",
TIMEOUTS.LONG,
);
}
}
}
</script>