@backstage/plugin-search-backend 0.2.7 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/index.cjs.js +45 -9
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/package.json +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @backstage/plugin-search-backend
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cd6854046e: Validate query string in search endpoint
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/backend-common@0.10.4
|
|
10
|
+
- @backstage/config@0.1.13
|
|
11
|
+
|
|
12
|
+
## 0.3.1-next.0
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @backstage/backend-common@0.10.4-next.0
|
|
18
|
+
|
|
19
|
+
## 0.3.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- a41fbfe739: Search result location filtering
|
|
24
|
+
|
|
25
|
+
This change introduces a filter for search results based on their location protocol. The intention is to filter out unsafe or
|
|
26
|
+
malicious values before they can be consumed by the frontend. By default locations must be http/https URLs (or paths).
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @backstage/backend-common@0.10.0
|
|
32
|
+
|
|
33
|
+
## 0.2.8
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly
|
|
38
|
+
- Updated dependencies
|
|
39
|
+
- @backstage/backend-common@0.9.13
|
|
40
|
+
|
|
3
41
|
## 0.2.7
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -3,26 +3,62 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var Router = require('express-promise-router');
|
|
6
|
+
var zod = require('zod');
|
|
7
|
+
var backendCommon = require('@backstage/backend-common');
|
|
8
|
+
var errors = require('@backstage/errors');
|
|
6
9
|
|
|
7
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
11
|
|
|
9
12
|
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const jsonObjectSchema = zod.z.lazy(() => {
|
|
15
|
+
const jsonValueSchema = zod.z.lazy(() => zod.z.union([
|
|
16
|
+
zod.z.string(),
|
|
17
|
+
zod.z.number(),
|
|
18
|
+
zod.z.boolean(),
|
|
19
|
+
zod.z.null(),
|
|
20
|
+
zod.z.array(jsonValueSchema),
|
|
21
|
+
jsonObjectSchema
|
|
22
|
+
]));
|
|
23
|
+
return zod.z.record(jsonValueSchema);
|
|
24
|
+
});
|
|
25
|
+
const allowedLocationProtocols = ["http:", "https:"];
|
|
26
|
+
async function createRouter(options) {
|
|
27
|
+
const { engine, logger } = options;
|
|
28
|
+
const requestSchema = zod.z.object({
|
|
29
|
+
term: zod.z.string().default(""),
|
|
30
|
+
filters: jsonObjectSchema.optional(),
|
|
31
|
+
types: zod.z.array(zod.z.string()).optional(),
|
|
32
|
+
pageCursor: zod.z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
const filterResultSet = ({ results, ...resultSet }) => ({
|
|
35
|
+
...resultSet,
|
|
36
|
+
results: results.filter((result) => {
|
|
37
|
+
const protocol = new URL(result.document.location, "https://example.com").protocol;
|
|
38
|
+
const isAllowed = allowedLocationProtocols.includes(protocol);
|
|
39
|
+
if (!isAllowed) {
|
|
40
|
+
logger.info(`Rejected search result for "${result.document.title}" as location protocol "${protocol}" is unsafe`);
|
|
41
|
+
}
|
|
42
|
+
return isAllowed;
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
const router = Router__default["default"]();
|
|
16
46
|
router.get("/query", async (req, res) => {
|
|
17
|
-
|
|
18
|
-
|
|
47
|
+
var _a;
|
|
48
|
+
const parseResult = requestSchema.safeParse(req.query);
|
|
49
|
+
if (!parseResult.success) {
|
|
50
|
+
throw new errors.InputError(`Invalid query string: ${parseResult.error}`);
|
|
51
|
+
}
|
|
52
|
+
const query = parseResult.data;
|
|
53
|
+
logger.info(`Search request received: term="${query.term}", filters=${JSON.stringify(query.filters)}, types=${query.types ? query.types.join(",") : ""}, pageCursor=${(_a = query.pageCursor) != null ? _a : ""}`);
|
|
19
54
|
try {
|
|
20
|
-
const
|
|
21
|
-
res.send(
|
|
55
|
+
const resultSet = await (engine == null ? void 0 : engine.query(query));
|
|
56
|
+
res.send(filterResultSet(resultSet));
|
|
22
57
|
} catch (err) {
|
|
23
58
|
throw new Error(`There was a problem performing the search query. ${err}`);
|
|
24
59
|
}
|
|
25
60
|
});
|
|
61
|
+
router.use(backendCommon.errorHandler());
|
|
26
62
|
return router;
|
|
27
63
|
}
|
|
28
64
|
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport {
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport { z } from 'zod';\nimport { errorHandler } from '@backstage/backend-common';\nimport { InputError } from '@backstage/errors';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { SearchResultSet } from '@backstage/search-common';\nimport { SearchEngine } from '@backstage/plugin-search-backend-node';\n\nconst jsonObjectSchema: z.ZodSchema<JsonObject> = z.lazy(() => {\n const jsonValueSchema: z.ZodSchema<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n jsonObjectSchema,\n ]),\n );\n\n return z.record(jsonValueSchema);\n});\n\nexport type RouterOptions = {\n engine: SearchEngine;\n logger: Logger;\n};\n\nconst allowedLocationProtocols = ['http:', 'https:'];\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { engine, logger } = options;\n\n const requestSchema = z.object({\n term: z.string().default(''),\n filters: jsonObjectSchema.optional(),\n types: z.array(z.string()).optional(),\n pageCursor: z.string().optional(),\n });\n\n const filterResultSet = ({ results, ...resultSet }: SearchResultSet) => ({\n ...resultSet,\n results: results.filter(result => {\n const protocol = new URL(result.document.location, 'https://example.com')\n .protocol;\n const isAllowed = allowedLocationProtocols.includes(protocol);\n if (!isAllowed) {\n logger.info(\n `Rejected search result for \"${result.document.title}\" as location protocol \"${protocol}\" is unsafe`,\n );\n }\n return isAllowed;\n }),\n });\n\n const router = Router();\n router.get(\n '/query',\n async (req: express.Request, res: express.Response<SearchResultSet>) => {\n const parseResult = requestSchema.safeParse(req.query);\n\n if (!parseResult.success) {\n throw new InputError(`Invalid query string: ${parseResult.error}`);\n }\n\n const query = parseResult.data;\n\n logger.info(\n `Search request received: term=\"${\n query.term\n }\", filters=${JSON.stringify(query.filters)}, types=${\n query.types ? query.types.join(',') : ''\n }, pageCursor=${query.pageCursor ?? ''}`,\n );\n\n try {\n const resultSet = await engine?.query(query);\n\n res.send(filterResultSet(resultSet));\n } catch (err) {\n throw new Error(\n `There was a problem performing the search query. ${err}`,\n );\n }\n },\n );\n\n router.use(errorHandler());\n\n return router;\n}\n"],"names":["z","Router","InputError","errorHandler"],"mappings":";;;;;;;;;;;;;AA0BA,MAAM,mBAA4CA,MAAE,KAAK,MAAM;AAC7D,QAAM,kBAA0CA,MAAE,KAAK,MACrDA,MAAE,MAAM;AAAA,IACNA,MAAE;AAAA,IACFA,MAAE;AAAA,IACFA,MAAE;AAAA,IACFA,MAAE;AAAA,IACFA,MAAE,MAAM;AAAA,IACR;AAAA;AAIJ,SAAOA,MAAE,OAAO;AAAA;AAQlB,MAAM,2BAA2B,CAAC,SAAS;4BAGzC,SACyB;AACzB,QAAM,EAAE,QAAQ,WAAW;AAE3B,QAAM,gBAAgBA,MAAE,OAAO;AAAA,IAC7B,MAAMA,MAAE,SAAS,QAAQ;AAAA,IACzB,SAAS,iBAAiB;AAAA,IAC1B,OAAOA,MAAE,MAAMA,MAAE,UAAU;AAAA,IAC3B,YAAYA,MAAE,SAAS;AAAA;AAGzB,QAAM,kBAAkB,CAAC,EAAE,YAAY;AAAkC,OACpE;AAAA,IACH,SAAS,QAAQ,OAAO,YAAU;AAChC,YAAM,WAAW,IAAI,IAAI,OAAO,SAAS,UAAU,uBAChD;AACH,YAAM,YAAY,yBAAyB,SAAS;AACpD,UAAI,CAAC,WAAW;AACd,eAAO,KACL,+BAA+B,OAAO,SAAS,gCAAgC;AAAA;AAGnF,aAAO;AAAA;AAAA;AAIX,QAAM,SAASC;AACf,SAAO,IACL,UACA,OAAO,KAAsB,QAA2C;AA9E5E;AA+EM,UAAM,cAAc,cAAc,UAAU,IAAI;AAEhD,QAAI,CAAC,YAAY,SAAS;AACxB,YAAM,IAAIC,kBAAW,yBAAyB,YAAY;AAAA;AAG5D,UAAM,QAAQ,YAAY;AAE1B,WAAO,KACL,kCACE,MAAM,kBACM,KAAK,UAAU,MAAM,mBACjC,MAAM,QAAQ,MAAM,MAAM,KAAK,OAAO,kBACxB,YAAM,eAAN,YAAoB;AAGtC,QAAI;AACF,YAAM,YAAY,wCAAc,MAAM;AAEtC,UAAI,KAAK,gBAAgB;AAAA,aAClB,KAAP;AACA,YAAM,IAAI,MACR,oDAAoD;AAAA;AAAA;AAM5D,SAAO,IAAIC;AAEX,SAAO;AAAA;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ declare type RouterOptions = {
|
|
|
6
6
|
engine: SearchEngine;
|
|
7
7
|
logger: Logger;
|
|
8
8
|
};
|
|
9
|
-
declare function createRouter(
|
|
9
|
+
declare function createRouter(options: RouterOptions): Promise<express.Router>;
|
|
10
10
|
|
|
11
|
-
export { createRouter };
|
|
11
|
+
export { RouterOptions, createRouter };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-search-backend",
|
|
3
3
|
"description": "The Backstage backend plugin that provides your backstage app with search",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -20,22 +20,26 @@
|
|
|
20
20
|
"clean": "backstage-cli clean"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@backstage/backend-common": "^0.
|
|
24
|
-
"@backstage/
|
|
23
|
+
"@backstage/backend-common": "^0.10.4",
|
|
24
|
+
"@backstage/config": "^0.1.13",
|
|
25
|
+
"@backstage/errors": "^0.2.0",
|
|
26
|
+
"@backstage/plugin-search-backend-node": "^0.4.4",
|
|
25
27
|
"@backstage/search-common": "^0.2.0",
|
|
28
|
+
"@backstage/types": "^0.1.1",
|
|
26
29
|
"@types/express": "^4.17.6",
|
|
27
30
|
"express": "^4.17.1",
|
|
28
31
|
"express-promise-router": "^4.1.0",
|
|
29
32
|
"winston": "^3.2.1",
|
|
30
|
-
"yn": "^4.0.0"
|
|
33
|
+
"yn": "^4.0.0",
|
|
34
|
+
"zod": "^3.11.6"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
|
-
"@backstage/cli": "^0.
|
|
37
|
+
"@backstage/cli": "^0.12.0",
|
|
34
38
|
"@types/supertest": "^2.0.8",
|
|
35
39
|
"supertest": "^6.1.3"
|
|
36
40
|
},
|
|
37
41
|
"files": [
|
|
38
42
|
"dist"
|
|
39
43
|
],
|
|
40
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "600d6e3c854bbfb12a0078ca6f726d1c0d1fea0b"
|
|
41
45
|
}
|