docs: update access control spec — fail-closed on corrupted permissions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-04-08 17:38:26 +03:00
parent aa143dfacd
commit d4ce5b6478

View File

@@ -33,12 +33,12 @@ interface ParsedUser {
id: number; id: number;
name: string; name: string;
role: 'admin' | 'viewer'; role: 'admin' | 'viewer';
allowedMuseums: string[]; // [] = unrestricted allowedMuseums: string[] | null; // [] = unrestricted, null = parse error (show nothing)
allowedChannels: string[]; // [] = unrestricted 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 ### NocoDB Users table
@@ -54,7 +54,7 @@ Both default to `"[]"` (unrestricted).
### 1. `src/services/usersService.ts` ### 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 - Update `createUser()` to serialize `allowedMuseums`/`allowedChannels` arrays as JSON strings
- **Add `updateUser(id, fields)`** — new function required (see Prerequisites below) - **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:** **Layer 2 — Data filtered at base:**
```typescript ```typescript
const permissionFilteredData = useMemo(() => { const permissionFilteredData = useMemo(() => {
// null = corrupted stored value → show nothing (fail-closed)
if (allowedMuseums === null || allowedChannels === null) return [];
let d = data; let d = data;
if (allowedMuseums.length > 0) if (allowedMuseums.length > 0)
d = d.filter(r => allowedMuseums.includes(r.museum_name)); d = d.filter(r => allowedMuseums.includes(r.museum_name));
@@ -167,7 +169,7 @@ Page reload
| Admin user | `allowedMuseums: []`, `allowedChannels: []` — full access | | Admin user | `allowedMuseums: []`, `allowedChannels: []` — full access |
| URL param references disallowed museum | Base filter removes it silently | | 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 | | 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 | | Page reload | Session restores access lists from server |
--- ---