25 lines
1.4 KiB
TypeScript
25 lines
1.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { decryptLogin, encryptLogin } from './credentialCipher';
|
|
|
|
describe('local encrypted login fields', () => {
|
|
const endpoint = 'https://drop.test/api/v1/';
|
|
const fields = { username: 'alice', password: 'a-test-password' };
|
|
it('restores fields using a non-exportable key while keeping plaintext out of the record', async () => {
|
|
const value = await encryptLogin(fields, endpoint);
|
|
expect(value.key.extractable).toBe(false);
|
|
expect(new TextDecoder().decode(value.ciphertext)).not.toContain(fields.password);
|
|
expect(await decryptLogin(value, endpoint)).toEqual(fields);
|
|
await expect(crypto.subtle.exportKey('raw', value.key)).rejects.toThrow();
|
|
});
|
|
it('binds a saved credential to its API server and rejects tampering', async () => {
|
|
const value = await encryptLogin(fields, endpoint);
|
|
await expect(decryptLogin(value, 'https://other.test/api/v1/')).rejects.toThrow();
|
|
const bytes = new Uint8Array(value.ciphertext); bytes[0] = (bytes[0] ?? 0) ^ 1;
|
|
await expect(decryptLogin({ ...value, ciphertext: bytes.buffer }, endpoint)).rejects.toThrow();
|
|
});
|
|
it('uses independent random encryption material for repeated saves', async () => {
|
|
const first = await encryptLogin(fields, endpoint); const second = await encryptLogin(fields, endpoint);
|
|
expect(first.iv).not.toEqual(second.iv); expect(new Uint8Array(first.ciphertext)).not.toEqual(new Uint8Array(second.ciphertext));
|
|
});
|
|
});
|