Skip to content

Commit ca86384

Browse files
authored
Merge pull request #343 from dedis/refactor/moves-auth-code-to-different-module
Refactor: split code into modules
2 parents 7d4121e + eeb54aa commit ca86384

File tree

8 files changed

+638
-603
lines changed

8 files changed

+638
-603
lines changed

web/backend/src/Server.ts

+14-603
Large diffs are not rendered by default.

web/backend/src/authManager.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { SequelizeAdapter } from 'casbin-sequelize-adapter';
2+
import { Enforcer, newEnforcer } from 'casbin';
3+
4+
export const PERMISSIONS = {
5+
SUBJECTS: {
6+
ROLES: 'roles',
7+
PROXIES: 'proxies',
8+
ELECTION: 'election',
9+
},
10+
ACTIONS: {
11+
LIST: 'list',
12+
REMOVE: 'remove',
13+
ADD: 'add',
14+
PUT: 'put',
15+
POST: 'post',
16+
DELETE: 'delete',
17+
OWN: 'own',
18+
CREATE: 'create',
19+
VOTE: 'vote',
20+
},
21+
};
22+
23+
let authEnforcer: Enforcer;
24+
/*
25+
We use the postgres adapter to store the Casbin policies
26+
we initialize the adapter with the connection string and the migrate option
27+
the connection string has the following format:
28+
postgres://username:password@host:port/database
29+
the migrate option is used to create the tables if they don't exist, we set it to false because we create the tables manually
30+
*/
31+
async function initEnforcer() {
32+
const dbAdapter = await SequelizeAdapter.newAdapter({
33+
dialect: 'postgres',
34+
host: process.env.DATABASE_HOST,
35+
port: parseInt(process.env.DATABASE_PORT || '5432', 10),
36+
username: process.env.DATABASE_USERNAME,
37+
password: process.env.DATABASE_PASSWORD,
38+
database: 'casbin',
39+
});
40+
return newEnforcer('src/model.conf', dbAdapter);
41+
}
42+
43+
Promise.all([initEnforcer()]).then((createdEnforcer) => {
44+
[authEnforcer] = createdEnforcer;
45+
});
46+
47+
export function isAuthorized(sciper: number | undefined, subject: string, action: string): boolean {
48+
return authEnforcer.enforceSync(sciper, subject, action);
49+
}
50+
51+
export async function getUserPermissions(userID: number) {
52+
let permissions: string[][] = [];
53+
await authEnforcer.getFilteredPolicy(0, String(userID)).then((authRights) => {
54+
permissions = authRights;
55+
});
56+
console.log(`[getUserPermissions] user has permissions: ${permissions}`);
57+
return permissions;
58+
}
59+
60+
export function assignUserPermissionToOwnElection(userID: string, ElectionID: string) {
61+
authEnforcer.addPolicy(userID, ElectionID, PERMISSIONS.ACTIONS.OWN);
62+
}
63+
64+
export function revokeUserPermissionToOwnElection(userID: string, ElectionID: string) {
65+
authEnforcer.removePolicy(userID, ElectionID, PERMISSIONS.ACTIONS.OWN);
66+
}
67+
68+
// This function helps us convert the double list of the authorization
69+
// returned by the casbin function getFilteredPolicy to a map that link
70+
// an object to the action authorized
71+
// list[0] contains the policies so list[i][0] is the sciper
72+
// list[i][1] is the subject and list[i][2] is the action
73+
export function setMapAuthorization(list: string[][]): Map<String, Array<String>> {
74+
const userRights = new Map<String, Array<String>>();
75+
for (let i = 0; i < list.length; i += 1) {
76+
const subject = list[i][1];
77+
const action = list[i][2];
78+
if (userRights.has(subject)) {
79+
userRights.get(subject)?.push(action);
80+
} else {
81+
userRights.set(subject, [action]);
82+
}
83+
}
84+
return userRights;
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import express from 'express';
2+
import axios, { AxiosError } from 'axios';
3+
import { sciper2sess } from '../session';
4+
import { getUserPermissions, setMapAuthorization } from '../authManager';
5+
6+
export const authenticationRouter = express.Router();
7+
8+
// This is via this endpoint that the client request the tequila key, this key
9+
// will then be used for redirection on the tequila server
10+
authenticationRouter.get('/get_teq_key', (req, res) => {
11+
axios
12+
.get(`https://tequila.epfl.ch/cgi-bin/tequila/createrequest`, {
13+
params: {
14+
urlaccess: `${process.env.FRONT_END_URL}/api/control_key`,
15+
service: 'Evoting',
16+
request: 'name,firstname,email,uniqueid,allunits',
17+
},
18+
})
19+
.then((response) => {
20+
console.info(`[tequila Key] Received response from tequila: ${response.data}`);
21+
const key = response.data.split('\n')[0].split('=')[1];
22+
const url = `https://tequila.epfl.ch/cgi-bin/tequila/requestauth?requestkey=${key}`;
23+
res.json({ url: url });
24+
})
25+
.catch((error: AxiosError) => {
26+
console.log('message:', error.message);
27+
res.status(500).send(`failed to request Tequila authentication: ${error.message}`);
28+
});
29+
});
30+
31+
// Here the client will send the key he/she received from the tequila, it is
32+
// then verified on the tequila. If the key is valid, the user is then logged
33+
// in the website through this backend
34+
authenticationRouter.get('/control_key', (req, res) => {
35+
const userKey = req.query.key;
36+
const body = `key=${userKey}`;
37+
38+
axios
39+
.post('https://tequila.epfl.ch/cgi-bin/tequila/fetchattributes', body)
40+
.then((response) => {
41+
if (!response.data.includes('status=ok')) {
42+
throw new Error('Login did not work');
43+
}
44+
45+
const sciper = response.data.split('uniqueid=')[1].split('\n')[0];
46+
const lastname = response.data.split('\nname=')[1].split('\n')[0];
47+
const firstname = response.data.split('\nfirstname=')[1].split('\n')[0];
48+
49+
req.session.userId = parseInt(sciper, 10);
50+
req.session.lastName = lastname;
51+
req.session.firstName = firstname;
52+
53+
const sciperSessions = sciper2sess.get(req.session.userId) || new Set<string>();
54+
sciperSessions.add(req.sessionID);
55+
sciper2sess.set(sciper, sciperSessions);
56+
57+
res.redirect('/logged');
58+
})
59+
.catch((error) => {
60+
res.status(500).send('Login did not work');
61+
console.log(error);
62+
});
63+
});
64+
65+
// This endpoint serves to log out from the app by clearing the session.
66+
authenticationRouter.post('/logout', (req, res) => {
67+
if (req.session.userId === undefined) {
68+
res.status(400).send('not logged in');
69+
}
70+
71+
const { userId } = req.session;
72+
73+
req.session.destroy(() => {
74+
const a = sciper2sess.get(userId as number);
75+
if (a !== undefined) {
76+
a.delete(req.sessionID);
77+
sciper2sess.set(userId as number, a);
78+
}
79+
res.redirect('/');
80+
});
81+
});
82+
83+
// As the user is logged on the app via this express but must also
84+
// be logged into react. This endpoint serves to send to the client (actually to react)
85+
// the information of the current user.
86+
authenticationRouter.get('/personal_info', async (req, res) => {
87+
if (!req.session.userId) {
88+
res.status(401).send('Unauthenticated');
89+
return;
90+
}
91+
const userPermissions = await getUserPermissions(req.session.userId);
92+
res.set('Access-Control-Allow-Origin', '*');
93+
res.json({
94+
sciper: req.session.userId,
95+
lastName: req.session.lastName,
96+
firstName: req.session.firstName,
97+
isLoggedIn: true,
98+
authorization: Object.fromEntries(setMapAuthorization(userPermissions)),
99+
});
100+
});

0 commit comments

Comments
 (0)