diff --git a/docs/superpowers/specs/2026-04-08-museum-channel-access-control-design.md b/docs/superpowers/specs/2026-04-08-museum-channel-access-control-design.md index b6dd38b..df0b1fa 100644 --- a/docs/superpowers/specs/2026-04-08-museum-channel-access-control-design.md +++ b/docs/superpowers/specs/2026-04-08-museum-channel-access-control-design.md @@ -33,12 +33,12 @@ interface ParsedUser { id: number; name: string; role: 'admin' | 'viewer'; - allowedMuseums: string[]; // [] = unrestricted - allowedChannels: string[]; // [] = unrestricted + allowedMuseums: string[] | null; // [] = unrestricted, null = parse error (show nothing) + allowedChannels: string[] | null; // [] = unrestricted, null = parse error (show nothing) } ``` -**Convention:** empty array = full access. Existing users require no migration (missing fields parsed as `[]`). +**Convention:** `[]` = full access (admins), `string[]` = restricted to list, `null` = corrupted value (fail-closed: no data shown). Existing users require no migration (missing fields parsed as `[]`). ### NocoDB Users table @@ -54,7 +54,7 @@ Both default to `"[]"` (unrestricted). ### 1. `src/services/usersService.ts` -- Update `fetchUsers()` to parse `AllowedMuseums` and `AllowedChannels` JSON strings into `string[]` (with safe fallback to `[]` on parse error) +- Update `fetchUsers()` to parse `AllowedMuseums` and `AllowedChannels` JSON strings into `string[]`; on parse error return `null` (fail-closed — no data shown to the user) - Update `createUser()` to serialize `allowedMuseums`/`allowedChannels` arrays as JSON strings - **Add `updateUser(id, fields)`** — new function required (see Prerequisites below) @@ -127,6 +127,8 @@ const visibleChannels = allowedChannels.length > 0 **Layer 2 — Data filtered at base:** ```typescript const permissionFilteredData = useMemo(() => { + // null = corrupted stored value → show nothing (fail-closed) + if (allowedMuseums === null || allowedChannels === null) return []; let d = data; if (allowedMuseums.length > 0) d = d.filter(r => allowedMuseums.includes(r.museum_name)); @@ -167,7 +169,7 @@ Page reload | Admin user | `allowedMuseums: []`, `allowedChannels: []` — full access | | URL param references disallowed museum | Base filter removes it silently | | New museum added to data, not in user's list | Not visible to restricted user | -| JSON parse error on stored value | Falls back to `[]` (unrestricted, fail-open) | +| JSON parse error on stored value | `null` returned → no data shown (fail-closed) | | Page reload | Session restores access lists from server | ---