1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

Desktop Autotype use managed object for IPC vault data payload (#17105)

* Desktop Autotype use managed object for IPC vault data payload

* claude feedback 1

* check for falsey values

* add unit test

* remove unecessary async

* helper result type

* dear claude
This commit is contained in:
neuronull
2025-11-04 08:13:08 -08:00
committed by GitHub
parent b79625def8
commit 7801fd3123
5 changed files with 99 additions and 27 deletions

View File

@@ -5,6 +5,7 @@ import { LogService } from "@bitwarden/logging";
import { WindowMain } from "../../main/window.main"; import { WindowMain } from "../../main/window.main";
import { stringIsNotUndefinedNullAndEmpty } from "../../utils"; import { stringIsNotUndefinedNullAndEmpty } from "../../utils";
import { AutotypeVaultData } from "../models/autotype-vault-data";
import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut"; import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut";
export class MainDesktopAutotypeService { export class MainDesktopAutotypeService {
@@ -47,18 +48,12 @@ export class MainDesktopAutotypeService {
} }
}); });
ipcMain.on("autofill.completeAutotypeRequest", (event, data) => { ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => {
const { response } = data;
if ( if (
stringIsNotUndefinedNullAndEmpty(response.username) && stringIsNotUndefinedNullAndEmpty(vaultData.username) &&
stringIsNotUndefinedNullAndEmpty(response.password) stringIsNotUndefinedNullAndEmpty(vaultData.password)
) { ) {
this.doAutotype( this.doAutotype(vaultData, this.autotypeKeyboardShortcut.getArrayFormat());
response.username,
response.password,
this.autotypeKeyboardShortcut.getArrayFormat(),
);
} }
}); });
} }
@@ -89,8 +84,9 @@ export class MainDesktopAutotypeService {
: this.logService.info("Enabling autotype failed."); : this.logService.info("Enabling autotype failed.");
} }
private doAutotype(username: string, password: string, keyboardShortcut: string[]) { private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) {
const inputPattern = username + "\t" + password; const TAB = "\t";
const inputPattern = vaultData.username + TAB + vaultData.password;
const inputArray = new Array<number>(inputPattern.length); const inputArray = new Array<number>(inputPattern.length);
for (let i = 0; i < inputPattern.length; i++) { for (let i = 0; i < inputPattern.length; i++) {

View File

@@ -0,0 +1,8 @@
/**
* Vault data used in autotype operations.
* `username` and `password` are guaranteed to be not null/undefined.
*/
export interface AutotypeVaultData {
username: string;
password: string;
}

View File

@@ -5,6 +5,8 @@ import type { autofill } from "@bitwarden/desktop-napi";
import { Command } from "../platform/main/autofill/command"; import { Command } from "../platform/main/autofill/command";
import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main";
import { AutotypeVaultData } from "./models/autotype-vault-data";
export default { export default {
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> => runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
ipcRenderer.invoke("autofill.runCommand", params), ipcRenderer.invoke("autofill.runCommand", params),
@@ -133,10 +135,7 @@ export default {
listenAutotypeRequest: ( listenAutotypeRequest: (
fn: ( fn: (
windowTitle: string, windowTitle: string,
completeCallback: ( completeCallback: (error: Error | null, response: AutotypeVaultData | null) => void,
error: Error | null,
response: { username?: string; password?: string },
) => void,
) => void, ) => void,
) => { ) => {
ipcRenderer.on( ipcRenderer.on(
@@ -149,7 +148,7 @@ export default {
) => { ) => {
const { windowTitle } = data; const { windowTitle } = data;
fn(windowTitle, (error, response) => { fn(windowTitle, (error, vaultData) => {
if (error) { if (error) {
ipcRenderer.send("autofill.completeError", { ipcRenderer.send("autofill.completeError", {
windowTitle, windowTitle,
@@ -157,11 +156,9 @@ export default {
}); });
return; return;
} }
if (vaultData !== null) {
ipcRenderer.send("autofill.completeAutotypeRequest", { ipcRenderer.send("autofill.completeAutotypeRequest", vaultData);
windowTitle, }
response,
});
}); });
}, },
); );

View File

@@ -0,0 +1,50 @@
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { getAutotypeVaultData } from "./desktop-autotype.service";
describe("getAutotypeVaultData", () => {
it("should return vault data when cipher has username and password", () => {
const cipherView = new CipherView();
cipherView.login.username = "foo";
cipherView.login.password = "bar";
const [error, vaultData] = getAutotypeVaultData(cipherView);
expect(error).toBeNull();
expect(vaultData?.username).toEqual("foo");
expect(vaultData?.password).toEqual("bar");
});
it("should return error when firstCipher is undefined", () => {
const cipherView = undefined;
const [error, vaultData] = getAutotypeVaultData(cipherView);
expect(vaultData).toBeNull();
expect(error).toBeDefined();
expect(error?.message).toEqual("No matching vault item.");
});
it("should return error when username is undefined", () => {
const cipherView = new CipherView();
cipherView.login.username = undefined;
cipherView.login.password = "bar";
const [error, vaultData] = getAutotypeVaultData(cipherView);
expect(vaultData).toBeNull();
expect(error).toBeDefined();
expect(error?.message).toEqual("Vault item is undefined.");
});
it("should return error when password is undefined", () => {
const cipherView = new CipherView();
cipherView.login.username = "foo";
cipherView.login.password = undefined;
const [error, vaultData] = getAutotypeVaultData(cipherView);
expect(vaultData).toBeNull();
expect(error).toBeDefined();
expect(error?.message).toEqual("Vault item is undefined.");
});
});

View File

@@ -17,6 +17,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { UserId } from "@bitwarden/user-core"; import { UserId } from "@bitwarden/user-core";
import { AutotypeVaultData } from "../models/autotype-vault-data";
import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service"; import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service";
export const defaultWindowsAutotypeKeyboardShortcut: string[] = ["Control", "Shift", "B"]; export const defaultWindowsAutotypeKeyboardShortcut: string[] = ["Control", "Shift", "B"];
@@ -27,6 +29,8 @@ export const AUTOTYPE_ENABLED = new KeyDefinition<boolean | null>(
{ deserializer: (b) => b }, { deserializer: (b) => b },
); );
export type Result<T, E = Error> = [E, null] | [null, T];
/* /*
Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z
Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z
@@ -63,11 +67,8 @@ export class DesktopAutotypeService {
ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => { ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => {
const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle); const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle);
const firstCipher = possibleCiphers?.at(0); const firstCipher = possibleCiphers?.at(0);
const [error, vaultData] = getAutotypeVaultData(firstCipher);
return callback(null, { callback(error, vaultData);
username: firstCipher?.login?.username,
password: firstCipher?.login?.password,
});
}); });
} }
@@ -176,3 +177,23 @@ export class DesktopAutotypeService {
return possibleCiphers; return possibleCiphers;
} }
} }
/**
* @return an `AutotypeVaultData` object or an `Error` if the
* cipher or vault data within are undefined.
*/
export function getAutotypeVaultData(
cipherView: CipherView | undefined,
): Result<AutotypeVaultData> {
if (!cipherView) {
return [Error("No matching vault item."), null];
} else if (cipherView.login.username === undefined || cipherView.login.password === undefined) {
return [Error("Vault item is undefined."), null];
} else {
const vaultData: AutotypeVaultData = {
username: cipherView.login.username,
password: cipherView.login.password,
};
return [null, vaultData];
}
}