mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-23 03:22:50 +02:00
[PM-10079] Add keyboard shortcut to autofill identity and credit cards (#10254)
* [BEEEP] Autofill Identity and Card Ciphers From Keyboard Shortcut * [PM-10079] Add keyboard shortcut to autofill identity and credit card ciphers * [PM-10079] Fixing jest tests * [PM-10079] Added an enum for the autofill commands, and adjusted how we filter out cipher types before sorting them by last used when calling for ID and card ciphers * [PM-10079] Updating copywriting for the autofill settings revolving around keyboard shortcuts * [PM-10079] Setting a method within CipherService as private
This commit is contained in:
parent
85c8ff04a1
commit
86acca3bec
@ -1343,8 +1343,14 @@
|
|||||||
"commandOpenSidebar": {
|
"commandOpenSidebar": {
|
||||||
"message": "Open vault in sidebar"
|
"message": "Open vault in sidebar"
|
||||||
},
|
},
|
||||||
"commandAutofillDesc": {
|
"commandAutofillLoginDesc": {
|
||||||
"message": "Auto-fill the last used login for the current website"
|
"message": "Autofill the last used login for the current website"
|
||||||
|
},
|
||||||
|
"commandAutofillCardDesc": {
|
||||||
|
"message": "Autofill the last used card for the current website"
|
||||||
|
},
|
||||||
|
"commandAutofillIdentityDesc": {
|
||||||
|
"message": "Autofill the last used identity for the current website"
|
||||||
},
|
},
|
||||||
"commandGeneratePasswordDesc": {
|
"commandGeneratePasswordDesc": {
|
||||||
"message": "Generate and copy a new random password to the clipboard"
|
"message": "Generate and copy a new random password to the clipboard"
|
||||||
@ -2774,14 +2780,17 @@
|
|||||||
"autofillKeyboardShortcutUpdateLabel": {
|
"autofillKeyboardShortcutUpdateLabel": {
|
||||||
"message": "Change shortcut"
|
"message": "Change shortcut"
|
||||||
},
|
},
|
||||||
|
"autofillKeyboardManagerShortcutsLabel": {
|
||||||
|
"message": "Manage shortcuts"
|
||||||
|
},
|
||||||
"autofillShortcut": {
|
"autofillShortcut": {
|
||||||
"message": "Autofill keyboard shortcut"
|
"message": "Autofill keyboard shortcut"
|
||||||
},
|
},
|
||||||
"autofillShortcutNotSet": {
|
"autofillLoginShortcutNotSet": {
|
||||||
"message": "The autofill shortcut is not set. Change this in the browser's settings."
|
"message": "The autofill login shortcut is not set. Change this in the browser's settings."
|
||||||
},
|
},
|
||||||
"autofillShortcutText": {
|
"autofillLoginShortcutText": {
|
||||||
"message": "The autofill shortcut is: $COMMAND$. Change this in the browser's settings.",
|
"message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"command": {
|
"command": {
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
|
@ -4,6 +4,7 @@ import { BehaviorSubject, firstValueFrom } from "rxjs";
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||||
|
import { ExtensionCommand } from "@bitwarden/common/autofill/constants";
|
||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@ -151,7 +152,7 @@ describe("NotificationBackground", () => {
|
|||||||
const message: NotificationBackgroundExtensionMessage = {
|
const message: NotificationBackgroundExtensionMessage = {
|
||||||
command: "unlockCompleted",
|
command: "unlockCompleted",
|
||||||
data: {
|
data: {
|
||||||
commandToRetry: { message: { command: "autofill_login" } },
|
commandToRetry: { message: { command: ExtensionCommand.AutofillLogin } },
|
||||||
} as LockedVaultPendingNotificationsData,
|
} as LockedVaultPendingNotificationsData,
|
||||||
};
|
};
|
||||||
jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation();
|
jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation();
|
||||||
|
@ -4,7 +4,11 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
|||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { NOTIFICATION_BAR_LIFESPAN_MS } from "@bitwarden/common/autofill/constants";
|
import {
|
||||||
|
ExtensionCommand,
|
||||||
|
ExtensionCommandType,
|
||||||
|
NOTIFICATION_BAR_LIFESPAN_MS,
|
||||||
|
} from "@bitwarden/common/autofill/constants";
|
||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||||
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
||||||
@ -45,6 +49,11 @@ export default class NotificationBackground {
|
|||||||
private openUnlockPopout = openUnlockPopout;
|
private openUnlockPopout = openUnlockPopout;
|
||||||
private openAddEditVaultItemPopout = openAddEditVaultItemPopout;
|
private openAddEditVaultItemPopout = openAddEditVaultItemPopout;
|
||||||
private notificationQueue: NotificationQueueMessageItem[] = [];
|
private notificationQueue: NotificationQueueMessageItem[] = [];
|
||||||
|
private allowedRetryCommands: Set<ExtensionCommandType> = new Set([
|
||||||
|
ExtensionCommand.AutofillLogin,
|
||||||
|
ExtensionCommand.AutofillCard,
|
||||||
|
ExtensionCommand.AutofillIdentity,
|
||||||
|
]);
|
||||||
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
|
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
|
||||||
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
|
unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender),
|
||||||
bgGetFolderData: () => this.getFolderData(),
|
bgGetFolderData: () => this.getFolderData(),
|
||||||
@ -689,8 +698,8 @@ export default class NotificationBackground {
|
|||||||
sender: chrome.runtime.MessageSender,
|
sender: chrome.runtime.MessageSender,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const messageData = message.data as LockedVaultPendingNotificationsData;
|
const messageData = message.data as LockedVaultPendingNotificationsData;
|
||||||
const retryCommand = messageData.commandToRetry.message.command;
|
const retryCommand = messageData.commandToRetry.message.command as ExtensionCommandType;
|
||||||
if (retryCommand === "autofill_login") {
|
if (this.allowedRetryCommands.has(retryCommand)) {
|
||||||
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
CREATE_CARD_ID,
|
CREATE_CARD_ID,
|
||||||
CREATE_IDENTITY_ID,
|
CREATE_IDENTITY_ID,
|
||||||
CREATE_LOGIN_ID,
|
CREATE_LOGIN_ID,
|
||||||
|
ExtensionCommand,
|
||||||
GENERATE_PASSWORD_ID,
|
GENERATE_PASSWORD_ID,
|
||||||
NOOP_COMMAND_SUFFIX,
|
NOOP_COMMAND_SUFFIX,
|
||||||
} from "@bitwarden/common/autofill/constants";
|
} from "@bitwarden/common/autofill/constants";
|
||||||
@ -79,7 +80,7 @@ export class ContextMenuClickedHandler {
|
|||||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||||
const retryMessage: LockedVaultPendingNotificationsData = {
|
const retryMessage: LockedVaultPendingNotificationsData = {
|
||||||
commandToRetry: {
|
commandToRetry: {
|
||||||
message: { command: NOOP_COMMAND_SUFFIX, contextMenuOnClickData: info },
|
message: { command: ExtensionCommand.NoopCommand, contextMenuOnClickData: info },
|
||||||
sender: { tab: tab },
|
sender: { tab: tab },
|
||||||
},
|
},
|
||||||
target: "contextmenus.background",
|
target: "contextmenus.background",
|
||||||
|
@ -159,9 +159,9 @@ export class AutofillV1Component implements OnInit {
|
|||||||
|
|
||||||
private async setAutofillKeyboardHelperText(command: string) {
|
private async setAutofillKeyboardHelperText(command: string) {
|
||||||
if (command) {
|
if (command) {
|
||||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutText", command);
|
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutText", command);
|
||||||
} else {
|
} else {
|
||||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutNotSet");
|
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutNotSet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
</bit-section-header>
|
</bit-section-header>
|
||||||
<bit-item>
|
<bit-item>
|
||||||
<button bit-item-content type="button" (click)="openURI($event, browserShortcutsURI)">
|
<button bit-item-content type="button" (click)="openURI($event, browserShortcutsURI)">
|
||||||
<h3 bitTypography="h5">{{ "autofillKeyboardShortcutUpdateLabel" | i18n }}</h3>
|
<h3 bitTypography="h5">{{ "autofillKeyboardManagerShortcutsLabel" | i18n }}</h3>
|
||||||
<bit-hint slot="secondary" class="tw-text-sm tw-whitespace-normal">
|
<bit-hint slot="secondary" class="tw-text-sm tw-whitespace-normal">
|
||||||
{{ autofillKeyboardHelperText }}
|
{{ autofillKeyboardHelperText }}
|
||||||
</bit-hint>
|
</bit-hint>
|
||||||
|
@ -215,9 +215,9 @@ export class AutofillComponent implements OnInit {
|
|||||||
|
|
||||||
private async setAutofillKeyboardHelperText(command: string) {
|
private async setAutofillKeyboardHelperText(command: string) {
|
||||||
if (command) {
|
if (command) {
|
||||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutText", command);
|
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutText", command);
|
||||||
} else {
|
} else {
|
||||||
this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutNotSet");
|
this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutNotSet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1232,22 +1232,21 @@ describe("AutofillService", () => {
|
|||||||
jest.spyOn(autofillService as any, "getActiveTab").mockResolvedValueOnce(tab);
|
jest.spyOn(autofillService as any, "getActiveTab").mockResolvedValueOnce(tab);
|
||||||
jest.spyOn(autofillService, "doAutoFill").mockImplementation();
|
jest.spyOn(autofillService, "doAutoFill").mockImplementation();
|
||||||
jest
|
jest
|
||||||
.spyOn(autofillService["cipherService"], "getAllDecryptedForUrl")
|
.spyOn(autofillService["cipherService"], "getNextCardCipher")
|
||||||
.mockResolvedValueOnce([cardCipher]);
|
.mockResolvedValueOnce(cardCipher);
|
||||||
|
|
||||||
await autofillService.doAutoFillActiveTab(cardFormPageDetails, false, CipherType.Card);
|
await autofillService.doAutoFillActiveTab(cardFormPageDetails, true, CipherType.Card);
|
||||||
|
|
||||||
expect(autofillService["cipherService"].getAllDecryptedForUrl).toHaveBeenCalled();
|
|
||||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||||
tab: tab,
|
tab: tab,
|
||||||
cipher: cardCipher,
|
cipher: cardCipher,
|
||||||
pageDetails: cardFormPageDetails,
|
pageDetails: cardFormPageDetails,
|
||||||
skipLastUsed: true,
|
skipLastUsed: false,
|
||||||
skipUsernameOnlyFill: true,
|
skipUsernameOnlyFill: false,
|
||||||
onlyEmptyFields: true,
|
onlyEmptyFields: false,
|
||||||
onlyVisibleFields: true,
|
onlyVisibleFields: false,
|
||||||
fillNewPassword: false,
|
fillNewPassword: false,
|
||||||
allowUntrustedIframe: false,
|
allowUntrustedIframe: true,
|
||||||
allowTotpAutofill: false,
|
allowTotpAutofill: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1280,26 +1279,21 @@ describe("AutofillService", () => {
|
|||||||
jest.spyOn(autofillService as any, "getActiveTab").mockResolvedValueOnce(tab);
|
jest.spyOn(autofillService as any, "getActiveTab").mockResolvedValueOnce(tab);
|
||||||
jest.spyOn(autofillService, "doAutoFill").mockImplementation();
|
jest.spyOn(autofillService, "doAutoFill").mockImplementation();
|
||||||
jest
|
jest
|
||||||
.spyOn(autofillService["cipherService"], "getAllDecryptedForUrl")
|
.spyOn(autofillService["cipherService"], "getNextIdentityCipher")
|
||||||
.mockResolvedValueOnce([identityCipher]);
|
.mockResolvedValueOnce(identityCipher);
|
||||||
|
|
||||||
await autofillService.doAutoFillActiveTab(
|
await autofillService.doAutoFillActiveTab(identityFormPageDetails, true, CipherType.Identity);
|
||||||
identityFormPageDetails,
|
|
||||||
false,
|
|
||||||
CipherType.Identity,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(autofillService["cipherService"].getAllDecryptedForUrl).toHaveBeenCalled();
|
|
||||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||||
tab: tab,
|
tab: tab,
|
||||||
cipher: identityCipher,
|
cipher: identityCipher,
|
||||||
pageDetails: identityFormPageDetails,
|
pageDetails: identityFormPageDetails,
|
||||||
skipLastUsed: true,
|
skipLastUsed: false,
|
||||||
skipUsernameOnlyFill: true,
|
skipUsernameOnlyFill: false,
|
||||||
onlyEmptyFields: true,
|
onlyEmptyFields: false,
|
||||||
onlyVisibleFields: true,
|
onlyVisibleFields: false,
|
||||||
fillNewPassword: false,
|
fillNewPassword: false,
|
||||||
allowUntrustedIframe: false,
|
allowUntrustedIframe: true,
|
||||||
allowTotpAutofill: false,
|
allowTotpAutofill: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -520,16 +520,30 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
return await this.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
return await this.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cipher is a non-login type
|
let cipher: CipherView;
|
||||||
const cipher: CipherView = (
|
let cacheKey = "";
|
||||||
(await this.cipherService.getAllDecryptedForUrl(tab.url, [cipherType])) || []
|
|
||||||
).find(({ type }) => type === cipherType);
|
|
||||||
|
|
||||||
if (!cipher || cipher.reprompt !== CipherRepromptType.None) {
|
if (cipherType === CipherType.Card) {
|
||||||
|
cacheKey = "cardCiphers";
|
||||||
|
cipher = await this.cipherService.getNextCardCipher();
|
||||||
|
} else {
|
||||||
|
cacheKey = "identityCiphers";
|
||||||
|
cipher = await this.cipherService.getNextIdentityCipher();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.doAutoFill({
|
if (await this.isPasswordRepromptRequired(cipher, tab)) {
|
||||||
|
if (fromCommand) {
|
||||||
|
this.cipherService.updateLastUsedIndexForUrl(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totpCode = await this.doAutoFill({
|
||||||
tab: tab,
|
tab: tab,
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
pageDetails: pageDetails,
|
pageDetails: pageDetails,
|
||||||
@ -541,6 +555,12 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
allowUntrustedIframe: fromCommand,
|
allowUntrustedIframe: fromCommand,
|
||||||
allowTotpAutofill: false,
|
allowTotpAutofill: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (fromCommand) {
|
||||||
|
this.cipherService.updateLastUsedIndexForUrl(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totpCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { ExtensionCommand, ExtensionCommandType } from "@bitwarden/common/autofill/constants";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||||
|
|
||||||
@ -47,8 +48,23 @@ export default class CommandsBackground {
|
|||||||
case "generate_password":
|
case "generate_password":
|
||||||
await this.generatePasswordToClipboard();
|
await this.generatePasswordToClipboard();
|
||||||
break;
|
break;
|
||||||
case "autofill_login":
|
case ExtensionCommand.AutofillLogin:
|
||||||
await this.autoFillLogin(sender ? sender.tab : null);
|
await this.triggerAutofillCommand(
|
||||||
|
sender ? sender.tab : null,
|
||||||
|
ExtensionCommand.AutofillCommand,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ExtensionCommand.AutofillCard:
|
||||||
|
await this.triggerAutofillCommand(
|
||||||
|
sender ? sender.tab : null,
|
||||||
|
ExtensionCommand.AutofillCard,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ExtensionCommand.AutofillIdentity:
|
||||||
|
await this.triggerAutofillCommand(
|
||||||
|
sender ? sender.tab : null,
|
||||||
|
ExtensionCommand.AutofillIdentity,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case "open_popup":
|
case "open_popup":
|
||||||
await this.openPopup();
|
await this.openPopup();
|
||||||
@ -68,19 +84,27 @@ export default class CommandsBackground {
|
|||||||
await this.passwordGenerationService.addHistory(password);
|
await this.passwordGenerationService.addHistory(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async autoFillLogin(tab?: chrome.tabs.Tab) {
|
private async triggerAutofillCommand(
|
||||||
|
tab?: chrome.tabs.Tab,
|
||||||
|
commandSender?: ExtensionCommandType,
|
||||||
|
) {
|
||||||
if (!tab) {
|
if (!tab) {
|
||||||
tab = await BrowserApi.getTabFromCurrentWindowId();
|
tab = await BrowserApi.getTabFromCurrentWindowId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab == null) {
|
if (tab == null || !commandSender) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||||
const retryMessage: LockedVaultPendingNotificationsData = {
|
const retryMessage: LockedVaultPendingNotificationsData = {
|
||||||
commandToRetry: {
|
commandToRetry: {
|
||||||
message: { command: "autofill_login" },
|
message: {
|
||||||
|
command:
|
||||||
|
commandSender === ExtensionCommand.AutofillCommand
|
||||||
|
? ExtensionCommand.AutofillLogin
|
||||||
|
: commandSender,
|
||||||
|
},
|
||||||
sender: { tab: tab },
|
sender: { tab: tab },
|
||||||
},
|
},
|
||||||
target: "commands.background",
|
target: "commands.background",
|
||||||
@ -95,7 +119,7 @@ export default class CommandsBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.main.collectPageDetailsForContentScript(tab, "autofill_cmd");
|
await this.main.collectPageDetailsForContentScript(tab, commandSender);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openPopup() {
|
private async openPopup() {
|
||||||
|
@ -2,7 +2,7 @@ import { firstValueFrom, map, mergeMap } from "rxjs";
|
|||||||
|
|
||||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants";
|
import { AutofillOverlayVisibility, ExtensionCommand } from "@bitwarden/common/autofill/constants";
|
||||||
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@ -117,7 +117,7 @@ export default class RuntimeBackground {
|
|||||||
case "collectPageDetailsResponse":
|
case "collectPageDetailsResponse":
|
||||||
switch (msg.sender) {
|
switch (msg.sender) {
|
||||||
case "autofiller":
|
case "autofiller":
|
||||||
case "autofill_cmd": {
|
case ExtensionCommand.AutofillCommand: {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
);
|
);
|
||||||
@ -130,14 +130,14 @@ export default class RuntimeBackground {
|
|||||||
details: msg.details,
|
details: msg.details,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
msg.sender === "autofill_cmd",
|
msg.sender === ExtensionCommand.AutofillCommand,
|
||||||
);
|
);
|
||||||
if (totpCode != null) {
|
if (totpCode != null) {
|
||||||
this.platformUtilsService.copyToClipboard(totpCode);
|
this.platformUtilsService.copyToClipboard(totpCode);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "autofill_card": {
|
case ExtensionCommand.AutofillCard: {
|
||||||
await this.autofillService.doAutoFillActiveTab(
|
await this.autofillService.doAutoFillActiveTab(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -146,12 +146,12 @@ export default class RuntimeBackground {
|
|||||||
details: msg.details,
|
details: msg.details,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
false,
|
msg.sender === ExtensionCommand.AutofillCard,
|
||||||
CipherType.Card,
|
CipherType.Card,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "autofill_identity": {
|
case ExtensionCommand.AutofillIdentity: {
|
||||||
await this.autofillService.doAutoFillActiveTab(
|
await this.autofillService.doAutoFillActiveTab(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -160,7 +160,7 @@ export default class RuntimeBackground {
|
|||||||
details: msg.details,
|
details: msg.details,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
false,
|
msg.sender === ExtensionCommand.AutofillIdentity,
|
||||||
CipherType.Identity,
|
CipherType.Identity,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
@ -94,7 +94,13 @@
|
|||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+L"
|
"default": "Ctrl+Shift+L"
|
||||||
},
|
},
|
||||||
"description": "__MSG_commandAutofillDesc__"
|
"description": "__MSG_commandAutofillLoginDesc__"
|
||||||
|
},
|
||||||
|
"autofill_card": {
|
||||||
|
"description": "__MSG_commandAutofillCardDesc__"
|
||||||
|
},
|
||||||
|
"autofill_identity": {
|
||||||
|
"description": "__MSG_commandAutofillIdentityDesc__"
|
||||||
},
|
},
|
||||||
"generate_password": {
|
"generate_password": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
|
@ -99,7 +99,13 @@
|
|||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+L"
|
"default": "Ctrl+Shift+L"
|
||||||
},
|
},
|
||||||
"description": "__MSG_commandAutofillDesc__"
|
"description": "__MSG_commandAutofillLoginDesc__"
|
||||||
|
},
|
||||||
|
"autofill_card": {
|
||||||
|
"description": "__MSG_commandAutofillCardDesc__"
|
||||||
|
},
|
||||||
|
"autofill_identity": {
|
||||||
|
"description": "__MSG_commandAutofillIdentityDesc__"
|
||||||
},
|
},
|
||||||
"generate_password": {
|
"generate_password": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ExtensionCommand } from "@bitwarden/common/autofill/constants";
|
||||||
import { ClientType, DeviceType } from "@bitwarden/common/enums";
|
import { ClientType, DeviceType } from "@bitwarden/common/enums";
|
||||||
import {
|
import {
|
||||||
ClipboardOptions,
|
ClipboardOptions,
|
||||||
@ -298,7 +299,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
|||||||
autofillCommand = "Cmd+Shift+L";
|
autofillCommand = "Cmd+Shift+L";
|
||||||
} else if (this.isFirefox()) {
|
} else if (this.isFirefox()) {
|
||||||
autofillCommand = (await browser.commands.getAll()).find(
|
autofillCommand = (await browser.commands.getAll()).find(
|
||||||
(c) => c.name === "autofill_login",
|
(c) => c.name === ExtensionCommand.AutofillLogin,
|
||||||
).shortcut;
|
).shortcut;
|
||||||
// Firefox is returning Ctrl instead of Cmd for the modifier key on macOS if
|
// Firefox is returning Ctrl instead of Cmd for the modifier key on macOS if
|
||||||
// the command is the default one set on installation.
|
// the command is the default one set on installation.
|
||||||
@ -311,7 +312,9 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic
|
|||||||
} else {
|
} else {
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
chrome.commands.getAll((c) =>
|
chrome.commands.getAll((c) =>
|
||||||
resolve((autofillCommand = c.find((c) => c.name === "autofill_login").shortcut)),
|
resolve(
|
||||||
|
(autofillCommand = c.find((c) => c.name === ExtensionCommand.AutofillLogin).shortcut),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,3 +85,17 @@ export const DisablePasswordManagerUris = {
|
|||||||
Vivaldi: "vivaldi://settings/autofill",
|
Vivaldi: "vivaldi://settings/autofill",
|
||||||
Unknown: "https://bitwarden.com/help/disable-browser-autofill/",
|
Unknown: "https://bitwarden.com/help/disable-browser-autofill/",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const ExtensionCommand = {
|
||||||
|
AutofillCommand: "autofill_cmd",
|
||||||
|
AutofillCard: "autofill_card",
|
||||||
|
AutofillIdentity: "autofill_identity",
|
||||||
|
AutofillLogin: "autofill_login",
|
||||||
|
OpenAutofillOverlay: "open_autofill_overlay",
|
||||||
|
GeneratePassword: "generate_password",
|
||||||
|
OpenPopup: "open_popup",
|
||||||
|
LockVault: "lock_vault",
|
||||||
|
NoopCommand: "noop",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ExtensionCommandType = (typeof ExtensionCommand)[keyof typeof ExtensionCommand];
|
||||||
|
@ -162,4 +162,6 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
newUserKey: UserKey,
|
newUserKey: UserKey,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
) => Promise<CipherWithIdRequest[]>;
|
) => Promise<CipherWithIdRequest[]>;
|
||||||
|
getNextCardCipher: () => Promise<CipherView>;
|
||||||
|
getNextIdentityCipher: () => Promise<CipherView>;
|
||||||
}
|
}
|
||||||
|
@ -500,6 +500,13 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getAllDecryptedCiphersOfType(type: CipherType[]): Promise<CipherView[]> {
|
||||||
|
const ciphers = await this.getAllDecrypted();
|
||||||
|
return ciphers
|
||||||
|
.filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type))
|
||||||
|
.sort((a, b) => this.sortCiphersByLastUsedThenName(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
async getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
async getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]> {
|
||||||
const response = await this.apiService.getCiphersOrganization(organizationId);
|
const response = await this.apiService.getCiphersOrganization(organizationId);
|
||||||
return await this.decryptOrganizationCiphersResponse(response, organizationId);
|
return await this.decryptOrganizationCiphersResponse(response, organizationId);
|
||||||
@ -549,6 +556,36 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return this.getCipherForUrl(url, false, false, false);
|
return this.getCipherForUrl(url, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getNextCardCipher(): Promise<CipherView> {
|
||||||
|
const cacheKey = "cardCiphers";
|
||||||
|
|
||||||
|
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
||||||
|
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card]);
|
||||||
|
if (!ciphers?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortedCiphersCache.addCiphers(cacheKey, ciphers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sortedCiphersCache.getNext(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNextIdentityCipher() {
|
||||||
|
const cacheKey = "identityCiphers";
|
||||||
|
|
||||||
|
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
||||||
|
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity]);
|
||||||
|
if (!ciphers?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortedCiphersCache.addCiphers(cacheKey, ciphers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sortedCiphersCache.getNext(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
updateLastUsedIndexForUrl(url: string) {
|
updateLastUsedIndexForUrl(url: string) {
|
||||||
this.sortedCiphersCache.updateLastUsedIndex(url);
|
this.sortedCiphersCache.updateLastUsedIndex(url);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user