Monitor websites uptime using Cloudflare Workers
Something went wrong. Try again.
23 kB · 634 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635import { afterAll, beforeAll, describe, expect, it } from 'vitest';import { acceptWorkspaceInvitation, computeInvitationExpiresAt, computeInvitationStatus, createWorkspaceInvitation, INVITATION_EXPIRY_MS, listWorkspaceInvitations, resendWorkspaceInvitation, revokeWorkspaceInvitation, WorkspaceInvitationError,} from './workspace-invitations.js';import { pgFirst, pgRun } from '@uptime/cloudflare/postgres';import { createControlFixture, seedControlUser, seedControlWorkspace, type ControlTestFixture,} from './testing/control-fixture.js';
let fixture: ControlTestFixture;
beforeAll(async () => { fixture = await createControlFixture();}, 60_000);
afterAll(async () => { await fixture?.close();});
describe('workspace-invitations module', { timeout: 60_000 }, () => { describe('concurrency races with two independent connections', () => { it('handles last seat race under competing transactions: one succeeds, one rejected with 409', async () => { const conn2 = await fixture.openConnection(); const wsId = 'race-last-seat-ws'; const ownerDid = 'did:plc:race_owner_1'; const member1Did = 'did:plc:race_m1'; const invitee1Did = 'did:plc:race_inv1'; const invitee2Did = 'did:plc:race_inv2';
await seedControlUser(fixture.db, ownerDid, 'owner.race1'); await seedControlUser(fixture.db, member1Did, 'm1.race1'); await seedControlUser(fixture.db, invitee1Did, 'inv1.race1'); await seedControlUser(fixture.db, invitee2Did, 'inv2.race1'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
// Add 2nd member -> 2 members total (free tier limit is 3, so exactly 1 seat left) await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('memberships')} (workspace_id, did, role, created_at) VALUES ($1, $2, 'maintainer', $3)`, [wsId, member1Did, new Date().toISOString()], );
// Two concurrent transactions attempt to claim the last remaining seat via invitation creation const [res1, res2] = await Promise.allSettled([ createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: invitee1Did, role: 'viewer', }), createWorkspaceInvitation(conn2, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: invitee2Did, role: 'viewer', }), ]);
const fulfilled = [res1, res2].filter((r) => r.status === 'fulfilled'); const rejected = [res1, res2].filter((r) => r.status === 'rejected');
expect(fulfilled).toHaveLength(1); expect(rejected).toHaveLength(1);
// The rejected one must be 409 member_limit const rejection = (rejected[0] as PromiseRejectedResult).reason; expect(rejection).toBeInstanceOf(WorkspaceInvitationError); expect(rejection.status).toBe(409); expect(rejection.code).toBe('member_limit');
// The winning invitee can accept the invitation to fill the workspace const winner = (fulfilled[0] as PromiseFulfilledResult<any>).value; await acceptWorkspaceInvitation(fixture.db, { invitationId: winner.invitation.id, principalDid: winner.invitation.inviteeDid, });
// Total memberships must be exactly 3 (owner + member1 + winning invitee) const countRow = await pgFirst<{ count: string | number }>( fixture.db, `SELECT count(*) AS count FROM ${fixture.db.table('memberships')} WHERE workspace_id = $1`, [wsId], ); expect(Number(countRow?.count)).toBe(3);
// Exactly 1 invitation_accepted event recorded const eventsRow = await pgFirst<{ count: string | number }>( fixture.db, `SELECT count(*) AS count FROM ${fixture.db.table('workspace_events')} WHERE workspace_id = $1 AND event = 'invitation_accepted'`, [wsId], ); expect(Number(eventsRow?.count)).toBe(1); });
it('handles replay race under competing transactions: one succeeds, one rejected with 404', async () => { const conn2 = await fixture.openConnection(); const wsId = 'race-replay-ws'; const ownerDid = 'did:plc:race_owner_2'; const inviteeDid = 'did:plc:race_inv_replay';
await seedControlUser(fixture.db, ownerDid, 'owner.race2'); await seedControlUser(fixture.db, inviteeDid, 'inv.race2'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
const { invitation } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'maintainer', });
// Both connections attempt to accept the SAME invitation concurrently const [res1, res2] = await Promise.allSettled([ acceptWorkspaceInvitation(fixture.db, { invitationId: invitation.id, principalDid: inviteeDid, }), acceptWorkspaceInvitation(conn2, { invitationId: invitation.id, principalDid: inviteeDid, }), ]);
const fulfilled = [res1, res2].filter((r) => r.status === 'fulfilled'); const rejected = [res1, res2].filter((r) => r.status === 'rejected');
expect(fulfilled).toHaveLength(1); expect(rejected).toHaveLength(1);
const rejection = (rejected[0] as PromiseRejectedResult).reason; expect(rejection).toBeInstanceOf(WorkspaceInvitationError); expect(rejection.status).toBe(404); expect(rejection.code).toBe('not_found');
// Only 1 membership created for invitee const memberCount = await pgFirst<{ count: string | number }>( fixture.db, `SELECT count(*) AS count FROM ${fixture.db.table('memberships')} WHERE workspace_id = $1 AND did = $2`, [wsId, inviteeDid], ); expect(Number(memberCount?.count)).toBe(1); });
it('handles revoke vs accept race under competing transactions: one succeeds, one rejected with 404', async () => { const conn2 = await fixture.openConnection(); const wsId = 'race-revoke-accept-ws'; const ownerDid = 'did:plc:race_owner_3'; const inviteeDid = 'did:plc:race_inv_revoke';
await seedControlUser(fixture.db, ownerDid, 'owner.race3'); await seedControlUser(fixture.db, inviteeDid, 'inv.race3'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
const { invitation } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'maintainer', });
// conn1 revokes, conn2 accepts const [revokeRes, acceptRes] = await Promise.allSettled([ revokeWorkspaceInvitation(fixture.db, { workspaceId: wsId, invitationId: invitation.id, actorDid: ownerDid, }), acceptWorkspaceInvitation(conn2, { invitationId: invitation.id, principalDid: inviteeDid, }), ]);
const fulfilled = [revokeRes, acceptRes].filter((r) => r.status === 'fulfilled'); const rejected = [revokeRes, acceptRes].filter((r) => r.status === 'rejected');
expect(fulfilled).toHaveLength(1); expect(rejected).toHaveLength(1);
const rejection = (rejected[0] as PromiseRejectedResult).reason; expect(rejection).toBeInstanceOf(WorkspaceInvitationError); expect(rejection.status).toBe(404); expect(rejection.code).toBe('not_found'); }); });
describe('changes()-based conditional audit semantics', () => { it('does not write audit event for failed mutation', async () => { const wsId = 'audit-ws'; const ownerDid = 'did:plc:owner_audit'; await seedControlUser(fixture.db, ownerDid, 'owner.audit'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
const eventsBefore = await pgFirst<{ count: string | number }>( fixture.db, `SELECT count(*) AS count FROM ${fixture.db.table('workspace_events')} WHERE workspace_id = $1`, [wsId], );
// Attempt revoke on non-existent invitation await expect( revokeWorkspaceInvitation(fixture.db, { workspaceId: wsId, invitationId: 'non-existent-invitation-id', actorDid: ownerDid, }), ).rejects.toMatchObject({ status: 404, code: 'not_found' });
const eventsAfter = await pgFirst<{ count: string | number }>( fixture.db, `SELECT count(*) AS count FROM ${fixture.db.table('workspace_events')} WHERE workspace_id = $1`, [wsId], ); expect(Number(eventsAfter?.count)).toBe(Number(eventsBefore?.count)); });
it('rolls back mutations if lifecycle audit persistence fails', async () => { const wsId = 'audit-rollback-ws'; const ownerDid = 'did:plc:owner_rollback'; const inviteeDid = 'did:plc:inv_rollback'; await seedControlUser(fixture.db, ownerDid, 'owner.rollback'); await seedControlUser(fixture.db, inviteeDid, 'inv.rollback'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
// Install a trigger that fails inserts on workspace_events await fixture.fixture.adminDb.query(` CREATE OR REPLACE FUNCTION ${fixture.schema}.fail_workspace_events() RETURNS trigger AS $$ BEGIN RAISE EXCEPTION 'audit unavailable'; END; $$ LANGUAGE plpgsql; `); await fixture.fixture.adminDb.query(` CREATE TRIGGER test_fail_audit_trigger BEFORE INSERT ON ${fixture.db.table('workspace_events')} FOR EACH ROW EXECUTE FUNCTION ${fixture.schema}.fail_workspace_events(); `);
try { await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'viewer', }), ).rejects.toThrow('audit unavailable');
// Mutation must be rolled back: no invitation row inserted! const invRow = await pgFirst<{ id: string }>( fixture.db, `SELECT id FROM ${fixture.db.table('workspace_invitations')} WHERE workspace_id = $1`, [wsId], ); expect(invRow).toBeNull(); } finally { await fixture.fixture.adminDb.query(` DROP TRIGGER IF EXISTS test_fail_audit_trigger ON ${fixture.db.table('workspace_events')}; `); await fixture.fixture.adminDb.query(` DROP FUNCTION IF EXISTS ${fixture.schema}.fail_workspace_events(); `); } }); });
describe('7-day expiry, quota, and authentication semantics', () => { it('exact 7-day expiry boundary calculation and enforcement', async () => { const wsId = '11111111-1111-4111-8111-111111111111'; const ownerDid = 'did:plc:owner111'; const inviteeDid = 'did:plc:invitee111';
await seedControlUser(fixture.db, ownerDid, 'owner111.test'); await seedControlUser(fixture.db, inviteeDid, 'invitee111.test'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
const baseTimeMs = 1700000000000; const baseIso = new Date(baseTimeMs).toISOString();
expect(computeInvitationExpiresAt(baseIso)).toBe( new Date(baseTimeMs + INVITATION_EXPIRY_MS).toISOString(), );
// 1ms before 7 days -> pending expect(computeInvitationStatus(baseIso, baseTimeMs + INVITATION_EXPIRY_MS - 1)).toBe( 'pending', ); // exact 7 days -> expired expect(computeInvitationStatus(baseIso, baseTimeMs + INVITATION_EXPIRY_MS)).toBe('expired'); // 1ms after 7 days -> expired expect(computeInvitationStatus(baseIso, baseTimeMs + INVITATION_EXPIRY_MS + 1)).toBe( 'expired', );
// Create invitation at baseTime const { invitation } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'maintainer', now: baseTimeMs, });
// Accepting at exact 7-day boundary must be denied await expect( acceptWorkspaceInvitation(fixture.db, { invitationId: invitation.id, principalDid: inviteeDid, now: baseTimeMs + INVITATION_EXPIRY_MS, }), ).rejects.toMatchObject({ status: 404, code: 'not_found', });
// Accepting 1ms before 7-day boundary succeeds const result = await acceptWorkspaceInvitation(fixture.db, { invitationId: invitation.id, principalDid: inviteeDid, now: baseTimeMs + INVITATION_EXPIRY_MS - 1, }); expect(result.workspaceId).toBe(wsId); });
it('renewal (resend) rotates new invitation ID and createdAt so old link is invalid', async () => { const wsId = '22222222-2222-4222-8222-222222222222'; const ownerDid = 'did:plc:owner222'; const inviteeDid = 'did:plc:invitee222';
await seedControlUser(fixture.db, ownerDid, 'owner222.test'); await seedControlUser(fixture.db, inviteeDid, 'invitee222.test'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
const t0 = 1700000000000; const { invitation: original } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'viewer', now: t0, });
// Advance 8 days (now expired) const t8Days = t0 + 8 * 86_400_000;
// Resend / renew invitation const { invitation: renewed } = await resendWorkspaceInvitation(fixture.db, { workspaceId: wsId, invitationId: original.id, actorDid: ownerDid, now: t8Days, });
expect(renewed.id).not.toBe(original.id); expect(renewed.createdAt).toBe(new Date(t8Days).toISOString()); expect(renewed.expiresAt).toBe(new Date(t8Days + INVITATION_EXPIRY_MS).toISOString()); expect(renewed.status).toBe('pending');
// Old link must fail await expect( acceptWorkspaceInvitation(fixture.db, { invitationId: original.id, principalDid: inviteeDid, now: t8Days + 1000, }), ).rejects.toMatchObject({ status: 404, code: 'not_found', });
// New link must succeed const accepted = await acceptWorkspaceInvitation(fixture.db, { invitationId: renewed.id, principalDid: inviteeDid, now: t8Days + 1000, }); expect(accepted.workspaceId).toBe(wsId); });
it('repeated existing invite upserts work at full quota', async () => { const wsId = '33333333-3333-4333-8333-333333333333'; const ownerDid = 'did:plc:owner333'; const member1Did = 'did:plc:member333_1'; const inviteeDid = 'did:plc:invitee333'; const thirdUserDid = 'did:plc:user333_3';
await seedControlUser(fixture.db, ownerDid, 'owner333.test'); await seedControlUser(fixture.db, member1Did, 'member333_1.test'); await seedControlUser(fixture.db, inviteeDid, 'invitee333.test'); await seedControlUser(fixture.db, thirdUserDid, 'third333.test'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
// Add 2nd member -> 2 members total await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('memberships')} (workspace_id, did, role, created_at) VALUES ($1, $2, 'maintainer', $3)`, [wsId, member1Did, new Date().toISOString()], );
// Create pending invite for inviteeDid -> 2 members + 1 pending = 3 (full quota!) const { invitation: firstInvite } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'viewer', }); expect(firstInvite.role).toBe('viewer');
// Inviting a NEW user when quota is full (3) must be rejected await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: thirdUserDid, role: 'maintainer', }), ).rejects.toMatchObject({ status: 409, code: 'member_limit', });
// Upserting existing invite for inviteeDid at full quota must SUCCEED and rotate ID const { invitation: upsertedInvite } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid, role: 'maintainer', }); expect(upsertedInvite.id).not.toBe(firstInvite.id); expect(upsertedInvite.role).toBe('maintainer');
// Resend for inviteeDid at full quota must also SUCCEED const { invitation: resentInvite } = await resendWorkspaceInvitation(fixture.db, { workspaceId: wsId, invitationId: upsertedInvite.id, actorDid: ownerDid, }); expect(resentInvite.id).not.toBe(upsertedInvite.id); expect(resentInvite.role).toBe('maintainer'); });
it('expired invites do not consume quota', async () => { const wsId = '44444444-4444-4444-8444-444444444444'; const ownerDid = 'did:plc:owner444'; const member1Did = 'did:plc:member444_1'; const expiredInviteeDid = 'did:plc:expired444'; const newInviteeDid = 'did:plc:new444';
await seedControlUser(fixture.db, ownerDid, 'owner444.test'); await seedControlUser(fixture.db, member1Did, 'member444.test'); await seedControlUser(fixture.db, expiredInviteeDid, 'expired444.test'); await seedControlUser(fixture.db, newInviteeDid, 'new444.test'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
// 2 members await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('memberships')} (workspace_id, did, role, created_at) VALUES ($1, $2, 'maintainer', $3)`, [wsId, member1Did, new Date().toISOString()], );
// 1 expired invite (created 10 days ago) const tenDaysAgo = new Date(Date.now() - 10 * 86_400_000).toISOString(); await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('workspace_invitations')} (id, workspace_id, invitee_did, role, created_at) VALUES ($1, $2, $3, $4, $5)`, ['expired-id', wsId, expiredInviteeDid, 'viewer', tenDaysAgo], );
// New invite must SUCCEED because expired invite does not consume quota const { invitation: newInvite } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: newInviteeDid, role: 'maintainer', }); expect(newInvite.status).toBe('pending');
// List shows both pending and expired status const { invitations } = await listWorkspaceInvitations(fixture.db, { workspaceId: wsId, actorDid: ownerDid, }); expect(invitations).toHaveLength(2); expect(invitations.find((i) => i.inviteeDid === expiredInviteeDid)?.status).toBe('expired'); expect(invitations.find((i) => i.inviteeDid === newInviteeDid)?.status).toBe('pending'); });
it('rejects inviting already-members explicitly', async () => { const wsId = '55555555-5555-4555-8555-555555555555'; const ownerDid = 'did:plc:owner555'; const memberDid = 'did:plc:member555';
await seedControlUser(fixture.db, ownerDid, 'owner555.test'); await seedControlUser(fixture.db, memberDid, 'member555.test'); await seedControlWorkspace(fixture.db, { workspaceId: wsId, ownerDid });
// Owner inviting self await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: ownerDid, role: 'maintainer', }), ).rejects.toMatchObject({ status: 400, code: 'invalid_invitation', });
// Add member await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('memberships')} (workspace_id, did, role, created_at) VALUES ($1, $2, 'maintainer', $3)`, [wsId, memberDid, new Date().toISOString()], );
// Owner inviting existing member await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsId, actorDid: ownerDid, inviteeDid: memberDid, role: 'viewer', }), ).rejects.toMatchObject({ status: 409, code: 'already_member', }); });
it('enforces owner-only authentication and prevents IDOR', async () => { const wsA = '77777777-7777-4777-8777-777777777771'; const wsB = '77777777-7777-4777-8777-777777777772'; const ownerA = 'did:plc:ownerA'; const ownerB = 'did:plc:ownerB'; const viewerA = 'did:plc:viewerA'; const invitee = 'did:plc:invitee777';
await seedControlUser(fixture.db, ownerA, 'ownerA.test'); await seedControlUser(fixture.db, ownerB, 'ownerB.test'); await seedControlUser(fixture.db, viewerA, 'viewerA.test'); await seedControlUser(fixture.db, invitee, 'invitee777.test');
await seedControlWorkspace(fixture.db, { workspaceId: wsA, ownerDid: ownerA }); await seedControlWorkspace(fixture.db, { workspaceId: wsB, ownerDid: ownerB });
// Add viewerA to wsA await pgRun( fixture.db, `INSERT INTO ${fixture.db.table('memberships')} (workspace_id, did, role, created_at) VALUES ($1, $2, 'viewer', $3)`, [wsA, viewerA, new Date().toISOString()], );
// Viewer trying to create invite in wsA -> 403 forbidden await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsA, actorDid: viewerA, inviteeDid: invitee, role: 'viewer', }), ).rejects.toMatchObject({ status: 403, code: 'forbidden', });
// Owner B trying to create invite in wsA (IDOR) -> 403 forbidden await expect( createWorkspaceInvitation(fixture.db, { workspaceId: wsA, actorDid: ownerB, inviteeDid: invitee, role: 'viewer', }), ).rejects.toMatchObject({ status: 403, code: 'forbidden', });
// Create valid invite in wsA by ownerA const { invitation } = await createWorkspaceInvitation(fixture.db, { workspaceId: wsA, actorDid: ownerA, inviteeDid: invitee, role: 'viewer', });
// Owner B trying to revoke wsA invite using wsB scope -> 404 not found await expect( revokeWorkspaceInvitation(fixture.db, { workspaceId: wsB, invitationId: invitation.id, actorDid: ownerB, }), ).rejects.toMatchObject({ status: 404, code: 'not_found', });
// Owner B trying to revoke wsA invite using wsA scope -> 403 forbidden await expect( revokeWorkspaceInvitation(fixture.db, { workspaceId: wsA, invitationId: invitation.id, actorDid: ownerB, }), ).rejects.toMatchObject({ status: 403, code: 'forbidden', }); }); });});