diff --git a/README.md b/README.md
index c93351af..040949ce 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ The `moleculer-web` is the official API gateway service for [Moleculer](https://
* support file uploading
* alias names (with named parameters & REST shorthand)
* whitelist
+* blacklist
* multiple body parsers (json, urlencoded)
* CORS headers
* ETags
diff --git a/index.d.ts b/index.d.ts
index f7815672..f0afa41a 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -367,6 +367,7 @@ declare module "moleculer-web" {
cors: CorsOptions;
etag: boolean | "weak" | "strong" | Function;
hasWhitelist: boolean;
+ hasBlacklist: boolean;
logging: boolean;
mappingPolicy: string;
middlewares: Function[];
@@ -375,6 +376,7 @@ declare module "moleculer-web" {
opts: any;
path: string;
whitelist: string[];
+ blacklist: string[];
}
type onBeforeCall = (
@@ -514,6 +516,7 @@ declare module "moleculer-web" {
* The gateway will dynamically build the full routes from service schema.
* Gateway will regenerate the routes every time a service joins or leaves the network.
* Use `whitelist` parameter to specify services that the Gateway should track and build the routes.
+ * And `blacklist` parameter to specify services that the Gateway should not track and build the routes.
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Auto-alias
*/
autoAliases?: boolean;
@@ -597,6 +600,15 @@ declare module "moleculer-web" {
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Whitelist
*/
whitelist?: (string | RegExp)[];
+ /**
+ * If you don’t want to publish all actions, you can filter them with blacklist option.
+ * Use match strings or regexp in list. To enable all actions, use "**" item.
+ * "posts.*": `Access any actions in 'posts' service`
+ * "users.list": `Access call only the 'users.list' action`
+ * /^math\.\w+$/: `Access any actions in 'math' service`
+ * @see https://moleculer.services/docs/0.14/moleculer-web.html#Blacklist
+ */
+ blacklist?: (string | RegExp)[];
}
type APISettingServer =
diff --git a/src/index.js b/src/index.js
index a6bf8037..4b68556b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -496,6 +496,7 @@ module.exports = {
/**
* Alias handler. Call action or call custom function
* - check whitelist
+ * - check blacklist
* - Rate limiter
* - Resolve endpoint
* - onBeforeCall
@@ -1154,6 +1155,23 @@ module.exports = {
}) != null;
},
+ /**
+ * Check the action name in blacklist
+ *
+ * @param {Object} route
+ * @param {String} action
+ * @returns {Boolean}
+ */
+ checkBlacklist(route, action) {
+ // Rewrite to for iterator (faster)
+ return (
+ route.blacklist.find((mask) => {
+ if (_.isString(mask)) return match(action, mask);
+ else if (_.isRegExp(mask)) return mask.test(action);
+ }) != null
+ );
+ },
+
/**
* Resolve alias names
*
@@ -1368,6 +1386,10 @@ module.exports = {
route.whitelist = opts.whitelist;
route.hasWhitelist = Array.isArray(route.whitelist);
+ // Handle blacklist
+ route.blacklist = opts.blacklist;
+ route.hasBlacklist = Array.isArray(route.blacklist);
+
// `onBeforeCall` handler
if (opts.onBeforeCall)
route.onBeforeCall = opts.onBeforeCall;
@@ -1522,6 +1544,18 @@ module.exports = {
// Check whitelist
if (route.hasWhitelist && !this.checkWhitelist(route, action.name)) return;
+ // Blacklist check
+ if (route.hasBlacklist) {
+ if (this.checkBlacklist(route, alias.action)) {
+ this.logger.debug(
+ ` The '${alias.action}' action is in the blacklist!`
+ );
+ throw new ServiceNotFoundError({
+ action: alias.action,
+ });
+ }
+ }
+
let restRoutes = [];
if (!_.isArray(action.rest)) {
restRoutes = [action.rest];
diff --git a/test/integration/index.spec.js b/test/integration/index.spec.js
index f058fee5..f94ab216 100644
--- a/test/integration/index.spec.js
+++ b/test/integration/index.spec.js
@@ -777,6 +777,125 @@ describe("Test whitelist", () => {
});
});
+describe("Test blacklist", () => {
+ let broker;
+ let service;
+ let server;
+ beforeAll(() => {
+ [broker, service, server] = setup({
+ routes: [
+ {
+ path: "/api",
+ blacklist: ["test.greeter", "math.sub", /^test\.json/],
+ },
+ ],
+ });
+ broker.loadService("./test/services/math.service");
+ return broker.start();
+ });
+ afterAll(() => broker.stop());
+ it("GET /api/test/hello", () => {
+ return request(server)
+ .get("/api/test/hello")
+ .then((res) => {
+ expect(res.statusCode).toBe(200);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toBe("Hello Moleculer");
+ });
+ });
+ it("GET /api/test/json", () => {
+ return request(server)
+ .get("/api/test/json")
+ .then((res) => {
+ expect(res.statusCode).toBe(404);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toEqual({
+ code: 404,
+ message: "Service 'test.json' is not found.",
+ name: "ServiceNotFoundError",
+ type: "SERVICE_NOT_FOUND",
+ data: {
+ action: "test.json",
+ },
+ });
+ });
+ });
+ it("GET /api/test/jsonArray", () => {
+ return request(server)
+ .get("/api/test/jsonArray")
+ .then((res) => {
+ expect(res.statusCode).toBe(404);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toEqual({
+ code: 404,
+ message: "Service 'test.jsonArray' is not found.",
+ name: "ServiceNotFoundError",
+ type: "SERVICE_NOT_FOUND",
+ data: {
+ action: "test.jsonArray",
+ },
+ });
+ });
+ });
+ it("GET /api/test/greeter", () => {
+ return request(server)
+ .get("/api/test/greeter")
+ .then((res) => {
+ expect(res.statusCode).toBe(404);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toEqual({
+ code: 404,
+ message: "Service 'test.greeter' is not found.",
+ name: "ServiceNotFoundError",
+ type: "SERVICE_NOT_FOUND",
+ data: {
+ action: "test.greeter",
+ },
+ });
+ });
+ });
+ it("GET /api/math.add", () => {
+ return request(server)
+ .get("/api/math.add")
+ .query({ a: 5, b: 8 })
+ .then((res) => {
+ expect(res.statusCode).toBe(200);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toBe(13);
+ });
+ });
+ it("GET /api/math.sub", () => {
+ return request(server)
+ .get("/api/math.sub")
+ .query({ a: 5, b: 8 })
+ .then((res) => {
+ expect(res.statusCode).toBe(404);
+ expect(res.headers["content-type"]).toBe(
+ "application/json; charset=utf-8"
+ );
+ expect(res.body).toEqual({
+ code: 404,
+ message: "Service 'math.sub' is not found.",
+ name: "ServiceNotFoundError",
+ type: "SERVICE_NOT_FOUND",
+ data: {
+ action: "math.sub",
+ },
+ });
+ });
+ });
+});
+
describe("Test aliases", () => {
let broker;
let service;