1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

Merged branch with master and fixed conflicts

This commit is contained in:
gbubemismith
2023-08-23 22:45:39 -04:00
628 changed files with 16595 additions and 7728 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
"version": "2023.7.1",
"version": "2023.8.0",
"scripts": {
"build": "webpack",
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "استعادة العنصر"
},
"restoreItemConfirmation": {
"message": "هل أنت متأكد من أنك تريد استعادة هذا العنصر؟"
},
"restoredItem": {
"message": "تم استعادة العنصر"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Elementi bərpa et"
},
"restoreItemConfirmation": {
"message": "Elementi bərpa etmək istədiyinizə əminsiniz?"
},
"restoredItem": {
"message": "Element bərpa edildi"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Аднавіць элемент"
},
"restoreItemConfirmation": {
"message": "Вы сапраўды хочаце аднавіць гэты элемент?"
},
"restoredItem": {
"message": "Элемент адноўлены"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Възстановяване на запис"
},
"restoreItemConfirmation": {
"message": "Сигурни ли сте, че искате да възстановите записа?"
},
"restoredItem": {
"message": "Записът е възстановен"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "বস্তু পুনরুদ্ধার"
},
"restoreItemConfirmation": {
"message": "আপনি কি নিশ্চিত যে আপনি এই বস্তুটি পুনরুদ্ধার করতে চান?"
},
"restoredItem": {
"message": "বস্তু পুনরুদ্ধারকৃত"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restaura l'element"
},
"restoreItemConfirmation": {
"message": "Esteu segur que voleu restaurar aquest element?"
},
"restoredItem": {
"message": "Element restaurat"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Obnovit položku"
},
"restoreItemConfirmation": {
"message": "Opravdu chcete tuto položku obnovit?"
},
"restoredItem": {
"message": "Položka byla obnovena"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Adfer yr eitem"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Gendan element"
},
"restoreItemConfirmation": {
"message": "Er du sikker på, at du vil gendanne dette element?"
},
"restoredItem": {
"message": "Element gendannet"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Eintrag wiederherstellen"
},
"restoreItemConfirmation": {
"message": "Soll dieser Eintrag wirklich wiederhergestellt werden?"
},
"restoredItem": {
"message": "Eintrag wiederhergestellt"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Ανάκτηση Στοιχείου"
},
"restoreItemConfirmation": {
"message": "Είστε βέβαιοι ότι θέλετε να ανακτήσετε αυτό το στοιχείο;"
},
"restoredItem": {
"message": "Στοιχείο που έχει Ανακτηθεί"
},

View File

@@ -338,6 +338,9 @@
"other": {
"message": "Other"
},
"unlockMethodNeededToChangeTimeoutActionDesc": {
"message": "Set up an unlock method to change your vault timeout action."
},
"rateExtension": {
"message": "Rate the extension"
},
@@ -1449,9 +1452,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},
@@ -1608,6 +1608,12 @@
"biometricsNotSupportedDesc": {
"message": "Browser biometrics is not supported on this device."
},
"biometricsFailedTitle": {
"message": "Biometrics failed"
},
"biometricsFailedDesc": {
"message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support."
},
"nativeMessaginPermissionErrorTitle": {
"message": "Permission not provided"
},
@@ -2158,8 +2164,8 @@
"notificationSentDevice": {
"message": "A notification has been sent to your device."
},
"logInInitiated": {
"message": "Log in initiated"
"loginInitiated": {
"message": "Login initiated"
},
"exposedMasterPassword": {
"message": "Exposed Master Password"
@@ -2245,6 +2251,31 @@
"opensInANewWindow": {
"message": "Opens in a new window"
},
"deviceApprovalRequired": {
"message": "Device approval required. Select an approval option below:"
},
"rememberThisDevice": {
"message": "Remember this device"
},
"uncheckIfPublicDevice": {
"message": "Uncheck if using a public device"
},
"approveFromYourOtherDevice": {
"message": "Approve from your other device"
},
"requestAdminApproval": {
"message": "Request admin approval"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
},
"ssoIdentifierRequired": {
"message": "Organization SSO identifier is required."
},
"eu": {
"message": "EU",
"description": "European Union"
},
"usDomain": {
"message": "bitwarden.com"
},
@@ -2260,6 +2291,134 @@
"display": {
"message": "Display"
},
"accountSuccessfullyCreated": {
"message": "Account successfully created!"
},
"adminApprovalRequested": {
"message": "Admin approval requested"
},
"adminApprovalRequestSentToAdmins": {
"message": "Your request has been sent to your admin."
},
"youWillBeNotifiedOnceApproved": {
"message": "You will be notified once approved."
},
"troubleLoggingIn": {
"message": "Trouble logging in?"
},
"loginApproved": {
"message": "Login approved"
},
"userEmailMissing": {
"message": "User email missing"
},
"deviceTrusted": {
"message": "Device trusted"
},
"inputRequired": {
"message": "Input is required."
},
"required": {
"message": "required"
},
"search": {
"message": "Search"
},
"inputMinLength": {
"message": "Input must be at least $COUNT$ characters long.",
"placeholders": {
"count": {
"content": "$1",
"example": "8"
}
}
},
"inputMaxLength": {
"message": "Input must not exceed $COUNT$ characters in length.",
"placeholders": {
"count": {
"content": "$1",
"example": "20"
}
}
},
"inputForbiddenCharacters": {
"message": "The following characters are not allowed: $CHARACTERS$",
"placeholders": {
"characters": {
"content": "$1",
"example": "@, #, $, %"
}
}
},
"inputMinValue": {
"message": "Input value must be at least $MIN$.",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
}
}
},
"inputMaxValue": {
"message": "Input value must not exceed $MAX$.",
"placeholders": {
"max": {
"content": "$1",
"example": "100"
}
}
},
"multipleInputEmails": {
"message": "1 or more emails are invalid"
},
"inputTrimValidator": {
"message": "Input must not contain only whitespace.",
"description": "Notification to inform the user that a form's input can't contain only whitespace."
},
"inputEmail": {
"message": "Input is not an email address."
},
"fieldsNeedAttention": {
"message": "$COUNT$ field(s) above need your attention.",
"placeholders": {
"count": {
"content": "$1",
"example": "4"
}
}
},
"selectPlaceholder": {
"message": "-- Select --"
},
"multiSelectPlaceholder": {
"message": "-- Type to filter --"
},
"multiSelectLoading": {
"message": "Retrieving options..."
},
"multiSelectNotFound": {
"message": "No items found"
},
"multiSelectClearAll": {
"message": "Clear all"
},
"plusNMore": {
"message": "+ $QUANTITY$ more",
"placeholders": {
"quantity": {
"content": "$1",
"example": "5"
}
}
},
"submenu": {
"message": "Submenu"
},
"toggleCollapse": {
"message": "Toggle collapse",
"description": "Toggling an expand/collapse state."
},
"loginPasskey": {
"message": "This login uses a passkey"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Restored item"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restaurar elemento"
},
"restoreItemConfirmation": {
"message": "¿Estás seguro de que quieres restaurar este elemento?"
},
"restoredItem": {
"message": "Elemento restaurado"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Taasta kirje"
},
"restoreItemConfirmation": {
"message": "Oled kindel, et soovid selle kirje taastada?"
},
"restoredItem": {
"message": "Kirje on taastatud"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Berreskuratu elementua"
},
"restoreItemConfirmation": {
"message": "Ziur zaude elementu hau berreskuratu nahi duzula?"
},
"restoredItem": {
"message": "Elementua berreskuratua"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "بازیابی مورد"
},
"restoreItemConfirmation": {
"message": "آیا مطمئن هستید که می‌خواهید این مورد را بازیابی کنید؟"
},
"restoredItem": {
"message": "مورد بازیابی شد"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Palauta kohde"
},
"restoreItemConfirmation": {
"message": "Haluatko varmasti palauttaa kohteen?"
},
"restoredItem": {
"message": "Kohde palautettiin"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Ibalik ang item"
},
"restoreItemConfirmation": {
"message": "Sigurado ka bang nais mong ibalik ang item na ito?"
},
"restoredItem": {
"message": "Item na nai-restore"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restaurer l'élément"
},
"restoreItemConfirmation": {
"message": "Êtes-vous sûr de vouloir restaurer cet élément ?"
},
"restoredItem": {
"message": "Élément restauré"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "שחזר פריט"
},
"restoreItemConfirmation": {
"message": "האם אתה בטוח שברצונך לשחזר פריט זה?"
},
"restoredItem": {
"message": "פריט ששוחזר"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "आइटम बहाल करें"
},
"restoreItemConfirmation": {
"message": "क्या आप सुनिश्चित हैं कि आप इस आइटम को बहाल करना चाहते हैं?"
},
"restoredItem": {
"message": "बहाल आइटम"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Vrati stavku"
},
"restoreItemConfirmation": {
"message": "Sigurno želiš vratiti ovu stavku?"
},
"restoredItem": {
"message": "Stavka vraćena"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Elem visszaállítása"
},
"restoreItemConfirmation": {
"message": "Biztosan visszaállításra kerüljön ezt az elem?"
},
"restoredItem": {
"message": "Visszaállított elem"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Pulihkan Item"
},
"restoreItemConfirmation": {
"message": "Apakah Anda yakin ingin memulihkan item ini?"
},
"restoredItem": {
"message": "Item Yang Dipulihkan"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Ripristina elemento"
},
"restoreItemConfirmation": {
"message": "Sei sicuro di voler ripristinare questo elemento?"
},
"restoredItem": {
"message": "Elemento ripristinato"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "アイテムをリストア"
},
"restoreItemConfirmation": {
"message": "このアイテムをリストアしますか?"
},
"restoredItem": {
"message": "リストアされたアイテム"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "ಐಟಂ ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಿ"
},
"restoreItemConfirmation": {
"message": "ಈ ಐಟಂ ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?"
},
"restoredItem": {
"message": "ಐಟಂ ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಲಾಗಿದೆ"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "항목 복원"
},
"restoreItemConfirmation": {
"message": "정말 이 항목을 복원하시겠습니까?"
},
"restoredItem": {
"message": "복원된 항목"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Atkurti elementą"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Elementas atkurtas"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Atjaunot vienumu"
},
"restoreItemConfirmation": {
"message": "Jūs tiešām atjaunot šo vienumu?"
},
"restoredItem": {
"message": "Vienums atjaunots"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "ഇനം വീണ്ടെടുക്കുക "
},
"restoreItemConfirmation": {
"message": "ഈ ഇനം വീണ്ടെടുക്കണമെന്ന് ഉറപ്പാണോ?"
},
"restoredItem": {
"message": "വീണ്ടെടുത്ത ഇനം"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Gjenopprett objekt"
},
"restoreItemConfirmation": {
"message": "Er du sikker på at du vil gjenopprette dette elementet?"
},
"restoredItem": {
"message": "Gjenopprettet objekt"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Item herstellen"
},
"restoreItemConfirmation": {
"message": "Weet je zeker dat je dit item wilt herstellen?"
},
"restoredItem": {
"message": "Hersteld item"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Przywróć element"
},
"restoreItemConfirmation": {
"message": "Czy na pewno chcesz przywrócić ten element?"
},
"restoredItem": {
"message": "Element został przywrócony"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restaurar Item"
},
"restoreItemConfirmation": {
"message": "Você tem certeza que deseja restaurar esse item?"
},
"restoredItem": {
"message": "Item Restaurado"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restaurar item"
},
"restoreItemConfirmation": {
"message": "Tem a certeza de que pretende restaurar este item?"
},
"restoredItem": {
"message": "Item restaurado"
},
@@ -2129,7 +2126,7 @@
"message": "Iniciar sessão com o dispositivo"
},
"loginWithDeviceEnabledInfo": {
"message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Necessita de outra opção?"
"message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?"
},
"fingerprintPhraseHeader": {
"message": "Frase de impressão digital"

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restabilire articol"
},
"restoreItemConfirmation": {
"message": "Sigur doriți să restabiliți acest articol?"
},
"restoredItem": {
"message": "Articol restabilit"
},

View File

@@ -631,7 +631,7 @@
"message": "Обновить"
},
"notificationUnlockDesc": {
"message": "Разблокируйте свое хранилище Bitwarden для завершения запроса автозаполнения."
"message": "Разблокируйте свое хранилище Bitwarden чтобы выполнить автозаполнение."
},
"notificationUnlock": {
"message": "Разблокировать"
@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Восстановить элемент"
},
"restoreItemConfirmation": {
"message": "Вы уверены, что хотите восстановить этот элемент?"
},
"restoredItem": {
"message": "Элемент восстановлен"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "අයිතමය යළි පිහිටුවන්න"
},
"restoreItemConfirmation": {
"message": "ඔබට මෙම අයිතමය යථා තත්වයට පත් කිරීමට අවශ්ය බව ඔබට විශ්වාසද?"
},
"restoredItem": {
"message": "ප්රතිෂ්ඨාපනය අයිතමය"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Obnoviť položku"
},
"restoreItemConfirmation": {
"message": "Naozaj chcete obnoviť túto položku?"
},
"restoredItem": {
"message": "Obnovená položka"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Obnovi element"
},
"restoreItemConfirmation": {
"message": "Ste prepričani, da želite obnoviti ta element?"
},
"restoredItem": {
"message": "Element obnovljen"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Врати ставку"
},
"restoreItemConfirmation": {
"message": "Да ли сте сигурни да желите да вратите ову ставку?"
},
"restoredItem": {
"message": "Ставка враћена"
},

View File

@@ -11,7 +11,7 @@
"description": "Extension description"
},
"loginOrCreateNewAccount": {
"message": "Logga in eller skapa ett nytt konto för att komma åt dina lösenord."
"message": "Logga in eller skapa ett nytt konto för att komma åt ditt säkra valv."
},
"createAccount": {
"message": "Skapa konto"
@@ -342,7 +342,7 @@
"message": "Betygsätt tillägget"
},
"rateExtensionDesc": {
"message": "Överväg gärna att hjälpa oss genom att ge oss en bra recension!"
"message": "Överväg gärna att skriva en recension om oss!"
},
"browserNotSupportClipboard": {
"message": "Din webbläsare har inte stöd för att enkelt kopiera till urklipp. Kopiera till urklipp manuellt istället."
@@ -370,7 +370,7 @@
}
},
"invalidMasterPassword": {
"message": "Felaktigt huvudlösenord"
"message": "Ogiltigt huvudlösenord"
},
"vaultTimeout": {
"message": "Valvets tidsgräns"
@@ -736,7 +736,7 @@
"message": "Kopiera verifieringskod"
},
"attachments": {
"message": "Bifogade filer"
"message": "Bilagor"
},
"deleteAttachment": {
"message": "Radera bilaga"
@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Återställ objekt"
},
"restoreItemConfirmation": {
"message": "Är du säker på att du vill återställa detta objekt?"
},
"restoredItem": {
"message": "Återställde objekt"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Restore item"
},
"restoreItemConfirmation": {
"message": "Are you sure you want to restore this item?"
},
"restoredItem": {
"message": "Item restored"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "กู้คืนรายการ"
},
"restoreItemConfirmation": {
"message": "คุณแน่ใจหรือไม่ว่าต้องการกู้คืนรายการนี้"
},
"restoredItem": {
"message": "คืนค่ารายการแล้ว"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Kaydı geri yükle"
},
"restoreItemConfirmation": {
"message": "Bu kaydı geri yüklemek istediğinizden emin misiniz?"
},
"restoredItem": {
"message": "Kayıt geri yüklendi"
},

View File

@@ -631,10 +631,10 @@
"message": "Оновити"
},
"notificationUnlockDesc": {
"message": "Unlock your Bitwarden vault to complete the auto-fill request."
"message": "Розблокуйте своє сховище Bitwarden, щоб завершити запит автозаповнення."
},
"notificationUnlock": {
"message": "Unlock"
"message": "Розблокувати"
},
"enableContextMenuItem": {
"message": "Показувати в контекстному меню"
@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Відновити запис"
},
"restoreItemConfirmation": {
"message": "Ви дійсно хочете відновити цей запис?"
},
"restoredItem": {
"message": "Запис відновлено"
},
@@ -2228,7 +2225,7 @@
}
},
"loggingInOn": {
"message": "Logging in on"
"message": "Увійти на"
},
"opensInANewWindow": {
"message": "Відкривається у новому вікні"

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "Khôi phục mục"
},
"restoreItemConfirmation": {
"message": "Bạn có chắc chắn muốn khôi phục mục này không?"
},
"restoredItem": {
"message": "Mục đã được khôi phục"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "恢复项目"
},
"restoreItemConfirmation": {
"message": "您确定要恢复此项目吗?"
},
"restoredItem": {
"message": "项目已恢复"
},

View File

@@ -1446,9 +1446,6 @@
"restoreItem": {
"message": "還原項目"
},
"restoreItemConfirmation": {
"message": "您確定要還原此項目嗎?"
},
"restoredItem": {
"message": "項目已還原"
},

View File

@@ -0,0 +1,29 @@
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
} from "../../../platform/background/service-factories/crypto-service.factory";
import {
CachedServices,
FactoryOptions,
factory,
} from "../../../platform/background/service-factories/factory-options";
type AuthRequestCryptoServiceFactoryOptions = FactoryOptions;
export type AuthRequestCryptoServiceInitOptions = AuthRequestCryptoServiceFactoryOptions &
CryptoServiceInitOptions;
export function authRequestCryptoServiceFactory(
cache: { authRequestCryptoService?: AuthRequestCryptoServiceAbstraction } & CachedServices,
opts: AuthRequestCryptoServiceInitOptions
): Promise<AuthRequestCryptoServiceAbstraction> {
return factory(
cache,
"authRequestCryptoService",
opts,
async () => new AuthRequestCryptoServiceImplementation(await cryptoServiceFactory(cache, opts))
);
}

View File

@@ -52,6 +52,14 @@ import {
PasswordStrengthServiceInitOptions,
} from "../../../tools/background/service_factories/password-strength-service.factory";
import {
authRequestCryptoServiceFactory,
AuthRequestCryptoServiceInitOptions,
} from "./auth-request-crypto-service.factory";
import {
deviceTrustCryptoServiceFactory,
DeviceTrustCryptoServiceInitOptions,
} from "./device-trust-crypto-service.factory";
import {
keyConnectorServiceFactory,
KeyConnectorServiceInitOptions,
@@ -75,7 +83,9 @@ export type AuthServiceInitOptions = AuthServiceFactoyOptions &
I18nServiceInitOptions &
EncryptServiceInitOptions &
PolicyServiceInitOptions &
PasswordStrengthServiceInitOptions;
PasswordStrengthServiceInitOptions &
DeviceTrustCryptoServiceInitOptions &
AuthRequestCryptoServiceInitOptions;
export function authServiceFactory(
cache: { authService?: AbstractAuthService } & CachedServices,
@@ -101,7 +111,9 @@ export function authServiceFactory(
await i18nServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts),
await passwordStrengthServiceFactory(cache, opts),
await policyServiceFactory(cache, opts)
await policyServiceFactory(cache, opts),
await deviceTrustCryptoServiceFactory(cache, opts),
await authRequestCryptoServiceFactory(cache, opts)
)
);
}

View File

@@ -0,0 +1,74 @@
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import {
DevicesApiServiceInitOptions,
devicesApiServiceFactory,
} from "../../../background/service-factories/devices-api-service.factory";
import {
AppIdServiceInitOptions,
appIdServiceFactory,
} from "../../../platform/background/service-factories/app-id-service.factory";
import {
CryptoFunctionServiceInitOptions,
cryptoFunctionServiceFactory,
} from "../../../platform/background/service-factories/crypto-function-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
} from "../../../platform/background/service-factories/crypto-service.factory";
import {
EncryptServiceInitOptions,
encryptServiceFactory,
} from "../../../platform/background/service-factories/encrypt-service.factory";
import {
CachedServices,
FactoryOptions,
factory,
} from "../../../platform/background/service-factories/factory-options";
import {
I18nServiceInitOptions,
i18nServiceFactory,
} from "../../../platform/background/service-factories/i18n-service.factory";
import {
PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory,
} from "../../../platform/background/service-factories/platform-utils-service.factory";
import {
StateServiceInitOptions,
stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory";
type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions;
export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions &
CryptoFunctionServiceInitOptions &
CryptoServiceInitOptions &
EncryptServiceInitOptions &
StateServiceInitOptions &
AppIdServiceInitOptions &
DevicesApiServiceInitOptions &
I18nServiceInitOptions &
PlatformUtilsServiceInitOptions;
export function deviceTrustCryptoServiceFactory(
cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices,
opts: DeviceTrustCryptoServiceInitOptions
): Promise<DeviceTrustCryptoServiceAbstraction> {
return factory(
cache,
"deviceTrustCryptoService",
opts,
async () =>
new DeviceTrustCryptoService(
await cryptoFunctionServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await appIdServiceFactory(cache, opts),
await devicesApiServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts),
await platformUtilsServiceFactory(cache, opts)
)
);
}

View File

@@ -0,0 +1,29 @@
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import {
ApiServiceInitOptions,
apiServiceFactory,
} from "../../../platform/background/service-factories/api-service.factory";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
type UserVerificationApiServiceFactoryOptions = FactoryOptions;
export type UserVerificationApiServiceInitOptions = UserVerificationApiServiceFactoryOptions &
ApiServiceInitOptions;
export function userVerificationApiServiceFactory(
cache: { userVerificationApiService?: UserVerificationApiServiceAbstraction } & CachedServices,
opts: UserVerificationApiServiceInitOptions
): Promise<UserVerificationApiServiceAbstraction> {
return factory(
cache,
"userVerificationApiService",
opts,
async () => new UserVerificationApiService(await apiServiceFactory(cache, opts))
);
}

View File

@@ -0,0 +1,51 @@
import { UserVerificationService as AbstractUserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
} from "../../../platform/background/service-factories/crypto-service.factory";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
import {
I18nServiceInitOptions,
i18nServiceFactory,
} from "../../../platform/background/service-factories/i18n-service.factory";
import {
StateServiceInitOptions,
stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory";
import {
UserVerificationApiServiceInitOptions,
userVerificationApiServiceFactory,
} from "./user-verification-api-service.factory";
type UserVerificationServiceFactoryOptions = FactoryOptions;
export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryOptions &
StateServiceInitOptions &
CryptoServiceInitOptions &
I18nServiceInitOptions &
UserVerificationApiServiceInitOptions;
export function userVerificationServiceFactory(
cache: { userVerificationService?: AbstractUserVerificationService } & CachedServices,
opts: UserVerificationServiceInitOptions
): Promise<AbstractUserVerificationService> {
return factory(
cache,
"userVerificationService",
opts,
async () =>
new UserVerificationService(
await stateServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts),
await userVerificationApiServiceFactory(cache, opts)
)
);
}

View File

@@ -5,14 +5,20 @@
<span class="title">{{ "verifyIdentity" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" *ngIf="!hideInput">{{ "unlock" | i18n }}</button>
<button type="submit" *ngIf="pinEnabled || masterPasswordEnabled">
{{ "unlock" | i18n }}
</button>
</div>
</header>
<main tabindex="-1">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<div
class="box-content-row box-content-row-flex"
appBoxRow
*ngIf="pinEnabled || masterPasswordEnabled"
>
<div class="row-main" *ngIf="pinEnabled">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
@@ -24,7 +30,7 @@
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<div class="row-main" *ngIf="masterPasswordEnabled && !pinEnabled">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"

View File

@@ -2,14 +2,14 @@ import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -19,6 +19,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogService } from "@bitwarden/components";
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
@@ -44,13 +45,14 @@ export class LockComponent extends BaseLockComponent {
stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone,
policyApiService: PolicyApiServiceAbstraction,
policyService: InternalPolicyService,
passwordStrengthService: PasswordStrengthServiceAbstraction,
private authService: AuthService,
dialogService: DialogServiceAbstraction
dialogService: DialogService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
userVerificationService: UserVerificationService
) {
super(
router,
@@ -64,12 +66,13 @@ export class LockComponent extends BaseLockComponent {
stateService,
apiService,
logService,
keyConnectorService,
ngZone,
policyApiService,
policyService,
passwordStrengthService,
dialogService
dialogService,
deviceTrustCryptoService,
userVerificationService
);
this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
@@ -81,7 +84,7 @@ export class LockComponent extends BaseLockComponent {
(await this.stateService.getDisableAutoBiometricsPrompt()) ?? true;
window.setTimeout(async () => {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus();
if (
this.biometricLock &&
!disableAutoBiometricsPrompt &&
@@ -93,7 +96,7 @@ export class LockComponent extends BaseLockComponent {
}, 100);
}
async unlockBiometric(): Promise<boolean> {
override async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}

View File

@@ -0,0 +1,108 @@
<div id="login-initiated">
<header>
<h1 class="margin-auto">
<span class="title">{{ "loginInitiated" | i18n }}</span>
</h1>
</header>
<div class="content login-page">
<div class="full-loading-spinner" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<ng-container *ngIf="!loading">
<ng-container *ngIf="data.state == State.ExistingUserUntrustedDevice">
<div class="standard-x-margin">
<p class="lead">{{ "loginInitiated" | i18n }}</p>
<h6 class="mb-20px">{{ "deviceApprovalRequired" | i18n }}</h6>
</div>
<form
id="rememberDeviceForm"
class="mb-20px standard-x-margin"
[formGroup]="rememberDeviceForm"
>
<div>
<input
type="checkbox"
id="rememberDevice"
name="rememberDevice"
formControlName="rememberDevice"
/>
<label for="rememberDevice">
{{ "rememberThisDevice" | i18n }}
</label>
<p id="rememberThisDeviceHintText">{{ "uncheckIfPublicDevice" | i18n }}</p>
</div>
</form>
<div class="box mb-20px">
<button
*ngIf="data.showApproveFromOtherDeviceBtn"
(click)="approveFromOtherDevice()"
type="button"
class="btn primary block"
>
<b>{{ "approveFromYourOtherDevice" | i18n }}</b>
</button>
<button
*ngIf="data.showReqAdminApprovalBtn"
(click)="requestAdminApproval()"
type="button"
class="btn block btn-top-margin"
>
{{ "requestAdminApproval" | i18n }}
</button>
<button
*ngIf="data.showApproveWithMasterPasswordBtn"
type="button"
class="btn block btn-top-margin"
(click)="approveWithMasterPassword()"
>
{{ "approveWithMasterPassword" | i18n }}
</button>
</div>
</ng-container>
<ng-container *ngIf="data.state == State.NewUser">
<div class="standard-x-margin">
<p class="lead">{{ "loginInitiated" | i18n }}</p>
</div>
<form
id="rememberDeviceForm"
class="mb-20px standard-x-margin"
[formGroup]="rememberDeviceForm"
>
<div>
<input
type="checkbox"
id="rememberDevice"
name="rememberDevice"
formControlName="rememberDevice"
/>
<label for="rememberDevice">
{{ "rememberThisDevice" | i18n }}
</label>
<p id="rememberThisDeviceHintText">{{ "uncheckIfPublicDevice" | i18n }}</p>
</div>
</form>
<div class="box mb-20px">
<button (click)="createUser()" type="button" class="btn primary block">
<b>{{ "continue" | i18n }}</b>
</button>
</div>
</ng-container>
<hr class="muted-hr mx-5px mb-20px" />
<div class="small mx-5px">
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ data.userEmail }}</p>
<a tabindex="0" role="button" style="cursor: pointer" (click)="logOut()">{{
"notYou" | i18n
}}</a>
</div>
</ng-container>
</div>
</div>

View File

@@ -0,0 +1,18 @@
import { Component } from "@angular/core";
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
@Component({
selector: "browser-login-decryption-options",
templateUrl: "login-decryption-options.component.html",
})
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
override async createUser(): Promise<void> {
try {
await super.createUser();
await this.router.navigate(["/tabs/vault"]);
} catch (error) {
this.validationService.showError(error);
}
}
}

View File

@@ -5,32 +5,57 @@
</h1>
</header>
<div class="content login-page">
<div>
<p class="lead">{{ "logInInitiated" | i18n }}</p>
<ng-container *ngIf="state == StateEnum.StandardAuthRequest">
<div>
<p>{{ "notificationSentDevice" | i18n }}</p>
<p class="lead">{{ "loginInitiated" | i18n }}</p>
<p>
{{ "fingerprintMatchInfo" | i18n }}
</p>
<div>
<p>{{ "notificationSentDevice" | i18n }}</p>
<p>
{{ "fingerprintMatchInfo" | i18n }}
</p>
</div>
<div>
<b class="fingerprint-phrase-header">{{ "fingerprintPhraseHeader" | i18n }}</b>
<p class="fingerprint-text">
<code>{{ fingerprintPhrase }}</code>
</p>
</div>
<div class="resend-notification" *ngIf="showResendNotification">
<a (click)="startPasswordlessLogin()">{{ "resendNotification" | i18n }}</a>
</div>
<div class="footer">
{{ "loginWithDeviceEnabledInfo" | i18n }}
<a href="#" (click)="back()">{{ "viewAllLoginOptions" | i18n }}</a>
</div>
</div>
</ng-container>
<ng-container *ngIf="state == StateEnum.AdminAuthRequest">
<div>
<b class="fingerprint-phrase-header">{{ "fingerprintPhraseHeader" | i18n }}</b>
<p class="fingerprint-text">
<code>{{ fingerprintPhrase }}</code>
</p>
</div>
<p class="lead">{{ "adminApprovalRequested" | i18n }}</p>
<div class="resend-notification" *ngIf="showResendNotification">
<a (click)="startPasswordlessLogin()">{{ "resendNotification" | i18n }}</a>
</div>
<div>
<p>{{ "adminApprovalRequestSentToAdmins" | i18n }}</p>
<p>{{ "youWillBeNotifiedOnceApproved" | i18n }}</p>
</div>
<div class="footer">
{{ "loginWithDeviceEnabledInfo" | i18n }}
<a routerLink="/login">{{ "viewAllLoginOptions" | i18n }}</a>
<div>
<b class="fingerprint-phrase-header">{{ "fingerprintPhraseHeader" | i18n }}</b>
<p class="fingerprint-text">
<code>{{ fingerprintPhrase }}</code>
</p>
</div>
<div class="footer">
{{ "troubleLoggingIn" | i18n }}
<a routerLink="/login-initiated">{{ "viewAllLoginOptions" | i18n }}</a>
</div>
</div>
</div>
</ng-container>
</div>
</div>

View File

@@ -1,10 +1,13 @@
import { Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { LoginWithDeviceComponent as BaseLoginWithDeviceComponent } from "@bitwarden/angular/auth/components/login-with-device.component";
import { AnonymousHubService } from "@bitwarden/common/abstractions/anonymousHub.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -42,7 +45,10 @@ export class LoginWithDeviceComponent
validationService: ValidationService,
stateService: StateService,
loginService: LoginService,
syncService: SyncService
syncService: SyncService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
authReqCryptoService: AuthRequestCryptoServiceAbstraction,
private location: Location
) {
super(
router,
@@ -59,10 +65,16 @@ export class LoginWithDeviceComponent
anonymousHubService,
validationService,
stateService,
loginService
loginService,
deviceTrustCryptoService,
authReqCryptoService
);
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
};
}
protected back() {
this.location.back();
}
}

View File

@@ -4,8 +4,8 @@ import { ActivatedRoute, Router } from "@angular/router";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";

View File

@@ -4,7 +4,6 @@ import { Router } from "@angular/router";
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -15,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-register",
@@ -38,7 +38,7 @@ export class RegisterComponent extends BaseRegisterComponent {
environmentService: EnvironmentService,
logService: LogService,
auditService: AuditService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
formValidationErrorService,

View File

@@ -1,2 +1 @@
export { LockGuardService } from "./lock-guard.service";
export { UnauthGuardService } from "./unauth-guard.service";

View File

@@ -1,8 +0,0 @@
import { Injectable } from "@angular/core";
import { LockGuard as BaseLockGuardService } from "@bitwarden/angular/auth/guards/lock.guard";
@Injectable()
export class LockGuardService extends BaseLockGuardService {
protected homepage = "tabs/current";
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from "@angular/core";
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards/unauth.guard";
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
@Injectable()
export class UnauthGuardService extends BaseUnauthGuardService {

View File

@@ -2,7 +2,6 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/components/set-password.component";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -15,6 +14,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
@Component({
selector: "app-set-password",
@@ -36,7 +36,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
route: ActivatedRoute,
organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService,
dialogService: DialogServiceAbstraction
dialogService: DialogService
) {
super(
i18nService,

View File

@@ -1,11 +1,12 @@
import { Component } from "@angular/core";
import { Component, Inject } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -35,7 +36,8 @@ export class SsoComponent extends BaseSsoComponent {
syncService: SyncService,
environmentService: EnvironmentService,
logService: LogService,
private vaultTimeoutService: VaultTimeoutService
configService: ConfigServiceAbstraction,
@Inject(WINDOW) private win: Window
) {
super(
authService,
@@ -48,7 +50,8 @@ export class SsoComponent extends BaseSsoComponent {
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
logService,
configService
);
const url = this.environmentService.getWebVaultUrl();
@@ -57,15 +60,22 @@ export class SsoComponent extends BaseSsoComponent {
this.clientId = "browser";
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
syncService.fullSync(true);
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
if ((await this.authService.getAuthStatus()) !== AuthenticationStatus.Unlocked) {
BrowserApi.reloadOpenWindows();
}
const thisWindow = window.open("", "_self");
thisWindow.close();
this.win.close();
};
super.onSuccessfulLoginTde = async () => {
syncService.fullSync(true);
};
super.onSuccessfulLoginTdeNavigate = async () => {
this.win.close();
};
}
}

View File

@@ -1,9 +1,9 @@
import { Component } from "@angular/core";
import { Component, Inject } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
@@ -11,6 +11,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -18,6 +19,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../platform/browser/browser-api";
import { PopupUtilsService } from "../../popup/services/popup-utils.service";
@@ -48,7 +50,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
twoFactorService: TwoFactorService,
appIdService: AppIdService,
loginService: LoginService,
private dialogService: DialogServiceAbstraction
configService: ConfigServiceAbstraction,
private dialogService: DialogService,
@Inject(WINDOW) protected win: Window
) {
super(
authService,
@@ -56,19 +60,28 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
i18nService,
apiService,
platformUtilsService,
window,
win,
environmentService,
stateService,
route,
logService,
twoFactorService,
appIdService,
loginService
loginService,
configService
);
super.onSuccessfulLogin = () => {
this.loginService.clearValues();
return syncService.fullSync(true);
super.onSuccessfulLogin = async () => {
syncService.fullSync(true);
};
super.onSuccessfulLoginTde = async () => {
syncService.fullSync(true);
};
super.onSuccessfulLoginTdeNavigate = async () => {
this.win.close();
};
super.successRoute = "/tabs/vault";
// FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe
this.webAuthnNewTab = true;
@@ -107,7 +120,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
content: { key: "popup2faCloseMessage" },
type: SimpleDialogType.WARNING,
type: "warning",
});
if (confirmed) {
this.popupUtilsService.popOut(window);
@@ -117,11 +130,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.sso === "true") {
super.onSuccessfulLogin = () => {
super.onSuccessfulLogin = async () => {
// This is not awaited so we don't pause the application while the sync is happening.
// This call is executed by the service that lives in the background script so it will continue
// the sync even if this tab closes.
const syncPromise = this.syncService.fullSync(true);
this.syncService.fullSync(true);
// Force sidebars (FF && Opera) to reload while exempting current window
// because we are just going to close the current window.
@@ -130,8 +143,6 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
// We don't need this window anymore because the intent is for the user to be left
// on the web vault screen which tells them to continue in the browser extension (sidebar or popup)
BrowserApi.closeBitwardenExtensionTab();
return syncPromise;
};
}
});

View File

@@ -2,6 +2,10 @@ import {
TotpServiceInitOptions,
totpServiceFactory,
} from "../../../auth/background/service-factories/totp-service.factory";
import {
UserVerificationServiceInitOptions,
userVerificationServiceFactory,
} from "../../../auth/background/service-factories/user-verification-service.factory";
import {
EventCollectionServiceInitOptions,
eventCollectionServiceFactory,
@@ -38,7 +42,8 @@ export type AutoFillServiceInitOptions = AutoFillServiceOptions &
TotpServiceInitOptions &
EventCollectionServiceInitOptions &
LogServiceInitOptions &
SettingsServiceInitOptions;
SettingsServiceInitOptions &
UserVerificationServiceInitOptions;
export function autofillServiceFactory(
cache: { autofillService?: AbstractAutoFillService } & CachedServices,
@@ -55,7 +60,8 @@ export function autofillServiceFactory(
await totpServiceFactory(cache, opts),
await eventCollectionServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await settingsServiceFactory(cache, opts)
await settingsServiceFactory(cache, opts),
await userVerificationServiceFactory(cache, opts)
)
);
}

View File

@@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
@@ -13,6 +14,7 @@ describe("CipherContextMenuHandler", () => {
let mainContextMenuHandler: MockProxy<MainContextMenuHandler>;
let authService: MockProxy<AuthService>;
let cipherService: MockProxy<CipherService>;
let userVerificationService: MockProxy<UserVerificationService>;
let sut: CipherContextMenuHandler;
@@ -20,10 +22,17 @@ describe("CipherContextMenuHandler", () => {
mainContextMenuHandler = mock();
authService = mock();
cipherService = mock();
userVerificationService = mock();
userVerificationService.hasMasterPassword.mockResolvedValue(true);
jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue();
sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService);
sut = new CipherContextMenuHandler(
mainContextMenuHandler,
authService,
cipherService,
userVerificationService
);
});
afterEach(() => jest.resetAllMocks());
@@ -83,11 +92,11 @@ describe("CipherContextMenuHandler", () => {
};
cipherService.getAllDecryptedForUrl.mockResolvedValue([
null,
undefined,
{ type: CipherType.Card },
{ type: CipherType.Login, reprompt: CipherRepromptType.Password },
realCipher,
null, // invalid cipher
undefined, // invalid cipher
{ type: CipherType.Card }, // invalid cipher
{ type: CipherType.Login, reprompt: CipherRepromptType.Password }, // invalid cipher
realCipher, // valid cipher
] as any[]);
await sut.update("https://test.com");
@@ -96,7 +105,7 @@ describe("CipherContextMenuHandler", () => {
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com");
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(1);
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(2);
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith(
"Test Cipher (Test Username)",
@@ -105,5 +114,61 @@ describe("CipherContextMenuHandler", () => {
realCipher
);
});
it("adds ciphers with master password reprompt if the user does not have a master password", async () => {
authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked);
// User does not have a master password, or has one but hasn't logged in with it (key connector user or TDE user)
userVerificationService.hasMasterPasswordAndMasterKeyHash.mockResolvedValue(false);
mainContextMenuHandler.init.mockResolvedValue(true);
const realCipher = {
id: "5",
type: CipherType.Login,
reprompt: CipherRepromptType.None,
name: "Test Cipher",
login: { username: "Test Username" },
};
const repromptCipher = {
id: "6",
type: CipherType.Login,
reprompt: CipherRepromptType.Password,
name: "Test Reprompt Cipher",
login: { username: "Test Username" },
};
cipherService.getAllDecryptedForUrl.mockResolvedValue([
null, // invalid cipher
undefined, // invalid cipher
{ type: CipherType.Card }, // invalid cipher
repromptCipher, // valid cipher
realCipher, // valid cipher
] as any[]);
await sut.update("https://test.com");
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com");
// Should call this twice, once for each valid cipher
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(2);
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith(
"Test Cipher (Test Username)",
"5",
"https://test.com",
realCipher
);
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith(
"Test Reprompt Cipher (Test Username)",
"6",
"https://test.com",
repromptCipher
);
});
});
});

View File

@@ -1,10 +1,10 @@
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -12,6 +12,7 @@ import {
authServiceFactory,
AuthServiceInitOptions,
} from "../../auth/background/service-factories/auth-service.factory";
import { userVerificationServiceFactory } from "../../auth/background/service-factories/user-verification-service.factory";
import { Account } from "../../models/account";
import { CachedServices } from "../../platform/background/service-factories/factory-options";
import { BrowserApi } from "../../platform/browser/browser-api";
@@ -38,7 +39,8 @@ export class CipherContextMenuHandler {
constructor(
private mainContextMenuHandler: MainContextMenuHandler,
private authService: AuthService,
private cipherService: CipherService
private cipherService: CipherService,
private userVerificationService: UserVerificationService
) {}
static async create(cachedServices: CachedServices) {
@@ -77,7 +79,8 @@ export class CipherContextMenuHandler {
return new CipherContextMenuHandler(
await MainContextMenuHandler.mv3Create(cachedServices),
await authServiceFactory(cachedServices, serviceOptions),
await cipherServiceFactory(cachedServices, serviceOptions)
await cipherServiceFactory(cachedServices, serviceOptions),
await userVerificationServiceFactory(cachedServices, serviceOptions)
);
}
@@ -180,7 +183,7 @@ export class CipherContextMenuHandler {
if (
cipher == null ||
cipher.type !== CipherType.Login ||
cipher.reprompt !== CipherRepromptType.None
(await this.userVerificationService.hasMasterPasswordAndMasterKeyHash())
) {
return;
}

View File

@@ -203,17 +203,45 @@ export class ContextMenuClickedHandler {
if (tab == null) {
return;
}
await this.autofillAction(tab, cipher);
if (cipher.reprompt !== CipherRepromptType.None) {
await BrowserApi.tabSendMessageData(tab, "passwordReprompt", {
cipherId: cipher.id,
action: AUTOFILL_ID,
});
} else {
await this.autofillAction(tab, cipher);
}
break;
case COPY_USERNAME_ID:
this.copyToClipboard({ text: cipher.login.username, tab: tab });
break;
case COPY_PASSWORD_ID:
this.copyToClipboard({ text: cipher.login.password, tab: tab });
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
if (cipher.reprompt !== CipherRepromptType.None) {
await BrowserApi.tabSendMessageData(tab, "passwordReprompt", {
cipherId: cipher.id,
action: COPY_PASSWORD_ID,
});
} else {
this.copyToClipboard({ text: cipher.login.password, tab: tab });
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
}
break;
case COPY_VERIFICATIONCODE_ID:
this.copyToClipboard({ text: await this.totpService.getCode(cipher.login.totp), tab: tab });
if (cipher.reprompt !== CipherRepromptType.None) {
await BrowserApi.tabSendMessageData(tab, "passwordReprompt", {
cipherId: cipher.id,
action: COPY_VERIFICATIONCODE_ID,
});
} else {
this.copyToClipboard({
text: await this.totpService.getCode(cipher.login.totp),
tab: tab,
});
}
break;
}
}

View File

@@ -28,6 +28,7 @@ window.addEventListener(
const forwardCommands = [
"promptForLogin",
"passwordReprompt",
"addToLockedVaultPendingNotifications",
"unlockCompleted",
"addedCipher",

View File

@@ -1,6 +1,7 @@
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EventType, FieldType, UriMatchType } from "@bitwarden/common/enums";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -45,7 +46,8 @@ export default class AutofillService implements AutofillServiceInterface {
private totpService: TotpService,
private eventCollectionService: EventCollectionService,
private logService: LogService,
private settingsService: SettingsService
private settingsService: SettingsService,
private userVerificationService: UserVerificationService
) {}
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
@@ -234,7 +236,19 @@ export default class AutofillService implements AutofillServiceInterface {
}
}
if (cipher == null || cipher.reprompt !== CipherRepromptType.None) {
if (cipher == null) {
return null;
}
if (
cipher.reprompt !== CipherRepromptType.None &&
(await this.userVerificationService.hasMasterPasswordAndMasterKeyHash())
) {
await BrowserApi.tabSendMessageData(tab, "passwordReprompt", {
cipherId: cipher.id,
action: "autofill",
});
return null;
}

View File

@@ -1,4 +1,4 @@
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -1,5 +1,5 @@
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";

View File

@@ -1,27 +1,33 @@
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
@@ -60,12 +66,13 @@ import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/we
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
import { DevicesServiceImplementation } from "@bitwarden/common/services/devices/devices.service.implementation";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { SearchService } from "@bitwarden/common/services/search.service";
import { TotpService } from "@bitwarden/common/services/totp.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import {
PasswordGenerationService,
PasswordGenerationServiceAbstraction,
@@ -136,7 +143,7 @@ import { PopupUtilsService } from "../popup/services/popup-utils.service";
import { BrowserSendService } from "../services/browser-send.service";
import { BrowserSettingsService } from "../services/browser-settings.service";
import { BrowserFido2UserInterfaceService } from "../services/fido2/browser-fido2-user-interface.service";
import VaultTimeoutService from "../services/vaultTimeout/vaultTimeout.service";
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
import { BrowserFolderService } from "../vault/services/browser-folder.service";
import { VaultFilterService } from "../vault/services/vault-filter.service";
@@ -164,7 +171,7 @@ export default class MainBackground {
cipherService: CipherServiceAbstraction;
folderService: InternalFolderServiceAbstraction;
collectionService: CollectionServiceAbstraction;
vaultTimeoutService: VaultTimeoutServiceAbstraction;
vaultTimeoutService: VaultTimeoutService;
vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction;
syncService: SyncServiceAbstraction;
passwordGenerationService: PasswordGenerationServiceAbstraction;
@@ -207,6 +214,10 @@ export default class MainBackground {
cipherContextMenuHandler: CipherContextMenuHandler;
configService: ConfigServiceAbstraction;
configApiService: ConfigApiServiceAbstraction;
devicesApiService: DevicesApiServiceAbstraction;
devicesService: DevicesServiceAbstraction;
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction;
authRequestCryptoService: AuthRequestCryptoServiceAbstraction;
popupUtilsService: PopupUtilsService;
browserPopoutWindowService: BrowserPopoutWindowService;
@@ -399,6 +410,23 @@ export default class MainBackground {
that.runtimeBackground.processMessage(message, that as any);
};
})();
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,
this.stateService,
this.appIdService,
this.devicesApiService,
this.i18nService,
this.platformUtilsService
);
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
this.authRequestCryptoService = new AuthRequestCryptoServiceImplementation(this.cryptoService);
this.authService = new AuthService(
this.cryptoService,
this.apiService,
@@ -414,14 +442,26 @@ export default class MainBackground {
this.i18nService,
this.encryptService,
this.passwordStrengthService,
this.policyService
this.policyService,
this.deviceTrustCryptoService,
this.authRequestCryptoService
);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.i18nService,
this.userVerificationApiService
);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
this.cryptoService,
this.tokenService,
this.policyService,
this.stateService
this.stateService,
this.userVerificationService
);
this.vaultTimeoutService = new VaultTimeoutService(
@@ -432,7 +472,6 @@ export default class MainBackground {
this.platformUtilsService,
this.messagingService,
this.searchService,
this.keyConnectorService,
this.stateService,
this.authService,
this.vaultTimeoutSettingsService,
@@ -483,13 +522,15 @@ export default class MainBackground {
this.eventUploadService
);
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
this.autofillService = new AutofillService(
this.cipherService,
this.stateService,
this.totpService,
this.eventCollectionService,
this.logService,
this.settingsService
this.settingsService,
this.userVerificationService
);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.exportService = new VaultExportService(
@@ -511,15 +552,6 @@ export default class MainBackground {
this.authService,
this.messagingService
);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.userVerificationService = new UserVerificationService(
this.cryptoService,
this.i18nService,
this.userVerificationApiService
);
this.configApiService = new ConfigApiService(this.apiService, this.authService);
this.configService = new ConfigService(
@@ -528,6 +560,7 @@ export default class MainBackground {
this.authService,
this.environmentService
);
this.browserPopoutWindowService = new BrowserPopoutWindowService();
this.popupUtilsService = new PopupUtilsService(this.isPrivateMode);
@@ -665,7 +698,8 @@ export default class MainBackground {
this.cipherContextMenuHandler = new CipherContextMenuHandler(
this.mainContextMenuHandler,
this.authService,
this.cipherService
this.cipherService,
this.userVerificationService
);
}
}
@@ -675,7 +709,7 @@ export default class MainBackground {
await this.stateService.init();
await (this.vaultTimeoutService as VaultTimeoutService).init(true);
await this.vaultTimeoutService.init(true);
await (this.i18nService as BrowserI18nService).init();
await (this.eventUploadService as EventUploadService).init(true);
await this.runtimeBackground.init();

View File

@@ -10,7 +10,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BrowserApi } from "../platform/browser/browser-api";
@@ -42,6 +46,7 @@ type ReceiveMessage = {
// Unlock key
keyB64?: string;
userKeyB64?: string;
};
type ReceiveMessageOuter = {
@@ -320,16 +325,55 @@ export class NativeMessagingBackground {
}
if (message.response === "unlocked") {
await this.cryptoService.setKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64))
);
try {
if (message.userKeyB64) {
const userKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(message.userKeyB64)
) as UserKey;
await this.cryptoService.setUserKey(userKey);
} else if (message.keyB64) {
// Backwards compatibility to support cases in which the user hasn't updated their desktop app
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472)
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
encUserKey ||= await this.stateService.getMasterKeyEncryptedUserKey();
if (!encUserKey) {
throw new Error("No encrypted user key found");
}
const masterKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(message.keyB64)
) as MasterKey;
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(
masterKey,
new EncString(encUserKey)
);
await this.cryptoService.setMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
} else {
throw new Error("No key received");
}
} catch (e) {
this.logService.error("Unable to set key: " + e);
this.messagingService.send("showDialog", {
title: { key: "biometricsFailedTitle" },
content: { key: "biometricsFailedDesc" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: "danger",
});
// Exit early
if (this.resolver) {
this.resolver(message);
}
return;
}
// Verify key is correct by attempting to decrypt a secret
try {
await this.cryptoService.getFingerprint(await this.stateService.getUserId());
} catch (e) {
this.logService.error("Unable to verify key: " + e);
await this.cryptoService.clearKey();
await this.cryptoService.clearKeys();
this.showWrongUserDialog();
// Exit early

View File

@@ -79,6 +79,8 @@ export default class RuntimeBackground {
}
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
const cipherId = msg.data?.cipherId;
switch (msg.command) {
case "loggedIn":
case "unlocked": {
@@ -86,7 +88,7 @@ export default class RuntimeBackground {
if (this.lockedVaultPendingNotifications?.length > 0) {
item = this.lockedVaultPendingNotifications.pop();
await this.browserPopoutWindowService.closeLoginPrompt();
await this.browserPopoutWindowService.closeUnlockPrompt();
}
await this.main.refreshBadge();
@@ -126,13 +128,22 @@ export default class RuntimeBackground {
break;
case "promptForLogin":
case "bgReopenPromptForLogin":
await this.browserPopoutWindowService.openLoginPrompt(sender.tab?.windowId);
await this.browserPopoutWindowService.openUnlockPrompt(sender.tab?.windowId);
break;
case "passwordReprompt":
if (cipherId) {
await this.browserPopoutWindowService.openPasswordRepromptPrompt(sender.tab?.windowId, {
cipherId: cipherId,
senderTabId: sender.tab.id,
action: msg.data?.action,
});
}
break;
case "openAddEditCipher": {
const addEditCipherUrl =
msg.data?.cipherId == null
cipherId == null
? "popup/index.html#/edit-cipher"
: "popup/index.html#/edit-cipher?cipherId=" + msg.data.cipherId;
: "popup/index.html#/edit-cipher?cipherId=" + cipherId;
BrowserApi.openBitwardenExtensionTab(addEditCipherUrl, true);
break;

View File

@@ -0,0 +1,28 @@
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import {
ApiServiceInitOptions,
apiServiceFactory,
} from "../../platform/background/service-factories/api-service.factory";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../platform/background/service-factories/factory-options";
type DevicesApiServiceFactoryOptions = FactoryOptions;
export type DevicesApiServiceInitOptions = DevicesApiServiceFactoryOptions & ApiServiceInitOptions;
export function devicesApiServiceFactory(
cache: { devicesApiService?: DevicesApiServiceAbstraction } & CachedServices,
opts: DevicesApiServiceInitOptions
): Promise<DevicesApiServiceAbstraction> {
return factory(
cache,
"devicesApiService",
opts,
async () => new DevicesApiServiceImplementation(await apiServiceFactory(cache, opts))
);
}

View File

@@ -1,13 +1,9 @@
import { VaultTimeoutService as AbstractVaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService as AbstractVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import {
authServiceFactory,
AuthServiceInitOptions,
} from "../../auth/background/service-factories/auth-service.factory";
import {
keyConnectorServiceFactory,
KeyConnectorServiceInitOptions,
} from "../../auth/background/service-factories/key-connector-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
@@ -29,7 +25,7 @@ import {
StateServiceInitOptions,
stateServiceFactory,
} from "../../platform/background/service-factories/state-service.factory";
import VaultTimeoutService from "../../services/vaultTimeout/vaultTimeout.service";
import VaultTimeoutService from "../../services/vault-timeout/vault-timeout.service";
import {
cipherServiceFactory,
CipherServiceInitOptions,
@@ -64,7 +60,6 @@ export type VaultTimeoutServiceInitOptions = VaultTimeoutServiceFactoryOptions &
PlatformUtilsServiceInitOptions &
MessagingServiceInitOptions &
SearchServiceInitOptions &
KeyConnectorServiceInitOptions &
StateServiceInitOptions &
AuthServiceInitOptions &
VaultTimeoutSettingsServiceInitOptions;
@@ -86,7 +81,6 @@ export function vaultTimeoutServiceFactory(
await platformUtilsServiceFactory(cache, opts),
await messagingServiceFactory(cache, opts),
await searchServiceFactory(cache, opts),
await keyConnectorServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await authServiceFactory(cache, opts),
await vaultTimeoutSettingsServiceFactory(cache, opts),

View File

@@ -1,5 +1,5 @@
import { VaultTimeoutSettingsService as AbstractVaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService as AbstractVaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import {
policyServiceFactory,
@@ -9,6 +9,10 @@ import {
tokenServiceFactory,
TokenServiceInitOptions,
} from "../../auth/background/service-factories/token-service.factory";
import {
userVerificationServiceFactory,
UserVerificationServiceInitOptions,
} from "../../auth/background/service-factories/user-verification-service.factory";
import {
CryptoServiceInitOptions,
cryptoServiceFactory,
@@ -29,7 +33,8 @@ export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsService
CryptoServiceInitOptions &
TokenServiceInitOptions &
PolicyServiceInitOptions &
StateServiceInitOptions;
StateServiceInitOptions &
UserVerificationServiceInitOptions;
export function vaultTimeoutSettingsServiceFactory(
cache: { vaultTimeoutSettingsService?: AbstractVaultTimeoutSettingsService } & CachedServices,
@@ -44,7 +49,8 @@ export function vaultTimeoutSettingsServiceFactory(
await cryptoServiceFactory(cache, opts),
await tokenServiceFactory(cache, opts),
await policyServiceFactory(cache, opts),
await stateServiceFactory(cache, opts)
await stateServiceFactory(cache, opts),
await userVerificationServiceFactory(cache, opts)
)
);
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2023.7.1",
"version": "2023.8.0",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2023.7.1",
"version": "2023.8.0",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@@ -44,11 +44,20 @@ export class BrowserApi {
});
}
static async getTab(tabId: number) {
if (tabId == null) {
static async getTab(tabId: number): Promise<chrome.tabs.Tab> | null {
if (!tabId) {
return null;
}
return await chrome.tabs.get(tabId);
if (BrowserApi.manifestVersion === 3) {
return await chrome.tabs.get(tabId);
}
return new Promise((resolve) =>
chrome.tabs.get(tabId, (tab) => {
resolve(tab);
})
);
}
static async getTabFromCurrentWindow(): Promise<chrome.tabs.Tab> | null {

View File

@@ -1,6 +1,15 @@
interface BrowserPopoutWindowService {
openLoginPrompt(senderWindowId: number): Promise<void>;
closeLoginPrompt(): Promise<void>;
openUnlockPrompt(senderWindowId: number): Promise<void>;
closeUnlockPrompt(): Promise<void>;
openPasswordRepromptPrompt(
senderWindowId: number,
promptData: {
action: string;
cipherId: string;
senderTabId: number;
}
): Promise<void>;
closePasswordRepromptPrompt(): Promise<void>;
}
export { BrowserPopoutWindowService };

View File

@@ -11,20 +11,48 @@ class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface {
height: 800,
};
async openLoginPrompt(senderWindowId: number) {
await this.closeLoginPrompt();
await this.openPopoutWindow(
async openUnlockPrompt(senderWindowId: number) {
await this.closeUnlockPrompt();
await this.openSingleActionPopout(
senderWindowId,
"popup/index.html?uilocation=popout",
"loginPrompt"
"unlockPrompt"
);
}
async closeLoginPrompt() {
await this.closeSingleActionPopout("loginPrompt");
async closeUnlockPrompt() {
await this.closeSingleActionPopout("unlockPrompt");
}
private async openPopoutWindow(
async openPasswordRepromptPrompt(
senderWindowId: number,
{
cipherId,
senderTabId,
action,
}: {
cipherId: string;
senderTabId: number;
action: string;
}
) {
await this.closePasswordRepromptPrompt();
const promptWindowPath =
"popup/index.html#/view-cipher" +
"?uilocation=popout" +
`&cipherId=${cipherId}` +
`&senderTabId=${senderTabId}` +
`&action=${action}`;
await this.openSingleActionPopout(senderWindowId, promptWindowPath, "passwordReprompt");
}
async closePasswordRepromptPrompt() {
await this.closeSingleActionPopout("passwordReprompt");
}
private async openSingleActionPopout(
senderWindowId: number,
popupWindowURL: string,
singleActionPopoutKey: string

Some files were not shown because too many files have changed in this diff Show More