feat(ext/setup): add device name step to setup wizard
New step 4 after vault creation: enter device name (defaults to "Chrome on Linux" based on detected browser/OS). Generates ed25519 keypair, stores private key in chrome.storage.local, registers device with vault. Wizard is now 5 steps (was 4). Also adds generate_device_keypair() to wasm.d.ts type declarations. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
/// Vault initialization wizard — 4-step flow for creating new relicario vaults.
|
||||
/// Vault initialization wizard — 5-step flow for creating new relicario vaults.
|
||||
///
|
||||
/// Step 1: Choose host type (Gitea / GitHub)
|
||||
/// Step 2: Configure connection (URL, repo, token) + test
|
||||
/// Step 3: Create vault (carrier image, passphrase, generate secrets, push files)
|
||||
/// Step 4: Finish (download reference image, push config to extension or copy JSON)
|
||||
/// Step 4: Name this device (generates ed25519 keypair, registers with vault)
|
||||
/// Step 5: Finish (download reference image, push config to extension or copy JSON)
|
||||
|
||||
import { createGitHost, uint8ArrayToBase64 } from '../service-worker/git-host';
|
||||
import type { VaultConfig } from '../shared/types';
|
||||
@@ -46,6 +47,7 @@ interface WizardState {
|
||||
error: string | null;
|
||||
extensionDetected: boolean;
|
||||
configPushed: boolean;
|
||||
deviceName: string;
|
||||
}
|
||||
|
||||
const state: WizardState = {
|
||||
@@ -67,6 +69,7 @@ const state: WizardState = {
|
||||
error: null,
|
||||
extensionDetected: false,
|
||||
configPushed: false,
|
||||
deviceName: '',
|
||||
};
|
||||
|
||||
// --- Helpers ---
|
||||
@@ -225,6 +228,7 @@ function render(): void {
|
||||
<div class="step ${state.step > 2 ? 'done' : state.step === 2 ? 'current' : ''}"></div>
|
||||
<div class="step ${state.step > 3 ? 'done' : state.step === 3 ? 'current' : ''}"></div>
|
||||
<div class="step ${state.step > 4 ? 'done' : state.step === 4 ? 'current' : ''}"></div>
|
||||
<div class="step ${state.step > 5 ? 'done' : state.step === 5 ? 'current' : ''}"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -234,6 +238,7 @@ function render(): void {
|
||||
case 2: stepHtml = renderStep2(); break;
|
||||
case 3: stepHtml = renderStep3(); break;
|
||||
case 4: stepHtml = renderStep4(); break;
|
||||
case 5: stepHtml = renderStep5(); break;
|
||||
}
|
||||
|
||||
app.innerHTML = `
|
||||
@@ -251,6 +256,7 @@ function render(): void {
|
||||
case 2: attachStep2(); break;
|
||||
case 3: attachStep3(); break;
|
||||
case 4: attachStep4(); break;
|
||||
case 5: attachStep5(); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,11 +651,10 @@ function attachStep3(): void {
|
||||
stage = 'release handle';
|
||||
w.lock(handle);
|
||||
|
||||
log('vault created — advancing to step 4');
|
||||
log('vault created — advancing to step 4 (device name)');
|
||||
state.creating = false;
|
||||
state.step = 4;
|
||||
state.step = 4; // device name step
|
||||
state.error = null;
|
||||
detectExtension();
|
||||
render();
|
||||
} catch (err: unknown) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -662,7 +667,59 @@ function attachStep3(): void {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Step 4: Finish ---
|
||||
// --- Step 4: Device Name ---
|
||||
|
||||
function renderStep4(): string {
|
||||
const platform = navigator.platform.toLowerCase();
|
||||
const isChrome = /chrome/i.test(navigator.userAgent) && !/edg/i.test(navigator.userAgent);
|
||||
const isFirefox = /firefox/i.test(navigator.userAgent);
|
||||
const browser = isFirefox ? 'Firefox' : isChrome ? 'Chrome' : 'Browser';
|
||||
const os = platform.includes('mac') ? 'macOS' : platform.includes('win') ? 'Windows' : 'Linux';
|
||||
const defaultName = state.deviceName || `${browser} on ${os}`;
|
||||
|
||||
return `
|
||||
<div class="wizard-step">
|
||||
<h3>name this device</h3>
|
||||
<p class="muted" style="margin-bottom:12px;">
|
||||
This helps you identify which devices have access to your vault.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="label" for="device-name">device name</label>
|
||||
<input id="device-name" type="text" value="${escapeHtml(defaultName)}" placeholder="e.g. Chrome on Linux">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" id="back-btn">back</button>
|
||||
<button class="btn btn-primary" id="next-btn">continue</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function attachStep4(): void {
|
||||
document.getElementById('back-btn')?.addEventListener('click', () => {
|
||||
state.step = 3;
|
||||
state.error = null;
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById('next-btn')?.addEventListener('click', async () => {
|
||||
const nameInput = document.getElementById('device-name') as HTMLInputElement;
|
||||
const name = nameInput.value.trim();
|
||||
if (!name) {
|
||||
state.error = 'Device name is required';
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
state.deviceName = name;
|
||||
state.step = 5;
|
||||
state.error = null;
|
||||
detectExtension();
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
// --- Step 5: Finish ---
|
||||
|
||||
function detectExtension(): void {
|
||||
try {
|
||||
@@ -682,7 +739,7 @@ function detectExtension(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function renderStep4(): string {
|
||||
function renderStep5(): string {
|
||||
const config: VaultConfig = {
|
||||
hostType: state.hostType,
|
||||
hostUrl: state.hostType === 'github' ? 'https://api.github.com' : state.hostUrl,
|
||||
@@ -730,7 +787,7 @@ function renderStep4(): string {
|
||||
`;
|
||||
}
|
||||
|
||||
function attachStep4(): void {
|
||||
function attachStep5(): void {
|
||||
document.getElementById('download-ref-btn')?.addEventListener('click', () => {
|
||||
if (!state.referenceImageBytes) return;
|
||||
const blob = new Blob([state.referenceImageBytes.buffer as ArrayBuffer], { type: 'image/jpeg' });
|
||||
@@ -759,9 +816,26 @@ function attachStep4(): void {
|
||||
try {
|
||||
chrome.runtime.sendMessage(
|
||||
{ type: 'save_setup', config, imageBase64 },
|
||||
(response: { ok: boolean; error?: string }) => {
|
||||
async (response: { ok: boolean; error?: string }) => {
|
||||
if (response?.ok) {
|
||||
state.configPushed = true;
|
||||
|
||||
// Generate device keypair and register
|
||||
const w = await loadWasm();
|
||||
const keypair = JSON.parse(w.generate_device_keypair()) as { public_key_hex: string; private_key_base64: string };
|
||||
|
||||
// Store private key locally
|
||||
await chrome.storage.local.set({
|
||||
device_name: state.deviceName,
|
||||
device_private_key: keypair.private_key_base64,
|
||||
});
|
||||
|
||||
// Register device with vault
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'add_device',
|
||||
name: state.deviceName,
|
||||
public_key: keypair.public_key_hex,
|
||||
});
|
||||
} else {
|
||||
state.error = response?.error ?? 'Failed to save config to extension';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user