feat(auth): remember encrypted local login credentials

This commit is contained in:
justme committed 2026-10-13 01:39:24 +08:00
1 parent f6cfb12716
commit b65e32885f
9 files changed
+148 -30

No files matched your search

@@ -0,0 +1,24 @@
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));
});
});