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' });
|
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 };
|
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':
|
case 'get_blacklist':
|
||||||
return { ok: true, data: { blacklist: await loadBlacklist() } };
|
return { ok: true, data: { blacklist: await loadBlacklist() } };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
Item, ItemId, Manifest, ManifestEntry, VaultConfig, SetupState,
|
Item, ItemId, Manifest, ManifestEntry, VaultConfig, SetupState,
|
||||||
DeviceSettings, GeneratorRequest,
|
DeviceSettings, GeneratorRequest, VaultSettings,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// --- Messages a popup (or setup page) may send ---
|
// --- Messages a popup (or setup page) may send ---
|
||||||
@@ -24,6 +24,8 @@ export type PopupMessage =
|
|||||||
| { type: 'ack_autofill_origin'; hostname: string }
|
| { type: 'ack_autofill_origin'; hostname: string }
|
||||||
| { type: 'get_settings' }
|
| { type: 'get_settings' }
|
||||||
| { type: 'update_settings'; settings: Partial<DeviceSettings> }
|
| { type: 'update_settings'; settings: Partial<DeviceSettings> }
|
||||||
|
| { type: 'get_vault_settings' }
|
||||||
|
| { type: 'update_vault_settings'; settings: VaultSettings }
|
||||||
| { type: 'get_blacklist' }
|
| { type: 'get_blacklist' }
|
||||||
| { type: 'remove_blacklist'; hostname: string };
|
| { type: 'remove_blacklist'; hostname: string };
|
||||||
|
|
||||||
@@ -88,13 +90,18 @@ export interface RatePassphraseResponse extends Extract<Response, { ok: true }>
|
|||||||
data: { score: number; guesses_log10: number };
|
data: { score: number; guesses_log10: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VaultSettingsResponse extends Extract<Response, { ok: true }> {
|
||||||
|
data: { settings: VaultSettings };
|
||||||
|
}
|
||||||
|
|
||||||
// --- Capability sets (consumed by the router) ---
|
// --- Capability sets (consumed by the router) ---
|
||||||
|
|
||||||
export const POPUP_ONLY_TYPES: ReadonlySet<PopupMessage['type']> = new Set([
|
export const POPUP_ONLY_TYPES: ReadonlySet<PopupMessage['type']> = new Set([
|
||||||
'is_unlocked', 'unlock', 'lock', 'list_items', 'get_item', 'add_item',
|
'is_unlocked', 'unlock', 'lock', 'list_items', 'get_item', 'add_item',
|
||||||
'update_item', 'delete_item', 'get_totp', 'sync', 'get_setup_state',
|
'update_item', 'delete_item', 'get_totp', 'sync', 'get_setup_state',
|
||||||
'save_setup', 'rate_passphrase', 'generate_password', 'fill_credentials',
|
'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',
|
'remove_blacklist',
|
||||||
] as PopupMessage['type'][]);
|
] as PopupMessage['type'][]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user