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)
Changes
@@@ -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 = createSeaconst 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) => {@@@ -75,6 +75,11 @@ t('anonymous sees public listed documents only — hits, snippets, facets, suggeassertNoLeak(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']);
@@ -9,7 +9,10 @@
@@ -9,7 +9,10 @@
*
*
* GET /api/v1/search?q=&owner=&type=&lang=&facet.<key>=<value>&facets=<k1,k2>&limit=&cursor=
* 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/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)
* subject; public noindex: anyone)
*
*
* Nothing here ever returns an ACL, a draft, an unpublished or deleted document, a total count, or
* 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
@@ -201,6 +204,12 @@ function queryRouter({ config, store, engine, auth, limits, searcher = createSea
const router = express.Router();
const router = express.Router();
const guarded = (limit, h) => withViewer(auth, h, limit);
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
// 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.
// 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) => {
router.get('/api/v1/search', guarded(limits('search.query'), async (req, res, viewer) => {
@@ -75,6 +75,11 @@ t('anonymous sees public listed documents only — hits, snippets, facets, sugge
@@ -75,6 +75,11 @@ t('anonymous sees public listed documents only — hits, snippets, facets, sugge
assertNoLeak(s, ['pub']);
assertNoLeak(s, ['pub']);
const s2 = await get('/api/v1/suggest?q=pub');
const s2 = await get('/api/v1/suggest?q=pub');
assert.deepStrictEqual(s2.body.suggestions.map(x => x.id), ['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');
const browse = await q('facets=category');
assert.deepStrictEqual(idsOf(browse), ['pub']);
assert.deepStrictEqual(idsOf(browse), ['pub']);
assertNoLeak(browse, ['pub']);
assertNoLeak(browse, ['pub']);