clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

feat/site-feedback
nzip / worker / test / vault_policy.runtime.test.ts
vault_policy.runtime.test.ts 7.3K
1import { env, SELF } from "cloudflare:test";2import { beforeEach, describe, expect, it } from "vitest";3import { verifyPassword } from "../src/password.ts";45const headers = { authorization: "Bearer runtime-test-token", "content-type": "application/json" };6const emptyHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";7const manifest = { v: 1, files: { "index.html": { h: emptyHash, s: 0, ct: "text/html" } } };89function api(path: string, body?: unknown, method = "POST") {10 return SELF.fetch(`https://share.demo.dev/api/${path}`, {11 method,12 headers,13 ...(body === undefined ? {} : { body: JSON.stringify(body) }),14 });15}1617async function createVault(policy: Record<string, unknown> = {}) {18 const response = await api("vaults", {19 name: "reviews",20 slot: 6,21 description: "Private short-lived personal review artifacts",22 defaultTtl: 7,23 maxTtl: 14,24 requirePassword: true,25 defaultPassword: "review-password",26 ...policy,27 });28 expect(response.status, await response.clone().text()).toBe(201);29 await env.CONTENT.put(`blob/${emptyHash}`, new Uint8Array());30 return response;31}3233async function publish(34 target: unknown = { vault: "reviews" },35 policy: Record<string, unknown> = {},36) {37 return await api("push/commit", { manifest, target, ...policy });38}3940describe("Vault purpose and enforced policy", () => {41 beforeEach(async () => {42 await env.DB.batch([43 env.DB.prepare("DELETE FROM sites WHERE vault_slot = 6"),44 env.DB.prepare("DELETE FROM vaults WHERE slot = 6"),45 ]);46 });47 it("exposes policy metadata but no verifier and inherits TTL/password on creation", async () => {48 const vault = await createVault();49 const metadata = await vault.json<Record<string, unknown>>();50 expect(metadata).toMatchObject({51 description: "Private short-lived personal review artifacts",52 effectiveDefaultTtl: 7,53 maxTtl: 14,54 requirePassword: true,55 hasDefaultPassword: true,56 });57 expect(JSON.stringify(metadata)).not.toContain("review-password");58 expect(JSON.stringify(metadata)).not.toContain("default_password_hash");59 const published = await publish();60 expect(published.status).toBe(200);61 const site = await published.json<62 { address: string; ttl: number; ttlSource: string; protected: boolean }63 >();64 expect(site).toMatchObject({ ttl: 7, ttlSource: "vault", protected: true });65 const stored = await env.DB.prepare("SELECT password_hash FROM sites WHERE address = ?")66 .bind(parseInt(site.address, 16)).first<{ password_hash: string }>();67 expect(await verifyPassword("review-password", stored!.password_hash)).toBe(true);68 expect((await SELF.fetch(`https://${site.address}.demo.dev/`)).status).toBe(401);69 const status = await api("status", undefined, "GET");70 const text = await status.text();71 expect(text).toContain('"hasDefaultPassword":true');72 expect(text).not.toContain(stored!.password_hash);73 });7475 it("enforces caps and required protection on create, revision, PATCH and raw DB writes", async () => {76 await createVault();77 for (const policy of [{ ttl: "forever" }, { ttl: 15 }, { password: null }]) {78 expect((await publish(undefined, policy)).status).toBe(400);79 }80 const created = await (await publish()).json<{ address: string; expiresAt: number }>();81 const target = { address: parseInt(created.address, 16) };82 const updated = await (await publish(target)).json<Record<string, unknown>>();83 expect(updated).toMatchObject({84 address: created.address,85 expiresAt: created.expiresAt,86 protected: true,87 ttlSource: "existing-site",88 seq: 2,89 });90 expect((await publish(target, { password: null })).status).toBe(400);91 expect((await publish(target, { ttl: 15 })).status).toBe(400);92 expect((await api(`sites/${created.address}`, { password: null }, "PATCH")).status).toBe(400);93 expect((await api(`sites/${created.address}`, { ttl: "forever" }, "PATCH")).status).toBe(400);94 const reverted = await api(`sites/${created.address}/revert`, { toSeq: 1 });95 expect(reverted.status).toBe(200);96 expect(await reverted.json()).toMatchObject({ protected: true, expiresAt: created.expiresAt });97 await expect(98 env.DB.prepare("UPDATE sites SET password_hash = NULL WHERE address = ?")99 .bind(target.address).run(),100 ).rejects.toThrow("vault requires password protection");101 await expect(102 env.DB.prepare("UPDATE sites SET expires_at = NULL WHERE address = ?")103 .bind(target.address).run(),104 ).rejects.toThrow("site expiry exceeds vault maximum TTL");105 });106107 it("does not silently change existing sites when tightening a vault policy", async () => {108 await createVault({ maxTtl: null, requirePassword: false, defaultPassword: null });109 const created = await (await publish(undefined, { ttl: 30 })).json<{ address: string }>();110 const protection = await api("vaults/reviews", { requirePassword: true }, "PATCH");111 expect(protection.status).toBe(409);112 expect(await protection.text()).toContain("protect existing sites");113 const retention = await api("vaults/reviews", { maxTtl: 14 }, "PATCH");114 expect(retention.status).toBe(409);115 expect(await retention.text()).toContain("shorten existing site expiry");116 expect(117 (await api(`sites/${created.address}`, { password: "site-password", ttl: 7 }, "PATCH"))118 .status,119 ).toBe(200);120 const tightened = await api("vaults/reviews", { requirePassword: true, maxTtl: 14 }, "PATCH");121 expect(tightened.status).toBe(200);122 expect(await tightened.json()).toMatchObject({ requirePassword: true, maxTtl: 14 });123 });124125 it("rotates defaults only for new sites and refuses unprotected creation without a default", async () => {126 await createVault();127 const first = await (await publish()).json<{ address: string }>();128 const rotated = await api("vaults/reviews", { defaultPassword: "next-password" }, "PATCH");129 expect(rotated.status).toBe(200);130 const second = await (await publish()).json<{ address: string }>();131 const hashFor = (address: string) =>132 env.DB.prepare("SELECT password_hash FROM sites WHERE address = ?")133 .bind(parseInt(address, 16)).first<{ password_hash: string }>();134 expect(await verifyPassword("review-password", (await hashFor(first.address))!.password_hash))135 .toBe(true);136 expect(await verifyPassword("next-password", (await hashFor(second.address))!.password_hash))137 .toBe(true);138 expect((await api("vaults/reviews", { defaultPassword: null }, "PATCH")).status).toBe(200);139 expect((await publish()).status).toBe(400);140 expect((await publish(undefined, { password: "explicit-password" })).status).toBe(200);141 expect((await publish({ address: parseInt(first.address, 16) })).status).toBe(200);142 expect(await verifyPassword("review-password", (await hashFor(first.address))!.password_hash))143 .toBe(true);144 });145146 it("rejects contradictory retention defaults and malformed security policy", async () => {147 for (148 const policy of [149 { defaultTtl: "forever", maxTtl: 7 },150 { defaultTtl: 30, maxTtl: 7 },151 { maxTtl: 0 },152 { requirePassword: "yes" },153 { defaultPassword: "x" },154 ]155 ) {156 const response = await api("vaults", { name: "invalid-policy", ...policy });157 expect(response.status).toBeGreaterThanOrEqual(400);158 expect(response.status).toBeLessThan(500);159 }160 });161});