diff --git a/backend/src/components/roles.js b/backend/src/components/roles.js index 7260226da..325e0e38d 100644 --- a/backend/src/components/roles.js +++ b/backend/src/components/roles.js @@ -24,6 +24,8 @@ const roles = { Exchange: ['SECURE_EXCHANGE'], //Help functions created in auth module: isValidPenTeamRoleUserToken, isValidPenTeamRoleUser PenTeamRole: config.get('server:edx:teamRoles:pen'), + //Help functions created in auth module: isValidSchoolMoveUserToken, isValidSchoolMoveUser + School: ['SCHOOL_ADMIN'] }, Admin: { //Help functions created in auth module: isValidGMPAdmin diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index edcd988e7..5a51d6acc 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -15,6 +15,7 @@ const { } = require('express-validator'); const isValidStaffUserWithRoles = auth.isValidUserWithRoles('GMP & UMP & PenRequestBatch & StudentSearch & StaffAdministration & NominalRoll & NominalRollReadOnly & GUMPAnalytics & PenRequestBatchAnalytics & Exchange', [...roles.User.GMP, ...roles.User.UMP, ...roles.User.PenRequestBatch, ...roles.User.StudentSearch, ...roles.User.StaffAdministration, ...roles.User.NominalRoll , ...roles.User.NominalRollReadOnly, ...roles.User.GUMPAnalytics, ...roles.User.PenRequestBatchAnalytics, ...roles.User.Exchange]); +const isValidWebSocketUserWithRoles = auth.isValidUserWithRoles('GMP & UMP & PenRequestBatch & Exchange & School', [...roles.User.GMP, ...roles.User.UMP, ...roles.User.PenRequestBatch, ...roles.User.Exchange, ...roles.User.School]); const router = express.Router(); @@ -75,11 +76,13 @@ async function generateTokens(req, res) { const isAuthorizedUser = isValidStaffUserWithRoles(req); const isValidUsers = auth.isValidUsers(req); const isValidAdminUsers = auth.isValidAdminUsers(req); + const isAuthorizedWebsocketUser = isValidWebSocketUserWithRoles(req); const responseJson = { jwtFrontend: req.user.jwtFrontend, isAuthorizedUser: isAuthorizedUser, ...isValidUsers, - ...isValidAdminUsers + ...isValidAdminUsers, + isAuthorizedWebsocketUser: isAuthorizedWebsocketUser }; return res.status(HttpStatus.OK).json(responseJson); } else { @@ -110,11 +113,13 @@ router.post('/refresh', [ const isAuthorizedUser = isValidStaffUserWithRoles(req); const isValidUsers = auth.isValidUsers(req); const isValidAdminUsers = auth.isValidAdminUsers(req); + const isAuthorizedWebsocketUser = isValidWebSocketUserWithRoles(req); const responseJson = { jwtFrontend: req.user.jwtFrontend, isAuthorizedUser: isAuthorizedUser, ...isValidUsers, - ...isValidAdminUsers + ...isValidAdminUsers, + isAuthorizedWebsocketUser: isAuthorizedWebsocketUser }; return res.status(HttpStatus.OK).json(responseJson); } @@ -127,12 +132,14 @@ router.get('/token', auth.refreshJWT, (req, res) => { const isAuthorizedUser = isValidStaffUserWithRoles(req); const isValidUsers = auth.isValidUsers(req); const isValidAdminUsers = auth.isValidAdminUsers(req); + const isAuthorizedWebsocketUser = isValidWebSocketUserWithRoles(req); if (req['user'] && req['user'].jwtFrontend && req['user'].refreshToken) { const responseJson = { jwtFrontend: req['user'].jwtFrontend, isAuthorizedUser: isAuthorizedUser, ...isValidUsers, - ...isValidAdminUsers + ...isValidAdminUsers, + isAuthorizedWebsocketUser: isAuthorizedWebsocketUser }; req.session.correlationID = uuidv4(); res.status(HttpStatus.OK).json(responseJson); diff --git a/backend/src/socket/web-socket.js b/backend/src/socket/web-socket.js index 60e9477b5..2bc9e1d29 100644 --- a/backend/src/socket/web-socket.js +++ b/backend/src/socket/web-socket.js @@ -1,4 +1,5 @@ 'use strict'; +const logger = require('../components/logger'); let connectedClients = []; const webSocket = { @@ -10,7 +11,32 @@ const webSocket = { init(app, server) { require('express-ws')(app, server); app.ws('/api/socket', (ws) => { + logger.debug('Connecting websocket client'); + connectedClients.push(ws); + let isAlive = true; + + // Send a ping message every 30 seconds + const pingInterval = setInterval(() => { + if (!isAlive) { + logger.debug('Ping test failed, closing client websocket connection'); + cleanupWebSocket(ws, pingInterval); + return ws.terminate(); + } + + isAlive = false; + ws.ping(); + }, 30000); + + ws.on('pong', () => { + // The client responded to the ping message, so it's still alive + isAlive = true; + }); + + ws.on('close', () => { + logger.debug('Closing websocket client connection'); + cleanupWebSocket(ws, pingInterval); + }); }); }, getWebSocketClients() { @@ -23,4 +49,13 @@ const webSocket = { return connectedClients; // returns only connected clients. } }; + +function cleanupWebSocket(ws, interval) { + clearInterval(interval); + const index = connectedClients.indexOf(ws); + if (index !== -1) { + connectedClients.splice(index, 1); // Remove the WebSocket connection from the array + } +} + module.exports = webSocket; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index eecd61f0c..f6d1065ec 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -47,12 +47,15 @@ export default { }; }, computed: { - ...mapState(authStore, ['jwtToken', 'isAuthenticated', 'userInfo', 'isValidGMPUser', 'isValidUMPUser', 'isValidPenRequestBatchUser']), + ...mapState(authStore, ['jwtToken', 'isAuthenticated', 'userInfo', 'isAuthorizedWebsocketUser']), ...mapState(appStore, ['pageTitle']), }, watch: { isAuthenticated() { this.handleWebSocket(); + }, + isAuthorizedWebsocketUser() { + this.handleWebSocket(); } }, beforeUnmount() { @@ -69,7 +72,7 @@ export default { authStore, ...mapActions(appStore, ['getConfig']), handleWebSocket() { - if(this.isAuthenticated && (this.isValidPenRequestBatchUser || this.isValidGMPUser || this.isValidUMPUser)) { + if(this.isAuthenticated && this.isAuthorizedWebsocketUser) { this.$webSocketsConnect(); } else { this.$webSocketsDisconnect(); diff --git a/frontend/src/services/web-socket-service.js b/frontend/src/services/web-socket-service.js index d81571b91..d15177e6f 100644 --- a/frontend/src/services/web-socket-service.js +++ b/frontend/src/services/web-socket-service.js @@ -19,7 +19,6 @@ webSocketsService.install = function (Vue, options) { ws = new WebSocket(options.url); ws.onopen = () => { - console.log('connection opened'); // Restart reconnect interval reconnectInterval = options.reconnectInterval || 1000; }; @@ -30,7 +29,6 @@ webSocketsService.install = function (Vue, options) { }; ws.onclose = (event) => { - console.log('connection closed'); if (event) { // Event.code 1000 is our normal close event if (event.code !== 1000) { @@ -65,7 +63,6 @@ webSocketsService.install = function (Vue, options) { Here we write our custom functions to not make a mess in one function */ function handleNotification (params) { - console.log('Received websocket event'); const noteStore = notificationsStore(); noteStore.setNotification(params.data); } diff --git a/frontend/src/store/modules/auth.js b/frontend/src/store/modules/auth.js index 3762bed89..664b3bc45 100644 --- a/frontend/src/store/modules/auth.js +++ b/frontend/src/store/modules/auth.js @@ -31,7 +31,8 @@ export const authStore = defineStore('auth', { isValidDistrictAdmin: localStorage.getItem('isValidDistrictAdmin') !== null, isValidSchoolAdmin: localStorage.getItem('isValidSchoolAdmin') !== null, isValidIndependentAuthorityAdmin: localStorage.getItem('isValidIndependentAuthorityAdmin') !== null, - isValidSchoolIndependentAdmin: localStorage.getItem('isValidSchoolIndependentAdmin') !== null + isValidSchoolIndependentAdmin: localStorage.getItem('isValidSchoolIndependentAdmin') !== null, + isAuthorizedWebsocketUser: localStorage.getItem('isAuthorizedWebsocketUser') !== null }), getters: { acronymsGet: state => state.acronyms, @@ -286,6 +287,16 @@ export const authStore = defineStore('auth', { localStorage.removeItem('isValidIndependentAuthorityAdmin'); } }, + async setAuthorizedWebsocketUser(isAuthorizedWebsocketUser){ + console.log(isAuthorizedWebsocketUser); + if (isAuthorizedWebsocketUser) { + this.isAuthorizedWebsocketUser = true; + localStorage.setItem('isAuthorizedWebsocketUser', 'true'); + } else { + this.isAuthorizedWebsocketUser = false; + localStorage.removeItem(('isAuthorizedWebsocketUser')); + } + }, async setUserInfo(userInf) { if (userInf) { this.userInfo = userInf; @@ -355,6 +366,7 @@ export const authStore = defineStore('auth', { await this.setIsValidSchoolAdmin(response.isValidSchoolAdmin); await this.setIsValidSchoolIndependentAdmin(response.isValidSchoolIndependentAdmin); await this.setIsValidIndependentAuthorityAdmin(response.isValidIndependentAuthorityAdmin); + await this.setAuthorizedWebsocketUser(response.isAuthorizedWebsocketUser); ApiService.setAuthHeader(response.jwtFrontend); }