openvibers/openvibe.search Commits

Search and suggest answer any origin (Access-Control-Allow-Origin *, never with credentials), so every site's navbar search and anyone's own box can ask from the browser; what they read is anonymous's (#111)

OP OpenVibers committed 16 days ago · 1c43775ddb · 2 files, +15 −1

Changes

ddiff --git a/server/api/query.js b/server/api/query.js
iindex 3350aec..4e9ba7f 100644
---- a/server/api/query.js
++++ b/server/api/query.js
@@@ -9,7 +9,10 @@
*
* GET /api/v1/search?q=&owner=&type=&lang=&facet.<key>=<value>&facets=<k1,k2>&limit=&cursor=
* GET /api/v1/suggest?q=&owner=&type=&limit=
-- * GET /api/v1/documents/:owner/:type/:id one document by exact id (unlisted: any signed-in
++ * GET /api/v1/documents/:owner/:type/:id
++ *
++ * Search and suggest answer any origin (Access-Control-Allow-Origin *, never with credentials): what another site's
++ * page reads is what anonymous reads. one document by exact id (unlisted: any signed-in
* subject; public noindex: anyone)
*
* Nothing here ever returns an ACL, a draft, an unpublished or deleted document, a total count, or
@@@ -201,6 +204,12 @@ function queryRouter({ config, store, engine, auth, limits, searcher = createSea
const router = express.Router();
const guarded = (limit, h) => withViewer(auth, h, limit);
++ // Any page may ask search and suggest from the browser (the navbar's search on every OpenVibe site, Shared 4.21, and
++ // anyone's own box): Access-Control-Allow-Origin * and nothing more. A browser never sends cookies to a * answer,
++ // and a cross-site Authorization header would need a preflight this router does not grant, so what another site's
++ // page can read is exactly what anonymous gets: public, published, indexable documents.
++ router.get(['/api/v1/search', '/api/v1/suggest'], (req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); });
++
// Per-actor limits (server/actor-limits.js): queries and document reads take the defaults. Suggest
// answers a search box as the person types, several requests a second, so it allows 300 a minute.
router.get('/api/v1/search', guarded(limits('search.query'), async (req, res, viewer) => {
ddiff --git a/test/acl-leak.test.js b/test/acl-leak.test.js
iindex 6a76815..2b2e24a 100644
---- a/test/acl-leak.test.js
++++ b/test/acl-leak.test.js
@@@ -75,6 +75,11 @@ t('anonymous sees public listed documents only — hits, snippets, facets, sugge
assertNoLeak(s, ['pub']);
const s2 = await get('/api/v1/suggest?q=pub');
assert.deepStrictEqual(s2.body.suggestions.map(x => x.id), ['pub']);
++ // Any site's page may read search and suggest (navbar search, Shared 4.21): *, and what it reads is anonymous's.
++ assert.deepStrictEqual([s.headers.get('access-control-allow-origin'), r.headers.get('access-control-allow-origin')], ['*', '*']);
++ assert.strictEqual(s.headers.get('access-control-allow-credentials'), null, 'never with credentials');
++ const doc = await get('/api/v1/documents/blog/post/pub');
++ assert.strictEqual(doc.headers.get('access-control-allow-origin'), null, 'only search and suggest');
const browse = await q('facets=category');
assert.deepStrictEqual(idsOf(browse), ['pub']);
assertNoLeak(browse, ['pub']);