import * as crypto from 'crypto'; import * as socketio from 'socket.io-client'; import DedupAwaiter from "./dedup-awaiter"; import Util from './util'; export default class SocketVerifier { 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 ) { socket.on('connect', async () => { await this.verify(); }); socket.on('disconnect', () => { this.isVerified = false; }); } // TODO: Move this to a "query/queryDedup" request /** 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; 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 = null; resolve(memberId); }); }); }); } // returns the memberId public async verify(): Promise { return await this.verifyDedup.call(); } // TODO: ensureVerified for send/update requests that may need to be in-order? public async ensureVerified() { if (this.isVerified) return; await this.verify(); } }