mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[EC-475] Auto-save password prompt enhancements (#4808)
* [EC-1062] Convert bar.js to TS and refactor (#4623) * [EC-476 / EC-478] Add notificationBar edit flow (#4626) * [EC-477] Enable auto-save for users without individual vault (#4760) * [EC-1057] Add data loss warning to notificationBar edit flow (#4761) * [AC-1173] Fix state bugs in auto-save edit flow (#4936) --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
parent
cafd2d2561
commit
f592963191
@ -10,8 +10,6 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
|
||||
import AddChangePasswordQueueMessage from "../../background/models/addChangePasswordQueueMessage";
|
||||
import AddLoginQueueMessage from "../../background/models/addLoginQueueMessage";
|
||||
@ -95,7 +93,7 @@ export default class NotificationBackground {
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
|
||||
return;
|
||||
}
|
||||
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
|
||||
await this.saveOrUpdateCredentials(sender.tab, msg.edit, msg.folder);
|
||||
break;
|
||||
case "bgNeverSave":
|
||||
await this.saveNever(sender.tab);
|
||||
@ -168,6 +166,7 @@ export default class NotificationBackground {
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
theme: await this.getCurrentTheme(),
|
||||
removeIndividualVault: await this.removeIndividualVault(),
|
||||
},
|
||||
});
|
||||
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.ChangePassword) {
|
||||
@ -225,10 +224,6 @@ export default class NotificationBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.allowPersonalOwnership())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
||||
return;
|
||||
}
|
||||
@ -242,10 +237,6 @@ export default class NotificationBackground {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.allowPersonalOwnership())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
||||
} else if (
|
||||
usernameMatches.length === 1 &&
|
||||
@ -332,14 +323,10 @@ export default class NotificationBackground {
|
||||
await this.checkNotificationQueue(tab);
|
||||
}
|
||||
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (
|
||||
queueMessage.tabId !== tab.id ||
|
||||
(queueMessage.type !== NotificationQueueMessageType.AddLogin &&
|
||||
queueMessage.type !== NotificationQueueMessageType.ChangePassword)
|
||||
) {
|
||||
if (queueMessage.tabId !== tab.id || !(queueMessage.type in NotificationQueueMessageType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -352,63 +339,79 @@ export default class NotificationBackground {
|
||||
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
||||
const changePasswordMessage = queueMessage as AddChangePasswordQueueMessage;
|
||||
const cipher = await this.getDecryptedCipherById(changePasswordMessage.cipherId);
|
||||
if (cipher == null) {
|
||||
return;
|
||||
}
|
||||
await this.updateCipher(cipher, changePasswordMessage.newPassword);
|
||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId);
|
||||
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab);
|
||||
return;
|
||||
}
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.AddLogin) {
|
||||
if (!queueMessage.wasVaultLocked) {
|
||||
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
||||
BrowserApi.tabSendMessageData(tab, "addedCipher");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||
const addLoginMessage = queueMessage as AddLoginQueueMessage;
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(addLoginMessage.uri);
|
||||
const usernameMatches = ciphers.filter(
|
||||
(c) =>
|
||||
c.login.username != null && c.login.username.toLowerCase() === addLoginMessage.username
|
||||
);
|
||||
if (queueMessage.wasVaultLocked) {
|
||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri);
|
||||
const existingCipher = allCiphers.find(
|
||||
(c) =>
|
||||
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username
|
||||
);
|
||||
|
||||
if (usernameMatches.length >= 1) {
|
||||
await this.updateCipher(usernameMatches[0], addLoginMessage.password);
|
||||
if (existingCipher != null) {
|
||||
await this.updatePassword(existingCipher, queueMessage.password, edit, tab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
folderId = (await this.folderExists(folderId)) ? folderId : null;
|
||||
const newCipher = AddLoginQueueMessage.toCipherView(queueMessage, folderId);
|
||||
|
||||
if (edit) {
|
||||
await this.editItem(newCipher, tab);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.createNewCipher(addLoginMessage, folderId);
|
||||
const cipher = await this.cipherService.encrypt(newCipher);
|
||||
await this.cipherService.createWithServer(cipher);
|
||||
BrowserApi.tabSendMessageData(tab, "addedCipher");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
|
||||
const loginModel = new LoginView();
|
||||
const loginUri = new LoginUriView();
|
||||
loginUri.uri = queueMessage.uri;
|
||||
loginModel.uris = [loginUri];
|
||||
loginModel.username = queueMessage.username;
|
||||
loginModel.password = queueMessage.password;
|
||||
const model = new CipherView();
|
||||
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
|
||||
model.name = model.name.replace(/^www\./, "");
|
||||
model.type = CipherType.Login;
|
||||
model.login = loginModel;
|
||||
private async updatePassword(
|
||||
cipherView: CipherView,
|
||||
newPassword: string,
|
||||
edit: boolean,
|
||||
tab: chrome.tabs.Tab
|
||||
) {
|
||||
cipherView.login.password = newPassword;
|
||||
|
||||
if (!Utils.isNullOrWhitespace(folderId)) {
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
if (folders.some((x) => x.id === folderId)) {
|
||||
model.folderId = folderId;
|
||||
}
|
||||
if (edit) {
|
||||
await this.editItem(cipherView, tab);
|
||||
BrowserApi.tabSendMessage(tab, "editedCipher");
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = await this.cipherService.encrypt(model);
|
||||
await this.cipherService.createWithServer(cipher);
|
||||
const cipher = await this.cipherService.encrypt(cipherView);
|
||||
await this.cipherService.updateWithServer(cipher);
|
||||
// We've only updated the password, no need to broadcast editedCipher message
|
||||
return;
|
||||
}
|
||||
|
||||
private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) {
|
||||
await this.stateService.setAddEditCipherInfo({
|
||||
cipher: cipherView,
|
||||
collectionIds: cipherView.collectionIds,
|
||||
});
|
||||
|
||||
await BrowserApi.tabSendMessageData(senderTab, "openAddEditCipher", {
|
||||
cipherId: cipherView.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async folderExists(folderId: string) {
|
||||
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
return folders.some((x) => x.id === folderId);
|
||||
}
|
||||
|
||||
private async getDecryptedCipherById(cipherId: string) {
|
||||
@ -419,14 +422,6 @@ export default class NotificationBackground {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async updateCipher(cipher: CipherView, newPassword: string) {
|
||||
if (cipher != null && cipher.type === CipherType.Login) {
|
||||
cipher.login.password = newPassword;
|
||||
const newCipher = await this.cipherService.encrypt(cipher);
|
||||
await this.cipherService.updateWithServer(newCipher);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveNever(tab: chrome.tabs.Tab) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
@ -459,9 +454,9 @@ export default class NotificationBackground {
|
||||
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||
}
|
||||
|
||||
private async allowPersonalOwnership(): Promise<boolean> {
|
||||
return !(await firstValueFrom(
|
||||
private async removeIndividualVault(): Promise<boolean> {
|
||||
return await firstValueFrom(
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ const forwardCommands = [
|
||||
"addToLockedVaultPendingNotifications",
|
||||
"unlockCompleted",
|
||||
"addedCipher",
|
||||
"openAddEditCipher",
|
||||
];
|
||||
|
||||
chrome.runtime.onMessage.addListener((event) => {
|
||||
|
@ -502,6 +502,7 @@ document.addEventListener("DOMContentLoaded", (event) => {
|
||||
type,
|
||||
isVaultLocked: typeData.isVaultLocked,
|
||||
theme: typeData.theme,
|
||||
removeIndividualVault: typeData.removeIndividualVault,
|
||||
};
|
||||
const barQueryString = new URLSearchParams(barQueryParams).toString();
|
||||
const barPage = "notification/bar.html?" + barQueryString;
|
||||
|
@ -28,21 +28,27 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="templates" style="display: none">
|
||||
<div class="inner-wrapper" id="template-add">
|
||||
<div class="add-text"></div>
|
||||
<div class="add-buttons">
|
||||
<button type="button" class="never-save link"></button>
|
||||
<select class="select-folder" isVaultLocked="false"></select>
|
||||
<button type="button" class="add-save"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inner-wrapper" id="template-change">
|
||||
<div class="change-text"></div>
|
||||
<div class="change-buttons">
|
||||
<button type="button" class="change-save"></button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template id="template-add">
|
||||
<div class="inner-wrapper">
|
||||
<div id="add-text"></div>
|
||||
<div>
|
||||
<button type="button" id="never-save" class="link"></button>
|
||||
<select id="select-folder"></select>
|
||||
<button type="button" id="add-edit" class="secondary"></button>
|
||||
<button type="button" id="add-save" class="primary"></button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</template>
|
||||
|
||||
<template id="template-change">
|
||||
<div class="inner-wrapper">
|
||||
<div id="change-text"></div>
|
||||
<div>
|
||||
<button type="button" id="change-edit" class="secondary"></button>
|
||||
<button type="button" id="change-save" class="primary"></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</html>
|
||||
|
@ -1,168 +0,0 @@
|
||||
// eslint-disable-next-line
|
||||
require("./bar.scss");
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const theme = getQueryVariable("theme");
|
||||
document.documentElement.classList.add("theme_" + theme);
|
||||
|
||||
let i18n = {};
|
||||
let lang = window.navigator.language;
|
||||
|
||||
i18n.appName = chrome.i18n.getMessage("appName");
|
||||
i18n.close = chrome.i18n.getMessage("close");
|
||||
i18n.never = chrome.i18n.getMessage("never");
|
||||
i18n.folder = chrome.i18n.getMessage("folder");
|
||||
i18n.notificationAddSave = chrome.i18n.getMessage("notificationAddSave");
|
||||
i18n.notificationAddDesc = chrome.i18n.getMessage("notificationAddDesc");
|
||||
i18n.notificationChangeSave = chrome.i18n.getMessage("notificationChangeSave");
|
||||
i18n.notificationChangeDesc = chrome.i18n.getMessage("notificationChangeDesc");
|
||||
lang = chrome.i18n.getUILanguage(); // eslint-disable-line
|
||||
|
||||
// delay 50ms so that we get proper body dimensions
|
||||
setTimeout(load, 50);
|
||||
|
||||
function load() {
|
||||
const isVaultLocked = getQueryVariable("isVaultLocked") == "true";
|
||||
document.getElementById("logo").src = isVaultLocked
|
||||
? chrome.runtime.getURL("images/icon38_locked.png")
|
||||
: chrome.runtime.getURL("images/icon38.png");
|
||||
|
||||
document.getElementById("logo-link").title = i18n.appName;
|
||||
|
||||
var neverButton = document.querySelector("#template-add .never-save");
|
||||
neverButton.textContent = i18n.never;
|
||||
|
||||
var selectFolder = document.querySelector("#template-add .select-folder");
|
||||
selectFolder.setAttribute("aria-label", i18n.folder);
|
||||
selectFolder.setAttribute("isVaultLocked", isVaultLocked.toString());
|
||||
|
||||
var addButton = document.querySelector("#template-add .add-save");
|
||||
addButton.textContent = i18n.notificationAddSave;
|
||||
|
||||
var changeButton = document.querySelector("#template-change .change-save");
|
||||
changeButton.textContent = i18n.notificationChangeSave;
|
||||
|
||||
var closeButton = document.getElementById("close-button");
|
||||
closeButton.title = i18n.close;
|
||||
closeButton.setAttribute("aria-label", i18n.close);
|
||||
|
||||
document.querySelector("#template-add .add-text").textContent = i18n.notificationAddDesc;
|
||||
document.querySelector("#template-change .change-text").textContent =
|
||||
i18n.notificationChangeDesc;
|
||||
|
||||
if (getQueryVariable("type") === "add") {
|
||||
handleTypeAdd(isVaultLocked);
|
||||
} else if (getQueryVariable("type") === "change") {
|
||||
handleTypeChange();
|
||||
}
|
||||
|
||||
closeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgCloseNotificationBar",
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("resize", adjustHeight);
|
||||
adjustHeight();
|
||||
}
|
||||
|
||||
function getQueryVariable(variable) {
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split("=");
|
||||
if (pair[0] === variable) {
|
||||
return pair[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleTypeAdd(isVaultLocked) {
|
||||
setContent(document.getElementById("template-add"));
|
||||
|
||||
var addButton = document.querySelector("#template-add-clone .add-save"), // eslint-disable-line
|
||||
neverButton = document.querySelector("#template-add-clone .never-save"); // eslint-disable-line
|
||||
|
||||
addButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const folderId = document.querySelector("#template-add-clone .select-folder").value;
|
||||
|
||||
const bgAddSaveMessage = {
|
||||
command: "bgAddSave",
|
||||
folder: folderId,
|
||||
};
|
||||
sendPlatformMessage(bgAddSaveMessage);
|
||||
});
|
||||
|
||||
neverButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgNeverSave",
|
||||
});
|
||||
});
|
||||
|
||||
if (!isVaultLocked) {
|
||||
const responseFoldersCommand = "notificationBarGetFoldersList";
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (msg.command === responseFoldersCommand && msg.data) {
|
||||
fillSelectorWithFolders(msg.data.folders);
|
||||
}
|
||||
});
|
||||
sendPlatformMessage({
|
||||
command: "bgGetDataForTab",
|
||||
responseCommand: responseFoldersCommand,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleTypeChange() {
|
||||
setContent(document.getElementById("template-change"));
|
||||
var changeButton = document.querySelector("#template-change-clone .change-save"); // eslint-disable-line
|
||||
changeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const bgChangeSaveMessage = {
|
||||
command: "bgChangeSave",
|
||||
};
|
||||
sendPlatformMessage(bgChangeSaveMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function setContent(element) {
|
||||
const content = document.getElementById("content");
|
||||
while (content.firstChild) {
|
||||
content.removeChild(content.firstChild);
|
||||
}
|
||||
|
||||
var newElement = element.cloneNode(true);
|
||||
newElement.id = newElement.id + "-clone";
|
||||
content.appendChild(newElement);
|
||||
}
|
||||
|
||||
function sendPlatformMessage(msg) {
|
||||
chrome.runtime.sendMessage(msg);
|
||||
}
|
||||
|
||||
function fillSelectorWithFolders(folders) {
|
||||
const select = document.querySelector("#template-add-clone .select-folder");
|
||||
select.appendChild(new Option(chrome.i18n.getMessage("selectFolder"), null, true));
|
||||
folders.forEach((folder) => {
|
||||
//Select "No Folder" (id=null) folder by default
|
||||
select.appendChild(new Option(folder.name, folder.id || "", false));
|
||||
});
|
||||
}
|
||||
|
||||
function adjustHeight() {
|
||||
sendPlatformMessage({
|
||||
command: "bgAdjustNotificationBar",
|
||||
data: {
|
||||
height: document.querySelector("body").scrollHeight,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
@ -80,7 +80,7 @@ button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:not(.neutral):not(.link) {
|
||||
button.primary:not(.neutral) {
|
||||
@include themify($themes) {
|
||||
background-color: themed("primaryColor");
|
||||
color: themed("textContrast");
|
||||
@ -95,6 +95,21 @@ button:not(.neutral):not(.link) {
|
||||
}
|
||||
}
|
||||
|
||||
button.secondary:not(.neutral) {
|
||||
@include themify($themes) {
|
||||
background-color: themed("backgroundColor");
|
||||
color: themed("mutedTextColor");
|
||||
border-color: themed("mutedTextColor");
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include themify($themes) {
|
||||
background-color: darken(themed("backgroundColor"), 1.5%);
|
||||
color: darken(themed("mutedTextColor"), 6%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.link,
|
||||
button.neutral {
|
||||
@include themify($themes) {
|
||||
@ -130,12 +145,8 @@ button {
|
||||
font-family: $font-family-sans-serif;
|
||||
}
|
||||
|
||||
.select-folder[isVaultLocked="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.select-folder {
|
||||
#select-folder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
218
apps/browser/src/autofill/notification/bar.ts
Normal file
218
apps/browser/src/autofill/notification/bar.ts
Normal file
@ -0,0 +1,218 @@
|
||||
import type { Jsonify } from "type-fest";
|
||||
|
||||
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
require("./bar.scss");
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// delay 50ms so that we get proper body dimensions
|
||||
setTimeout(load, 50);
|
||||
});
|
||||
|
||||
function load() {
|
||||
const theme = getQueryVariable("theme");
|
||||
document.documentElement.classList.add("theme_" + theme);
|
||||
|
||||
const isVaultLocked = getQueryVariable("isVaultLocked") == "true";
|
||||
(document.getElementById("logo") as HTMLImageElement).src = isVaultLocked
|
||||
? chrome.runtime.getURL("images/icon38_locked.png")
|
||||
: chrome.runtime.getURL("images/icon38.png");
|
||||
|
||||
const i18n = {
|
||||
appName: chrome.i18n.getMessage("appName"),
|
||||
close: chrome.i18n.getMessage("close"),
|
||||
never: chrome.i18n.getMessage("never"),
|
||||
folder: chrome.i18n.getMessage("folder"),
|
||||
notificationAddSave: chrome.i18n.getMessage("notificationAddSave"),
|
||||
notificationAddDesc: chrome.i18n.getMessage("notificationAddDesc"),
|
||||
notificationEdit: chrome.i18n.getMessage("edit"),
|
||||
notificationChangeSave: chrome.i18n.getMessage("notificationChangeSave"),
|
||||
notificationChangeDesc: chrome.i18n.getMessage("notificationChangeDesc"),
|
||||
};
|
||||
|
||||
document.getElementById("logo-link").title = i18n.appName;
|
||||
|
||||
// i18n for "Add" template
|
||||
const addTemplate = document.getElementById("template-add") as HTMLTemplateElement;
|
||||
|
||||
const neverButton = addTemplate.content.getElementById("never-save");
|
||||
neverButton.textContent = i18n.never;
|
||||
|
||||
const selectFolder = addTemplate.content.getElementById("select-folder");
|
||||
selectFolder.hidden = isVaultLocked || removeIndividualVault();
|
||||
selectFolder.setAttribute("aria-label", i18n.folder);
|
||||
|
||||
const addButton = addTemplate.content.getElementById("add-save");
|
||||
addButton.textContent = i18n.notificationAddSave;
|
||||
|
||||
const addEditButton = addTemplate.content.getElementById("add-edit");
|
||||
// If Remove Individual Vault policy applies, "Add" opens the edit tab, so we hide the Edit button
|
||||
addEditButton.hidden = removeIndividualVault();
|
||||
addEditButton.textContent = i18n.notificationEdit;
|
||||
|
||||
addTemplate.content.getElementById("add-text").textContent = i18n.notificationAddDesc;
|
||||
|
||||
// i18n for "Change" (update password) template
|
||||
const changeTemplate = document.getElementById("template-change") as HTMLTemplateElement;
|
||||
|
||||
const changeButton = changeTemplate.content.getElementById("change-save");
|
||||
changeButton.textContent = i18n.notificationChangeSave;
|
||||
|
||||
const changeEditButton = changeTemplate.content.getElementById("change-edit");
|
||||
changeEditButton.textContent = i18n.notificationEdit;
|
||||
|
||||
changeTemplate.content.getElementById("change-text").textContent = i18n.notificationChangeDesc;
|
||||
|
||||
// i18n for body content
|
||||
const closeButton = document.getElementById("close-button");
|
||||
closeButton.title = i18n.close;
|
||||
|
||||
if (getQueryVariable("type") === "add") {
|
||||
handleTypeAdd();
|
||||
} else if (getQueryVariable("type") === "change") {
|
||||
handleTypeChange();
|
||||
}
|
||||
|
||||
closeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgCloseNotificationBar",
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("resize", adjustHeight);
|
||||
adjustHeight();
|
||||
}
|
||||
|
||||
function getQueryVariable(variable: string) {
|
||||
const query = window.location.search.substring(1);
|
||||
const vars = query.split("&");
|
||||
|
||||
for (let i = 0; i < vars.length; i++) {
|
||||
const pair = vars[i].split("=");
|
||||
if (pair[0] === variable) {
|
||||
return pair[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleTypeAdd() {
|
||||
setContent(document.getElementById("template-add") as HTMLTemplateElement);
|
||||
|
||||
const addButton = document.getElementById("add-save");
|
||||
addButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// If Remove Individual Vault policy applies, "Add" opens the edit tab
|
||||
sendPlatformMessage({
|
||||
command: "bgAddSave",
|
||||
folder: getSelectedFolder(),
|
||||
edit: removeIndividualVault(),
|
||||
});
|
||||
});
|
||||
|
||||
if (removeIndividualVault()) {
|
||||
// Everything past this point is only required if user has an individual vault
|
||||
return;
|
||||
}
|
||||
|
||||
const editButton = document.getElementById("add-edit");
|
||||
editButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
sendPlatformMessage({
|
||||
command: "bgAddSave",
|
||||
folder: getSelectedFolder(),
|
||||
edit: true,
|
||||
});
|
||||
});
|
||||
|
||||
const neverButton = document.getElementById("never-save");
|
||||
neverButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgNeverSave",
|
||||
});
|
||||
});
|
||||
|
||||
loadFolderSelector();
|
||||
}
|
||||
|
||||
function handleTypeChange() {
|
||||
setContent(document.getElementById("template-change") as HTMLTemplateElement);
|
||||
const changeButton = document.getElementById("change-save");
|
||||
changeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
sendPlatformMessage({
|
||||
command: "bgChangeSave",
|
||||
edit: false,
|
||||
});
|
||||
});
|
||||
|
||||
const editButton = document.getElementById("change-edit");
|
||||
editButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
sendPlatformMessage({
|
||||
command: "bgChangeSave",
|
||||
edit: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setContent(template: HTMLTemplateElement) {
|
||||
const content = document.getElementById("content");
|
||||
while (content.firstChild) {
|
||||
content.removeChild(content.firstChild);
|
||||
}
|
||||
|
||||
const newElement = template.content.cloneNode(true) as HTMLElement;
|
||||
content.appendChild(newElement);
|
||||
}
|
||||
|
||||
function sendPlatformMessage(msg: Record<string, unknown>) {
|
||||
chrome.runtime.sendMessage(msg);
|
||||
}
|
||||
|
||||
function loadFolderSelector() {
|
||||
const responseFoldersCommand = "notificationBarGetFoldersList";
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (msg.command !== responseFoldersCommand || msg.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = msg.data.folders as Jsonify<FolderView[]>;
|
||||
const select = document.getElementById("select-folder");
|
||||
select.appendChild(new Option(chrome.i18n.getMessage("selectFolder"), null, true));
|
||||
folders.forEach((folder) => {
|
||||
// Select "No Folder" (id=null) folder by default
|
||||
select.appendChild(new Option(folder.name, folder.id || "", false));
|
||||
});
|
||||
});
|
||||
|
||||
sendPlatformMessage({
|
||||
command: "bgGetDataForTab",
|
||||
responseCommand: responseFoldersCommand,
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectedFolder(): string {
|
||||
return (document.getElementById("select-folder") as HTMLSelectElement).value;
|
||||
}
|
||||
|
||||
function removeIndividualVault(): boolean {
|
||||
return getQueryVariable("removeIndividualVault") == "true";
|
||||
}
|
||||
|
||||
function adjustHeight() {
|
||||
sendPlatformMessage({
|
||||
command: "bgAdjustNotificationBar",
|
||||
data: {
|
||||
height: document.querySelector("body").scrollHeight,
|
||||
},
|
||||
});
|
||||
}
|
@ -10,6 +10,7 @@ $brand-primary: #175ddc;
|
||||
|
||||
$background-color: #f0f0f0;
|
||||
|
||||
$solarizedDarkBase0: #839496;
|
||||
$solarizedDarkBase03: #002b36;
|
||||
$solarizedDarkBase02: #073642;
|
||||
$solarizedDarkBase01: #586e75;
|
||||
@ -20,6 +21,7 @@ $solarizedDarkGreen: #859900;
|
||||
$themes: (
|
||||
light: (
|
||||
textColor: $text-color,
|
||||
mutedTextColor: #6d757e,
|
||||
backgroundColor: $background-color,
|
||||
primaryColor: $brand-primary,
|
||||
buttonPrimaryColor: $brand-primary,
|
||||
@ -29,6 +31,7 @@ $themes: (
|
||||
),
|
||||
dark: (
|
||||
textColor: #ffffff,
|
||||
mutedTextColor: #bac0ce,
|
||||
backgroundColor: #2f343d,
|
||||
buttonPrimaryColor: #6f9df1,
|
||||
primaryColor: #6f9df1,
|
||||
@ -38,6 +41,7 @@ $themes: (
|
||||
),
|
||||
nord: (
|
||||
textColor: $nord5,
|
||||
mutedTextColor: $nord4,
|
||||
backgroundColor: $nord1,
|
||||
buttonPrimaryColor: $nord8,
|
||||
primaryColor: $nord9,
|
||||
@ -47,6 +51,8 @@ $themes: (
|
||||
),
|
||||
solarizedDark: (
|
||||
textColor: $solarizedDarkBase2,
|
||||
// Muted uses main text color to avoid contrast issues
|
||||
mutedTextColor: $solarizedDarkBase2,
|
||||
backgroundColor: $solarizedDarkBase03,
|
||||
buttonPrimaryColor: $solarizedDarkCyan,
|
||||
primaryColor: $solarizedDarkGreen,
|
||||
|
@ -96,7 +96,6 @@ import { SafariApp } from "../browser/safariApp";
|
||||
import { flagEnabled } from "../flags";
|
||||
import { UpdateBadge } from "../listeners/update-badge";
|
||||
import { Account } from "../models/account";
|
||||
import { PopupUtilsService } from "../popup/services/popup-utils.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../services/abstractions/browser-state.service";
|
||||
import { BrowserEnvironmentService } from "../services/browser-environment.service";
|
||||
import { BrowserI18nService } from "../services/browser-i18n.service";
|
||||
@ -157,7 +156,6 @@ export default class MainBackground {
|
||||
eventCollectionService: EventCollectionServiceAbstraction;
|
||||
eventUploadService: EventUploadServiceAbstraction;
|
||||
policyService: InternalPolicyServiceAbstraction;
|
||||
popupUtilsService: PopupUtilsService;
|
||||
sendService: SendServiceAbstraction;
|
||||
fileUploadService: FileUploadServiceAbstraction;
|
||||
organizationService: InternalOrganizationServiceAbstraction;
|
||||
@ -358,7 +356,7 @@ export default class MainBackground {
|
||||
// AuthService should send the messages to the background not popup.
|
||||
send = (subscriber: string, arg: any = {}) => {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
that.runtimeBackground.processMessage(message, that, null);
|
||||
that.runtimeBackground.processMessage(message, that as any, null);
|
||||
};
|
||||
})();
|
||||
this.authService = new AuthService(
|
||||
@ -463,7 +461,6 @@ export default class MainBackground {
|
||||
this.authService,
|
||||
this.messagingService
|
||||
);
|
||||
this.popupUtilsService = new PopupUtilsService(isPrivateMode);
|
||||
|
||||
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
|
||||
|
||||
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
|
||||
type: NotificationQueueMessageType.ChangePassword;
|
||||
cipherId: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
@ -1,7 +1,33 @@
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
|
||||
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
|
||||
|
||||
export default class AddLoginQueueMessage extends NotificationQueueMessage {
|
||||
type: NotificationQueueMessageType.AddLogin;
|
||||
username: string;
|
||||
password: string;
|
||||
uri: string;
|
||||
|
||||
static toCipherView(message: AddLoginQueueMessage, folderId?: string): CipherView {
|
||||
const uriView = new LoginUriView();
|
||||
uriView.uri = message.uri;
|
||||
|
||||
const loginView = new LoginView();
|
||||
loginView.uris = [uriView];
|
||||
loginView.username = message.username;
|
||||
loginView.password = message.password;
|
||||
|
||||
const cipherView = new CipherView();
|
||||
cipherView.name = (Utils.getHostname(message.uri) || message.domain).replace(/^www\./, "");
|
||||
cipherView.folderId = folderId;
|
||||
cipherView.type = CipherType.Login;
|
||||
cipherView.login = loginView;
|
||||
|
||||
return cipherView;
|
||||
}
|
||||
}
|
||||
|
@ -56,19 +56,15 @@ export default class RuntimeBackground {
|
||||
}
|
||||
}
|
||||
|
||||
async processMessage(msg: any, sender: any, sendResponse: any) {
|
||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) {
|
||||
switch (msg.command) {
|
||||
case "loggedIn":
|
||||
case "unlocked": {
|
||||
let item: LockedVaultPendingNotificationsItem;
|
||||
|
||||
if (this.lockedVaultPendingNotifications?.length > 0) {
|
||||
await BrowserApi.closeLoginTab();
|
||||
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
if (item.commandToRetry.sender?.tab?.id) {
|
||||
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
|
||||
}
|
||||
BrowserApi.closeBitwardenExtensionTab();
|
||||
}
|
||||
|
||||
await this.main.refreshBadge();
|
||||
@ -104,7 +100,21 @@ export default class RuntimeBackground {
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case "promptForLogin":
|
||||
await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
|
||||
BrowserApi.openBitwardenExtensionTab("popup/index.html", true, sender.tab);
|
||||
break;
|
||||
case "openAddEditCipher": {
|
||||
const addEditCipherUrl =
|
||||
msg.data?.cipherId == null
|
||||
? "popup/index.html#/edit-cipher"
|
||||
: "popup/index.html#/edit-cipher?cipherId=" + msg.data.cipherId;
|
||||
|
||||
BrowserApi.openBitwardenExtensionTab(addEditCipherUrl, true, sender.tab);
|
||||
break;
|
||||
}
|
||||
case "closeTab":
|
||||
setTimeout(() => {
|
||||
BrowserApi.closeBitwardenExtensionTab();
|
||||
}, msg.delay ?? 0);
|
||||
break;
|
||||
case "showDialogResolve":
|
||||
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
||||
@ -183,11 +193,7 @@ export default class RuntimeBackground {
|
||||
const params =
|
||||
`webAuthnResponse=${encodeURIComponent(msg.data)};` +
|
||||
`remember=${encodeURIComponent(msg.remember)}`;
|
||||
BrowserApi.createNewTab(
|
||||
`popup/index.html?uilocation=popout#/2fa;${params}`,
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
BrowserApi.openBitwardenExtensionTab(`popup/index.html#/2fa;${params}`, false);
|
||||
break;
|
||||
}
|
||||
case "reloadPopup":
|
||||
|
@ -127,8 +127,44 @@ export class BrowserApi {
|
||||
return Promise.resolve(chrome.extension.getViews({ type: "popup" }).length > 0);
|
||||
}
|
||||
|
||||
static createNewTab(url: string, extensionPage = false, active = true) {
|
||||
chrome.tabs.create({ url: url, active: active });
|
||||
static createNewTab(url: string, active = true, openerTab?: chrome.tabs.Tab) {
|
||||
chrome.tabs.create({ url: url, active: active, openerTabId: openerTab?.id });
|
||||
}
|
||||
|
||||
static openBitwardenExtensionTab(
|
||||
relativeUrl: string,
|
||||
active = true,
|
||||
openerTab?: chrome.tabs.Tab
|
||||
) {
|
||||
if (relativeUrl.includes("uilocation=tab")) {
|
||||
this.createNewTab(relativeUrl, active, openerTab);
|
||||
return;
|
||||
}
|
||||
|
||||
const fullUrl = chrome.extension.getURL(relativeUrl);
|
||||
const parsedUrl = new URL(fullUrl);
|
||||
parsedUrl.searchParams.set("uilocation", "tab");
|
||||
this.createNewTab(parsedUrl.toString(), active, openerTab);
|
||||
}
|
||||
|
||||
static async closeBitwardenExtensionTab() {
|
||||
const tabs = await BrowserApi.tabsQuery({
|
||||
active: true,
|
||||
title: "Bitwarden",
|
||||
windowType: "normal",
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (tabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabToClose = tabs[tabs.length - 1];
|
||||
chrome.tabs.remove(tabToClose.id);
|
||||
|
||||
if (tabToClose.openerTabId) {
|
||||
this.focusTab(tabToClose.openerTabId);
|
||||
}
|
||||
}
|
||||
|
||||
static messageListener(
|
||||
@ -147,23 +183,7 @@ export class BrowserApi {
|
||||
return chrome.runtime.sendMessage(message);
|
||||
}
|
||||
|
||||
static async closeLoginTab() {
|
||||
const tabs = await BrowserApi.tabsQuery({
|
||||
active: true,
|
||||
title: "Bitwarden",
|
||||
windowType: "normal",
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (tabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabToClose = tabs[tabs.length - 1].id;
|
||||
chrome.tabs.remove(tabToClose);
|
||||
}
|
||||
|
||||
static async focusSpecifiedTab(tabId: number) {
|
||||
static async focusTab(tabId: number) {
|
||||
chrome.tabs.update(tabId, { active: true, highlighted: true });
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,14 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { UsernameGenerationService } from "@bitwarden/common/abstractions/usernameGeneration.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info";
|
||||
|
||||
@Component({
|
||||
selector: "app-generator",
|
||||
templateUrl: "generator.component.html",
|
||||
})
|
||||
export class GeneratorComponent extends BaseGeneratorComponent {
|
||||
private addEditCipherInfo: any;
|
||||
private addEditCipherInfo: AddEditCipherInfo;
|
||||
private cipherState: CipherView;
|
||||
|
||||
constructor(
|
||||
|
@ -408,28 +408,10 @@ app-root {
|
||||
}
|
||||
}
|
||||
|
||||
// Adds padding on each side of the content if opened in a tab
|
||||
@media only screen and (min-width: 601px) {
|
||||
app-login header {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
|
||||
app-login main {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
|
||||
app-two-factor header {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
|
||||
app-two-factor main {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
|
||||
app-lock header {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
|
||||
app-lock main {
|
||||
header,
|
||||
main {
|
||||
padding: 0 calc((100% - 500px) / 2);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { fromEvent, Subscription } from "rxjs";
|
||||
|
||||
import { BrowserApi } from "../../browser/browserApi";
|
||||
|
||||
@Injectable()
|
||||
export class PopupUtilsService {
|
||||
private unloadSubscription: Subscription;
|
||||
|
||||
constructor(private privateMode: boolean = false) {}
|
||||
|
||||
inSidebar(win: Window): boolean {
|
||||
@ -80,4 +83,36 @@ export class PopupUtilsService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a pop-up warning before the user exits the window/tab, or navigates away.
|
||||
* This warns the user that they may lose unsaved data if they leave the page.
|
||||
* (Note: navigating within the Angular app will not trigger it because it's an SPA.)
|
||||
* Make sure you call `disableTabCloseWarning` when it is no longer relevant.
|
||||
*/
|
||||
enableCloseTabWarning() {
|
||||
this.disableCloseTabWarning();
|
||||
|
||||
this.unloadSubscription = fromEvent(window, "beforeunload").subscribe(
|
||||
(e: BeforeUnloadEvent) => {
|
||||
// Recommended method but not widely supported
|
||||
e.preventDefault();
|
||||
|
||||
// Modern browsers do not display this message, it just needs to be a non-nullish value
|
||||
// Exact wording is determined by the browser
|
||||
const confirmationMessage = "";
|
||||
|
||||
// Older methods with better support
|
||||
e.returnValue = confirmationMessage;
|
||||
return confirmationMessage;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the warning enabled by enableCloseTabWarning.
|
||||
*/
|
||||
disableCloseTabWarning() {
|
||||
this.unloadSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
@ -130,15 +130,11 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
: tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url);
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (!this.editMode) {
|
||||
if (this.cipher.name != null && this.cipher.name !== "") {
|
||||
document.getElementById("loginUsername").focus();
|
||||
} else {
|
||||
document.getElementById("name").focus();
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
this.setFocus();
|
||||
|
||||
if (this.popupUtilsService.inTab(window)) {
|
||||
this.popupUtilsService.enableCloseTabWarning();
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
@ -149,16 +145,23 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
if (await super.submit()) {
|
||||
if (this.cloneMode) {
|
||||
this.router.navigate(["/tabs/vault"]);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
const success = await super.submit();
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.popupUtilsService.inTab(window)) {
|
||||
this.popupUtilsService.disableCloseTabWarning();
|
||||
this.messagingService.send("closeTab", { delay: 1000 });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (this.cloneMode) {
|
||||
this.router.navigate(["/tabs/vault"]);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
attachments() {
|
||||
@ -184,6 +187,12 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
|
||||
cancel() {
|
||||
super.cancel();
|
||||
|
||||
if (this.popupUtilsService.inTab(window)) {
|
||||
this.messagingService.send("closeTab");
|
||||
return;
|
||||
}
|
||||
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
@ -235,4 +244,18 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
: this.collections.filter((c) => (c as any).checked).map((c) => c.id),
|
||||
});
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
window.setTimeout(() => {
|
||||
if (this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cipher.name != null && this.cipher.name !== "") {
|
||||
document.getElementById("loginUsername").focus();
|
||||
} else {
|
||||
document.getElementById("name").focus();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ const mainConfig = {
|
||||
"content/notificationBar": "./src/autofill/content/notification-bar.ts",
|
||||
"content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts",
|
||||
"content/message_handler": "./src/autofill/content/message_handler.ts",
|
||||
"notification/bar": "./src/autofill/notification/bar.js",
|
||||
"notification/bar": "./src/autofill/notification/bar.ts",
|
||||
"encrypt-worker": "../../libs/common/src/services/cryptography/encrypt.worker.ts",
|
||||
},
|
||||
optimization: {
|
||||
|
@ -202,7 +202,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
if (!this.allowPersonal) {
|
||||
this.organizationId = this.ownershipOptions[0].value;
|
||||
this.organizationId = this.defaultOwnerId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,12 +220,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.title = this.i18nService.t("addItem");
|
||||
}
|
||||
|
||||
const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo();
|
||||
if (addEditCipherInfo != null) {
|
||||
this.cipher = addEditCipherInfo.cipher;
|
||||
this.collectionIds = addEditCipherInfo.collectionIds;
|
||||
}
|
||||
await this.stateService.setAddEditCipherInfo(null);
|
||||
const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo();
|
||||
|
||||
if (this.cipher == null) {
|
||||
if (this.editMode) {
|
||||
@ -255,7 +250,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cipher != null && (!this.editMode || addEditCipherInfo != null || this.cloneMode)) {
|
||||
if (this.cipher != null && (!this.editMode || loadedAddEditCipherInfo || this.cloneMode)) {
|
||||
await this.organizationChanged();
|
||||
if (
|
||||
this.collectionIds != null &&
|
||||
@ -618,4 +613,27 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
protected restoreCipher() {
|
||||
return this.cipherService.restoreWithServer(this.cipher.id);
|
||||
}
|
||||
|
||||
get defaultOwnerId(): string | null {
|
||||
return this.ownershipOptions[0].value;
|
||||
}
|
||||
|
||||
async loadAddEditCipherInfo(): Promise<boolean> {
|
||||
const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo();
|
||||
const loadedSavedInfo = addEditCipherInfo != null;
|
||||
|
||||
if (loadedSavedInfo) {
|
||||
this.cipher = addEditCipherInfo.cipher;
|
||||
this.collectionIds = addEditCipherInfo.collectionIds;
|
||||
|
||||
if (!this.editMode && !this.allowPersonal && this.cipher.organizationId == null) {
|
||||
// This is a new cipher and personal ownership isn't allowed, so we need to set the default owner
|
||||
this.cipher.organizationId = this.defaultOwnerId;
|
||||
}
|
||||
}
|
||||
|
||||
await this.stateService.setAddEditCipherInfo(null);
|
||||
|
||||
return loadedSavedInfo;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import { CipherData } from "../vault/models/data/cipher.data";
|
||||
import { FolderData } from "../vault/models/data/folder.data";
|
||||
import { LocalData } from "../vault/models/data/local.data";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "../vault/types/add-edit-cipher-info";
|
||||
|
||||
export abstract class StateService<T extends Account = Account> {
|
||||
accounts$: Observable<{ [userId: string]: T }>;
|
||||
@ -39,8 +40,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||
|
||||
getAccessToken: (options?: StorageOptions) => Promise<string>;
|
||||
setAccessToken: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getAddEditCipherInfo: (options?: StorageOptions) => Promise<any>;
|
||||
setAddEditCipherInfo: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>;
|
||||
setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>;
|
||||
getAlwaysShowDock: (options?: StorageOptions) => Promise<boolean>;
|
||||
setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getApiKeyClientId: (options?: StorageOptions) => Promise<string>;
|
||||
|
@ -45,6 +45,7 @@ import { CipherData } from "../vault/models/data/cipher.data";
|
||||
import { FolderData } from "../vault/models/data/folder.data";
|
||||
import { LocalData } from "../vault/models/data/local.data";
|
||||
import { CipherView } from "../vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "../vault/types/add-edit-cipher-info";
|
||||
|
||||
const keys = {
|
||||
state: "state",
|
||||
@ -222,13 +223,13 @@ export class StateService<
|
||||
await this.saveAccount(account, options);
|
||||
}
|
||||
|
||||
async getAddEditCipherInfo(options?: StorageOptions): Promise<any> {
|
||||
async getAddEditCipherInfo(options?: StorageOptions): Promise<AddEditCipherInfo> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.data?.addEditCipherInfo;
|
||||
}
|
||||
|
||||
async setAddEditCipherInfo(value: any, options?: StorageOptions): Promise<void> {
|
||||
async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
|
12
libs/common/src/vault/types/add-edit-cipher-info.ts
Normal file
12
libs/common/src/vault/types/add-edit-cipher-info.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
|
||||
/**
|
||||
* Used to temporarily save the state of the AddEditComponent, e.g. when the user navigates away to the Generator page.
|
||||
* @property cipher The unsaved item being added or edited
|
||||
* @property collectionIds The collections that are selected for the item (currently these are not mapped back to
|
||||
* cipher.collectionIds until the item is saved)
|
||||
*/
|
||||
export type AddEditCipherInfo = {
|
||||
cipher: CipherView;
|
||||
collectionIds?: string[];
|
||||
};
|
Loading…
Reference in New Issue
Block a user