76 lines
2.5 KiB
TypeScript
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();
|
|
}
|
|
} |