@@ -8,9 +8,20 @@ import crypto from 'crypto';
8
8
import lmdb , { RangeOptions } from 'lmdb' ;
9
9
import xss from 'xss' ;
10
10
import createMemoryStore from 'memorystore' ;
11
+ import { Enforcer , newEnforcer } from 'casbin' ;
11
12
12
13
const MemoryStore = createMemoryStore ( session ) ;
13
-
14
+ const SUBJECT_ROLES = 'roles' ;
15
+ const SUBJECT_PROXIES = 'proxies' ;
16
+ const SUBJECT_ELECTION = 'election' ;
17
+
18
+ const ACTION_LIST = 'list' ;
19
+ const ACTION_ACTION_REMOVE = 'remove' ;
20
+ const ACTION_ADD = 'add' ;
21
+ const ACTION_PUT = 'put' ;
22
+ const ACTION_POST = 'post' ;
23
+ const ACTION_DELETE = 'delete' ;
24
+ const ACTION_CREATE = 'create' ;
14
25
// store is used to store the session
15
26
const store = new MemoryStore ( {
16
27
checkPeriod : 86400000 , // prune expired entries every 24h
@@ -25,6 +36,26 @@ const app = express();
25
36
26
37
app . use ( morgan ( 'tiny' ) ) ;
27
38
39
+ let enf : Enforcer ;
40
+
41
+ const enforcerLoading = newEnforcer ( 'model.conf' , 'policy.csv' ) ;
42
+ const port = process . env . PORT || 5000 ;
43
+
44
+ Promise . all ( [ enforcerLoading ] )
45
+ . then ( ( res ) => {
46
+ [ enf ] = res ;
47
+ console . log ( `🛡 Casbin loaded` ) ;
48
+ app . listen ( port ) ;
49
+ console . log ( `🚀 App is listening on port ${ port } ` ) ;
50
+ } )
51
+ . catch ( ( err ) => {
52
+ console . error ( '❌ failed to start:' , err ) ;
53
+ } ) ;
54
+
55
+ function isAuthorized ( sciper : number | undefined , subject : string , action : string ) : boolean {
56
+ return enf . enforceSync ( sciper , subject , action ) ;
57
+ }
58
+
28
59
declare module 'express-session' {
29
60
export interface SessionData {
30
61
userid : number ;
@@ -71,6 +102,7 @@ app.use(express.urlencoded({ extended: true }));
71
102
72
103
// This endpoint allows anyone to get a "default" proxy. Clients can still use
73
104
// the proxy of their choice thought.
105
+
74
106
app . get ( '/api/config/proxy' , ( req , res ) => {
75
107
res . status ( 200 ) . send ( process . env . DELA_NODE_URL ) ;
76
108
} ) ;
@@ -151,52 +183,64 @@ app.post('/api/logout', (req, res) => {
151
183
} ) ;
152
184
} ) ;
153
185
186
+ // This function helps us convert the double list of the authorization
187
+ // returned by the casbin function getFilteredPolicy to a map that link
188
+ // an object to the action authorized
189
+ // list[0] contains the policies so list[i][0] is the sciper
190
+ // list[i][1] is the subject and list[i][2] is the action
191
+ function setMapAuthorization ( list : string [ ] [ ] ) : Map < String , Array < String > > {
192
+ const m = new Map < String , Array < String > > ( ) ;
193
+ for ( let i = 0 ; i < list . length ; i += 1 ) {
194
+ const subject = list [ i ] [ 1 ] ;
195
+ const action = list [ i ] [ 2 ] ;
196
+ if ( m . has ( subject ) ) {
197
+ m . get ( subject ) ?. push ( action ) ;
198
+ } else {
199
+ m . set ( subject , [ action ] ) ;
200
+ }
201
+ }
202
+ console . log ( m ) ;
203
+ return m ;
204
+ }
205
+
154
206
// As the user is logged on the app via this express but must also be logged in
155
207
// the react. This endpoint serves to send to the client (actually to react)
156
208
// the information of the current user.
157
209
app . get ( '/api/personal_info' , ( req , res ) => {
158
- res . set ( 'Access-Control-Allow-Origin' , '*' ) ;
159
- if ( req . session . userid ) {
160
- res . json ( {
161
- sciper : req . session . userid ,
162
- lastname : req . session . lastname ,
163
- firstname : req . session . firstname ,
164
- role : req . session . role ,
165
- islogged : true ,
166
- } ) ;
167
- } else {
168
- res . json ( {
169
- sciper : 0 ,
170
- lastname : '' ,
171
- firstname : '' ,
172
- role : '' ,
173
- islogged : false ,
174
- } ) ;
175
- }
210
+ enf . getFilteredPolicy ( 0 , String ( req . session . userid ) ) . then ( ( list ) => {
211
+ res . set ( 'Access-Control-Allow-Origin' , '*' ) ;
212
+ if ( req . session . userid ) {
213
+ res . json ( {
214
+ sciper : req . session . userid ,
215
+ lastname : req . session . lastname ,
216
+ firstname : req . session . firstname ,
217
+ role : req . session . role ,
218
+ islogged : true ,
219
+ authorization : Object . fromEntries ( setMapAuthorization ( list ) ) ,
220
+ } ) ;
221
+ } else {
222
+ res . json ( {
223
+ sciper : 0 ,
224
+ lastname : '' ,
225
+ firstname : '' ,
226
+ role : '' ,
227
+ islogged : false ,
228
+ authorization : { } ,
229
+ } ) ;
230
+ }
231
+ } ) ;
176
232
} ) ;
177
233
178
- function isAuthorized ( roles : string [ ] , req : express . Request ) : boolean {
179
- if ( ! req . session || ! req . session . userid ) {
180
- return false ;
181
- }
182
-
183
- const { role } = req . session ;
184
-
185
- return roles . includes ( role as string ) ;
186
- }
187
-
188
234
// ---
189
235
// Users role
190
236
// ---
191
-
192
237
// This call allow a user that is admin to get the list of the people that have
193
238
// a special role (not a voter).
194
239
app . get ( '/api/user_rights' , ( req , res ) => {
195
- if ( ! isAuthorized ( [ 'admin' ] , req ) ) {
240
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_ROLES , ACTION_LIST ) ) {
196
241
res . status ( 400 ) . send ( 'Unauthorized - only admins allowed' ) ;
197
242
return ;
198
243
}
199
-
200
244
const opts : RangeOptions = { } ;
201
245
const users = Array . from (
202
246
usersDB . getRange ( opts ) . map ( ( { key, value } ) => ( { id : '0' , sciper : key , role : value } ) )
@@ -206,13 +250,11 @@ app.get('/api/user_rights', (req, res) => {
206
250
207
251
// This call (only for admins) allow an admin to add a role to a voter.
208
252
app . post ( '/api/add_role' , ( req , res ) => {
209
- if ( ! isAuthorized ( [ 'admin' ] , req ) ) {
253
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_ROLES , ACTION_ADD ) ) {
210
254
res . status ( 400 ) . send ( 'Unauthorized - only admins allowed' ) ;
211
255
return ;
212
256
}
213
257
214
- // {sciper: xxx, role: xxx}
215
-
216
258
const { sciper } = req . body ;
217
259
const { role } = req . body ;
218
260
@@ -224,13 +266,12 @@ app.post('/api/add_role', (req, res) => {
224
266
225
267
// This call (only for admins) allow an admin to remove a role to a user.
226
268
app . post ( '/api/remove_role' , ( req , res ) => {
227
- if ( ! isAuthorized ( [ 'admin' ] , req ) ) {
269
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_ROLES , ACTION_ACTION_REMOVE ) ) {
228
270
res . status ( 400 ) . send ( 'Unauthorized - only admins allowed' ) ;
229
271
return ;
230
272
}
231
273
232
274
const { sciper } = req . body ;
233
-
234
275
usersDB
235
276
. remove ( sciper )
236
277
. then ( ( ) => {
@@ -256,40 +297,12 @@ app.post('/api/remove_role', (req, res) => {
256
297
// ---
257
298
// Proxies
258
299
// ---
259
-
260
300
const proxiesDB = lmdb . open < string , string > ( { path : `${ process . env . DB_PATH } proxies` } ) ;
261
-
262
- app . get ( '/api/proxies' , ( req , res ) => {
263
- const output = new Map < string , string > ( ) ;
264
- proxiesDB . getRange ( { } ) . forEach ( ( entry ) => {
265
- output . set ( entry . key , entry . value ) ;
266
- } ) ;
267
-
268
- res . status ( 200 ) . json ( { Proxies : Object . fromEntries ( output ) } ) ;
269
- } ) ;
270
-
271
- app . get ( '/api/proxies/:nodeAddr' , ( req , res ) => {
272
- const { nodeAddr } = req . params ;
273
-
274
- const proxy = proxiesDB . get ( decodeURIComponent ( nodeAddr ) ) ;
275
-
276
- if ( proxy === undefined ) {
277
- res . status ( 404 ) . send ( 'not found' ) ;
278
- return ;
279
- }
280
-
281
- res . status ( 200 ) . json ( {
282
- NodeAddr : nodeAddr ,
283
- Proxy : proxy ,
284
- } ) ;
285
- } ) ;
286
-
287
301
app . post ( '/api/proxies' , ( req , res ) => {
288
- if ( ! isAuthorized ( [ 'admin' , 'operator' ] , req ) ) {
302
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_PROXIES , ACTION_POST ) ) {
289
303
res . status ( 400 ) . send ( 'Unauthorized - only admins and operators allowed' ) ;
290
304
return ;
291
305
}
292
-
293
306
try {
294
307
const bodydata = req . body ;
295
308
proxiesDB . put ( bodydata . NodeAddr , bodydata . Proxy ) ;
@@ -301,7 +314,7 @@ app.post('/api/proxies', (req, res) => {
301
314
} ) ;
302
315
303
316
app . put ( '/api/proxies/:nodeAddr' , ( req , res ) => {
304
- if ( ! isAuthorized ( [ 'admin' , 'operator' ] , req ) ) {
317
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_PROXIES , ACTION_PUT ) ) {
305
318
res . status ( 400 ) . send ( 'Unauthorized - only admins and operators allowed' ) ;
306
319
return ;
307
320
}
@@ -316,7 +329,6 @@ app.put('/api/proxies/:nodeAddr', (req, res) => {
316
329
res . status ( 404 ) . send ( 'not found' ) ;
317
330
return ;
318
331
}
319
-
320
332
try {
321
333
const bodydata = req . body ;
322
334
if ( bodydata . Proxy === undefined ) {
@@ -333,7 +345,7 @@ app.put('/api/proxies/:nodeAddr', (req, res) => {
333
345
} ) ;
334
346
335
347
app . delete ( '/api/proxies/:nodeAddr' , ( req , res ) => {
336
- if ( ! isAuthorized ( [ 'admin' , 'operator' ] , req ) ) {
348
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_PROXIES , ACTION_DELETE ) ) {
337
349
res . status ( 400 ) . send ( 'Unauthorized - only admins and operators allowed' ) ;
338
350
return ;
339
351
}
@@ -358,6 +370,31 @@ app.delete('/api/proxies/:nodeAddr', (req, res) => {
358
370
}
359
371
} ) ;
360
372
373
+ app . get ( '/api/proxies' , ( req , res ) => {
374
+ const output = new Map < string , string > ( ) ;
375
+ proxiesDB . getRange ( { } ) . forEach ( ( entry ) => {
376
+ output . set ( entry . key , entry . value ) ;
377
+ } ) ;
378
+
379
+ res . status ( 200 ) . json ( { Proxies : Object . fromEntries ( output ) } ) ;
380
+ } ) ;
381
+
382
+ app . get ( '/api/proxies/:nodeAddr' , ( req , res ) => {
383
+ const { nodeAddr } = req . params ;
384
+
385
+ const proxy = proxiesDB . get ( decodeURIComponent ( nodeAddr ) ) ;
386
+
387
+ if ( proxy === undefined ) {
388
+ res . status ( 404 ) . send ( 'not found' ) ;
389
+ return ;
390
+ }
391
+
392
+ res . status ( 200 ) . json ( {
393
+ NodeAddr : nodeAddr ,
394
+ Proxy : proxy ,
395
+ } ) ;
396
+ } ) ;
397
+
361
398
// ---
362
399
// end of proxies
363
400
// ---
@@ -461,12 +498,10 @@ function sendToDela(dataStr: string, req: express.Request, res: express.Response
461
498
462
499
// Secure /api/evoting to admins and operators
463
500
app . use ( '/api/evoting/*' , ( req , res , next ) => {
464
- if ( ! isAuthorized ( [ 'admin' , 'operator' ] , req ) ) {
465
- console . log ( 'role is:' , req . session . role ) ;
501
+ if ( ! isAuthorized ( req . session . userid , SUBJECT_ELECTION , ACTION_CREATE ) ) {
466
502
res . status ( 400 ) . send ( 'Unauthorized - only admins and operators allowed' ) ;
467
503
return ;
468
504
}
469
-
470
505
next ( ) ;
471
506
} ) ;
472
507
@@ -557,8 +592,3 @@ app.get('*', (req, res) => {
557
592
const url = new URL ( req . url , `http://${ req . headers . host } ` ) ;
558
593
res . status ( 404 ) . send ( `not found ${ xss ( url . toString ( ) ) } ` ) ;
559
594
} ) ;
560
-
561
- const port = process . env . PORT || 5000 ;
562
- app . listen ( port ) ;
563
-
564
- console . log ( `App is listening on port ${ port } ` ) ;
0 commit comments