Make boards have individual access rights #78
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente#78
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Individual users should not see any difference, they should just have access to the boards they're supposed to, without any layer of complexity added. But from the admin stand point, access rights should be separate per board. This is the next step in our effort to turn this into a multi-board version. We will add board creation and management for invited users in the future and also a landing and discovery page, but those are not in the scope of this issue.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Pushed. Here's my reply for the issue:
Boards now carry their own access rights. Branch:
claude/issue-78(compare).The shape
A board is either everyone's or a named few's.
Board.Visibilitysays which, and a newBoardMemberjoin table is the grant list the second one consults — a join table besideBoardrather than a column on it, since a board isn't owned.The switch is there so an empty grant list never has to be interpreted. Read as "everyone", taking the last person off a board would open it to the whole deployment — from the very screen that was removing access. Read as "nobody", every board that predates the feature disappears. One column settles both, and it defaults to
Everyone: the migration needs no backfill and nobody's board changes by deploying this.Nothing new for a member to learn
That was the ask, and it's what the diff does: no lock icon, no greyed-out rows. The nav switcher lists the boards you have,
/lands on the first of those, and a board you weren't given is simply not in the menu. The only screens written for this are empty states — because a link can still be pasted into a chat: no such board, there is one and it isn't yours, you have none yet. They're told apart in the DOM bydata-outcome, not by their copy.From the admin side
/admin/access[/{id}]— same shape as the columns and tags pages (board picker on top, bare path is the default board). Two radio options with their consequences spelled out under them, then the member list. Save stays disabled until something actually differs from the database./admin/boardsgrew a per-row link that reads as the state ("Shared with everyone" / "Shared with 3 member(s)") and links straight at that page.Admins see every board, and the page says so beside each admin in the list. An admin can grant themselves any board in one click and the admin pages must be able to edit a board nobody shared with them — hiding one would be theatre. Grants on an open board are kept rather than cleared, so closing a board again is one click and not a new round of invitations.
Where the enforcement actually is
BoardAccess.VisibleTo(viewer)is anExpression, so the same predicate runs in Postgres and compiled in the unit tests. A hand-written second copy for tests would drift, and it'd drift toward a board someone can read that nobody meant them to.BoardServiceread comes in two — one taking aBoardViewer, one ending inForAdmin— and neither takes an optional viewer. The old one-argumentResolveAsyncwas removed rather than kept, so a new call site can't silently get the unfiltered one..icsendpoints were the real hole: they're the only paths out of a board that are plain HTTP, so a member could otherwise have walked board ids and read every deadline on the deployment out of the URL bar. Both now build a viewer (cookie principal / the DID behind the feed token) and 404 a board that isn't theirs.SetAccessAsyncfiresBoardsChanged, and both the board and the calendar re-resolve on every reload instead of trusting the answer they got on entry.Verified
dotnet build+ all 165 unit tests, Debug and Release (CI's exact commands). 9 of those are new (BoardAccessTests).Everyonedefault checked by hand, and member-deletion confirmed to cascade its grants away (so removing a member still works)..ics404 → granted → board back, in the switcher,.ics200. Plus the admin's own view of a board shared with nobody.boards.spec.js(6),board.spec.js,calendar.spec.js,filter.spec.js— 87 passing. Two failures in that run were environmental, not from this change:i18n.spec.jshard-codeslocalhost:8090for the locale cookie, and oneboard.spec.jsEscape-key test passed on its own (flaked under a 90-test load against a single local instance).One thing I couldn't run here:
tests/e2e/access.spec.jsas committed makes its second member through a PDS signup, and this container has no container engine to bring the stack up. The locally-driven run above is that same test with a pre-seeded member instead — worth onenpx playwright test access.spec.jsagainst the real compose stack before merging.CLAUDE.mdandREADME.mdare updated, including the two places that said per-board rights didn't exist yet.⏱ 34m 23s · 185 turns · tokens: 277.5k in / 102.7k out (+36515.5k cache-read)
📜 Full session transcript
→ Opened PR: #80