~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
108 lines · 4.0 KB Raw History
// 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);
  });
});