diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index bd96b388c6a..e91fba2e87a 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -312,6 +312,7 @@ jobs: cosign sign --yes ${images} - name: Scan Docker image + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 with: @@ -320,9 +321,12 @@ jobs: output-format: sarif - name: Upload Grype results to GitHub + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} - name: Log out of Docker run: docker logout diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index c5e189c4666..77b66ba8bf1 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -49,6 +49,8 @@ jobs: uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: cx_result.sarif + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} quality: name: Quality scan diff --git a/apps/browser/package.json b/apps/browser/package.json index e3bccf3f0df..5a8ddd03b41 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.3.0", + "version": "2025.3.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 4df2fd25b3c..a7439db6432 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -1668,7 +1668,7 @@ "message": "Zum Sortieren ziehen" }, "dragToReorder": { - "message": "Ziehen zum umsortieren" + "message": "Ziehen zum Umsortieren" }, "cfTypeText": { "message": "Text" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 400e70b864f..b88fc45493f 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -81,7 +81,7 @@ "message": "Podsjetnik glavne lozinke (neobavezno)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Ocjena jačine lozinke: $SCORE$", "placeholders": { "score": { "content": "$1", @@ -186,7 +186,7 @@ "message": "Kopiraj bilješke" }, "copy": { - "message": "Copy", + "message": "Kopiraj", "description": "Copy to clipboard" }, "fill": { @@ -380,7 +380,7 @@ "message": "Uredi mapu" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Uredi mapu: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -653,7 +653,7 @@ "message": "Web preglednik ne podržava jednostavno kopiranje međuspremnika. Umjesto toga ručno kopirajte." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Potvrdi svoj identitet" }, "weDontRecognizeThisDevice": { "message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom." @@ -869,19 +869,19 @@ "message": "Prijavi se u Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Unesi kôd poslan e-poštom" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Unesi kôd iz svoje aplikacije za autentifikaciju" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Za autentifikaciju dodirni svoj YubiKey" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Prati korake za dovršetak prijave." }, "restartRegistration": { "message": "Ponovno pokreni registraciju" @@ -905,7 +905,7 @@ "message": "Ne" }, "location": { - "message": "Location" + "message": "Lokacija" }, "unexpectedError": { "message": "Došlo je do neočekivane pogreške." @@ -1040,7 +1040,7 @@ "message": "Klikni stavke za auto-ispunu na prikazu trezora" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "Kliknite stavku u prijedlogu auto-ispune za popunjavanje" }, "clearClipboard": { "message": "Očisti međuspremnik", @@ -1057,7 +1057,7 @@ "message": "Spremi" }, "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "message": "$USERNAME$ spremljeno u Bitwarden.", "placeholders": { "username": { "content": "$1" @@ -1066,7 +1066,7 @@ "description": "Shown to user after login is saved." }, "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "message": "$USERNAME$ ažurirano u Bitwardenu.", "placeholders": { "username": { "content": "$1" @@ -1075,35 +1075,35 @@ "description": "Shown to user after login is updated." }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "Spremi novu prijavu", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "Ažuriraj prijavu", "description": "Button text for updating an existing login entry." }, "saveLoginPrompt": { - "message": "Save login?", + "message": "Spremiti prijavu?", "description": "Prompt asking the user if they want to save their login details." }, "updateLoginPrompt": { - "message": "Update existing login?", + "message": "Ažurirati postojeću prijavu?", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "Prijava spremljena", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "Prijava ažurirana", "description": "Message displayed when login details are successfully updated." }, "saveFailure": { - "message": "Error saving", + "message": "Greška kod spremanja", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! We couldn't save this. Try entering the details manually.", + "message": "Ups! Nismo mogli ovo spasiti. Pokušaj ručno unijeti detalje.", "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { @@ -1422,7 +1422,7 @@ "message": "Zapamti me" }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Ne pitaj na ovom uređaju idućih 30 dana" }, "sendVerificationCodeEmailAgain": { "message": "Ponovno slanje kontrolnog koda e-poštom" @@ -1431,11 +1431,11 @@ "message": "Koristiti drugi način prijave dvostrukom autentifikacijom" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Odaberi drugi način", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Koristi kôd za oporavak" }, "insertYubiKey": { "message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku." @@ -1450,16 +1450,16 @@ "message": "Otvori novu karticu" }, "openInNewTab": { - "message": "Open in new tab" + "message": "Otvori u novoj kartici" }, "webAuthnAuthenticate": { "message": "Ovjeri WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "Pročitaj sigurnosni ključ" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "Čekanje na interakciju sa sigurnosnim ključem..." }, "loginUnavailable": { "message": "Prijava nije dostupna" @@ -1474,7 +1474,7 @@ "message": "Mogućnosti prijave dvostrukom autentifikacijom" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Odaberi način prijave dvostrukom autentifikacijom" }, "recoveryCodeDesc": { "message": "Izgubljen je pristup uređaju za dvostruku autentifikaciju? Koristi svoj kôd za oporavak za onemogućavanje svih pružatelja usluga dvostruke autentifikacije na tvojem računu." @@ -1668,7 +1668,7 @@ "message": "Povuci za sortiranje" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Povuci za premještanje" }, "cfTypeText": { "message": "Tekst" @@ -2164,7 +2164,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "Prilagodba trezora" }, "vaultTimeoutAction": { "message": "Nakon isteka trezora" @@ -2173,13 +2173,13 @@ "message": "Radnja nakon isteka " }, "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" + "message": "Nove mogućnosti prilagodbe" }, "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + "message": "Prilagodi svoje iskustvo trezora brzim kopiranjem, kompaktnim načinom rada i više!" }, "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" + "message": "Pogledaj sve postavke izgleda" }, "lock": { "message": "Zaključaj", @@ -2476,7 +2476,7 @@ "message": "Rizične lozinke" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "message": "$ORGANIZATION$ traži da promijeniš jednu rizičnu lozinku.", "placeholders": { "organization": { "content": "$1", @@ -2485,7 +2485,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "Broj rizičnih lozinki koje $ORGANIZATION$ traži da promijeniš: $COUNT$.", "placeholders": { "organization": { "content": "$1", @@ -2498,7 +2498,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "Broj rizičnih lozinki koje tvoja orgnaizacija traži da promijeniš: $COUNT$.", "placeholders": { "count": { "content": "$1", @@ -2525,34 +2525,34 @@ "message": "Ažuriraj svoje postavke kako za brzu auto-ispunu svojih lozinki i generiranje novih" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "Pregledaj rizične prijave" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "Pregdledaj rizične lozinke" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "Lozinke tvoje organizacije su rizične jer su slabe, nanovo korištene i/ili iscurile.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAlt": { - "message": "Illustration of a list of logins that are at-risk" + "message": "Ilustracija liste rizičnih prijava" }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "Brzo generiraj jake, jedinstvene lozinke koristeći Bitwarden dijalog auto-ispune direktno na stranici.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAlt": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + "message": "Ilustracija Bitwarden dijalog auto-ispune s prikazom generirane lozinke" }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Ažuriraj u Bitwardenu" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "Bitwarden će te pitati treba li ažurirati lozinku u upravitelju lozinki.", "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAlt": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + "message": "Ilustracija Bitwarden upita za ažuriranje prijave" }, "turnOnAutofill": { "message": "Uključi auto-ispunu" @@ -3168,7 +3168,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -3178,7 +3178,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -4080,7 +4080,7 @@ "message": "Aktivni račun" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "Bitwarden račun" }, "availableAccounts": { "message": "Dostupni računi" @@ -4281,7 +4281,7 @@ } }, "copyFieldValue": { - "message": "Copy $FIELD$, $VALUE$", + "message": "Kopiraj $FIELD$, $VALUE$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4671,7 +4671,7 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "Ponovno poredaj URI. Koristi tipke sa strelicom za pomicanje stavke gore ili dolje." }, "reorderFieldUp": { "message": "$LABEL$ pomaknut gore, pozicija $INDEX$ od $LENGTH$", @@ -5096,31 +5096,31 @@ "message": "Ekstra široko" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Unesena lozinka nije ispravna." }, "importSshKey": { - "message": "Import" + "message": "Uvoz" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Potvrdi lozinku" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Unesi lozinku za SSH ključ." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Unesi lozinku" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH ključ nije valjan" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "Tip SSH ključa nije podržan" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Uvezi ključ iz međuspremnika" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "SSH ključ uspješno uvezen" }, "cannotRemoveViewOnlyCollections": { "message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$", @@ -5138,6 +5138,6 @@ "message": "Za korištenje biometrijskog otključavanja ažuriraj desktop aplikaciju ili nemogući otključavanje otiskom prsta u desktop aplikaciji." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "Promijeni rizičnu lozinku" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 33f44c60579..0146fb2a000 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -186,7 +186,7 @@ "message": "Salin catatan" }, "copy": { - "message": "Copy", + "message": "Salin", "description": "Copy to clipboard" }, "fill": { @@ -380,7 +380,7 @@ "message": "Sunting Folder" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Sunting folder: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -462,16 +462,16 @@ "message": "Buat frasa sandi" }, "passwordGenerated": { - "message": "Password generated" + "message": "Kata sandi dibuat" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "Frasa sandi dibuat" }, "usernameGenerated": { - "message": "Username generated" + "message": "Nama pengguna dibuat" }, "emailGenerated": { - "message": "Email generated" + "message": "Surel dibuat" }, "regeneratePassword": { "message": "Buat Ulang Kata Sandi" @@ -653,7 +653,7 @@ "message": "Peramban Anda tidak mendukung menyalin clipboard dengan mudah. Salin secara manual." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verifikasikan identitas Anda" }, "weDontRecognizeThisDevice": { "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." @@ -905,7 +905,7 @@ "message": "Tidak" }, "location": { - "message": "Location" + "message": "Lokasi" }, "unexpectedError": { "message": "Terjadi kesalahan yang tak diduga." @@ -2461,7 +2461,7 @@ "message": "Change this in settings" }, "change": { - "message": "Change" + "message": "Ubah" }, "changeButtonTitle": { "message": "Change password - $ITEMNAME$", @@ -4784,7 +4784,7 @@ "message": "Text Sends" }, "accountActions": { - "message": "Account actions" + "message": "Tindakan akun" }, "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" @@ -4793,22 +4793,22 @@ "message": "Show quick copy actions on Vault" }, "systemDefault": { - "message": "System default" + "message": "Baku sistem" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "Persyaratan kebijakan perusahaan telah diterapkan ke pengaturan ini" }, "sshPrivateKey": { - "message": "Private key" + "message": "Kunci privat" }, "sshPublicKey": { - "message": "Public key" + "message": "Kunci publik" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Sidik jari" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipe kunci" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4823,7 +4823,7 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "Coba lagi" }, "vaultCustomTimeoutMinimum": { "message": "Minimum custom timeout is 1 minute." @@ -5075,16 +5075,16 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Tidak" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Ya, saya dapat mengakses surel saya secara handla" }, "turnOnTwoStepLogin": { "message": "Turn on two-step login" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Ubah surel akun" }, "extensionWidth": { "message": "Lebar ekstensi" @@ -5096,31 +5096,31 @@ "message": "Ekstra lebar" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Kata sandi yang Anda masukkan tidak benar." }, "importSshKey": { - "message": "Import" + "message": "Impor" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Konfirmasi kata sandi" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Masukkan kata sandi untuk kunci SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Masukkan kata sandi" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "Kunci SSH tidak valid" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "Tipe kunci SSH tidak didukung" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Impor kunci dari papan klip" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "Kunci SSH sukses diimpor" }, "cannotRemoveViewOnlyCollections": { "message": "Anda tidak dapat menghapus koleksi dengan izin hanya lihat: $COLLECTIONS$", @@ -5132,12 +5132,12 @@ } }, "updateDesktopAppOrDisableFingerprintDialogTitle": { - "message": "Please update your desktop application" + "message": "Harap perbarui aplikasi desktop Anda" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + "message": "Untuk memakai pembuka kunci biometrik, harap perbarui aplikasi desktop Anda, atau matikan buka kunci sidik jari dalam pengaturan desktop." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "Ubah kata sandi yang berrisiko" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 65d8d6f740f..1c2b09ca3e3 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -81,7 +81,7 @@ "message": "Hoofdwachtwoordhint (optioneel)" }, "passwordStrengthScore": { - "message": "Score wachtwoordsterkte $SCORE$", + "message": "Wachtwoordsterkte score $SCORE$", "placeholders": { "score": { "content": "$1", @@ -878,10 +878,10 @@ "message": "Druk op je YubiKey om te verifiëren" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Jouw account vereist Duo-tweestapsaanmelding. Volg de onderstaande stappen om het inloggen te voltooien." + "message": "Jouw account vereist Duo tweestapslogin. Volg de onderstaande stappen om het inloggen te voltooien." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Volg de onderstaande stappen om in te loggen." + "message": "Volg de onderstaande stappen om het inloggen af te ronden." }, "restartRegistration": { "message": "Registratie herstarten" @@ -1040,7 +1040,7 @@ "message": "Klik op items om automatisch in te vullen op de kluisweergave" }, "clickToAutofill": { - "message": "Klik op gesuggereerde items om deze automatisch invullen" + "message": "Klik items in de automatisch invullen suggestie om in te vullen" }, "clearClipboard": { "message": "Klembord wissen", @@ -1075,7 +1075,7 @@ "description": "Shown to user after login is updated." }, "saveAsNewLoginAction": { - "message": "Als nieuwe login opslaan", + "message": "Opslaan als nieuwe login", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { @@ -1422,7 +1422,7 @@ "message": "Mijn gegevens onthouden" }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "30 dagen niet meer vragen op dit apparaat" + "message": "30 dagen niet opnieuw vragen op dit apparaat" }, "sendVerificationCodeEmailAgain": { "message": "E-mail met verificatiecode opnieuw versturen" @@ -1459,7 +1459,7 @@ "message": "Beveiligingssleutel lezen" }, "awaitingSecurityKeyInteraction": { - "message": "Wacht op interactie met beveiligingssleutel..." + "message": "Wacht op interactie met beveiligingssleutel…" }, "loginUnavailable": { "message": "Login niet beschikbaar" @@ -1474,7 +1474,7 @@ "message": "Opties voor tweestapsaanmelding" }, "selectTwoStepLoginMethod": { - "message": "Kies methode voor tweestapsaanmelding" + "message": "Kies methode voor tweestapslogin" }, "recoveryCodeDesc": { "message": "Ben je de toegang tot al je tweestapsaanbieders verloren? Gebruik dan je herstelcode om alle tweestapsaanbieders op je account uit te schakelen." @@ -2164,7 +2164,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Kluis-aanpassingen" + "message": "Kluis aanpassingen" }, "vaultTimeoutAction": { "message": "Actie bij time-out" @@ -2176,10 +2176,10 @@ "message": "Nieuwe aanpassingsopties" }, "newCustomizationOptionsCalloutContent": { - "message": "Personaliseer je kluiservaring met snelle kopieeracties, compacte modus en meer!" + "message": "Pas je kluis ervaring aan met snelle kopieeracties, compacte modus en meer!" }, "newCustomizationOptionsCalloutLink": { - "message": "Alle personalisatie-instellingen bekijken" + "message": "Alle weergave-instellingen bekijken" }, "lock": { "message": "Vergrendelen", @@ -2498,7 +2498,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Je organisatie(s) vragen je de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.", + "message": "Je organisaties vragen je de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.", "placeholders": { "count": { "content": "$1", @@ -2531,14 +2531,14 @@ "message": "Risicovolle wachtwoorden bekijken" }, "reviewAtRiskLoginsSlideDesc": { - "message": "De wachtwoorden van je organisatie zijn in gevaar omdat ze zwak, hergebruikt en/of blootgelegd zijn.", + "message": "De wachtwoorden van je organisatie zijn in gevaar omdat ze zwak, hergebruikt en/of gelekt zijn.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAlt": { "message": "Voorbeeld van een lijst van risicovolle logins" }, "generatePasswordSlideDesc": { - "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwaren op de risicovolle website.", + "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwarden op de risicovolle website.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAlt": { @@ -2552,7 +2552,7 @@ "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAlt": { - "message": "Voorbeeld van een Bitwarden-melding die de gebruiker aanspoort tot het bijwerken van de login" + "message": "Voorbeeld van een Bitwarden melding die de gebruiker aanspoort tot het bijwerken van de login" }, "turnOnAutofill": { "message": "Automatisch invullen inschakelen" @@ -4080,7 +4080,7 @@ "message": "Actief account" }, "bitwardenAccount": { - "message": "Bitwarden-account" + "message": "Bitwarden account" }, "availableAccounts": { "message": "Beschikbare accounts" @@ -5096,7 +5096,7 @@ "message": "Extra breed" }, "sshKeyWrongPassword": { - "message": "Het door jou ingevoerde wachtwoord is onjuist." + "message": "Het wachtwoord dat je hebt ingevoerd is onjuist." }, "importSshKey": { "message": "Importeren" @@ -5117,7 +5117,7 @@ "message": "Het type SSH-sleutel is niet ondersteund" }, "importSshKeyFromClipboard": { - "message": "Sleutel van klembord importeren" + "message": "Sleutel importeren van klembord" }, "sshKeyImported": { "message": "SSH-sleutel succesvol geïmporteerd" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 4595b20ddbf..bdfaeb92531 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2208,7 +2208,7 @@ "message": "项目已恢复" }, "alreadyHaveAccount": { - "message": "已经拥有账户了吗?" + "message": "已经有账户了吗?" }, "vaultTimeoutLogOutConfirmation": { "message": "超时后注销账户将解除对密码库的所有访问权限,并需要进行在线身份验证。确定使用此设置吗?" @@ -3628,7 +3628,7 @@ "message": "正在获取选项..." }, "multiSelectNotFound": { - "message": "未找到任何条目" + "message": "未找到任何项目" }, "multiSelectClearAll": { "message": "清除全部" @@ -4209,7 +4209,7 @@ "message": "建议的项目" }, "autofillSuggestionsTip": { - "message": "将此站点保存为登录项目以用于自动填充" + "message": "为这个站点保存一个登录项目以自动填充" }, "yourVaultIsEmpty": { "message": "您的密码库是空的" diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index e02e3d8d951..d474e303336 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -59,6 +60,7 @@ describe("NotificationBackground", () => { const themeStateService = mock(); const configService = mock(); const accountService = mock(); + const organizationService = mock(); const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, @@ -73,18 +75,19 @@ describe("NotificationBackground", () => { authService.activeAccountStatus$ = activeAccountStatusMock$; accountService.activeAccount$ = activeAccountSubject; notificationBackground = new NotificationBackground( + accountService, + authService, autofillService, cipherService, - authService, - policyService, - folderService, - userNotificationSettingsService, + configService, domainSettingsService, environmentService, + folderService, logService, + organizationService, + policyService, themeStateService, - configService, - accountService, + userNotificationSettingsService, ); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 11037e7e261..50e0ee0aa75 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -63,46 +64,48 @@ export default class NotificationBackground { ExtensionCommand.AutofillIdentity, ]); private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = { - unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), - bgGetFolderData: () => this.getFolderData(), - bgCloseNotificationBar: ({ message, sender }) => - this.handleCloseNotificationBarMessage(message, sender), + bgAddLogin: ({ message, sender }) => this.addLogin(message, sender), bgAdjustNotificationBar: ({ message, sender }) => this.handleAdjustNotificationBarMessage(message, sender), - bgAddLogin: ({ message, sender }) => this.addLogin(message, sender), bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender), - bgRemoveTabFromNotificationQueue: ({ sender }) => - this.removeTabFromNotificationQueue(sender.tab), - bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), - bgNeverSave: ({ sender }) => this.saveNever(sender.tab), - collectPageDetailsResponse: ({ message }) => - this.handleCollectPageDetailsResponseMessage(message), - bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), - checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), - bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), + bgCloseNotificationBar: ({ message, sender }) => + this.handleCloseNotificationBarMessage(message, sender), + bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(), + bgGetDecryptedCiphers: () => this.getNotificationCipherData(), bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(), bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(), bgGetExcludedDomains: () => this.getExcludedDomains(), - bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(), + bgGetFolderData: () => this.getFolderData(), + bgGetOrgData: () => this.getOrgData(), + bgNeverSave: ({ sender }) => this.saveNever(sender.tab), + bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab), + bgRemoveTabFromNotificationQueue: ({ sender }) => + this.removeTabFromNotificationQueue(sender.tab), + bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), + bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), + bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), + checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), + collectPageDetailsResponse: ({ message }) => + this.handleCollectPageDetailsResponseMessage(message), getWebVaultUrlForNotification: () => this.getWebVaultUrl(), notificationRefreshFlagValue: () => this.getNotificationFlag(), - bgGetDecryptedCiphers: () => this.getNotificationCipherData(), - bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab), + unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), }; constructor( + private accountService: AccountService, + private authService: AuthService, private autofillService: AutofillService, private cipherService: CipherService, - private authService: AuthService, - private policyService: PolicyService, - private folderService: FolderService, - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, + private configService: ConfigService, private domainSettingsService: DomainSettingsService, private environmentService: EnvironmentService, + private folderService: FolderService, private logService: LogService, + private organizationService: OrganizationService, + private policyService: PolicyService, private themeStateService: ThemeStateService, - private configService: ConfigService, - private accountService: AccountService, + private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, ) {} init() { @@ -744,6 +747,26 @@ export default class NotificationBackground { ); } + /** + * Returns the first value found from the organization service organizations$ observable. + */ + private async getOrgData() { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + const organizations = await firstValueFrom( + this.organizationService.organizations$(activeUserId), + ); + return organizations.map((org) => { + const { id, name, productTierType } = org; + return { + id, + name, + productTierType, + }; + }); + } + /** * Handles the unlockCompleted extension message. Will close the notification bar * after an attempted autofill action, and retry the autofill action if the message diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index 2433381dfba..66b580bde43 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -16,12 +16,12 @@ const { css } = createEmotion({ }); export function NotificationBody({ - ciphers, + ciphers = [], notificationType, theme = ThemeTypes.Light, handleEditOrUpdateAction, }: { - ciphers: NotificationCipherData[]; + ciphers?: NotificationCipherData[]; customClasses?: string[]; notificationType?: NotificationType; theme: Theme; diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts index 1eb0a4ac5f4..8661f5957e1 100644 --- a/apps/browser/src/autofill/content/components/notification/button-row.ts +++ b/apps/browser/src/autofill/content/components/notification/button-row.ts @@ -60,23 +60,18 @@ export function NotificationButtonRow({ ) : ([] as Option[]); - const noFolderOption: Option = { - default: true, - icon: Folder, - text: "No folder", // @TODO localize - value: "0", - }; const folderOptions: Option[] = folders?.length - ? folders.reduce( + ? folders.reduce( (options, { id, name }: FolderView) => [ ...options, { icon: Folder, text: name, - value: id, + value: id === null ? "0" : id, + default: id === null, }, ], - [noFolderOption], + [], ) : []; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index f98ef795749..8d80dc9fb50 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -9,6 +9,7 @@ import { NotificationType, } from "../../../notification/abstractions/notification-bar"; import { NotificationCipherData } from "../cipher/types"; +import { FolderView, OrgView } from "../common-types"; import { themes, spacing } from "../constants/styles"; import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body"; @@ -20,20 +21,24 @@ import { export function NotificationContainer({ handleCloseNotification, + handleEditOrUpdateAction, + handleSaveAction, + ciphers, + folders, i18n, + organizations, theme = ThemeTypes.Light, type, - ciphers, - handleSaveAction, - handleEditOrUpdateAction, }: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void; handleSaveAction: (e: Event) => void; handleEditOrUpdateAction: (e: Event) => void; } & { + ciphers?: NotificationCipherData[]; + folders?: FolderView[]; i18n: { [key: string]: string }; + organizations?: OrgView[]; type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` - ciphers: NotificationCipherData[]; }) { const headerMessage = getHeaderMessage(i18n, type); const showBody = true; @@ -42,8 +47,8 @@ export function NotificationContainer({
${NotificationHeader({ handleCloseNotification, - standalone: showBody, message: headerMessage, + standalone: showBody, theme, })} ${showBody @@ -56,9 +61,11 @@ export function NotificationContainer({ : null} ${NotificationFooter({ handleSaveAction, - theme, - notificationType: type, + folders, i18n, + notificationType: type, + organizations, + theme, })}
`; diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index ae489ea956b..bf3d562a0ef 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -880,7 +880,7 @@ async function loadNotificationBar() { const baseStyle = useComponentBar ? isNotificationFresh - ? "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(100%); opacity:0;" + ? "height: calc(276px + 50px); width: 450px; right: 0; transform:translateX(100%); opacity:0;" : "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(0%); opacity:1;" : "height: 42px; width: 100%;"; @@ -910,7 +910,7 @@ async function loadNotificationBar() { function getFrameStyle(useComponentBar: boolean): string { return ( (useComponentBar - ? "height: calc(276px + 25px); width: 450px; right: 0;" + ? "height: calc(276px + 50px); width: 450px; right: 0;" : "height: 42px; width: 100%; left: 0;") + " top: 0; padding: 0; position: fixed;" + " z-index: 2147483647; visibility: visible;" diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index cb14a86dffa..6e7427e3a38 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,5 +1,8 @@ import { Theme } from "@bitwarden/common/platform/enums"; +import { NotificationCipherData } from "../../../autofill/content/components/cipher/types"; +import { FolderView, OrgView } from "../../../autofill/content/components/common-types"; + const NotificationTypes = { Add: "add", Change: "change", @@ -9,21 +12,24 @@ const NotificationTypes = { type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; type NotificationBarIframeInitData = { - type?: string; // @TODO use `NotificationType` - isVaultLocked?: boolean; - theme?: Theme; - removeIndividualVault?: boolean; - importType?: string; applyRedesign?: boolean; + ciphers?: NotificationCipherData[]; + folders?: FolderView[]; + importType?: string; + isVaultLocked?: boolean; launchTimestamp?: number; + organizations?: OrgView[]; + removeIndividualVault?: boolean; + theme?: Theme; + type?: string; // @TODO use `NotificationType` }; type NotificationBarWindowMessage = { + cipherId?: string; command: string; error?: string; initData?: NotificationBarIframeInitData; username?: string; - cipherId?: string; }; type NotificationBarWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 617b1e58c14..d17c008372d 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -5,6 +5,8 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; +import { NotificationCipherData } from "../content/components/cipher/types"; +import { OrgView } from "../content/components/common-types"; import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container"; import { NotificationContainer } from "../content/components/notification/container"; import { buildSvgDomElement } from "../utils"; @@ -115,7 +117,7 @@ function setElementText(template: HTMLTemplateElement, elementId: string, text: } } -function initNotificationBar(message: NotificationBarWindowMessage) { +async function initNotificationBar(message: NotificationBarWindowMessage) { const { initData } = message; if (!initData) { return; @@ -131,7 +133,23 @@ function initNotificationBar(message: NotificationBarWindowMessage) { // Current implementations utilize a require for scss files which creates the need to remove the node. document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove()); - sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => { + await Promise.all([ + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetOrgData" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetFolderData" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), + ), + ]).then(([organizations, folders, ciphers]) => { + notificationBarIframeInitData = { + ...notificationBarIframeInitData, + folders, + ciphers, + organizations, + }; // @TODO use context to avoid prop drilling return render( NotificationContainer({ @@ -142,7 +160,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) { handleSaveAction, handleEditOrUpdateAction, i18n, - ciphers: cipherData, }), document.body, ); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f8f86e6a277..74fa6acdf79 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1173,18 +1173,19 @@ export default class MainBackground { () => this.generatePasswordToClipboard(), ); this.notificationBackground = new NotificationBackground( + this.accountService, + this.authService, this.autofillService, this.cipherService, - this.authService, - this.policyService, - this.folderService, - this.userNotificationSettingsService, + this.configService, this.domainSettingsService, this.environmentService, + this.folderService, this.logService, + this.organizationService, + this.policyService, this.themeStateService, - this.configService, - this.accountService, + this.userNotificationSettingsService, ); this.overlayNotificationsBackground = new OverlayNotificationsBackground( diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 5bfca440b99..4510c2f342d 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.3.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 1e2ac1812ca..fc897c1b1c3 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.3.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index b7ff0718b35..f986bdfca31 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -440,6 +440,32 @@ const mapAddEditCipherInfoToInitialValues = ( initialValues.name = cipher.name; } + if (cipher.type === CipherType.Card) { + const card = cipher.card; + + if (card != null) { + if (card.cardholderName != null) { + initialValues.cardholderName = card.cardholderName; + } + + if (card.number != null) { + initialValues.number = card.number; + } + + if (card.expMonth != null) { + initialValues.expMonth = card.expMonth; + } + + if (card.expYear != null) { + initialValues.expYear = card.expYear; + } + + if (card.code != null) { + initialValues.code = card.code; + } + } + } + if (cipher.type === CipherType.Login) { const login = cipher.login; diff --git a/apps/desktop/desktop_native/macos_provider/src/assertion.rs b/apps/desktop/desktop_native/macos_provider/src/assertion.rs index 762ceaaed48..c5b43bb87fa 100644 --- a/apps/desktop/desktop_native/macos_provider/src/assertion.rs +++ b/apps/desktop/desktop_native/macos_provider/src/assertion.rs @@ -2,11 +2,22 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use crate::{BitwardenError, Callback, UserVerification}; +use crate::{BitwardenError, Callback, Position, UserVerification}; #[derive(uniffi::Record, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PasskeyAssertionRequest { + rp_id: String, + client_data_hash: Vec, + user_verification: UserVerification, + allowed_credentials: Vec>, + window_xy: Position, + //extension_input: Vec, TODO: Implement support for extensions +} + +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasskeyAssertionWithoutUserInterfaceRequest { rp_id: String, credential_id: Vec, user_name: String, @@ -14,6 +25,7 @@ pub struct PasskeyAssertionRequest { record_identifier: Option, client_data_hash: Vec, user_verification: UserVerification, + window_xy: Position, } #[derive(uniffi::Record, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs index 5623436d874..8f2499ae68d 100644 --- a/apps/desktop/desktop_native/macos_provider/src/lib.rs +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -15,7 +15,10 @@ uniffi::setup_scaffolding!(); mod assertion; mod registration; -use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback}; +use assertion::{ + PasskeyAssertionRequest, PasskeyAssertionWithoutUserInterfaceRequest, + PreparePasskeyAssertionCallback, +}; use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback}; #[derive(uniffi::Enum, Debug, Serialize, Deserialize)] @@ -26,6 +29,13 @@ pub enum UserVerification { Discouraged, } +#[derive(uniffi::Record, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Position { + pub x: i32, + pub y: i32, +} + #[derive(Debug, uniffi::Error, Serialize, Deserialize)] pub enum BitwardenError { Internal(String), @@ -141,6 +151,14 @@ impl MacOSProviderClient { ) { self.send_message(request, Box::new(callback)); } + + pub fn prepare_passkey_assertion_without_user_interface( + &self, + request: PasskeyAssertionWithoutUserInterfaceRequest, + callback: Arc, + ) { + self.send_message(request, Box::new(callback)); + } } #[derive(Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/macos_provider/src/registration.rs b/apps/desktop/desktop_native/macos_provider/src/registration.rs index d484af58b6c..9e697b75c16 100644 --- a/apps/desktop/desktop_native/macos_provider/src/registration.rs +++ b/apps/desktop/desktop_native/macos_provider/src/registration.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use crate::{BitwardenError, Callback, UserVerification}; +use crate::{BitwardenError, Callback, Position, UserVerification}; #[derive(uniffi::Record, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -13,6 +13,7 @@ pub struct PasskeyRegistrationRequest { client_data_hash: Vec, user_verification: UserVerification, supported_algorithms: Vec, + window_xy: Position, } #[derive(uniffi::Record, Serialize, Deserialize)] diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 92f31cf5f89..ca1fe29e254 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -118,6 +118,10 @@ export declare namespace autofill { Required = 'required', Discouraged = 'discouraged' } + export interface Position { + x: number + y: number + } export interface PasskeyRegistrationRequest { rpId: string userName: string @@ -125,6 +129,7 @@ export declare namespace autofill { clientDataHash: Array userVerification: UserVerification supportedAlgorithms: Array + windowXy: Position } export interface PasskeyRegistrationResponse { rpId: string @@ -133,6 +138,13 @@ export declare namespace autofill { attestationObject: Array } export interface PasskeyAssertionRequest { + rpId: string + clientDataHash: Array + userVerification: UserVerification + allowedCredentials: Array> + windowXy: Position + } + export interface PasskeyAssertionWithoutUserInterfaceRequest { rpId: string credentialId: Array userName: string @@ -140,6 +152,7 @@ export declare namespace autofill { recordIdentifier?: string clientDataHash: Array userVerification: UserVerification + windowXy: Position } export interface PasskeyAssertionResponse { rpId: string @@ -156,7 +169,7 @@ export declare namespace autofill { * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. * @param callback This function will be called whenever a message is received from a client. */ - static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void): Promise + static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void): Promise /** Return the path to the IPC server. */ getPath(): string /** Stop the IPC server. */ diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index d0c859d427c..f02be2b27b6 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -515,6 +515,14 @@ pub mod autofill { pub value: Result, } + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Position { + pub x: i32, + pub y: i32, + } + #[napi(object)] #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -525,6 +533,7 @@ pub mod autofill { pub client_data_hash: Vec, pub user_verification: UserVerification, pub supported_algorithms: Vec, + pub window_xy: Position, } #[napi(object)] @@ -541,6 +550,18 @@ pub mod autofill { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub allowed_credentials: Vec>, + pub window_xy: Position, + //extension_input: Vec, TODO: Implement support for extensions + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionWithoutUserInterfaceRequest { pub rp_id: String, pub credential_id: Vec, pub user_name: String, @@ -548,6 +569,7 @@ pub mod autofill { pub record_identifier: Option, pub client_data_hash: Vec, pub user_verification: UserVerification, + pub window_xy: Position, } #[napi(object)] @@ -592,6 +614,13 @@ pub mod autofill { (u32, u32, PasskeyAssertionRequest), ErrorStrategy::CalleeHandled, >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" + )] + assertion_without_user_interface_callback: ThreadsafeFunction< + (u32, u32, PasskeyAssertionWithoutUserInterfaceRequest), + ErrorStrategy::CalleeHandled, + >, ) -> napi::Result { let (send, mut recv) = tokio::sync::mpsc::channel::(32); tokio::spawn(async move { @@ -628,6 +657,25 @@ pub mod autofill { } } + match serde_json::from_str::< + PasskeyMessage, + >(&message) + { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + + assertion_without_user_interface_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + println!("[ERROR] Error deserializing message1: {e}"); + } + } + match serde_json::from_str::>( &message, ) { diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib index ace3497a58b..1e47cc54de2 100644 --- a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -1,22 +1,23 @@ - + - + + - + - +