chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import Database from 'better-sqlite3';
import { beforeEach, describe, expect, it } from 'vitest';
import { createSharedStorageSql as createCloudflareStorage } from '../packages/cloudflare/src/index.js';
import { createSharedStorage as createSqliteStorage } from '../packages/storage-sqlite/src/index.js';
// The two adapters answer the same contract from mirrored schemas and share no
// code: one runs on better-sqlite3, the other on Durable Object SQLite. Running
// one suite against both is what keeps them honest about the same behaviour.
const DID = 'did:plc:alice';
const OTHER_DID = 'did:plc:bob';
/**
* A Durable Object `state.storage.sql` over better-sqlite3. The dialects differ
* in places, so a pass here is not a promise the query runs on the real thing.
* @param {Database.Database} db
*/
function createDoSql(db) {
return {
/** @param {string} query @param {...unknown} params */
exec(query, ...params) {
let statement;
try {
statement = db.prepare(query);
} catch (err) {
// A schema string holds several statements; prepare() takes one.
if (!/more than one statement/.test(String(err))) throw err;
db.exec(query);
return { toArray: () => [], rowsRead: 0, rowsWritten: 0 };
}
if (statement.reader) {
const rows = statement.all(...params);
return { toArray: () => rows, rowsRead: rows.length, rowsWritten: 0 };
}
const info = statement.run(...params);
return { toArray: () => [], rowsRead: 0, rowsWritten: info.changes };
},
};
}
const adapters = {
'storage-sqlite': () => createSqliteStorage(new Database(':memory:')),
'cloudflare-do-sqlite': () =>
createCloudflareStorage(
/** @type {any} */ (createDoSql(new Database(':memory:'))),
),
};
for (const [name, create] of Object.entries(adapters)) {
describe(`app password storage (${name})`, () => {
/** @type {ReturnType<typeof create>} */
let storage;
beforeEach(() => {
storage = create();
});
it('round-trips a password by hash', async () => {
const stored = await storage.putAppPassword(
DID,
'client',
'hash-a',
false,
'2026-01-01T00:00:00.000Z',
);
expect(stored).toBe(true);
expect(await storage.getAppPasswordByHash(DID, 'hash-a')).toEqual({
name: 'client',
privileged: false,
createdAt: '2026-01-01T00:00:00.000Z',
});
});
it('preserves the privileged flag', async () => {
await storage.putAppPassword(
DID,
'trusted',
'hash-p',
true,
'2026-01-01T00:00:00.000Z',
);
const found = await storage.getAppPasswordByHash(DID, 'hash-p');
expect(found?.privileged).toBe(true);
});
it('returns null for an unknown hash', async () => {
expect(await storage.getAppPasswordByHash(DID, 'nope')).toBeNull();
});
it('refuses a duplicate name without disturbing the original', async () => {
await storage.putAppPassword(
DID,
'client',
'hash-first',
false,
'2026-01-01T00:00:00.000Z',
);
const second = await storage.putAppPassword(
DID,
'client',
'hash-second',
true,
'2026-01-02T00:00:00.000Z',
);
expect(second).toBe(false);
// The first password must still authenticate, and the rejected one must not.
expect(
await storage.getAppPasswordByHash(DID, 'hash-first'),
).not.toBeNull();
expect(await storage.getAppPasswordByHash(DID, 'hash-second')).toBeNull();
});
it('scopes passwords to their account', async () => {
await storage.putAppPassword(
DID,
'client',
'hash-shared',
false,
'2026-01-01T00:00:00.000Z',
);
// Same hash, different DID: must not authenticate. In practice the hash
// is salted per-DID so this cannot collide, but the query must not rely
// on that.
expect(
await storage.getAppPasswordByHash(OTHER_DID, 'hash-shared'),
).toBeNull();
expect(await storage.listAppPasswords(OTHER_DID)).toEqual([]);
});
it('lists passwords oldest first and never exposes a hash', async () => {
await storage.putAppPassword(
DID,
'second',
'h2',
false,
'2026-02-01T00:00:00.000Z',
);
await storage.putAppPassword(
DID,
'first',
'h1',
true,
'2026-01-01T00:00:00.000Z',
);
const listed = await storage.listAppPasswords(DID);
expect(listed.map((p) => p.name)).toEqual(['first', 'second']);
expect(listed[0]).toEqual({
name: 'first',
privileged: true,
createdAt: '2026-01-01T00:00:00.000Z',
});
for (const entry of listed) {
expect(Object.keys(entry).sort()).toEqual([
'createdAt',
'name',
'privileged',
]);
}
});
it('deletes by name and stops the hash authenticating', async () => {
await storage.putAppPassword(
DID,
'client',
'hash-x',
false,
'2026-01-01T00:00:00.000Z',
);
expect(await storage.deleteAppPassword(DID, 'client')).toBe(true);
expect(await storage.getAppPasswordByHash(DID, 'hash-x')).toBeNull();
expect(await storage.listAppPasswords(DID)).toEqual([]);
});
it('reports deleting a name that is not there', async () => {
expect(await storage.deleteAppPassword(DID, 'ghost')).toBe(false);
});
it('frees the name once revoked', async () => {
await storage.putAppPassword(
DID,
'client',
'hash-old',
false,
'2026-01-01T00:00:00.000Z',
);
await storage.deleteAppPassword(DID, 'client');
const reused = await storage.putAppPassword(
DID,
'client',
'hash-new',
false,
'2026-01-03T00:00:00.000Z',
);
expect(reused).toBe(true);
expect(
await storage.getAppPasswordByHash(DID, 'hash-new'),
).not.toBeNull();
expect(await storage.getAppPasswordByHash(DID, 'hash-old')).toBeNull();
});
});
describe(`oauth token storage (${name})`, () => {
/** @type {ReturnType<typeof create>} */
let storage;
beforeEach(() => {
storage = create();
});
it("lists a DID's tokens with the key needed to revoke one", async () => {
await storage.putOAuthToken('refresh-a', {
did: DID,
clientId: 'https://app.example',
scope: 'atproto',
createdAt: 1,
});
await storage.putOAuthToken('refresh-b', {
did: OTHER_DID,
clientId: 'https://other.example',
createdAt: 2,
});
const listed = await storage.listOAuthTokensByDid(DID);
expect(listed).toHaveLength(1);
expect(listed[0].clientId).toBe('https://app.example');
expect(listed[0].tokenId).toBe('refresh-a');
});
it('deletes the token a listing pointed at', async () => {
await storage.putOAuthToken('refresh-a', { did: DID, createdAt: 1 });
await storage.putOAuthToken('refresh-c', { did: DID, createdAt: 2 });
const [first] = await storage.listOAuthTokensByDid(DID);
await storage.deleteOAuthToken(/** @type {string} */ (first.tokenId));
const remaining = await storage.listOAuthTokensByDid(DID);
expect(remaining).toHaveLength(1);
expect(remaining[0].tokenId).not.toBe(first.tokenId);
});
});
describe(`passkey storage (${name})`, () => {
/** @type {ReturnType<typeof create>} */
let storage;
/** @param {string} credentialId @param {string} did */
const record = (credentialId, did = DID) => ({
credentialId,
did,
name: 'laptop',
publicKey: 'spki-bytes',
algorithm: -7,
signCount: 0,
createdAt: '2026-01-01T00:00:00.000Z',
lastUsedAt: null,
});
beforeEach(() => {
storage = create();
});
it('round-trips a credential by its id alone', async () => {
expect(await storage.putPasskey(record('cred-a'))).toBe(true);
// A sign-in knows the credential and not the account
const found = await storage.getPasskey('cred-a');
expect(found?.did).toBe(DID);
expect(found?.publicKey).toBe('spki-bytes');
expect(found?.algorithm).toBe(-7);
});
it("lists only the account's own credentials", async () => {
await storage.putPasskey(record('cred-a'));
await storage.putPasskey(record('cred-b', OTHER_DID));
const listed = await storage.listPasskeys(DID);
expect(listed).toHaveLength(1);
expect(listed[0].credentialId).toBe('cred-a');
});
it('refuses to register the same credential twice', async () => {
await storage.putPasskey(record('cred-a'));
expect(await storage.putPasskey(record('cred-a'))).toBe(false);
});
it('records the counter and last use', async () => {
await storage.putPasskey(record('cred-a'));
await storage.touchPasskey('cred-a', 42, '2026-02-02T00:00:00.000Z');
const found = await storage.getPasskey('cred-a');
expect(found?.signCount).toBe(42);
expect(found?.lastUsedAt).toBe('2026-02-02T00:00:00.000Z');
});
it('deletes only on a matching account', async () => {
await storage.putPasskey(record('cred-a'));
expect(await storage.deletePasskey(OTHER_DID, 'cred-a')).toBe(false);
expect(await storage.getPasskey('cred-a')).not.toBeNull();
expect(await storage.deletePasskey(DID, 'cred-a')).toBe(true);
expect(await storage.getPasskey('cred-a')).toBeNull();
expect(await storage.listPasskeys(DID)).toHaveLength(0);
});
});
}