Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added blacklist functionality for auto aliases #361

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -375,6 +376,7 @@ declare module "moleculer-web" {
opts: any;
path: string;
whitelist: string[];
blacklist: string[];
}

type onBeforeCall = (
Expand Down Expand Up @@ -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.<br>
* 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;
Expand Down Expand Up @@ -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.<br>
* Use match strings or regexp in list. To enable all actions, use "**" item.<br>
* "posts.*": `Access any actions in 'posts' service`<br>
* "users.list": `Access call only the 'users.list' action`<br>
* /^math\.\w+$/: `Access any actions in 'math' service`<br>
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Blacklist
*/
blacklist?: (string | RegExp)[];
}

type APISettingServer =
Expand Down
34 changes: 34 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ module.exports = {
/**
* Alias handler. Call action or call custom function
* - check whitelist
* - check blacklist
* - Rate limiter
* - Resolve endpoint
* - onBeforeCall
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down
119 changes: 119 additions & 0 deletions test/integration/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading