1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 18:53:29 +00:00

[SG-520] Native messaging handler (#3566)

* [SG-523] Base test runner app for native messages (#3269)

* Base test runner app for native messages

* Remove default test script

* Add case for canceled status

* Modify to allow usage of libs crypto services and functions

* Small adjustments

* Handshake request (#3277)

* Handshake request

* Fix capitalization

* Update info text

* lock node-ipc to 9.2.1

* [SG-569] Native Messaging settings bug (#3285)

* Fix bug where updating setting wasn't starting the native messaging listener

* Update test runner error message

* [SG-532] Implement Status command in Native Messaging Service (#3310)

* Status command start

* Refactor ipc test service and add status command

* fixed linter errors

* Move types into a model file

* Cleanup and comments

* Fix auth status condition

* Remove .vscode settings file. Fix this in a separate work item

* Add active field to status response

* Extract native messaging types into their own files

* Remove experimental decorators

* Turn off no console lint rule for the test runner

* Casing fix

* Models import casing fixes

* Remove in progress file (merge error)

* Move models to their own folder and add index.ts

* Remove file that got un-deleted

* Remove file that will be added in separate command

* Fix imports that got borked

* [SG-533] Implement bw-credential-retrieval (#3334)

* Status command start

* Refactor ipc test service and add status command

* fixed linter errors

* Move types into a model file

* Cleanup and comments

* Fix auth status condition

* Remove .vscode settings file. Fix this in a separate work item

* Implement bw-credential-retrieval

* Add active field to status response

* Extract native messaging types into their own files

* Remove experimental decorators

* Turn off no console lint rule for the test runner

* Casing fix

* Models import casing fixes

* Add error handling for passing a bad public key to handshake

* [SG-534] and [SG-535] Implement Credential Create and Update commands (#3342)

* Status command start

* Refactor ipc test service and add status command

* fixed linter errors

* Move types into a model file

* Cleanup and comments

* Fix auth status condition

* Remove .vscode settings file. Fix this in a separate work item

* Implement bw-credential-retrieval

* Add active field to status response

* Add bw-credential-create

* Better response handling in test runner

* Extract native messaging types into their own files

* Remove experimental decorators

* Turn off no console lint rule for the test runner

* Casing fix

* Models import casing fixes

* bw-cipher-create move type into its own file

* Use LogUtils for all logging

* Implement bw-credential-update

* Give naming conventions for types

* Rename file correctly

* Update handleEncyptedMessage with EncString changes

* [SG-626] Fix Desktop app not showing updated credentials from native messages (#3380)

* Add MessagingService to send messages on login create and update

* Add `not-active-user` error to create and update and other refactors

* [SG-536] Implement bw-generate-password (#3370)

* implement bw-generate-password

* Fix merge conflict resolution errors

* Update apps/desktop/native-messaging-test-runner/src/bw-generate-password.ts

Co-authored-by: Addison Beck <addisonbeck1@gmail.com>

* Logging improvements

* Add NativeMessagingVersion enum

* Add version check in NativeMessagingHandler

Co-authored-by: Addison Beck <addisonbeck1@gmail.com>

* Refactor account status checks and check for locked state in generate command (#3461)

* Add feawture flag to show/hide ddg setting (#3506)

* [SG-649] Add confirmation dialog and tweak shared key retrieval  (#3451)

* Add confirmation dialog when completing handshake

* Copy updates for dialog

* HandshakeResponse type fixes

* Add longer timeout for handshake command

* [SG-663] RefactorNativeMessagingHandlerService and strengthen typing (#3551)

* NativeMessageHandlerService refactor and additional types

* Return empty array if no uri to retrieve command

* Move commands from test runner into a separate folder

* Fix bug where confirmation dialog messes with styling

* Enable DDG feature

* Fix generated password not saving to history

* Take credentialId as parameter to update

* Add applicationName to handshake payload

* Add warning text to confirmation modal

Co-authored-by: Addison Beck <addisonbeck1@gmail.com>
This commit is contained in:
Robyn MacCallum
2022-09-23 15:47:17 -04:00
committed by GitHub
parent 32eac70c82
commit f4e61d1cec
57 changed files with 2386 additions and 27 deletions

View File

@@ -0,0 +1,166 @@
import { homedir } from "os";
import * as NodeIPC from "node-ipc";
import { MessageCommon } from "../../src/models/nativeMessaging/messageCommon";
import { UnencryptedMessageResponse } from "../../src/models/nativeMessaging/unencryptedMessageResponse";
import Deferred from "./deferred";
import { race } from "./race";
NodeIPC.config.id = "native-messaging-test-runner";
NodeIPC.config.maxRetries = 0;
NodeIPC.config.silent = true;
const DESKTOP_APP_PATH = `${homedir}/tmp/app.bitwarden`;
const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds
export type MessageHandler = (MessageCommon) => void;
export enum IPCConnectionState {
Disconnected = "disconnected",
Connecting = "connecting",
Connected = "connected",
}
export type IPCOptions = {
overrideTimeout?: number;
};
export default class IPCService {
// The current connection state of the socket.
private connectionState: IPCConnectionState = IPCConnectionState.Disconnected;
// Messages that have been sent, but have not yet received responses
private pendingMessages = new Map<string, Deferred<UnencryptedMessageResponse>>();
// A set of deferred promises that are awaiting socket connection
private awaitingConnection = new Set<Deferred<void>>();
constructor(private socketName: string, private messageHandler: MessageHandler) {}
async connect(): Promise<void> {
console.log("[IPCService] connecting...");
if (this.connectionState === IPCConnectionState.Connected) {
// Socket is already connected. Don't throw, just allow the callsite to proceed
return;
}
const deferredConnections = new Deferred<void>();
this.awaitingConnection.add(deferredConnections);
// If the current connection state is disconnected, we should start trying to connect.
// The only other possible connection state at this point is "connecting" and if this
// is the case, we just want to add a deferred promise to the awaitingConnection collection
// and not try to initiate the connection again.
if (this.connectionState === IPCConnectionState.Disconnected) {
this._connect();
}
return deferredConnections.getPromise();
}
private _connect() {
this.connectionState = IPCConnectionState.Connecting;
NodeIPC.connectTo(this.socketName, DESKTOP_APP_PATH, () => {
// Process incoming message
this.getSocket().on("message", (message: any) => {
this.processMessage(message);
});
this.getSocket().on("error", (error: Error) => {
// Only makes sense as long as config.maxRetries stays set to 0. Otherwise this will be
// invoked multiple times each time a connection error happens
console.log("[IPCService] errored");
console.log(
"\x1b[33m Please make sure the desktop app is running locally and 'Allow DuckDuckGo browser integration' setting is enabled \x1b[0m"
);
this.awaitingConnection.forEach((deferred) => {
console.log(`rejecting: ${deferred}`);
deferred.reject(error);
});
this.awaitingConnection.clear();
});
this.getSocket().on("connect", () => {
console.log("[IPCService] connected");
this.connectionState = IPCConnectionState.Connected;
this.awaitingConnection.forEach((deferred) => {
deferred.resolve(null);
});
this.awaitingConnection.clear();
});
this.getSocket().on("disconnect", () => {
console.log("[IPCService] disconnected");
this.connectionState = IPCConnectionState.Disconnected;
});
});
}
disconnect() {
console.log("[IPCService] disconnecting...");
if (this.connectionState !== IPCConnectionState.Disconnected) {
NodeIPC.disconnect(this.socketName);
}
}
async sendMessage(
message: MessageCommon,
options: IPCOptions = {}
): Promise<UnencryptedMessageResponse> {
console.log("[IPCService] sendMessage");
if (this.pendingMessages.has(message.messageId)) {
throw new Error(`A message with the id: ${message.messageId} has already been sent.`);
}
// Creates a new deferred promise that allows us to convert a message received over the IPC socket
// into a response for a message that we previously sent. This mechanism relies on the fact that we
// create a unique message id and attach it with each message. Response messages are expected to
// include the message id of the message they are responding to.
const deferred = new Deferred<UnencryptedMessageResponse>();
this.pendingMessages.set(message.messageId, deferred);
this.getSocket().emit("message", message);
try {
// Since we can not guarentee that a response message will ever be sent, we put a timeout
// on messages
return race({
promise: deferred.getPromise(),
timeout: options?.overrideTimeout ?? DEFAULT_MESSAGE_TIMEOUT,
error: new Error(`Message: ${message.messageId} timed out`),
});
} catch (error) {
// If there is a timeout, remove the message from the pending messages set
// before triggering error handling elsewhere.
this.pendingMessages.delete(message.messageId);
throw error;
}
}
private getSocket() {
return NodeIPC.of[this.socketName];
}
private processMessage(message: any) {
// If the message is a response to a previous message, resolve the deferred promise that
// is awaiting that response. Otherwise, assume this was a new message that wasn't sent as a
// response and invoke the message handler.
if (message.messageId && this.pendingMessages.has(message.messageId)) {
const deferred = this.pendingMessages.get(message.messageId);
// In the future, this could be improved to add ability to reject, but most messages coming in are
// encrypted at this point so we're unable to determine if they contain error info.
deferred.resolve(message);
this.pendingMessages.delete(message.messageId);
} else {
this.messageHandler(message);
}
}
}