| … | … | @@ -17,6 +17,24 @@ import { json, error, readJson } from '../response.js' |
| 17 | 17 | import { UnauthorizedError, ForbiddenError } from '../errors.js' |
| 18 | 18 | import { requireAuth } from './auth-helpers.js' |
| 19 | 19 | |
| 20 | /** |
| 21 | * Deny a shared-notes read when the slug also has a regular notebook entry that |
| 22 | * is private and the caller isn't authorized to read it. Pure shared notebooks |
| 23 | * (no regular metadata) stay readable — they have no visibility model. Sends a |
| 24 | * not-found response and returns true when access is denied. |
| 25 | */ |
| 26 | export async function denyPrivateSharedRead (req, res, server, slug) { |
| 27 | const meta = await server.noteManager.getNotebook(slug) |
| 28 | if (meta && meta.visibility === 'private') { |
| 29 | const { authorized } = await server.authManager.authorizeRequest(req, 'read', slug) |
| 30 | if (!authorized) { |
| 31 | json(res, { error: 'Notebook not shared', code: 'NOT_FOUND', status: 404 }, 404) |
| 32 | return true |
| 33 | } |
| 34 | } |
| 35 | return false |
| 36 | } |
| 37 | |
| 20 | 38 | /** |
| 21 | 39 | * Register all multi-writer routes |
| 22 | 40 | */ |
| … | … | @@ -156,9 +174,10 @@ export function registerMultiwriterRoutes (router, server) { |
| 156 | 174 | |
| 157 | 175 | // ==================== SHARED NOTES ==================== |
| 158 | 176 | |
| 159 | | // List notes from shared Autobase (open read — matches notebook pattern) |
| 177 | // List notes from shared Autobase (public read; private notebooks gated) |
| 160 | 178 | router.get('/api/notebooks/:notebook/shared-notes', async (req, res) => { |
| 161 | 179 | const slug = req.params.notebook |
| 180 | if (await denyPrivateSharedRead(req, res, server, slug)) return |
| 162 | 181 | const nb = server.multiwriter.getSharedNotebook(slug) |
| 163 | 182 | if (!nb) { |
| 164 | 183 | json(res, { error: 'Notebook not shared', code: 'NOT_FOUND', status: 404 }, 404) |
| … | … | @@ -208,9 +227,10 @@ export function registerMultiwriterRoutes (router, server) { |
| 208 | 227 | json(res, { ok: true }) |
| 209 | 228 | }) |
| 210 | 229 | |
| 211 | | // Get shared note content (open read) |
| 230 | // Get shared note content (public read; private notebooks gated) |
| 212 | 231 | router.get('/api/notebooks/:notebook/shared-notes/:noteId/content', async (req, res) => { |
| 213 | 232 | const slug = req.params.notebook |
| 233 | if (await denyPrivateSharedRead(req, res, server, slug)) return |
| 214 | 234 | const nb = server.multiwriter.getSharedNotebook(slug) |
| 215 | 235 | if (!nb) { |
| 216 | 236 | json(res, { error: 'Notebook not shared', code: 'NOT_FOUND', status: 404 }, 404) |