import * as electronRemote from '@electron/remote'; const electronConsole = electronRemote.getGlobal('console') as Console; import Logger from '../../logger/logger'; const LOG = Logger.create(__filename, electronConsole); import * as crypto from 'crypto'; import * as socketio from 'socket.io-client'; import { EventEmitter } from 'tsee'; import DedupAwaiter from "./dedup-awaiter"; import Util from './util'; // Automatically re-verifies the socket when connected export default class SocketVerifier extends EventEmitter<{ 'verified': () => void }> { public isVerified = false; private memberId: string | null = null; private verifyDedup = new DedupAwaiter(async () => { return await this.doVerify(); }); constructor( private socket: socketio.Socket, private publicKey: crypto.KeyObject, private privateKey: crypto.KeyObject ) { super(); socket.on('connect', async () => { await this.verify(); }); socket.on('disconnect', () => { this.isVerified = false; this.memberId = null; }); } /** Verifies this client with the server. This function must be called before the server will send messages or give results */ private async doVerify(): Promise { if (this.socket.disconnected) throw new Error('socket is disconnected'); if (this.memberId) return this.memberId; this.isVerified = false; return await new Promise(async (resolve, reject) => { // Solve the server's challenge let publicKeyBuff = this.publicKey.export({ type: 'spki', format: 'der' }); Util.socketEmitTimeout(this.socket, 5000, 'challenge', publicKeyBuff, (errMsg: string, algo: string, type: crypto.BinaryToTextEncoding, challenge: Buffer) => { if (errMsg) { reject(new Error('challenge request failed: ' + errMsg)); return; } const sign = crypto.createSign(algo); sign.write(challenge); sign.end(); let signature = sign.sign(this.privateKey, type); Util.socketEmitTimeout(this.socket, 5000, 'verify', signature, (errMsg: string, memberId: string) => { if (errMsg) { reject(new Error('verification request failed: ' + errMsg)); return; } this.memberId = memberId; this.isVerified = true; LOG.debug(`verified as u#${memberId}`) resolve(memberId); this.emit('verified'); }); }); }); } // returns the memberId public async verify(): Promise { if (this.socket.disconnected) { // wait for the socket to be connected first await new Promise((resolve) => { this.socket.once('connect', resolve); }); } return await this.verifyDedup.callOrDedup(); } // TODO: ensureVerified for send/update requests that may need to be in-order? public async ensureVerified() { if (this.isVerified) return; await this.verify(); } }