137 lines
4.5 KiB
TypeScript
137 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import {
|
|
fileId,
|
|
GrantManager,
|
|
InMemoryPartitionStore,
|
|
KeyRing,
|
|
PartitionManager,
|
|
TokenManager,
|
|
TransferManager,
|
|
userId,
|
|
wrapKey,
|
|
} from "../src/index.js";
|
|
import { getDefaultPolicy } from "../src/partitions/partition-policy/partition-policy.js";
|
|
|
|
describe("drive-sharing — end-to-end integration", () => {
|
|
it("create partition, add member, grant access, transfer file", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
// Build the full lifecycle stack with deterministic clocks / IDs.
|
|
const events = new Map<string, unknown[]>();
|
|
const bus = {
|
|
on: () => () => undefined,
|
|
emit: (k: string, p: unknown) => {
|
|
const arr = events.get(k) ?? [];
|
|
arr.push(p);
|
|
events.set(k, arr);
|
|
},
|
|
};
|
|
let counter = 0;
|
|
const idGen = () => `id-${(counter += 1)}`;
|
|
|
|
const masterKey = new Uint8Array(32).fill(0x77);
|
|
const keyRing = new KeyRing({ masterKey });
|
|
const store = new InMemoryPartitionStore();
|
|
const partitions = new PartitionManager({
|
|
store,
|
|
keyRing,
|
|
clock: () => 1_000,
|
|
idGenerator: idGen,
|
|
events: bus,
|
|
});
|
|
const grants = new GrantManager({
|
|
clock: () => 1_000,
|
|
idGenerator: idGen,
|
|
events: bus,
|
|
});
|
|
const tokens = new TokenManager({
|
|
clock: () => 1_000,
|
|
defaultTTL: 60_000,
|
|
masterKey,
|
|
idGenerator: idGen,
|
|
});
|
|
|
|
// 1. Create source and target partitions.
|
|
const src = await partitions.create({
|
|
name: "research",
|
|
type: "shared",
|
|
ownerId: userId("alice"),
|
|
});
|
|
const dst = await partitions.create({
|
|
name: "review",
|
|
type: "shared",
|
|
ownerId: userId("alice"),
|
|
});
|
|
|
|
// 2. Add bob as a member of source.
|
|
const updatedSrc = await partitions.addMember(src.id, userId("bob"), {
|
|
permissions: ["read", "share"],
|
|
});
|
|
expect(updatedSrc.members.length).toBe(1);
|
|
|
|
// 3. Mint a grant for bob to read the file.
|
|
const grant = grants.create({
|
|
grantorId: userId("alice"),
|
|
granteeId: userId("bob"),
|
|
partitionId: src.id,
|
|
permissions: ["read"],
|
|
expiresAt: 5_000_000,
|
|
});
|
|
const check = grants.check(grant.id, userId("bob"), "read");
|
|
expect(check.allowed).toBe(true);
|
|
|
|
// 4. Mint a share token for the file.
|
|
const token = tokens.create({
|
|
partitionId: src.id,
|
|
createdBy: userId("alice"),
|
|
permissions: ["read"],
|
|
});
|
|
expect(tokens.validate(token.id).valid).toBe(true);
|
|
|
|
// 5. Set up transfer with a real wrapped DEK.
|
|
const sourceKek = new Uint8Array(32).fill(0xaa);
|
|
const targetKek = new Uint8Array(32).fill(0xbb);
|
|
const dek = new Uint8Array(32).fill(0xcc);
|
|
const aadSrc = new TextEncoder().encode(
|
|
`drive-sharing/v1/transfer/${src.id}/${fileId("f-1")}`
|
|
);
|
|
const wrapped = await wrapKey(sourceKek, dek, { aad: aadSrc });
|
|
|
|
const transfer = new TransferManager({
|
|
kekResolver: async (_uid, pid) =>
|
|
pid === src.id ? sourceKek : targetKek,
|
|
policyResolver: async (pid) => {
|
|
const partition =
|
|
pid === src.id ? updatedSrc : { ...dst, members: [] };
|
|
return {
|
|
policy: getDefaultPolicy(),
|
|
partition,
|
|
};
|
|
},
|
|
wrappedKeyResolver: async () => wrapped,
|
|
clock: () => 2_000,
|
|
});
|
|
|
|
const result = await transfer.transfer({
|
|
fileId: fileId("f-1"),
|
|
sourcePartitionId: src.id,
|
|
targetPartitionId: dst.id,
|
|
userId: userId("alice"),
|
|
direction: "export",
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(result.rewrappedKey).toBeDefined();
|
|
|
|
// 6. Verify lifecycle event emission.
|
|
expect(events.get("partition:created")?.length).toBe(2);
|
|
expect(events.get("member:added")?.length).toBe(1);
|
|
expect(events.get("grant:created")?.length).toBe(1);
|
|
|
|
// 7. Revoke the grant and confirm subsequent checks are denied.
|
|
grants.revoke(grant.id);
|
|
expect(grants.check(grant.id, userId("bob"), "read").allowed).toBe(
|
|
false
|
|
);
|
|
});
|
|
});
|