cordis/client/webapp/socket-verifier.ts
2021-11-30 20:45:06 -06:00

76 lines
2.5 KiB
TypeScript

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;
});
}
/** Verifies this client with the server. This function must be called before the server will send messages or give results */
private async doVerify(): Promise<string> {
if (this.socket.disconnected) throw new Error('socket is disconnected');
if (this.memberId) return this.memberId;
this.isVerified = false;
return await new Promise<string>(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<string> {
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();
}
}