mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 08:57:08 +00:00
125 lines
3.1 KiB
JavaScript
125 lines
3.1 KiB
JavaScript
const ws = require('ws');
|
|
const URL = require('url');
|
|
const { listen } = require('../lib/db/notify');
|
|
const channels = require('../lib/db/channels');
|
|
const jwt = require('../lib/jwt');
|
|
|
|
function start (server, pingInterval=30000) {
|
|
|
|
const wsServer = new ws.Server({ noServer: true });
|
|
wsServer.on('connection', socket => {
|
|
|
|
function scheduleJwtRefresh (token) {
|
|
if (!token) {
|
|
console.warn("No token to refresh!");
|
|
return;
|
|
}
|
|
const decoded = jwt.decode(token);
|
|
console.log("scheduleJwtRefresh for token", token);
|
|
console.log("decoded as", decoded);
|
|
const exp = decoded?.exp;
|
|
if (exp) {
|
|
const timeout = (exp*1000 - Date.now()) / 2;
|
|
if (!socket._jwtRefresh) {
|
|
socket._jwtRefresh = setTimeout(() => refreshJwt(token), timeout);
|
|
console.log(`Scheduled JWT refresh in ${timeout/1000} seconds at time ${(new Date(Date.now() + timeout)).toISOString()}`);
|
|
}
|
|
} else {
|
|
console.log("Token has no exp claim. Refresh not scheduled");
|
|
}
|
|
}
|
|
|
|
function refreshJwt (token) {
|
|
console.log("refreshJwt called");
|
|
jwt.checkValidCredentials({jwt: token}).then( decoded => {
|
|
console.log("refreshJwt decoded JWT = ", decoded);
|
|
if (decoded) {
|
|
// The connection is now authenticated.
|
|
// Let us remember this user's details
|
|
socket._jwt = decoded;
|
|
|
|
console.log("Renewing JWT via websocket");
|
|
delete decoded.exp;
|
|
const token = jwt.issue(decoded);
|
|
socket.send(JSON.stringify({
|
|
channel: ".jwt",
|
|
payload: {
|
|
token
|
|
}
|
|
}));
|
|
|
|
scheduleJwtRefresh(token);
|
|
} else {
|
|
console.warn("FAILED to decode JWT");
|
|
delete socket._jwt;
|
|
}
|
|
})
|
|
.catch( err => {
|
|
console.log("refreshJwt: Invalid credentials found");
|
|
console.error(err);
|
|
delete socket._jwt;
|
|
socket.close();
|
|
});
|
|
}
|
|
|
|
socket.alive = true;
|
|
socket.on('pong', function () { this.alive = true; })
|
|
socket.on('message', message => {
|
|
// console.log("Websocket message:");
|
|
// console.log(message);
|
|
try {
|
|
const payload = JSON.parse(message);
|
|
if (payload?.jwt) {
|
|
refreshJwt(payload.jwt);
|
|
}
|
|
} catch (err) {
|
|
console.warn("Websocket message decoding failed", err);
|
|
}
|
|
|
|
});
|
|
socket.on('close', () => {
|
|
if (socket._jwtRefresh) {
|
|
clearTimeout(socket._jwtRefresh);
|
|
}
|
|
});
|
|
});
|
|
|
|
server.on('upgrade', (request, socket, head) => {
|
|
// console.log("Received upgrade request", request.url);
|
|
const url = URL.parse(request.url);
|
|
if (/^\/ws\/?$/.test(url.pathname)) {
|
|
wsServer.handleUpgrade(request, socket, head, socket => {
|
|
wsServer.emit('connection', socket, request);
|
|
});
|
|
}
|
|
});
|
|
|
|
listen(channels, (data) => {
|
|
wsServer.clients.forEach( (socket) => {
|
|
if (socket._jwt) {
|
|
// Only send notifications to authenticated users
|
|
// FIXME should implement authorisation control as in the API
|
|
socket.send(JSON.stringify(data));
|
|
}
|
|
})
|
|
});
|
|
|
|
const interval = setInterval( () => {
|
|
wsServer.clients.forEach( (socket) => {
|
|
if (!socket.alive) {
|
|
return socket.terminate();
|
|
}
|
|
socket.alive = false;
|
|
socket.ping();
|
|
})
|
|
}, pingInterval);
|
|
|
|
wsServer.on('close', () => clearInterval(interval));
|
|
|
|
return wsServer;
|
|
}
|
|
|
|
module.exports = {
|
|
start
|
|
}
|