chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// 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);
});
});