// The account password as a stored credential: the configured password seeds // a fresh server, a user change stores a hash that wins, and rotating the // configured password is the operator's reset over the stored hash. import { describe, expect, it } from 'vitest'; import { createAccountPasswordStore } from '../src/account-password.js'; const DID = 'did:plc:passwordtestaaaaaaaaaaaa'; /** One mutable account-password slot, shareable across store instances. */ function makeStorage() { /** @type {import('../src/ports.js').AccountPasswordState|null} */ let state = null; return { actorStorage: /** @type {any} */ ({ async getAccountPassword() { return state; }, async setAccountPassword(/** @type {typeof state} */ next) { state = next; }, }), read: () => state, }; } /** @param {any} actorStorage @param {string} [env] */ function makeStore(actorStorage, env) { return createAccountPasswordStore({ actorStorage, getDid: async () => DID, configuredPassword: env, }); } describe('account password store', () => { it('verifies against the configured password when nothing is stored', async () => { const s = makeStore(makeStorage().actorStorage, 'seed-pw'); expect(await s.verify('seed-pw')).toBe(true); expect(await s.verify('wrong')).toBe(false); expect(await s.configured()).toBe(true); }); it('refuses everything with no password anywhere', async () => { const s = makeStore(makeStorage().actorStorage); expect(await s.verify('anything')).toBe(false); expect(await s.verify('')).toBe(false); expect(await s.configured()).toBe(false); }); it('a changed password wins over the configured one', async () => { const s = makeStore(makeStorage().actorStorage, 'seed-pw'); expect(await s.change('user-pw-1')).toBe(true); expect(await s.verify('user-pw-1')).toBe(true); expect(await s.verify('seed-pw')).toBe(false); }); it('a rotated configured password resets the stored one', async () => { const storage = makeStorage(); const before = makeStore(storage.actorStorage, 'seed-pw'); await before.change('user-pw-1'); // The operator set a new secret: the next server start sees it. const after = makeStore(storage.actorStorage, 'rotated-pw'); expect(await after.verify('user-pw-1')).toBe(false); expect(await after.verify('rotated-pw')).toBe(true); // The reset cleared the stored hash. expect(storage.read()).toBe(null); }); it('removing the configured password keeps the stored one working', async () => { const storage = makeStorage(); const before = makeStore(storage.actorStorage, 'seed-pw'); await before.change('user-pw-1'); const after = makeStore(storage.actorStorage, undefined); expect(await after.verify('user-pw-1')).toBe(true); expect(await after.verify('seed-pw')).toBe(false); expect(await after.configured()).toBe(true); }); it('setting a configured password where none existed resets a stored one', async () => { const storage = makeStorage(); const before = makeStore(storage.actorStorage, undefined); await before.change('user-pw-1'); // Setting a secret where none existed is also the operator's reset. const after = makeStore(storage.actorStorage, 'late-pw'); expect(await after.verify('late-pw')).toBe(true); expect(storage.read()).toBe(null); }); it('cannot change on storage without the password methods', async () => { const s = makeStore(/** @type {any} */ ({}), 'seed-pw'); expect(s.changeable()).toBe(false); expect(await s.change('user-pw-1')).toBe(false); // Verification falls back to the configured password. expect(await s.verify('seed-pw')).toBe(true); }); it('rejects non-string and empty candidates', async () => { const s = makeStore(makeStorage().actorStorage, 'seed-pw'); expect(await s.verify(undefined)).toBe(false); expect(await s.verify(null)).toBe(false); expect(await s.verify('')).toBe(false); expect(await s.verify({ length: 7 })).toBe(false); }); });