feat(ext/sw): get_vault_settings + update_vault_settings popup-only messages
This commit is contained in:
@@ -654,3 +654,81 @@ describe('get_totp handler covers both Login.totp and Totp.config', () => {
|
||||
expect(res).toEqual({ ok: false, error: 'no_totp' });
|
||||
});
|
||||
});
|
||||
|
||||
// --- get_vault_settings / update_vault_settings (β₂ Slice 3) ---
|
||||
|
||||
describe('get_vault_settings / update_vault_settings', () => {
|
||||
function primeUnlocked(state: RouterState): void {
|
||||
vi.mocked(session.getCurrent).mockReturnValue({ free: () => {} } as never);
|
||||
state.gitHost = {} as never;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(session.getCurrent).mockReset();
|
||||
vi.mocked(vault.fetchAndDecryptSettings).mockReset();
|
||||
vi.mocked(vault.encryptAndWriteSettings).mockReset();
|
||||
});
|
||||
|
||||
it('get_vault_settings accepted from popup; returns VaultSettings', async () => {
|
||||
const state = makeState();
|
||||
primeUnlocked(state);
|
||||
const mockSettings = {
|
||||
trash_retention: { kind: 'days', value: 30 },
|
||||
field_history_retention: { kind: 'forever' },
|
||||
generator_defaults: {
|
||||
kind: 'random', length: 20,
|
||||
classes: { lower: true, upper: true, digits: true, symbols: true },
|
||||
symbol_charset: { kind: 'safe_only' },
|
||||
},
|
||||
attachment_caps: {},
|
||||
autofill_origin_acks: { 'github.com': 1000 },
|
||||
};
|
||||
vi.mocked(vault.fetchAndDecryptSettings).mockResolvedValueOnce(mockSettings as never);
|
||||
const res = await route({ type: 'get_vault_settings' }, state, makePopupSender());
|
||||
expect(res).toMatchObject({ ok: true });
|
||||
if (res.ok) {
|
||||
const d = res.data as { settings: typeof mockSettings };
|
||||
expect(d.settings).toEqual(mockSettings);
|
||||
}
|
||||
});
|
||||
|
||||
it('get_vault_settings rejected from content', async () => {
|
||||
const state = makeState();
|
||||
const res = await route({ type: 'get_vault_settings' }, state, makeContentSender());
|
||||
expect(res).toEqual({ ok: false, error: 'unauthorized_sender' });
|
||||
});
|
||||
|
||||
it('update_vault_settings accepted from popup; calls encryptAndWriteSettings', async () => {
|
||||
const state = makeState();
|
||||
primeUnlocked(state);
|
||||
vi.mocked(vault.encryptAndWriteSettings).mockResolvedValueOnce(undefined);
|
||||
const newSettings = {
|
||||
trash_retention: { kind: 'forever' },
|
||||
field_history_retention: { kind: 'last_n', value: 5 },
|
||||
generator_defaults: {
|
||||
kind: 'bip39', word_count: 6, separator: '-', capitalization: 'lower',
|
||||
},
|
||||
attachment_caps: {},
|
||||
autofill_origin_acks: {},
|
||||
};
|
||||
const res = await route(
|
||||
{ type: 'update_vault_settings', settings: newSettings as never },
|
||||
state,
|
||||
makePopupSender(),
|
||||
);
|
||||
expect(res).toMatchObject({ ok: true });
|
||||
expect(vault.encryptAndWriteSettings).toHaveBeenCalledWith(
|
||||
expect.anything(), expect.anything(), newSettings, expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it('update_vault_settings rejected from setup tab (not in SETUP_ALLOWED)', async () => {
|
||||
const state = makeState();
|
||||
const res = await route(
|
||||
{ type: 'update_vault_settings', settings: {} as never },
|
||||
state,
|
||||
makeSetupSender(),
|
||||
);
|
||||
expect(res).toEqual({ ok: false, error: 'unauthorized_sender' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,6 +171,23 @@ export async function handle(
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
case 'get_vault_settings': {
|
||||
const handle = session.getCurrent();
|
||||
if (!handle || !state.gitHost) return { ok: false, error: 'vault_locked' };
|
||||
const settings = await vault.fetchAndDecryptSettings(state.gitHost, handle);
|
||||
return { ok: true, data: { settings } };
|
||||
}
|
||||
|
||||
case 'update_vault_settings': {
|
||||
const handle = session.getCurrent();
|
||||
if (!handle || !state.gitHost) return { ok: false, error: 'vault_locked' };
|
||||
await vault.encryptAndWriteSettings(
|
||||
state.gitHost, handle, msg.settings,
|
||||
'settings: update vault-level config',
|
||||
);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
case 'get_blacklist':
|
||||
return { ok: true, data: { blacklist: await loadBlacklist() } };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {
|
||||
Item, ItemId, Manifest, ManifestEntry, VaultConfig, SetupState,
|
||||
DeviceSettings, GeneratorRequest,
|
||||
DeviceSettings, GeneratorRequest, VaultSettings,
|
||||
} from './types';
|
||||
|
||||
// --- Messages a popup (or setup page) may send ---
|
||||
@@ -24,6 +24,8 @@ export type PopupMessage =
|
||||
| { type: 'ack_autofill_origin'; hostname: string }
|
||||
| { type: 'get_settings' }
|
||||
| { type: 'update_settings'; settings: Partial<DeviceSettings> }
|
||||
| { type: 'get_vault_settings' }
|
||||
| { type: 'update_vault_settings'; settings: VaultSettings }
|
||||
| { type: 'get_blacklist' }
|
||||
| { type: 'remove_blacklist'; hostname: string };
|
||||
|
||||
@@ -88,13 +90,18 @@ export interface RatePassphraseResponse extends Extract<Response, { ok: true }>
|
||||
data: { score: number; guesses_log10: number };
|
||||
}
|
||||
|
||||
export interface VaultSettingsResponse extends Extract<Response, { ok: true }> {
|
||||
data: { settings: VaultSettings };
|
||||
}
|
||||
|
||||
// --- Capability sets (consumed by the router) ---
|
||||
|
||||
export const POPUP_ONLY_TYPES: ReadonlySet<PopupMessage['type']> = new Set([
|
||||
'is_unlocked', 'unlock', 'lock', 'list_items', 'get_item', 'add_item',
|
||||
'update_item', 'delete_item', 'get_totp', 'sync', 'get_setup_state',
|
||||
'save_setup', 'rate_passphrase', 'generate_password', 'fill_credentials',
|
||||
'ack_autofill_origin', 'get_settings', 'update_settings', 'get_blacklist',
|
||||
'ack_autofill_origin', 'get_settings', 'update_settings',
|
||||
'get_vault_settings', 'update_vault_settings', 'get_blacklist',
|
||||
'remove_blacklist',
|
||||
] as PopupMessage['type'][]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user