Skip to content

Commit 4f49198

Browse files
Merge pull request #25 from giuseppealbrizio/development
RBAC logic implemented
2 parents 3fad42f + 3571f17 commit 4f49198

23 files changed

+389
-218
lines changed

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Change Log
2+
3+
[EXPRESS ECMA BOILERPLATE](https://github.com/giuseppealbrizio/express-ecma-boilerplate-mongodb)
4+
5+
All notable changes to this project will be documented in this file.
6+
7+
## [1.1.0] - 2021-10-10
8+
9+
Here we would have the update steps for 1.1.0 for people to follow.
10+
11+
### Added
12+
13+
- **RBAC - Logic** to authorize user to use routes based on role permission.
14+
1. Roles configuration can be customized in:
15+
`./src/config/roles.config.js`
16+
2. RBAC middleware definition is in:
17+
`./src/middlewares/verifyRights.middleware.js`
18+
3. You can use the middleware `verifyRights` in routes like this:
19+
```js
20+
//Only users with getUsers permission can see this route
21+
router.get(
22+
'/users',
23+
authenticate,
24+
verifyRights('getUsers'),
25+
catchAsync(findAllUsers),
26+
);
27+
```
28+
29+
### Changed
30+
31+
- Refactor routes to keep API versioning tidy and clear. Now routes are defined in `./src/routes/v1/index.route.js`
32+
- Added a role field in user model definition `./src/models/user.model.js` to use the verifyRights middleware
33+
- Renamed `./src/config/database.config.js` to `./src/config/mongodb.config.js`
34+
- Moved the entrypoint routes to `./src/routes/v1/app.route.js`
35+
- Refactor events (publisher/subscriber) to keep pub/sub logic tidy and clean. Now all events related controller & routes can be find here:
36+
1. `./src/controllers/events`
37+
2. `./src/routes/events`
38+
39+
### Fixed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "express-ecma-boilerplate-mongodb",
33
"description": "A minimalist express ecma boilerplate with mongodb",
4-
"version": "1.0.11",
4+
"version": "1.1.0",
55
"private": false,
66
"license": "MIT",
77
"bin": "src/bin/www.js",

src/app.js

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,20 @@ import passport from 'passport';
1212
import path from 'path';
1313
import xss from 'xss-clean';
1414

15-
// Import custom logger function using winston
15+
/**
16+
* Import currentUser middleware from shared library
17+
*/
18+
import { currentUser } from './middlewares/customAuthMiddleware/currentUser.middleware';
19+
20+
/**
21+
* Import custom logger function using winston
22+
*/
1623
import logger from './utils/logger.utils';
1724

18-
import databaseConfig from './config/database.config';
25+
/**
26+
* Import database configuration
27+
*/
28+
import mongoDbConfig from './config/mongodb.config';
1929

2030
/**
2131
* Custom error handling
@@ -27,19 +37,7 @@ import errorHandler from './middlewares/errorHandler.middleware';
2737
* Routes import
2838
* @type {Router | {readonly default?: Router}}
2939
*/
30-
import indexRouter from './routes/index.route';
31-
import authRouter from './routes/auth.route';
32-
import userRouter from './routes/user.route';
33-
import uploadRouter from './routes/upload.route';
34-
/**
35-
* Documentation Router
36-
*/
37-
import swaggerRouter from './routes/swagger.route';
38-
/**
39-
* Pub/Sub Routers
40-
*/
41-
import publisherRouter from './routes/publisher.route';
42-
import subscriberRouter from './routes/subscriber.route';
40+
import v1Routes from './routes/v1/index.route';
4341

4442
/**
4543
* import { User } from './models/Users.model';
@@ -59,9 +57,9 @@ dotenv.config();
5957
* and return info about db name
6058
*/
6159
if (process.env.NODE_ENV === 'production') {
62-
databaseConfig.MongoDB().catch((err) => console.log(err));
60+
mongoDbConfig.MongoDB().catch((err) => console.log(err));
6361
} else {
64-
databaseConfig.MongoDBTest().catch((err) => console.log(err));
62+
mongoDbConfig.MongoDBTest().catch((err) => console.log(err));
6563
}
6664

6765
/**
@@ -144,6 +142,12 @@ app.use(
144142
}),
145143
);
146144

145+
/**
146+
* This middleware is responsible for bringing custom auth middleware
147+
* as fallback to Passport if you don't want to use it
148+
*/
149+
app.use(currentUser);
150+
147151
/**
148152
* Initialize Passport and pass the session to session storage of express
149153
*/
@@ -181,21 +185,7 @@ app.use((req, res, next) => {
181185
/**
182186
* Routes definitions
183187
*/
184-
app.use('/api/v1/', indexRouter);
185-
app.use('/api/v1/auth/', authRouter);
186-
app.use('/api/v1/users/', userRouter);
187-
app.use('/api/v1/upload/', uploadRouter);
188-
189-
/**
190-
* Swagger Documentation endpoint
191-
*/
192-
app.use('/api/v1/docs/', swaggerRouter);
193-
194-
/**
195-
* Publisher endpoint
196-
*/
197-
app.use('/api/v1/publisher/', publisherRouter);
198-
app.use('/api/v1/subscriber/', subscriberRouter);
188+
app.use('/api/v1/', v1Routes);
199189

200190
/**
201191
* This helper function is useful if we use express as a pure API endpoint

src/config/roles.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const allRoles = {
2+
superAdmin: ['*', 'getUsers', 'manageUsers', 'deleteUsers'],
3+
admin: ['getUsers', 'manageUsers', 'deleteUsers'],
4+
user: ['getUsers'],
5+
};
6+
7+
export const roles = Object.keys(allRoles);
8+
9+
export const roleRights = new Map(Object.entries(allRoles));
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default {
4646
userInCookie: cookiePayload,
4747
userInPassport: req.user,
4848
userInSession: sessionPayload,
49-
// userInCustomMiddleware: req.currentUser,
49+
userInCustomMiddleware: req.currentUser,
5050
});
5151
} catch (error) {
5252
res.status(500).json({

src/controllers/publisher.controller.js renamed to src/controllers/events/publisher.controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import debug from 'debug';
55
*/
66
import { PubSub } from '@google-cloud/pubsub';
77

8-
import { ApplicationError } from '../helpers/errors.helper';
8+
import { ApplicationError } from '../../helpers/errors.helper';
99

1010
/**
1111
* Load PubSub Custom Service
1212
*/
13-
import pubSubService from '../services/pubsub/pub-sub.service';
13+
import pubSubService from '../../services/pubsub/pub-sub.service';
1414

1515
const pubSubClient = new PubSub();
1616

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* This controller should be used in another app or service and not here
3+
* This is just for testing purposes
4+
*/
5+
import debug from 'debug';
6+
7+
/**
8+
* Load PubSub Library
9+
*/
10+
import { PubSub } from '@google-cloud/pubsub';
11+
12+
import { ApplicationError } from '../../helpers/errors.helper';
13+
14+
const pubSubClient = new PubSub();
15+
16+
const DEBUG = debug('dev');
17+
18+
export default {
19+
/**
20+
* Test subscriber route API
21+
* @param req
22+
* @param res
23+
* @return {Promise<void>}
24+
*/
25+
testSubscriberRoute: async (req, res) => {
26+
try {
27+
res.status(200).json({
28+
status: 'success',
29+
message: 'Subscriber route is ready!',
30+
});
31+
} catch (error) {
32+
DEBUG(error);
33+
throw new ApplicationError(500, error);
34+
}
35+
},
36+
/**
37+
* Controller for listen to message published from a publisher
38+
* This controller receive type "pull" subscription messages
39+
* @param req
40+
* @param res
41+
*/
42+
// eslint-disable-next-line no-unused-vars
43+
subscribeToPullEventExample: async (req, res) => {
44+
try {
45+
// Define some options for subscription
46+
const subscriberOptions = {
47+
flowControl: {
48+
maxMessages: 10,
49+
},
50+
};
51+
52+
// References an existing subscription
53+
const subscription = await pubSubClient.subscription(
54+
'subscription-name',
55+
subscriberOptions,
56+
);
57+
58+
// Instantiate the message counter
59+
let messageCount = 0;
60+
61+
// Create an event handler to handle messages
62+
const messageHandler = async (message) => {
63+
// Buffering the message data
64+
const data = Buffer.from(message.data, 'base64').toString('utf-8');
65+
66+
// Parse message in a JSON Object
67+
const result = JSON.parse(data);
68+
69+
// Do something with the result
70+
console.log(result);
71+
72+
// Increase message counter
73+
messageCount += 1;
74+
75+
// "Ack" (acknowledge receipt of) the message
76+
message.ack();
77+
};
78+
79+
// Create an event handler to handle errors
80+
const errorHandler = function (error) {
81+
throw new Error(error);
82+
};
83+
84+
// Listen for new messages until timeout is hit
85+
subscription.on('message', messageHandler);
86+
87+
// Listen for errors
88+
subscription.on('error', errorHandler);
89+
90+
// Set the timeout to 60 seconds
91+
setTimeout(() => {
92+
subscription.removeListener('message', messageHandler);
93+
subscription.removeListener('error', errorHandler);
94+
console.log(`${messageCount} message(s) received.`);
95+
}, 60 * 1000);
96+
97+
// Send a 200 status code
98+
res.status(200).send();
99+
} catch (error) {
100+
DEBUG(error);
101+
throw new ApplicationError(
102+
500,
103+
"Couldn't receive publisher data object :(",
104+
error,
105+
);
106+
}
107+
},
108+
/**
109+
* As soon as Google receives a message it send back since
110+
* subscription is of a PUSH type. This controller take the
111+
* message and display it
112+
* @param req
113+
* @param res
114+
* @return {Promise<*>}
115+
*/
116+
subscribeToPushEventExample: async (req, res) => {
117+
try {
118+
// Await for message coming from Pub/Sub in a push notification.
119+
const data = Buffer.from(req.body.message.data, 'base64').toString(
120+
'utf-8',
121+
);
122+
123+
const result = await JSON.parse(data);
124+
125+
console.log('Push event contains this object: ', result);
126+
127+
res.status(200).send();
128+
} catch (error) {
129+
DEBUG(error);
130+
throw new ApplicationError(
131+
500,
132+
"Couldn't receive orders object :(",
133+
error,
134+
);
135+
}
136+
},
137+
};

0 commit comments

Comments
 (0)