Skip to content

Commit 9467be1

Browse files
committed
feat: added blacklist functionality for auto aliases
1 parent 77e0780 commit 9467be1

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The `moleculer-web` is the official API gateway service for [Moleculer](https://
2121
* support file uploading
2222
* alias names (with named parameters & REST shorthand)
2323
* whitelist
24+
* blacklist
2425
* multiple body parsers (json, urlencoded)
2526
* CORS headers
2627
* ETags

index.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ declare module "moleculer-web" {
367367
cors: CorsOptions;
368368
etag: boolean | "weak" | "strong" | Function;
369369
hasWhitelist: boolean;
370+
hasBlacklist: boolean;
370371
logging: boolean;
371372
mappingPolicy: string;
372373
middlewares: Function[];
@@ -375,6 +376,7 @@ declare module "moleculer-web" {
375376
opts: any;
376377
path: string;
377378
whitelist: string[];
379+
blacklist: string[];
378380
}
379381

380382
type onBeforeCall = (
@@ -514,6 +516,7 @@ declare module "moleculer-web" {
514516
* The gateway will dynamically build the full routes from service schema.
515517
* Gateway will regenerate the routes every time a service joins or leaves the network.<br>
516518
* Use `whitelist` parameter to specify services that the Gateway should track and build the routes.
519+
* And `blacklist` parameter to specify services that the Gateway should not track and build the routes.
517520
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Auto-alias
518521
*/
519522
autoAliases?: boolean;
@@ -597,6 +600,15 @@ declare module "moleculer-web" {
597600
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Whitelist
598601
*/
599602
whitelist?: (string | RegExp)[];
603+
/**
604+
* If you don’t want to publish all actions, you can filter them with blacklist option.<br>
605+
* Use match strings or regexp in list. To enable all actions, use "**" item.<br>
606+
* "posts.*": `Access any actions in 'posts' service`<br>
607+
* "users.list": `Access call only the 'users.list' action`<br>
608+
* /^math\.\w+$/: `Access any actions in 'math' service`<br>
609+
* @see https://moleculer.services/docs/0.14/moleculer-web.html#Blacklist
610+
*/
611+
blacklist?: (string | RegExp)[];
600612
}
601613

602614
type APISettingServer =

src/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ module.exports = {
496496
/**
497497
* Alias handler. Call action or call custom function
498498
* - check whitelist
499+
* - check blacklist
499500
* - Rate limiter
500501
* - Resolve endpoint
501502
* - onBeforeCall
@@ -1154,6 +1155,23 @@ module.exports = {
11541155
}) != null;
11551156
},
11561157

1158+
/**
1159+
* Check the action name in blacklist
1160+
*
1161+
* @param {Object} route
1162+
* @param {String} action
1163+
* @returns {Boolean}
1164+
*/
1165+
checkBlacklist(route, action) {
1166+
// Rewrite to for iterator (faster)
1167+
return (
1168+
route.blacklist.find((mask) => {
1169+
if (_.isString(mask)) return match(action, mask);
1170+
else if (_.isRegExp(mask)) return mask.test(action);
1171+
}) != null
1172+
);
1173+
},
1174+
11571175
/**
11581176
* Resolve alias names
11591177
*
@@ -1368,6 +1386,10 @@ module.exports = {
13681386
route.whitelist = opts.whitelist;
13691387
route.hasWhitelist = Array.isArray(route.whitelist);
13701388

1389+
// Handle blacklist
1390+
route.blacklist = opts.blacklist;
1391+
route.hasBlacklist = Array.isArray(route.blacklist);
1392+
13711393
// `onBeforeCall` handler
13721394
if (opts.onBeforeCall)
13731395
route.onBeforeCall = opts.onBeforeCall;
@@ -1522,6 +1544,18 @@ module.exports = {
15221544
// Check whitelist
15231545
if (route.hasWhitelist && !this.checkWhitelist(route, action.name)) return;
15241546

1547+
// Blacklist check
1548+
if (route.hasBlacklist) {
1549+
if (this.checkBlacklist(route, alias.action)) {
1550+
this.logger.debug(
1551+
` The '${alias.action}' action is in the blacklist!`
1552+
);
1553+
throw new ServiceNotFoundError({
1554+
action: alias.action,
1555+
});
1556+
}
1557+
}
1558+
15251559
let restRoutes = [];
15261560
if (!_.isArray(action.rest)) {
15271561
restRoutes = [action.rest];

test/integration/index.spec.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,125 @@ describe("Test whitelist", () => {
777777
});
778778
});
779779

780+
describe("Test blacklist", () => {
781+
let broker;
782+
let service;
783+
let server;
784+
beforeAll(() => {
785+
[broker, service, server] = setup({
786+
routes: [
787+
{
788+
path: "/api",
789+
blacklist: ["test.greeter", "math.sub", /^test\.json/],
790+
},
791+
],
792+
});
793+
broker.loadService("./test/services/math.service");
794+
return broker.start();
795+
});
796+
afterAll(() => broker.stop());
797+
it("GET /api/test/hello", () => {
798+
return request(server)
799+
.get("/api/test/hello")
800+
.then((res) => {
801+
expect(res.statusCode).toBe(200);
802+
expect(res.headers["content-type"]).toBe(
803+
"application/json; charset=utf-8"
804+
);
805+
expect(res.body).toBe("Hello Moleculer");
806+
});
807+
});
808+
it("GET /api/test/json", () => {
809+
return request(server)
810+
.get("/api/test/json")
811+
.then((res) => {
812+
expect(res.statusCode).toBe(404);
813+
expect(res.headers["content-type"]).toBe(
814+
"application/json; charset=utf-8"
815+
);
816+
expect(res.body).toEqual({
817+
code: 404,
818+
message: "Service 'test.json' is not found.",
819+
name: "ServiceNotFoundError",
820+
type: "SERVICE_NOT_FOUND",
821+
data: {
822+
action: "test.json",
823+
},
824+
});
825+
});
826+
});
827+
it("GET /api/test/jsonArray", () => {
828+
return request(server)
829+
.get("/api/test/jsonArray")
830+
.then((res) => {
831+
expect(res.statusCode).toBe(404);
832+
expect(res.headers["content-type"]).toBe(
833+
"application/json; charset=utf-8"
834+
);
835+
expect(res.body).toEqual({
836+
code: 404,
837+
message: "Service 'test.jsonArray' is not found.",
838+
name: "ServiceNotFoundError",
839+
type: "SERVICE_NOT_FOUND",
840+
data: {
841+
action: "test.jsonArray",
842+
},
843+
});
844+
});
845+
});
846+
it("GET /api/test/greeter", () => {
847+
return request(server)
848+
.get("/api/test/greeter")
849+
.then((res) => {
850+
expect(res.statusCode).toBe(404);
851+
expect(res.headers["content-type"]).toBe(
852+
"application/json; charset=utf-8"
853+
);
854+
expect(res.body).toEqual({
855+
code: 404,
856+
message: "Service 'test.greeter' is not found.",
857+
name: "ServiceNotFoundError",
858+
type: "SERVICE_NOT_FOUND",
859+
data: {
860+
action: "test.greeter",
861+
},
862+
});
863+
});
864+
});
865+
it("GET /api/math.add", () => {
866+
return request(server)
867+
.get("/api/math.add")
868+
.query({ a: 5, b: 8 })
869+
.then((res) => {
870+
expect(res.statusCode).toBe(200);
871+
expect(res.headers["content-type"]).toBe(
872+
"application/json; charset=utf-8"
873+
);
874+
expect(res.body).toBe(13);
875+
});
876+
});
877+
it("GET /api/math.sub", () => {
878+
return request(server)
879+
.get("/api/math.sub")
880+
.query({ a: 5, b: 8 })
881+
.then((res) => {
882+
expect(res.statusCode).toBe(404);
883+
expect(res.headers["content-type"]).toBe(
884+
"application/json; charset=utf-8"
885+
);
886+
expect(res.body).toEqual({
887+
code: 404,
888+
message: "Service 'math.sub' is not found.",
889+
name: "ServiceNotFoundError",
890+
type: "SERVICE_NOT_FOUND",
891+
data: {
892+
action: "math.sub",
893+
},
894+
});
895+
});
896+
});
897+
});
898+
780899
describe("Test aliases", () => {
781900
let broker;
782901
let service;

0 commit comments

Comments
 (0)