swaf/src/auth/magic_link/MagicLinkWebSocketListener.ts

69 lines
2.3 KiB
TypeScript

import {Session} from "express-session";
import {IncomingMessage} from "http";
import WebSocket from "ws";
import Application from "../../Application.js";
import SessionWebSocketListener from "../../SessionWebSocketListener.js";
import MagicLink from "../models/MagicLink.js";
export default class MagicLinkWebSocketListener<A extends Application> extends SessionWebSocketListener<A> {
private readonly connections: { [p: string]: (() => void)[] | undefined } = {};
public refreshMagicLink(sessionId: string): void {
const fs = this.connections[sessionId];
if (fs) {
fs.forEach(f => f());
}
}
public async handleSessionSocket(socket: WebSocket, request: IncomingMessage, session: Session): Promise<void> {
// Refuse any incoming data
socket.on('message', () => {
socket.close(1003);
});
// Get magic link
const magicLink = await MagicLink.select()
.where('session_id', session.id)
.sortBy('authorized')
.first();
// Refresh if immediately applicable
if (!magicLink || !await magicLink.isValid() || await magicLink.isAuthorized()) {
socket.send('refresh');
const reason = magicLink ?
'Magic link state changed.' :
'Magic link not found for session ' + session.id;
socket.close(1000, reason);
return;
}
const validityTimeout = setTimeout(() => {
socket.send('refresh');
socket.close(1000, 'Timed out');
}, magicLink.getExpirationDate().getTime() - new Date().getTime());
const f = () => {
clearTimeout(validityTimeout);
socket.send('refresh');
socket.close(1000, 'Closed by server');
};
socket.on('close', () => {
const connections = this.connections[session.id];
if (connections) {
this.connections[session.id] = connections.filter(f => f !== f);
if (connections.length === 0) delete this.connections[session.id];
}
});
let connections = this.connections[session.id];
if (!connections) connections = this.connections[session.id] = [];
connections.push(f);
}
public path(): string {
return '/magic-link';
}
}