feat(ext/setup): unified device registration in Step 5; fixes silent dropped pubkey
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
/// Step 5: Finish (download reference image, push config to extension or copy JSON)
|
/// Step 5: Finish (download reference image, push config to extension or copy JSON)
|
||||||
|
|
||||||
import { createGitHost, uint8ArrayToBase64 } from '../service-worker/git-host';
|
import { createGitHost, uint8ArrayToBase64 } from '../service-worker/git-host';
|
||||||
|
import { addDevice } from '../service-worker/devices';
|
||||||
import { probeVault } from './probe';
|
import { probeVault } from './probe';
|
||||||
import type { VaultConfig } from '../shared/types';
|
import type { VaultConfig } from '../shared/types';
|
||||||
import type { SessionHandle } from 'relicario-wasm';
|
import type { SessionHandle } from 'relicario-wasm';
|
||||||
@@ -1042,32 +1043,38 @@ function renderStep5(): string {
|
|||||||
repoPath: state.repoPath,
|
repoPath: state.repoPath,
|
||||||
apiToken: state.apiToken,
|
apiToken: state.apiToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
const configJson = JSON.stringify(config, null, 2);
|
const configJson = JSON.stringify(config, null, 2);
|
||||||
|
const isAttach = state.mode === 'attach';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="wizard-step">
|
<div class="wizard-step">
|
||||||
<div class="success-box">
|
<div class="success-box">
|
||||||
<h3>vault created</h3>
|
<h3>${isAttach ? 'device verified' : 'vault created'}</h3>
|
||||||
<p class="secondary">Your vault has been initialized and pushed to the repository.</p>
|
<p class="secondary">
|
||||||
|
${isAttach
|
||||||
|
? 'Your passphrase and reference image decrypt the vault successfully.'
|
||||||
|
: 'Your vault has been initialized and pushed to the repository.'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
${isAttach ? '' : `
|
||||||
<label class="label">reference image</label>
|
<div class="form-group">
|
||||||
<p class="muted" style="margin-bottom:8px;">
|
<label class="label">reference image</label>
|
||||||
Download and store this image securely. It is your second factor for decryption.
|
<p class="muted" style="margin-bottom:8px;">
|
||||||
Without it, you cannot unlock the vault.
|
Download and store this image securely. It is your second factor for decryption.
|
||||||
</p>
|
Without it, you cannot unlock the vault.
|
||||||
<button class="btn btn-primary" id="download-ref-btn">download reference.jpg</button>
|
</p>
|
||||||
</div>
|
<button class="btn btn-primary" id="download-ref-btn">download reference.jpg</button>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
|
||||||
${state.extensionDetected ? `
|
${state.extensionDetected ? `
|
||||||
<div class="form-group" style="margin-top:16px;">
|
<div class="form-group" style="margin-top:16px;">
|
||||||
<label class="label">extension configuration</label>
|
<label class="label">register this device</label>
|
||||||
<button class="btn btn-primary" id="push-config-btn" ${state.configPushed ? 'disabled' : ''}>
|
<button class="btn btn-primary" id="push-config-btn" ${state.configPushed ? 'disabled' : ''}>
|
||||||
${state.configPushed ? 'config saved to extension' : 'save config to extension'}
|
${state.configPushed ? 'device registered' : (isAttach ? 'attach this device' : 'register this device')}
|
||||||
</button>
|
</button>
|
||||||
${state.configPushed ? '<span class="test-result pass" style="margin-left:8px;">saved</span>' : ''}
|
${state.configPushed ? '<span class="test-result pass" style="margin-left:8px;">done</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div class="form-group" style="margin-top:16px;">
|
<div class="form-group" style="margin-top:16px;">
|
||||||
@@ -1098,7 +1105,8 @@ function attachStep5(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('push-config-btn')?.addEventListener('click', async () => {
|
document.getElementById('push-config-btn')?.addEventListener('click', async () => {
|
||||||
if (!state.referenceImageBytes) return;
|
state.error = null;
|
||||||
|
render();
|
||||||
|
|
||||||
const config: VaultConfig = {
|
const config: VaultConfig = {
|
||||||
hostType: state.hostType,
|
hostType: state.hostType,
|
||||||
@@ -1107,39 +1115,55 @@ function attachStep5(): void {
|
|||||||
apiToken: state.apiToken,
|
apiToken: state.apiToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageBase64 = uint8ArrayToBase64(state.referenceImageBytes);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
chrome.runtime.sendMessage(
|
const w = await loadWasm();
|
||||||
{ type: 'save_setup', config, imageBase64 },
|
const keypair = JSON.parse(w.generate_device_keypair()) as {
|
||||||
async (response: { ok: boolean; error?: string }) => {
|
public_key_hex: string; private_key_base64: string;
|
||||||
if (response?.ok) {
|
};
|
||||||
state.configPushed = true;
|
|
||||||
|
|
||||||
// Generate device keypair and register
|
// 1) Save private key + name locally.
|
||||||
const w = await loadWasm();
|
await chrome.storage.local.set({
|
||||||
const keypair = JSON.parse(w.generate_device_keypair()) as { public_key_hex: string; private_key_base64: string };
|
device_name: state.deviceName,
|
||||||
|
device_private_key: keypair.private_key_base64,
|
||||||
|
});
|
||||||
|
|
||||||
// Store private key locally
|
// 2) Save vault config + reference image to extension storage.
|
||||||
await chrome.storage.local.set({
|
const imageBytes = state.referenceImageBytes ?? state.referenceImageBytesAttach;
|
||||||
device_name: state.deviceName,
|
const imageBase64 = imageBytes ? uint8ArrayToBase64(imageBytes) : '';
|
||||||
device_private_key: keypair.private_key_base64,
|
const saveOk = await new Promise<boolean>((resolve) => {
|
||||||
});
|
chrome.runtime.sendMessage(
|
||||||
|
{ type: 'save_setup', config, imageBase64 },
|
||||||
|
(response: { ok: boolean; error?: string }) => {
|
||||||
|
if (!response?.ok) {
|
||||||
|
state.error = response?.error ?? 'Failed to save config to extension';
|
||||||
|
resolve(false); return;
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (!saveOk) { render(); return; }
|
||||||
|
|
||||||
// Register device with vault
|
// 3) Register device on the remote (read-modify-write devices.json).
|
||||||
chrome.runtime.sendMessage({
|
const hostUrl = state.hostType === 'github' ? 'https://api.github.com' : state.hostUrl;
|
||||||
type: 'add_device',
|
const host = createGitHost(state.hostType, hostUrl, state.repoPath, state.apiToken);
|
||||||
name: state.deviceName,
|
await addDevice(host, {
|
||||||
public_key: keypair.public_key_hex,
|
name: state.deviceName,
|
||||||
});
|
public_key: keypair.public_key_hex,
|
||||||
} else {
|
added_at: Math.floor(Date.now() / 1000),
|
||||||
state.error = response?.error ?? 'Failed to save config to extension';
|
});
|
||||||
}
|
|
||||||
render();
|
// 4) Release any attach-mode WASM handle.
|
||||||
},
|
if (state.verifiedHandle !== null) {
|
||||||
);
|
try { w.lock(state.verifiedHandle); } catch { /* best effort */ }
|
||||||
|
state.verifiedHandle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.configPushed = true;
|
||||||
|
render();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
state.error = `Failed to communicate with extension: ${err instanceof Error ? err.message : String(err)}`;
|
console.error('[relicario setup] register device failed:', err);
|
||||||
|
state.error = `Failed to register device: ${err instanceof Error ? err.message : String(err)}`;
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user