swaf/src/auth/magic_link/MagicLinkWebSocketListener.ts

71 lines
2.3 KiB
TypeScript

import WebSocket from "ws";
import {IncomingMessage} from "http";
import WebSocketListener from "../../WebSocketListener";
import MagicLink from "../models/MagicLink";
import Application from "../../Application";
import {Session} from "express-session";
export default class MagicLinkWebSocketListener<A extends Application> extends WebSocketListener<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 handle(socket: WebSocket, request: IncomingMessage, session: Session | null): Promise<void> {
// Drop if requested without session
if (!session) {
socket.close(1002, 'Session is required for this request.');
return;
}
// 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');
socket.close(1000);
return;
}
const validityTimeout = setTimeout(() => {
socket.send('refresh');
socket.close(1000);
}, magicLink.getExpirationDate().getTime() - new Date().getTime());
const f = () => {
clearTimeout(validityTimeout);
socket.send('refresh');
socket.close(1000);
};
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';
}
}