1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +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 { stringIsNotUndefinedNullAndEmpty } from "../../utils";
import { AutotypeVaultData } from "../models/autotype-vault-data";
import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut";
export class MainDesktopAutotypeService {
@@ -47,18 +48,12 @@ export class MainDesktopAutotypeService {
}
});
ipcMain.on("autofill.completeAutotypeRequest", (event, data) => {
const { response } = data;
ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => {
if (
stringIsNotUndefinedNullAndEmpty(response.username) &&
stringIsNotUndefinedNullAndEmpty(response.password)
stringIsNotUndefinedNullAndEmpty(vaultData.username) &&
stringIsNotUndefinedNullAndEmpty(vaultData.password)
) {
this.doAutotype(
response.username,
response.password,
this.autotypeKeyboardShortcut.getArrayFormat(),
);
this.doAutotype(vaultData, this.autotypeKeyboardShortcut.getArrayFormat());
}
});
}
@@ -89,8 +84,9 @@ export class MainDesktopAutotypeService {
: this.logService.info("Enabling autotype failed.");
}
private doAutotype(username: string, password: string, keyboardShortcut: string[]) {
const inputPattern = username + "\t" + password;
private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) {
const TAB = "\t";
const inputPattern = vaultData.username + TAB + vaultData.password;
const inputArray = new Array<number>(inputPattern.length);
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 { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main";
import { AutotypeVaultData } from "./models/autotype-vault-data";
export default {
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
ipcRenderer.invoke("autofill.runCommand", params),
@@ -133,10 +135,7 @@ export default {
listenAutotypeRequest: (
fn: (
windowTitle: string,
completeCallback: (
error: Error | null,
response: { username?: string; password?: string },
) => void,
completeCallback: (error: Error | null, response: AutotypeVaultData | null) => void,
) => void,
) => {
ipcRenderer.on(
@@ -149,7 +148,7 @@ export default {
) => {
const { windowTitle } = data;
fn(windowTitle, (error, response) => {
fn(windowTitle, (error, vaultData) => {
if (error) {
ipcRenderer.send("autofill.completeError", {
windowTitle,
@@ -157,11 +156,9 @@ export default {
});
return;
}
ipcRenderer.send("autofill.completeAutotypeRequest", {
windowTitle,
response,
});
if (vaultData !== null) {
ipcRenderer.send("autofill.completeAutotypeRequest", vaultData);
}
});
},
);

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 { UserId } from "@bitwarden/user-core";
import { AutotypeVaultData } from "../models/autotype-vault-data";
import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service";
export const defaultWindowsAutotypeKeyboardShortcut: string[] = ["Control", "Shift", "B"];
@@ -27,6 +29,8 @@ export const AUTOTYPE_ENABLED = new KeyDefinition<boolean | null>(
{ 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 macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z
@@ -63,11 +67,8 @@ export class DesktopAutotypeService {
ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => {
const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle);
const firstCipher = possibleCiphers?.at(0);
return callback(null, {
username: firstCipher?.login?.username,
password: firstCipher?.login?.password,
});
const [error, vaultData] = getAutotypeVaultData(firstCipher);
callback(error, vaultData);
});
}
@@ -176,3 +177,23 @@ export class DesktopAutotypeService {
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];
}
}