The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
| 4 |
| 5 | const alice: ColineActorRef = { kind: "user", userId: "u_alice" }; |
| 6 | const bob: ColineActorRef = { kind: "user", userId: "u_bob" }; |
| 7 | const app: ColineActorRef = { kind: "app" }; |
| 8 | const guest: ColineActorRef = { kind: "visitor", visitorId: "vis_a", signedIn: false, name: null, email: null }; |
| 9 | const otherGuest: ColineActorRef = { kind: "visitor", visitorId: "vis_b", signedIn: false, name: null, email: null }; |
| 10 | const privateAccess = { workspace: "none" as const, readers: [], writers: [] }; |
| 11 |
| 12 | describe("canAccessRecord", () => { |
| 13 | it("lets the exact creator manage its record", () => { |
| 14 | expect(canAccessRecord(privateAccess, alice, alice, "manage")).toBe(true); |
| 15 | expect(canAccessRecord(privateAccess, app, app, "manage")).toBe(true); |
| 16 | }); |
| 17 |
| 18 | it("does not treat other principals as the owner", () => { |
| 19 | expect(canAccessRecord(privateAccess, alice, bob, "read")).toBe(false); |
| 20 | expect(canAccessRecord(privateAccess, alice, app, "read")).toBe(false); |
| 21 | expect(canAccessRecord(privateAccess, guest, app, "read")).toBe(false); |
| 22 | }); |
| 23 |
| 24 | it("never admits a visitor through the record ACL, not even the creator", () => { |
| 25 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, guest, guest, "read")).toBe(false); |
| 26 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, app, guest, "read")).toBe(false); |
| 27 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, alice, guest, "write")).toBe(false); |
| 28 | }); |
| 29 |
| 30 | it("keeps the workspace default readable and writable by members and app logic", () => { |
| 31 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, guest, alice, "write")).toBe(true); |
| 32 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, guest, app, "write")).toBe(true); |
| 33 | expect(canAccessRecord(DEFAULT_RECORD_ACCESS, guest, app, "manage")).toBe(false); |
| 34 | }); |
| 35 |
| 36 | it("matches readers and writers by user id only", () => { |
| 37 | const shared = { workspace: "none" as const, readers: ["u_bob"], writers: [] }; |
| 38 | expect(canAccessRecord(shared, alice, bob, "read")).toBe(true); |
| 39 | expect(canAccessRecord(shared, alice, bob, "write")).toBe(false); |
| 40 | expect(canAccessRecord(shared, alice, app, "read")).toBe(false); |
| 41 | }); |
| 42 | }); |
| 43 |
| 44 | describe("canVisitorAccess", () => { |
| 45 | const signedIn = { visitorId: "vis_a", signedIn: true }; |
| 46 | const anonymous = { visitorId: "vis_a", signedIn: false }; |
| 47 |
| 48 | it("maps each subject to the visitors it admits", () => { |
| 49 | expect(canVisitorAccess("public", anonymous, null)).toBe(true); |
| 50 | expect(canVisitorAccess("visitor", anonymous, null)).toBe(true); |
| 51 | expect(canVisitorAccess("signedIn", anonymous, null)).toBe(false); |
| 52 | expect(canVisitorAccess("signedIn", signedIn, null)).toBe(true); |
| 53 | expect(canVisitorAccess("team", signedIn, null)).toBe(false); |
| 54 | }); |
| 55 |
| 56 | it("admits only the creating visitor for owner", () => { |
| 57 | expect(canVisitorAccess("owner", anonymous, guest)).toBe(true); |
| 58 | expect(canVisitorAccess("owner", anonymous, otherGuest)).toBe(false); |
| 59 | expect(canVisitorAccess("owner", anonymous, app)).toBe(false); |
| 60 | expect(canVisitorAccess("owner", anonymous, null)).toBe(false); |
| 61 | }); |
| 62 | }); |
| 63 |
| 64 | describe("actor keys", () => { |
| 65 | it("round-trips every principal kind", () => { |
| 66 | expect(actorKey(alice)).toBe("user:u_alice"); |
| 67 | expect(actorKey(guest)).toBe("visitor:vis_a"); |
| 68 | expect(actorKey(app)).toBe("app"); |
| 69 | expect(parseActorKey("visitor:vis_a")).toEqual(guest); |
| 70 | expect(parseActorKey("user:u_alice")).toEqual(alice); |
| 71 | expect(parseActorKey("app")).toEqual(app); |
| 72 | }); |
| 73 | }); |
| 74 |