feat: isolate asset release access classes (TASK-WP5-04)
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 59s
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 59s
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { createApp } from "../../apps/api/src/app.js";
|
||||
import { RegistrationService } from "../../apps/api/src/registration.js";
|
||||
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
|
||||
import { createAssetReleaseManifest } from "../../packages/asset-release-manifest/src/index.js";
|
||||
|
||||
const now = Date.parse("2026-08-03T10:00:00.000Z");
|
||||
const releaseVersion = "asset-20260803.1";
|
||||
const publicId = "7f0c9530-a7d9-4bf1-8c65-0e9298dd04ac";
|
||||
const previewId = "ab18fd72-60e1-44e3-a9a0-3dfccb12e17c";
|
||||
const ungrantedPreviewId = "d8fe890d-6df4-46a9-a578-96c1f8361ac0";
|
||||
const privateId = "e3792605-5252-4d3b-a101-827408ab3515";
|
||||
const headers = { host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" };
|
||||
const roots: string[] = [];
|
||||
const registrations: RegistrationService[] = [];
|
||||
|
||||
function addUser(registration: RegistrationService, role: "super_admin" | "user") {
|
||||
const userId = randomUUID();
|
||||
registration.database.prepare(`INSERT INTO users (
|
||||
user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at
|
||||
) VALUES (?, ?, ?, 'active', ?, ?, ?)`).run(
|
||||
userId,
|
||||
`${role}-${userId}@example.invalid`,
|
||||
role,
|
||||
role === "user" ? 1 : 0,
|
||||
randomUUID(),
|
||||
now,
|
||||
);
|
||||
if (role === "user") {
|
||||
registration.database.prepare("INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Asset User', '@asset_user')").run(userId);
|
||||
registration.database.prepare("INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 10, 0, ?)").run(userId, now);
|
||||
} else {
|
||||
registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(userId);
|
||||
}
|
||||
return { session: registration.issueAuthenticatedSession(userId, role === "user" ? "user" : "admin"), userId };
|
||||
}
|
||||
|
||||
function evidence(name: string, value: unknown) {
|
||||
const directory = process.env.DADA_EVIDENCE_DIR_WP5_RES;
|
||||
if (!directory) return;
|
||||
mkdirSync(directory, { recursive: true });
|
||||
writeFileSync(resolve(directory, name), `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function harness() {
|
||||
const root = mkdtempSync(join(tmpdir(), "dada-wp5-04-api-"));
|
||||
roots.push(root);
|
||||
const registration = new RegistrationService({
|
||||
challengePepper: Buffer.alloc(32, 0x31), clock: () => now,
|
||||
currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath: join(root, "dada.sqlite3"),
|
||||
invitePepper: Buffer.alloc(32, 0x32), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x33),
|
||||
});
|
||||
registrations.push(registration);
|
||||
const owner = addUser(registration, "user");
|
||||
const intruder = addUser(registration, "user");
|
||||
const admin = addUser(registration, "super_admin");
|
||||
const assetReleases = createAssetReleaseManifest({
|
||||
items: [
|
||||
{ access_class: "public_release_asset", cache_kind: "thumbnail", content: Buffer.from("public"), mime_type: "image/png", relative_path: "public/FLOWER001.png", resource_id: publicId, root_ref: "canonical-assets" },
|
||||
{ access_class: "internal_preview_asset", content: Buffer.from("preview"), mime_type: "image/webp", relative_path: "preview/FLOWER009.webp", resource_id: previewId, root_ref: "canonical-assets" },
|
||||
{ access_class: "internal_preview_asset", content: Buffer.from("ungranted-preview"), mime_type: "image/webp", relative_path: "preview/FLOWER010.webp", resource_id: ungrantedPreviewId, root_ref: "canonical-assets" },
|
||||
{ access_class: "private_user_asset", content: Buffer.from("private"), mime_type: "image/png", owner_id: owner.userId, relative_path: "private/generated.png", resource_id: privateId, root_ref: "managed-assets" },
|
||||
],
|
||||
release_version: releaseVersion,
|
||||
});
|
||||
let previewGranted = true;
|
||||
let previewChecks = 0;
|
||||
const appPromise = createApp({
|
||||
assetReleases,
|
||||
browserGate: false,
|
||||
networkBoundary: { allowTestPort: true },
|
||||
previewAssetAuthorizer: ({ resourceId, userId }) => {
|
||||
previewChecks += 1;
|
||||
return previewGranted && userId === owner.userId && resourceId === previewId;
|
||||
},
|
||||
privateAssetAdminAuthorizer: ({ adminUserId, ownerId }) => adminUserId === admin.userId && ownerId === owner.userId,
|
||||
registration,
|
||||
});
|
||||
return { admin, appPromise, intruder, owner, previewChecks: () => previewChecks, revokePreview: () => { previewGranted = false; } };
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const registration of registrations.splice(0)) registration.close();
|
||||
for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
describe("TDD-WP5-RES-001 three access classes", () => {
|
||||
it("separates route projections, per-request authorization, and cache headers", async () => {
|
||||
const test = harness();
|
||||
const app = await test.appPromise;
|
||||
const ownerCookie = `dada_session=${test.owner.session.sessionToken}`;
|
||||
const intruderCookie = `dada_session=${test.intruder.session.sessionToken}`;
|
||||
const adminCookie = `dada_admin_session=${test.admin.session.sessionToken}`;
|
||||
|
||||
const publicManifest = await app.inject({ headers, method: "GET", url: `/api/v1/assets/public/${releaseVersion}/manifest` });
|
||||
const publicAsset = await app.inject({ headers, method: "GET", url: `/api/v1/assets/public/${releaseVersion}/${publicId}` });
|
||||
const previewManifest = await app.inject({ headers: { ...headers, cookie: ownerCookie }, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/manifest` });
|
||||
const previewAsset = await app.inject({ headers: { ...headers, cookie: ownerCookie }, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${previewId}` });
|
||||
const privateManifest = await app.inject({ headers: { ...headers, cookie: ownerCookie }, method: "GET", url: `/api/v1/private-assets/${releaseVersion}/manifest` });
|
||||
const privateAsset = await app.inject({ headers: { ...headers, cookie: ownerCookie }, method: "GET", url: `/api/v1/private-assets/${releaseVersion}/${privateId}` });
|
||||
const deniedPrivate = await app.inject({ headers: { ...headers, cookie: intruderCookie }, method: "GET", url: `/api/v1/private-assets/${releaseVersion}/${privateId}` });
|
||||
const controlledAdmin = await app.inject({ headers: { ...headers, cookie: adminCookie }, method: "GET", url: `/api/v1/private-assets/${releaseVersion}/${privateId}` });
|
||||
|
||||
expect(publicManifest.statusCode).toBe(200);
|
||||
expect(publicManifest.headers["cache-control"]).toBe("public, max-age=31536000, immutable");
|
||||
expect(publicManifest.json().items).toEqual([expect.objectContaining({ access_class: "public_release_asset", resource_id: publicId })]);
|
||||
expect(JSON.stringify(publicManifest.json())).not.toMatch(/preview|private|relative_path|root_ref|[A-Z]:\\\\/i);
|
||||
expect(publicAsset.statusCode).toBe(200);
|
||||
expect(publicAsset.rawPayload).toEqual(Buffer.from("public"));
|
||||
expect(publicAsset.headers["cache-control"]).toBe("public, max-age=31536000, immutable");
|
||||
|
||||
expect(previewManifest.statusCode).toBe(200);
|
||||
expect(previewManifest.headers["cache-control"]).toBe("private, no-store");
|
||||
expect(previewManifest.json().items).toEqual([expect.objectContaining({ resource_id: previewId })]);
|
||||
expect(JSON.stringify(previewManifest.json())).not.toContain(ungrantedPreviewId);
|
||||
expect(previewAsset.statusCode).toBe(200);
|
||||
expect(previewAsset.headers["cache-control"]).toBe("private, no-store");
|
||||
expect(privateManifest.json().items).toEqual([expect.objectContaining({ access_class: "private_user_asset", resource_id: privateId })]);
|
||||
expect(privateAsset.rawPayload).toEqual(Buffer.from("private"));
|
||||
expect(privateAsset.headers["cache-control"]).toBe("private, no-store");
|
||||
expect(deniedPrivate.statusCode).toBe(404);
|
||||
expect(controlledAdmin.statusCode).toBe(200);
|
||||
|
||||
const publicGuess = await app.inject({ headers, method: "GET", url: `/api/v1/assets/public/${releaseVersion}/${privateId}` });
|
||||
expect(publicGuess.statusCode).toBe(404);
|
||||
test.revokePreview();
|
||||
const revoked = await app.inject({ headers: { ...headers, cookie: ownerCookie }, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${previewId}` });
|
||||
expect(revoked.statusCode).toBe(404);
|
||||
expect(test.previewChecks()).toBe(4);
|
||||
|
||||
evidence("response.json", {
|
||||
controlled_admin_status: controlledAdmin.statusCode,
|
||||
private_intruder_status: deniedPrivate.statusCode,
|
||||
public_guess_status: publicGuess.statusCode,
|
||||
revoked_preview_status: revoked.statusCode,
|
||||
});
|
||||
evidence("headers.json", {
|
||||
private: privateAsset.headers["cache-control"],
|
||||
preview: previewAsset.headers["cache-control"],
|
||||
public: publicAsset.headers["cache-control"],
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user