blob: 76706952113c409765650524257377da0e3060e8 [file] [log] [blame]
Issam E. Maghni0ef4a362022-10-05 23:20:16 +00001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import { IncomingMessage } from 'node:http';
19import { Duplex } from 'node:stream';
20
Issam E. Maghnif796a092022-10-09 20:25:26 +000021import { jwtVerify } from 'jose';
22import log from 'loglevel';
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000023import { Service } from 'typedi';
Issam E. Maghnif796a092022-10-09 20:25:26 +000024import { URL } from 'whatwg-url';
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000025import { WebSocket, WebSocketServer } from 'ws';
26
Issam E. Maghnif796a092022-10-09 20:25:26 +000027import { Vault } from './vault.js';
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000028
Issam E. Maghnif796a092022-10-09 20:25:26 +000029@Service()
30export class Ws {
31 constructor(private readonly vault: Vault) {}
32
33 async build() {
34 const wss = new WebSocketServer({ noServer: true });
35 wss.on('connection', (ws: WebSocket, _req: IncomingMessage, accountId: string) => {
36 log.info('New connection', accountId);
37
38 ws.on('message', (_data) => {
39 ws.send(JSON.stringify({ accountId }));
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000040 });
41 });
42
Issam E. Maghnif796a092022-10-09 20:25:26 +000043 const pubKey = await this.vault.pubKey();
44
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000045 return (request: IncomingMessage, socket: Duplex, head: Buffer) => {
Issam E. Maghnif796a092022-10-09 20:25:26 +000046 // Do not use parseURL because it returns a URLRecord and not a URL.
47 const url = new URL(request.url ?? '/', 'http://localhost/');
48 const accessToken = url.searchParams.get('accessToken');
49 if (!accessToken) {
50 socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
51 socket.destroy();
52 return;
53 }
54
55 jwtVerify(accessToken, pubKey, {
56 issuer: 'urn:example:issuer',
57 audience: 'urn:example:audience',
58 })
59 .then(({ payload }) => {
60 const id = payload.id as string;
61 log.info('Authentication successful', id);
62 wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, id));
63 })
64 .catch((reason) => {
65 log.debug('Authentication failed', reason);
66 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
67 socket.destroy();
68 });
Issam E. Maghni0ef4a362022-10-05 23:20:16 +000069 };
70 }
71}