1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Vincent Salucci
2022-08-29 10:48:20 -05:00
279 changed files with 4467 additions and 2169 deletions

View File

@@ -1,5 +1,6 @@
**/build **/build
**/dist **/dist
**/coverage
.angular .angular
**/node_modules **/node_modules
@@ -21,3 +22,7 @@ apps/web/src/theme.js
apps/web/tailwind.config.js apps/web/tailwind.config.js
apps/cli/config/config.js apps/cli/config/config.js
tailwind.config.js
libs/components/tailwind.config.base.js
libs/components/tailwind.config.js

View File

@@ -4,13 +4,19 @@
"browser": true, "browser": true,
"webextensions": true "webextensions": true
}, },
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.eslint.json"],
"sourceType": "module"
},
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:import/recommended", "plugin:import/recommended",
"plugin:import/typescript", "plugin:import/typescript",
"prettier" "prettier",
"plugin:rxjs/recommended"
], ],
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled
@@ -50,6 +56,7 @@
], ],
"pathGroupsExcludedImportTypes": ["builtin"] "pathGroupsExcludedImportTypes": ["builtin"]
} }
] ],
"rxjs-angular/prefer-takeuntil": "error"
} }
} }

View File

@@ -138,10 +138,13 @@ jobs:
run: npm ci run: npm ci
working-directory: ./ working-directory: ./
- name: Build & Test - name: Build
run: | run: |
npm run dist npm run dist
npm run test
- name: Build Manifest v3
run: |
npm run dist:chrome:mv3
- name: Gulp - name: Gulp
run: gulp ci run: gulp ci
@@ -181,6 +184,13 @@ jobs:
path: apps/browser/dist/dist-chrome.zip path: apps/browser/dist/dist-chrome.zip
if-no-files-found: error if-no-files-found: error
- name: Upload Chrome MV3 artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip
path: apps/browser/dist/dist-chrome-MV3.zip
if-no-files-found: error
- name: Upload Firefox artifact - name: Upload Firefox artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with: with:

View File

@@ -155,9 +155,6 @@ jobs:
run: npm ci run: npm ci
working-directory: ./ working-directory: ./
- name: Run tests
run: npm run test
- name: Build & Package - name: Build & Package
run: npm run dist --quiet run: npm run dist --quiet

View File

@@ -652,7 +652,7 @@ jobs:
run: npm run build run: npm run build
- name: Download artifact from hotfix-rc - name: Download artifact from hotfix-rc
if: github.ref == 'refs/heads/hotfix-rc') if: github.ref == 'refs/heads/hotfix-rc'
uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0
with: with:
workflow: build-browser.yml workflow: build-browser.yml
@@ -854,7 +854,7 @@ jobs:
run: npm run build run: npm run build
- name: Download artifact from hotfix-rc - name: Download artifact from hotfix-rc
if: github.ref == 'refs/heads/hotfix-rc') if: github.ref == 'refs/heads/hotfix-rc'
uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0
with: with:
workflow: build-browser.yml workflow: build-browser.yml

View File

@@ -94,7 +94,7 @@ jobs:
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2 uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.2
- name: Download latest cloud asset - name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8 uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
with: with:
workflow: build-web.yml workflow: build-web.yml
path: apps/web path: apps/web

48
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: Run tests
on:
workflow_dispatch:
pull_request:
branches-ignore:
- 'l10n_master'
- 'cf-pages'
paths:
- 'apps/**'
- 'libs/**'
- '*'
- '!*.md'
- '!*.txt'
- '.github/workflows/test.yml'
defaults:
run:
shell: bash
jobs:
test:
name: Run tests
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: '16'
- name: Print environment
run: |
node --version
npm --version
- name: Install Node dependencies
run: npm ci
- name: Run tests
run: |
export NODE_OPTIONS=--max_old_space_size=6144
npm run test

1
.gitignore vendored
View File

@@ -39,3 +39,4 @@ coverage
# Storybook # Storybook
documentation.json documentation.json
.eslintcache

View File

@@ -3,6 +3,7 @@
**/dist **/dist
**/coverage **/coverage
.angular .angular
documentation.json
# External libraries / auto synced locales # External libraries / auto synced locales
apps/browser/src/_locales apps/browser/src/_locales

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cSpell.words": ["Popout", "Reprompt", "takeuntil"]
}

View File

@@ -5,8 +5,14 @@
<a href="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:master" target="_blank"> <a href="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=master" alt="Github Workflow browser build on master" /> <img src="https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=master" alt="Github Workflow browser build on master" />
</a> </a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-cli.yml/badge.svg?branch=master" alt="Github Workflow CLI build on master" />
</a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:master" target="_blank"> <a href="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=master" alt="Github Workflow desktop build on master" /> <img src="https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=master" alt="Github Workflow desktop build on master" />
</a>
<a href="https://github.com/bitwarden/clients/actions/workflows/build-web.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/clients/actions/workflows/build-web.yml/badge.svg?branch=master" alt="Github Workflow web build on master" />
</a> </a>
<a href="https://gitter.im/bitwarden/Lobby" target="_blank"> <a href="https://gitter.im/bitwarden/Lobby" target="_blank">
<img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" /> <img src="https://badges.gitter.im/bitwarden/Lobby.svg" alt="gitter chat" />

View File

@@ -34,6 +34,9 @@ const filters = {
function buildString() { function buildString() {
var build = ""; var build = "";
if (process.env.MANIFEST_VERSION) {
build = `-mv${process.env.MANIFEST_VERSION}`;
}
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") { if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
build = `-${process.env.BUILD_NUMBER}`; build = `-${process.env.BUILD_NUMBER}`;
} }
@@ -57,6 +60,7 @@ function dist(browserName, manifest) {
function distFirefox() { function distFirefox() {
return dist("firefox", (manifest) => { return dist("firefox", (manifest) => {
delete manifest.content_security_policy; delete manifest.content_security_policy;
delete manifest.storage;
removeShortcuts(manifest); removeShortcuts(manifest);
return manifest; return manifest;
}); });

View File

@@ -10,7 +10,7 @@
"build:prod:watch": "cross-env NODE_ENV=production webpack --watch", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch",
"dist": "npm run build:prod && gulp dist", "dist": "npm run build:prod && gulp dist",
"dist:chrome": "npm run build:prod && gulp dist:chrome", "dist:chrome": "npm run build:prod && gulp dist:chrome",
"dist:chrome:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && gulp dist:chrome", "dist:chrome:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && cross-env MANIFEST_VERSION=3 gulp dist:chrome",
"dist:firefox": "npm run build:prod && gulp dist:firefox", "dist:firefox": "npm run build:prod && gulp dist:firefox",
"dist:opera": "npm run build:prod && gulp dist:opera", "dist:opera": "npm run build:prod && gulp dist:opera",
"dist:safari": "npm run build:prod && gulp dist:safari", "dist:safari": "npm run build:prod && gulp dist:safari",

View File

@@ -53,13 +53,13 @@
"message": "Karta" "message": "Karta"
}, },
"vault": { "vault": {
"message": "Vault" "message": "Trezor"
}, },
"myVault": { "myVault": {
"message": "Můj trezor" "message": "Můj trezor"
}, },
"allVaults": { "allVaults": {
"message": "All Vaults" "message": "Všechny trezory"
}, },
"tools": { "tools": {
"message": "Nástroje" "message": "Nástroje"
@@ -424,13 +424,13 @@
"message": "Neplatná e-mailová adresa." "message": "Neplatná e-mailová adresa."
}, },
"masterPasswordRequired": { "masterPasswordRequired": {
"message": "Master password is required." "message": "Je vyžadováno hlavní heslo."
}, },
"confirmMasterPasswordRequired": { "confirmMasterPasswordRequired": {
"message": "Master password retype is required." "message": "Je vyžadováno zopakovat zadání hlavního hesla."
}, },
"masterPasswordMinlength": { "masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long." "message": "Hlavní heslo musí obsahovat alespoň 8 znaků."
}, },
"masterPassDoesntMatch": { "masterPassDoesntMatch": {
"message": "Potvrzení hlavního hesla se neshoduje." "message": "Potvrzení hlavního hesla se neshoduje."
@@ -603,7 +603,7 @@
"message": "Ano, uložit nyní" "message": "Ano, uložit nyní"
}, },
"enableChangedPasswordNotification": { "enableChangedPasswordNotification": {
"message": "Ask to update existing login" "message": "Zeptat se na aktualizaci existujícího přihlášení"
}, },
"changedPasswordNotificationDesc": { "changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website." "message": "Ask to update a login's password when a change is detected on a website."
@@ -810,13 +810,13 @@
"message": "Obnova je dokončena" "message": "Obnova je dokončena"
}, },
"enableAutoTotpCopy": { "enableAutoTotpCopy": {
"message": "Copy TOTP automatically" "message": "Automaticky kopírovat TOTP"
}, },
"disableAutoTotpCopyDesc": { "disableAutoTotpCopyDesc": {
"message": "Pokud mají vaše přihlašovací údaje přidán autentizační klíč pro TOTP, vygenerovaný ověřovací kód (TOTP) se automaticky zkopíruje do schránky při každém automatickém vyplnění přihlašovacích údajů." "message": "Pokud mají vaše přihlašovací údaje přidán autentizační klíč pro TOTP, vygenerovaný ověřovací kód (TOTP) se automaticky zkopíruje do schránky při každém automatickém vyplnění přihlašovacích údajů."
}, },
"enableAutoBiometricsPrompt": { "enableAutoBiometricsPrompt": {
"message": "Ask for biometrics on launch" "message": "Ověřit biometrické údaje při spuštění"
}, },
"premiumRequired": { "premiumRequired": {
"message": "Vyžaduje prémiové členství" "message": "Vyžaduje prémiové členství"
@@ -1037,16 +1037,16 @@
"message": "Tento prohlížeč nemůže zpracovat U2F požadavky ve vyskakovacím okně. Chcete otevřít toto vyskakovací okno v novém okně, abyste se mohli přihlásit pomocí U2F?" "message": "Tento prohlížeč nemůže zpracovat U2F požadavky ve vyskakovacím okně. Chcete otevřít toto vyskakovací okno v novém okně, abyste se mohli přihlásit pomocí U2F?"
}, },
"enableFavicon": { "enableFavicon": {
"message": "Show website icons" "message": "Zobrazit ikony webových stránek"
}, },
"faviconDesc": { "faviconDesc": {
"message": "Show a recognizable image next to each login." "message": "Zobrazit rozeznatelný obrázek vedle každého přihlášení."
}, },
"enableBadgeCounter": { "enableBadgeCounter": {
"message": "Show badge counter" "message": "Show badge counter"
}, },
"badgeCounterDesc": { "badgeCounterDesc": {
"message": "Indicate how many logins you have for the current web page." "message": "Zobrazit počet přihlašovacích údajů pro aktuální webovou stránku."
}, },
"cardholderName": { "cardholderName": {
"message": "Jméno držitele karty" "message": "Jméno držitele karty"
@@ -1907,7 +1907,7 @@
} }
}, },
"error": { "error": {
"message": "Error" "message": "Chyba"
}, },
"regenerateUsername": { "regenerateUsername": {
"message": "Znovu vygenerovat uživatelské jméno" "message": "Znovu vygenerovat uživatelské jméno"
@@ -1969,7 +1969,7 @@
"message": "Key Connector error: make sure Key Connector is available and working correctly." "message": "Key Connector error: make sure Key Connector is available and working correctly."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Vyžadováno prémiové předplatné"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Organization is disabled."
@@ -1990,10 +1990,10 @@
} }
}, },
"settingsEdited": { "settingsEdited": {
"message": "Settings have been edited" "message": "Nastavení byla upravena"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Klikněte zde"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "to reset to pre-configured settings"

View File

@@ -1969,7 +1969,7 @@
"message": "Key Connector-fejl: Sørg for, at Key Connector er tilgængelig og fungerer korrekt." "message": "Key Connector-fejl: Sørg for, at Key Connector er tilgængelig og fungerer korrekt."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Premium-abonnement kræves"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organisationen er deaktiveret." "message": "Organisationen er deaktiveret."
@@ -1981,7 +1981,7 @@
"message": "Mir" "message": "Mir"
}, },
"loggingInTo": { "loggingInTo": {
"message": "Logging in to $DOMAIN$", "message": "Logger ind på $DOMAIN$",
"placeholders": { "placeholders": {
"domain": { "domain": {
"content": "$1", "content": "$1",
@@ -1990,12 +1990,12 @@
} }
}, },
"settingsEdited": { "settingsEdited": {
"message": "Settings have been edited" "message": "Indstillinger er blevet redigeret"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Klik her"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "for at nulstille til forudkonfigurerede indstillinger"
} }
} }

View File

@@ -1565,7 +1565,7 @@
"message": "Eine Organisationsrichtlinie beeinflusst deine Eigentümer-Optionen." "message": "Eine Organisationsrichtlinie beeinflusst deine Eigentümer-Optionen."
}, },
"excludedDomains": { "excludedDomains": {
"message": "Ausgeschlossene Domänen" "message": "Ausgeschlossene Domains"
}, },
"excludedDomainsDesc": { "excludedDomainsDesc": {
"message": "Bitwarden wird keine Login-Daten für diese Domäne speichern. Du musst die Seite aktualisieren, damit die Änderungen wirksam werden." "message": "Bitwarden wird keine Login-Daten für diese Domäne speichern. Du musst die Seite aktualisieren, damit die Änderungen wirksam werden."

View File

@@ -424,13 +424,13 @@
"message": "Μη έγκυρη διεύθυνση e-mail." "message": "Μη έγκυρη διεύθυνση e-mail."
}, },
"masterPasswordRequired": { "masterPasswordRequired": {
"message": "Master password is required." "message": "Απαιτείται κύριος κωδικός πρόσβασης."
}, },
"confirmMasterPasswordRequired": { "confirmMasterPasswordRequired": {
"message": "Master password retype is required." "message": "Απαιτείται ξανά ο κύριος κωδικός πρόσβασης."
}, },
"masterPasswordMinlength": { "masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long." "message": "Ο κύριος κωδικός πρέπει να έχει μήκος τουλάχιστον 8 χαρακτήρες."
}, },
"masterPassDoesntMatch": { "masterPassDoesntMatch": {
"message": "Η επιβεβαίωση κύριου κωδικού δεν ταιριάζει." "message": "Η επιβεβαίωση κύριου κωδικού δεν ταιριάζει."
@@ -571,22 +571,22 @@
"description": "This is the folder for uncategorized items" "description": "This is the folder for uncategorized items"
}, },
"enableAddLoginNotification": { "enableAddLoginNotification": {
"message": "Ask to add login" "message": "Ζητήστε να προσθέσετε σύνδεση"
}, },
"addLoginNotificationDesc": { "addLoginNotificationDesc": {
"message": "Η \"Προσθήκη Ειδοποίησης Σύνδεσης\" σας προτρέπει αυτόματα να αποθηκεύσετε νέες συνδέσεις στο vault σας κάθε φορά που θα συνδεθείτε για πρώτη φορά." "message": "Η \"Προσθήκη Ειδοποίησης Σύνδεσης\" σας προτρέπει αυτόματα να αποθηκεύσετε νέες συνδέσεις στο vault σας κάθε φορά που θα συνδεθείτε για πρώτη φορά."
}, },
"showCardsCurrentTab": { "showCardsCurrentTab": {
"message": "Show cards on Tab page" "message": "Εμφάνιση καρτών στη σελίδα Καρτέλας"
}, },
"showCardsCurrentTabDesc": { "showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill." "message": "Λίστα αντικειμένων καρτών στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση."
}, },
"showIdentitiesCurrentTab": { "showIdentitiesCurrentTab": {
"message": "Show identities on Tab page" "message": "Εμφάνιση ταυτοτήτων στη σελίδα καρτέλας"
}, },
"showIdentitiesCurrentTabDesc": { "showIdentitiesCurrentTabDesc": {
"message": "List identity items on the Tab page for easy auto-fill." "message": "Λίστα στοιχείων ταυτότητας στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση."
}, },
"clearClipboard": { "clearClipboard": {
"message": "Εκκαθάριση Πρόχειρου", "message": "Εκκαθάριση Πρόχειρου",
@@ -603,10 +603,10 @@
"message": "Ναι, Αποθήκευση Τώρα" "message": "Ναι, Αποθήκευση Τώρα"
}, },
"enableChangedPasswordNotification": { "enableChangedPasswordNotification": {
"message": "Ask to update existing login" "message": "Ζητήστε να ενημερώσετε την υπάρχουσα σύνδεση"
}, },
"changedPasswordNotificationDesc": { "changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website." "message": "Ζητήστε να ενημερώσετε τον κωδικό πρόσβασης μιας σύνδεσης όταν εντοπιστεί μια αλλαγή σε μια ιστοσελίδα."
}, },
"notificationChangeDesc": { "notificationChangeDesc": {
"message": "Θέλετε να ενημερώσετε αυτό τον κωδικό στο Bitwarden ;" "message": "Θέλετε να ενημερώσετε αυτό τον κωδικό στο Bitwarden ;"
@@ -615,10 +615,10 @@
"message": "Ναι, Ενημέρωση Τώρα" "message": "Ναι, Ενημέρωση Τώρα"
}, },
"enableContextMenuItem": { "enableContextMenuItem": {
"message": "Show context menu options" "message": "Εμφάνιση επιλογών μενού περιβάλλοντος"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "Χρησιμοποιήστε ένα δευτερεύον κλικ για να αποκτήσετε πρόσβαση στη δημιουργία κωδικού πρόσβασης και να ταιριάξετε τις συνδέσεις για την ιστοσελίδα. "
}, },
"defaultUriMatchDetection": { "defaultUriMatchDetection": {
"message": "Προεπιλεγμένη ανίχνευση αντιστοιχίας URI", "message": "Προεπιλεγμένη ανίχνευση αντιστοιχίας URI",
@@ -810,13 +810,13 @@
"message": "Επιτυχής ανανέωση" "message": "Επιτυχής ανανέωση"
}, },
"enableAutoTotpCopy": { "enableAutoTotpCopy": {
"message": "Copy TOTP automatically" "message": "Αυτόματη αντιγραφή TOTP"
}, },
"disableAutoTotpCopyDesc": { "disableAutoTotpCopyDesc": {
"message": "Εάν η σύνδεση έχει συνημμένο κάποιο κλειδί επαλήθευσης, ο κωδικός επαλήθευσης TOTP αντιγράφεται αυτόματα στο πρόχειρο κάθε φορά που συμπληρώνετε αυτόματα τα στοιχεία σύνδεσης." "message": "Εάν η σύνδεση έχει συνημμένο κάποιο κλειδί επαλήθευσης, ο κωδικός επαλήθευσης TOTP αντιγράφεται αυτόματα στο πρόχειρο κάθε φορά που συμπληρώνετε αυτόματα τα στοιχεία σύνδεσης."
}, },
"enableAutoBiometricsPrompt": { "enableAutoBiometricsPrompt": {
"message": "Ask for biometrics on launch" "message": "Ζητήστε βιομετρικά κατά την εκκίνηση"
}, },
"premiumRequired": { "premiumRequired": {
"message": "Απαιτείται Έκδοση Premium" "message": "Απαιτείται Έκδοση Premium"
@@ -966,7 +966,7 @@
"message": "Προεπιλεγμένη ρύθμιση αυτόματης συμπλήρωσης για στοιχεία σύνδεσης" "message": "Προεπιλεγμένη ρύθμιση αυτόματης συμπλήρωσης για στοιχεία σύνδεσης"
}, },
"defaultAutoFillOnPageLoadDesc": { "defaultAutoFillOnPageLoadDesc": {
"message": "Αφού ενεργοποιήσετε την αυτόματη συμπλήρωση κατά τη Φόρτωση Σελίδας, μπορείτε να ενεργοποιήσετε ή να απενεργοποιήσετε τη λειτουργία για μεμονωμένα στοιχεία σύνδεσης. Αυτή είναι η προεπιλεγμένη ρύθμιση για στοιχεία σύνδεσης που δεν έχουν ρυθμιστεί ξεχωριστά." "message": "Μπορείτε να απενεργοποιήσετε την αυτόματη συμπλήρωση φόρτωσης σελίδας για μεμονωμένα στοιχεία σύνδεσης από την προβολή Επεξεργασία στοιχείου."
}, },
"itemAutoFillOnPageLoad": { "itemAutoFillOnPageLoad": {
"message": "Αυτόματη συμπλήρωση της Φόρτισης Σελίδας (αν είναι ενεργοποιημένη στις Επιλογές)" "message": "Αυτόματη συμπλήρωση της Φόρτισης Σελίδας (αν είναι ενεργοποιημένη στις Επιλογές)"
@@ -1037,16 +1037,16 @@
"message": "Αυτό το πρόγραμμα περιήγησης δεν μπορεί να επεξεργαστεί αιτήματα του U2F σε αυτό το αναδυόμενο παράθυρο. Θέλετε να ανοίξετε το αναδυόμενο σε νέο παράθυρο, ώστε να μπορείτε να συνδεθείτε χρησιμοποιώντας U2F;" "message": "Αυτό το πρόγραμμα περιήγησης δεν μπορεί να επεξεργαστεί αιτήματα του U2F σε αυτό το αναδυόμενο παράθυρο. Θέλετε να ανοίξετε το αναδυόμενο σε νέο παράθυρο, ώστε να μπορείτε να συνδεθείτε χρησιμοποιώντας U2F;"
}, },
"enableFavicon": { "enableFavicon": {
"message": "Show website icons" "message": "Εμφάνιση εικονιδίων ιστοσελίδας"
}, },
"faviconDesc": { "faviconDesc": {
"message": "Show a recognizable image next to each login." "message": "Εμφάνιση μιας αναγνωρίσιμης εικόνας δίπλα σε κάθε σύνδεση."
}, },
"enableBadgeCounter": { "enableBadgeCounter": {
"message": "Show badge counter" "message": "Εμφάνιση μετρητή εμβλημάτων"
}, },
"badgeCounterDesc": { "badgeCounterDesc": {
"message": "Indicate how many logins you have for the current web page." "message": "Υποδείξτε πόσες συνδέσεις έχετε για την τρέχουσα ιστοσελίδα."
}, },
"cardholderName": { "cardholderName": {
"message": "Όνομα κατόχου της κάρτας" "message": "Όνομα κατόχου της κάρτας"
@@ -1484,7 +1484,7 @@
"message": "Επιλέγοντας αυτό το πλαίσιο, συμφωνείτε με τα εξής:" "message": "Επιλέγοντας αυτό το πλαίσιο, συμφωνείτε με τα εξής:"
}, },
"acceptPoliciesRequired": { "acceptPoliciesRequired": {
"message": "Terms of Service and Privacy Policy have not been acknowledged." "message": "Οι Όροι Παροχής Υπηρεσιών και η Πολιτική Απορρήτου δεν έχουν αναγνωριστεί."
}, },
"termsOfService": { "termsOfService": {
"message": "Όροι Χρήσης" "message": "Όροι Χρήσης"
@@ -1850,7 +1850,7 @@
} }
}, },
"vaultTimeoutTooLarge": { "vaultTimeoutTooLarge": {
"message": "Your vault timeout exceeds the restrictions set by your organization." "message": "Το χρονικό όριο του vault σας υπερβαίνει τους περιορισμούς που έχει ορίσει ο οργανισμός σας."
}, },
"vaultExportDisabled": { "vaultExportDisabled": {
"message": "Εξαγωγή vault Απενεργοποιημένη" "message": "Εξαγωγή vault Απενεργοποιημένη"
@@ -1969,19 +1969,19 @@
"message": "Σφάλμα Key Connector: βεβαιωθείτε ότι το Key Connector είναι διαθέσιμο και λειτουργεί σωστά." "message": "Σφάλμα Key Connector: βεβαιωθείτε ότι το Key Connector είναι διαθέσιμο και λειτουργεί σωστά."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Απαιτείται συνδρομή Premium"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "Ο οργανισμός είναι απενεργοποιημένος."
}, },
"disabledOrganizationFilterError": { "disabledOrganizationFilterError": {
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." "message": "Δεν είναι δυνατή η πρόσβαση σε αντικείμενα σε απενεργοποιημένους οργανισμούς. Επικοινωνήστε με τον ιδιοκτήτη του Οργανισμού για βοήθεια."
}, },
"cardBrandMir": { "cardBrandMir": {
"message": "Mir" "message": "Mir"
}, },
"loggingInTo": { "loggingInTo": {
"message": "Logging in to $DOMAIN$", "message": "Σύνδεση στο $DOMAIN$",
"placeholders": { "placeholders": {
"domain": { "domain": {
"content": "$1", "content": "$1",
@@ -1990,12 +1990,12 @@
} }
}, },
"settingsEdited": { "settingsEdited": {
"message": "Settings have been edited" "message": "Οι ρυθμίσεις έχουν επεξεργαστεί"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Κάντε κλικ εδώ"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "επαναφορά στις προ-ρυθμισμένες ρυθμίσεις"
} }
} }

View File

@@ -1969,7 +1969,7 @@
"message": "Error en el conector de claves: asegúrate de que el conector de claves está disponible y que funciona correctamente." "message": "Error en el conector de claves: asegúrate de que el conector de claves está disponible y que funciona correctamente."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Se requiere una Suscripción Premium"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "La organización está desactivada." "message": "La organización está desactivada."

View File

@@ -128,7 +128,7 @@
"message": "Jatka" "message": "Jatka"
}, },
"sendVerificationCode": { "sendVerificationCode": {
"message": "Lähetä vahvistuskoodi sähköpostiisi" "message": "Lähetä todennuskoodi sähköpostiisi"
}, },
"sendCode": { "sendCode": {
"message": "Lähetä koodi" "message": "Lähetä koodi"
@@ -1031,7 +1031,7 @@
"description": "This describes a value that is 'linked' (tied) to another value." "description": "This describes a value that is 'linked' (tied) to another value."
}, },
"popup2faCloseMessage": { "popup2faCloseMessage": {
"message": "Klikkaus ponnahdusikkunan ulkopuolelle todennuskoodin sähköpostista noutoa varten sulkee ikkunan. Haluatko avata näkymän uuteen ikkunaan, jotta se pysyy avoinna?" "message": "Painallus ponnahdusikkunan ulkopuolelle todennuskoodin sähköpostista noutoa varten sulkee ikkunan. Haluatko avata näkymän uuteen ikkunaan, jotta se pysyy avoinna?"
}, },
"popupU2fCloseMessage": { "popupU2fCloseMessage": {
"message": "Tämä selain ei voi käsitellä U2F-pyyntöjä tässä ponnahdusikkunassa. Haluatko avata näkymän uuteen ikkunaan, jotta voit vahvistaa kirjautumisen U2F-todennuslaitteella?" "message": "Tämä selain ei voi käsitellä U2F-pyyntöjä tässä ponnahdusikkunassa. Haluatko avata näkymän uuteen ikkunaan, jotta voit vahvistaa kirjautumisen U2F-todennuslaitteella?"
@@ -1996,6 +1996,6 @@
"message": "Paina tästä" "message": "Paina tästä"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "palauttaaksesi esimääritetyt asetukset" "message": "palauttaaksesi oletusasetukset"
} }
} }

View File

@@ -603,10 +603,10 @@
"message": "Enregistrer" "message": "Enregistrer"
}, },
"enableChangedPasswordNotification": { "enableChangedPasswordNotification": {
"message": "Ask to update existing login" "message": "Demander à mettre à jour l'identifiant existant"
}, },
"changedPasswordNotificationDesc": { "changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website." "message": "Demander à mettre à jour le mot de passe d'un identifiant lorsqu'un changement est détecté sur un site Web."
}, },
"notificationChangeDesc": { "notificationChangeDesc": {
"message": "Souhaitez-vous mettre à jour ce mot de passe dans Bitwarden ?" "message": "Souhaitez-vous mettre à jour ce mot de passe dans Bitwarden ?"
@@ -615,7 +615,7 @@
"message": "Mettre à jour" "message": "Mettre à jour"
}, },
"enableContextMenuItem": { "enableContextMenuItem": {
"message": "Show context menu options" "message": "Afficher les options du menu contextuel"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "Use a secondary click to access password generation and matching logins for the website. "
@@ -810,7 +810,7 @@
"message": "Actualisation terminée" "message": "Actualisation terminée"
}, },
"enableAutoTotpCopy": { "enableAutoTotpCopy": {
"message": "Copy TOTP automatically" "message": "Copier le code TOTP automatiquement"
}, },
"disableAutoTotpCopyDesc": { "disableAutoTotpCopyDesc": {
"message": "Si une clé d'authentification est rattachée à votre identifiant, alors le code de vérification TOTP est automatiquement copié dans le presse-papiers lorsque vous renseignez l'identifiant." "message": "Si une clé d'authentification est rattachée à votre identifiant, alors le code de vérification TOTP est automatiquement copié dans le presse-papiers lorsque vous renseignez l'identifiant."
@@ -1037,10 +1037,10 @@
"message": "Ce navigateur ne peut pas traiter les requêtes U2F dans cette popup. Voulez-vous ouvrir cette popup dans une nouvelle fenêtre pour que vous puissiez vous connecter en utilisant U2F ?" "message": "Ce navigateur ne peut pas traiter les requêtes U2F dans cette popup. Voulez-vous ouvrir cette popup dans une nouvelle fenêtre pour que vous puissiez vous connecter en utilisant U2F ?"
}, },
"enableFavicon": { "enableFavicon": {
"message": "Show website icons" "message": "Afficher les icônes du site web"
}, },
"faviconDesc": { "faviconDesc": {
"message": "Show a recognizable image next to each login." "message": "Afficher une image reconnaissable à côté de chaque identifiant."
}, },
"enableBadgeCounter": { "enableBadgeCounter": {
"message": "Show badge counter" "message": "Show badge counter"
@@ -1484,7 +1484,7 @@
"message": "En cochant cette case, vous acceptez les éléments suivants :" "message": "En cochant cette case, vous acceptez les éléments suivants :"
}, },
"acceptPoliciesRequired": { "acceptPoliciesRequired": {
"message": "Terms of Service and Privacy Policy have not been acknowledged." "message": "Les Conditions d'Utilisation et la Politique de Confidentialité n'ont pas été acceptées."
}, },
"termsOfService": { "termsOfService": {
"message": "Conditions d'utilisation" "message": "Conditions d'utilisation"
@@ -1850,7 +1850,7 @@
} }
}, },
"vaultTimeoutTooLarge": { "vaultTimeoutTooLarge": {
"message": "Your vault timeout exceeds the restrictions set by your organization." "message": "Le délai d'expiration de votre coffre-fort dépasse les restrictions définies par votre organisation."
}, },
"vaultExportDisabled": { "vaultExportDisabled": {
"message": "Export du coffre désactivé" "message": "Export du coffre désactivé"
@@ -1975,7 +1975,7 @@
"message": "L'organisation est désactivée." "message": "L'organisation est désactivée."
}, },
"disabledOrganizationFilterError": { "disabledOrganizationFilterError": {
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." "message": "Les éléments des Organisations désactivées ne sont pas accessibles. Contactez le propriétaire de votre Organisation pour obtenir de l'aide."
}, },
"cardBrandMir": { "cardBrandMir": {
"message": "Mir" "message": "Mir"

View File

@@ -342,13 +342,13 @@
"message": "本人確認を行う" "message": "本人確認を行う"
}, },
"yourVaultIsLocked": { "yourVaultIsLocked": {
"message": "保管庫がロックされています。開くにはマスターパスワードを入力してください。" "message": "保管庫がロックされています。続行するには本人確認を行ってください。"
}, },
"unlock": { "unlock": {
"message": "ロック解除" "message": "ロック解除"
}, },
"loggedInAsOn": { "loggedInAsOn": {
"message": "$HOSTNAME$ の $EMAIL$ としてログインしました。", "message": "$EMAIL$ として $HOSTNAME$ にログインしました。",
"placeholders": { "placeholders": {
"email": { "email": {
"content": "$1", "content": "$1",

View File

@@ -1981,7 +1981,7 @@
"message": "Mir" "message": "Mir"
}, },
"loggingInTo": { "loggingInTo": {
"message": "Logging in to $DOMAIN$", "message": "Пријављивање на $DOMAIN$",
"placeholders": { "placeholders": {
"domain": { "domain": {
"content": "$1", "content": "$1",
@@ -1990,12 +1990,12 @@
} }
}, },
"settingsEdited": { "settingsEdited": {
"message": "Settings have been edited" "message": "Поставке су уређене"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Кликните овде"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "за рисетовање на подразумевана подешавања"
} }
} }

View File

@@ -577,16 +577,16 @@
"message": "Aviseringar för nya inloggningar frågar dig om du vill lägga till en inloggning automatiskt till ditt valv, när du använder en inloggning som inte redan finns i ditt valv." "message": "Aviseringar för nya inloggningar frågar dig om du vill lägga till en inloggning automatiskt till ditt valv, när du använder en inloggning som inte redan finns i ditt valv."
}, },
"showCardsCurrentTab": { "showCardsCurrentTab": {
"message": "Show cards on Tab page" "message": "Visa kort på fliksida"
}, },
"showCardsCurrentTabDesc": { "showCardsCurrentTabDesc": {
"message": "List card items on the Tab page for easy auto-fill." "message": "Lista kortobjekt på fliksidan för enkel automatisk fyllning."
}, },
"showIdentitiesCurrentTab": { "showIdentitiesCurrentTab": {
"message": "Show identities on Tab page" "message": "Visa identiteter på fliksidan"
}, },
"showIdentitiesCurrentTabDesc": { "showIdentitiesCurrentTabDesc": {
"message": "List identity items on the Tab page for easy auto-fill." "message": "Lista identitetsobjekt på fliksidan för enkel automatisk fyllning."
}, },
"clearClipboard": { "clearClipboard": {
"message": "Rensa urklipp", "message": "Rensa urklipp",
@@ -618,7 +618,7 @@
"message": "Visa alternativ för snabbmenyn" "message": "Visa alternativ för snabbmenyn"
}, },
"contextMenuItemDesc": { "contextMenuItemDesc": {
"message": "Use a secondary click to access password generation and matching logins for the website. " "message": "Använd ett andra klick för att komma åt lösenordsgenerering och matchande inloggningar för webbplatsen. "
}, },
"defaultUriMatchDetection": { "defaultUriMatchDetection": {
"message": "Standardmatchning för URI", "message": "Standardmatchning för URI",
@@ -1969,7 +1969,7 @@
"message": "Key Connector-fel: säkerställ att Key Connector är tillgänglig och fungerar korrekt." "message": "Key Connector-fel: säkerställ att Key Connector är tillgänglig och fungerar korrekt."
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Premiumabonnemang krävs"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organisationen är inaktiverad." "message": "Organisationen är inaktiverad."
@@ -1981,7 +1981,7 @@
"message": "Mir" "message": "Mir"
}, },
"loggingInTo": { "loggingInTo": {
"message": "Logging in to $DOMAIN$", "message": "Loggar in $DOMAIN$",
"placeholders": { "placeholders": {
"domain": { "domain": {
"content": "$1", "content": "$1",
@@ -1990,12 +1990,12 @@
} }
}, },
"settingsEdited": { "settingsEdited": {
"message": "Settings have been edited" "message": "Inställningarna har ändrats"
}, },
"environmentEditedClick": { "environmentEditedClick": {
"message": "Click here" "message": "Klicka här"
}, },
"environmentEditedReset": { "environmentEditedReset": {
"message": "to reset to pre-configured settings" "message": "för att återställa till förkonfigurerade inställningar"
} }
} }

View File

@@ -699,7 +699,7 @@
} }
}, },
"moveToOrgDesc": { "moveToOrgDesc": {
"message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. При переміщенні до організації власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення." "message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. Переміщуючи до організації, власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення."
}, },
"learnMore": { "learnMore": {
"message": "Докладніше" "message": "Докладніше"

View File

@@ -60,7 +60,7 @@ export default class CommandsBackground {
await this.openPopup(); await this.openPopup();
break; break;
case "lock_vault": case "lock_vault":
await this.vaultTimeoutService.lock(true); await this.vaultTimeoutService.lock();
break; break;
default: default:
break; break;

View File

@@ -48,7 +48,7 @@ export default class IdleBackground {
if (action === "logOut") { if (action === "logOut") {
await this.vaultTimeoutService.logOut(); await this.vaultTimeoutService.logOut();
} else { } else {
await this.vaultTimeoutService.lock(true); await this.vaultTimeoutService.lock();
} }
} }
} }

View File

@@ -235,7 +235,6 @@ export default class MainBackground {
}); });
this.platformUtilsService = new BrowserPlatformUtilsService( this.platformUtilsService = new BrowserPlatformUtilsService(
this.messagingService, this.messagingService,
this.stateService,
(clipboardValue, clearMs) => { (clipboardValue, clearMs) => {
if (this.systemService != null) { if (this.systemService != null) {
this.systemService.clearClipboard(clipboardValue, clearMs); this.systemService.clearClipboard(clipboardValue, clearMs);

View File

@@ -1,6 +1,6 @@
import { devFlagEnabled, DevFlagName } from "../flags"; import { devFlagEnabled, DevFlags } from "../flags";
export function devFlag(flag: DevFlagName) { export function devFlag(flag: keyof DevFlags) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value; const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) { descriptor.value = function (...args: any[]) {

View File

@@ -29,7 +29,7 @@ describe("session syncer", () => {
afterEach(() => { afterEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
behaviorSubject.unsubscribe(); behaviorSubject.complete();
}); });
describe("constructor", () => { describe("constructor", () => {

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject, Subscription } from "rxjs"; import { BehaviorSubject, concatMap, Subscription } from "rxjs";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
@@ -41,13 +41,17 @@ export class SessionSyncer {
// This may be a memory leak. // This may be a memory leak.
// There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary // There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary
// contexts. If so, this is handled by destruction of the context. // contexts. If so, this is handled by destruction of the context.
this.subscription = this.behaviorSubject.subscribe(async (next) => { this.subscription = this.behaviorSubject
if (this.ignoreNextUpdate) { .pipe(
this.ignoreNextUpdate = false; concatMap(async (next) => {
return; if (this.ignoreNextUpdate) {
} this.ignoreNextUpdate = false;
await this.updateSession(next); return;
}); }
await this.updateSession(next);
})
)
.subscribe();
} }
private listenForUpdates() { private listenForUpdates() {

View File

@@ -1,61 +1,32 @@
import {
flagEnabled as baseFlagEnabled,
devFlagEnabled as baseDevFlagEnabled,
devFlagValue as baseDevFlagValue,
SharedFlags,
SharedDevFlags,
} from "@bitwarden/common/misc/flags";
import { GroupPolicyEnvironment } from "./types/group-policy-environment"; import { GroupPolicyEnvironment } from "./types/group-policy-environment";
function getFlags<T>(envFlags: string | T): T { // required to avoid linting errors when there are no flags
if (typeof envFlags === "string") { /* eslint-disable-next-line @typescript-eslint/ban-types */
return JSON.parse(envFlags) as T; export type Flags = {} & SharedFlags;
} else {
return envFlags as T;
}
}
/* Placeholder for when we have a relevant feature flag // required to avoid linting errors when there are no flags
export type Flags = { test?: boolean }; /* eslint-disable-next-line @typescript-eslint/ban-types */
export type FlagName = keyof Flags;
export function flagEnabled(flag: FlagName): boolean {
const flags = getFlags<Flags>(process.env.FLAGS);
return flags[flag] == null || flags[flag];
}
*/
/**
* These flags are useful for development and testing.
* Dev Flags are always OFF in production.
*/
export type DevFlags = { export type DevFlags = {
storeSessionDecrypted?: boolean; storeSessionDecrypted?: boolean;
managedEnvironment?: GroupPolicyEnvironment; managedEnvironment?: GroupPolicyEnvironment;
}; } & SharedDevFlags;
export type DevFlagName = keyof DevFlags; export function flagEnabled(flag: keyof Flags): boolean {
return baseFlagEnabled<Flags>(flag);
/**
* Gets whether the given dev flag is truthy.
* Gets the value of a dev flag from environment.
* Will always return false unless in development.
* @param flag The name of the dev flag to check
* @returns The value of the flag
*/
export function devFlagEnabled(flag: DevFlagName): boolean {
if (process.env.ENV !== "development") {
return false;
}
const devFlags = getFlags<DevFlags>(process.env.DEV_FLAGS);
return devFlags[flag] == null || !!devFlags[flag];
} }
/** export function devFlagEnabled(flag: keyof DevFlags) {
* Gets the value of a dev flag from environment. return baseDevFlagEnabled<DevFlags>(flag);
* Will always return false unless in development. }
* @param flag The name of the dev flag to check
* @returns The value of the flag export function devFlagValue(flag: keyof DevFlags) {
* @throws Error if the flag is not enabled return baseDevFlagValue(flag);
*/
export function devFlagValue<K extends DevFlagName>(flag: K): DevFlags[K] {
if (!devFlagEnabled(flag)) {
throw new Error(`This method should not be called, it is protected by a disabled dev flag.`);
}
const devFlags = getFlags<DevFlags>(process.env.DEV_FLAGS);
return devFlags[flag];
} }

View File

@@ -66,7 +66,6 @@ const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise<void> => {
const platformUtils = new BrowserPlatformUtilsService( const platformUtils = new BrowserPlatformUtilsService(
null, // MessagingService null, // MessagingService
stateService,
null, // clipboardWriteCallback null, // clipboardWriteCallback
null // biometricCallback null // biometricCallback
); );

View File

@@ -124,5 +124,8 @@
"default_title": "Bitwarden", "default_title": "Bitwarden",
"default_panel": "popup/index.html?uilocation=sidebar", "default_panel": "popup/index.html?uilocation=sidebar",
"default_icon": "images/icon19.png" "default_icon": "images/icon19.png"
},
"storage": {
"managed_schema": "managed_schema.json"
} }
} }

View File

@@ -6,6 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
@@ -30,7 +31,8 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
policyService: PolicyService, policyService: PolicyService,
router: Router, router: Router,
syncService: SyncService, syncService: SyncService,
route: ActivatedRoute route: ActivatedRoute,
organizationApiService: OrganizationApiServiceAbstraction
) { ) {
super( super(
i18nService, i18nService,
@@ -44,7 +46,8 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
apiService, apiService,
syncService, syncService,
route, route,
stateService stateService,
organizationApiService
); );
} }
} }

View File

@@ -109,6 +109,7 @@ 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) => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.sso === "true") { if (qParams.sso === "true") {
super.onSuccessfulLogin = () => { super.onSuccessfulLogin = () => {

View File

@@ -35,7 +35,7 @@ export class AppComponent implements OnInit, OnDestroy {
private lastActivity: number = null; private lastActivity: number = null;
private activeUserId: string; private activeUserId: string;
private destroy$: Subject<void> = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
private toastrService: ToastrService, private toastrService: ToastrService,
@@ -132,6 +132,7 @@ export class AppComponent implements OnInit, OnDestroy {
BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener); BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener);
// eslint-disable-next-line rxjs/no-async-subscribe
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => { this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url || ""; const url = event.urlAfterRedirects || event.url || "";

View File

@@ -18,6 +18,7 @@ import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN"; import localeEnIn from "@angular/common/locales/en-IN";
import localeEs from "@angular/common/locales/es"; import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et"; import localeEt from "@angular/common/locales/et";
import localeEu from "@angular/common/locales/eu";
import localeFa from "@angular/common/locales/fa"; import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi"; import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil"; import localeFil from "@angular/common/locales/fil";
@@ -129,6 +130,7 @@ registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN"); registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, "es"); registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et"); registerLocaleData(localeEt, "et");
registerLocaleData(localeEu, "eu");
registerLocaleData(localeFa, "fa"); registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi"); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil"); registerLocaleData(localeFil, "fil");

View File

@@ -349,6 +349,18 @@
/> />
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
<div class="box-content-row" appBoxRow>
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
<input
id="duckduckgo-apikey"
type="password"
name="DuckDudkGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'"> <ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label> <label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label>
@@ -383,6 +395,18 @@
/> />
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'fastmail'">
<div class="box-content-row" appBoxRow>
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="fastmail-apiToken"
type="password"
name="FastmailApiToken"
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
</div> </div>
</div> </div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'"> <div class="box" *ngIf="usernameOptions.type === 'subaddress'">

View File

@@ -19,6 +19,7 @@ import { PopupUtilsService } from "../services/popup-utils.service";
selector: "app-send-add-edit", selector: "app-send-add-edit",
templateUrl: "send-add-edit.component.html", templateUrl: "send-add-edit.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class SendAddEditComponent extends BaseAddEditComponent { export class SendAddEditComponent extends BaseAddEditComponent {
// Options header // Options header
showOptions = false; showOptions = false;
@@ -98,6 +99,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
this.isUnsupportedMac = this.isUnsupportedMac =
this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes("Mac OS X 11"); this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes("Mac OS X 11");
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.sendId) { if (params.sendId) {
this.sendId = params.sendId; this.sendId = params.sendId;

View File

@@ -70,6 +70,7 @@ export class SendTypeComponent extends BaseSendComponent {
async ngOnInit() { async ngOnInit() {
// Let super class finish // Let super class finish
await super.ngOnInit(); await super.ngOnInit();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (this.applySavedState) { if (this.applySavedState) {
this.state = await this.stateService.getBrowserSendTypeComponentState(); this.state = await this.stateService.getBrowserSendTypeComponentState();

View File

@@ -13,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
selector: "app-folder-add-edit", selector: "app-folder-add-edit",
templateUrl: "folder-add-edit.component.html", templateUrl: "folder-add-edit.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class FolderAddEditComponent extends BaseFolderAddEditComponent { export class FolderAddEditComponent extends BaseFolderAddEditComponent {
constructor( constructor(
folderService: FolderService, folderService: FolderService,
@@ -27,6 +28,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.folderId) { if (params.folderId) {
this.folderId = params.folderId; this.folderId = params.folderId;

View File

@@ -37,6 +37,7 @@ const RateUrls = {
selector: "app-settings", selector: "app-settings",
templateUrl: "settings.component.html", templateUrl: "settings.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
@ViewChild("vaultTimeoutActionSelect", { read: ElementRef, static: true }) @ViewChild("vaultTimeoutActionSelect", { read: ElementRef, static: true })
vaultTimeoutActionSelectRef: ElementRef; vaultTimeoutActionSelectRef: ElementRef;
@@ -102,6 +103,7 @@ export class SettingsComponent implements OnInit {
this.vaultTimeout.setValue(timeout); this.vaultTimeout.setValue(timeout);
} }
this.previousVaultTimeout = this.vaultTimeout.value; this.previousVaultTimeout = this.vaultTimeout.value;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.vaultTimeout.valueChanges.subscribe(async (value) => { this.vaultTimeout.valueChanges.subscribe(async (value) => {
await this.saveVaultTimeout(value); await this.saveVaultTimeout(value);
}); });
@@ -302,7 +304,7 @@ export class SettingsComponent implements OnInit {
} }
async lock() { async lock() {
await this.vaultTimeoutService.lock(true); await this.vaultTimeoutService.lock();
} }
async logOut() { async logOut() {

View File

@@ -6,11 +6,13 @@
<!-- Current custom fields --> <!-- Current custom fields -->
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields"> <div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div <div
role="group"
class="box-content-row box-content-row-multi box-draggable-row" class="box-content-row box-content-row-multi box-draggable-row"
appBoxRow appBoxRow
cdkDrag cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction" *ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
[ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }" [ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
attr.aria-label="{{ f.name }}"
> >
<button <button
type="button" type="button"
@@ -41,6 +43,7 @@
*ngIf="f.type === fieldType.Text" *ngIf="f.type === fieldType.Text"
placeholder="{{ 'value' | i18n }}" placeholder="{{ 'value' | i18n }}"
appInputVerbatim appInputVerbatim
attr.aria-describedby="fieldName{{ i }}"
/> />
<!-- Hidden --> <!-- Hidden -->
<input <input
@@ -53,6 +56,7 @@
*ngIf="f.type === fieldType.Hidden" *ngIf="f.type === fieldType.Hidden"
placeholder="{{ 'value' | i18n }}" placeholder="{{ 'value' | i18n }}"
[disabled]="!cipher.viewPassword && !f.newField" [disabled]="!cipher.viewPassword && !f.newField"
attr.aria-describedby="fieldName{{ i }}"
/> />
<!-- Linked --> <!-- Linked -->
<select <select
@@ -60,6 +64,7 @@
name="Field.Value{{ i }}" name="Field.Value{{ i }}"
[(ngModel)]="f.linkedId" [(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null" *ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
attr.aria-describedby="fieldName{{ i }}"
> >
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option> <option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
@@ -74,6 +79,7 @@
appTrueFalseValue appTrueFalseValue
trueValue="true" trueValue="true"
falseValue="false" falseValue="false"
attr.aria-describedby="fieldName{{ i }}"
/> />
<div <div
class="action-buttons" class="action-buttons"

View File

@@ -402,9 +402,11 @@
<div class="box-content"> <div class="box-content">
<ng-container *ngIf="cipher.login.hasUris"> <ng-container *ngIf="cipher.login.hasUris">
<div <div
role="group"
class="box-content-row box-content-row-multi" class="box-content-row box-content-row-multi"
appBoxRow appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction" *ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
attr.aria-label="{{ 'uriPosition' | i18n: i + 1 }}"
> >
<button <button
type="button" type="button"

View File

@@ -27,6 +27,7 @@ import { PopupUtilsService } from "../services/popup-utils.service";
selector: "app-vault-add-edit", selector: "app-vault-add-edit",
templateUrl: "add-edit.component.html", templateUrl: "add-edit.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class AddEditComponent extends BaseAddEditComponent { export class AddEditComponent extends BaseAddEditComponent {
currentUris: string[]; currentUris: string[];
showAttachments = true; showAttachments = true;
@@ -72,6 +73,7 @@ export class AddEditComponent extends BaseAddEditComponent {
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.cipherId) { if (params.cipherId) {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;

View File

@@ -17,6 +17,7 @@ import { StateService } from "@bitwarden/common/abstractions/state.service";
selector: "app-vault-attachments", selector: "app-vault-attachments",
templateUrl: "attachments.component.html", templateUrl: "attachments.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class AttachmentsComponent extends BaseAttachmentsComponent { export class AttachmentsComponent extends BaseAttachmentsComponent {
openedAttachmentsInPopup: boolean; openedAttachmentsInPopup: boolean;
@@ -46,6 +47,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;
await this.init(); await this.init();

View File

@@ -80,6 +80,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
this.searchTypeSearch = !this.platformUtilsService.isSafari(); this.searchTypeSearch = !this.platformUtilsService.isSafari();
this.showOrganizations = await this.organizationService.hasOrganizations(); this.showOrganizations = await this.organizationService.hasOrganizations();
this.vaultFilter = this.vaultFilterService.getVaultFilter(); this.vaultFilter = this.vaultFilterService.getVaultFilter();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (this.applySavedState) { if (this.applySavedState) {
this.state = await this.stateService.getBrowserCipherComponentState(); this.state = await this.stateService.getBrowserCipherComponentState();

View File

@@ -14,6 +14,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
selector: "app-vault-collections", selector: "app-vault-collections",
templateUrl: "collections.component.html", templateUrl: "collections.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class CollectionsComponent extends BaseCollectionsComponent { export class CollectionsComponent extends BaseCollectionsComponent {
constructor( constructor(
collectionService: CollectionService, collectionService: CollectionService,
@@ -28,9 +29,11 @@ export class CollectionsComponent extends BaseCollectionsComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.onSavedCollections.subscribe(() => { this.onSavedCollections.subscribe(() => {
this.back(); this.back();
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;
await this.load(); await this.load();

View File

@@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
selector: "app-password-history", selector: "app-password-history",
templateUrl: "password-history.component.html", templateUrl: "password-history.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class PasswordHistoryComponent extends BasePasswordHistoryComponent { export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
constructor( constructor(
cipherService: CipherService, cipherService: CipherService,
@@ -24,6 +25,7 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.cipherId) { if (params.cipherId) {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;

View File

@@ -14,6 +14,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
selector: "app-vault-share", selector: "app-vault-share",
templateUrl: "share.component.html", templateUrl: "share.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ShareComponent extends BaseShareComponent { export class ShareComponent extends BaseShareComponent {
constructor( constructor(
collectionService: CollectionService, collectionService: CollectionService,
@@ -36,9 +37,11 @@ export class ShareComponent extends BaseShareComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.onSharedCipher.subscribe(() => { this.onSharedCipher.subscribe(() => {
this.router.navigate(["view-cipher", { cipherId: this.cipherId }]); this.router.navigate(["view-cipher", { cipherId: this.cipherId }]);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;
await this.load(); await this.load();

View File

@@ -114,6 +114,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}); });
const restoredScopeState = await this.restoreState(); const restoredScopeState = await this.restoreState();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
this.state = await this.browserStateService.getBrowserGroupingComponentState(); this.state = await this.browserStateService.getBrowserGroupingComponentState();
if (this.state?.searchText) { if (this.state?.searchText) {

View File

@@ -47,6 +47,7 @@ import { VaultFilterService } from "../../services/vaultFilter.service";
]), ]),
], ],
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class VaultSelectComponent implements OnInit { export class VaultSelectComponent implements OnInit {
@Output() onVaultSelectionChanged = new EventEmitter(); @Output() onVaultSelectionChanged = new EventEmitter();
@@ -168,6 +169,7 @@ export class VaultSelectComponent implements OnInit {
this.overlayRef.outsidePointerEvents(), this.overlayRef.outsidePointerEvents(),
this.overlayRef.backdropClick(), this.overlayRef.backdropClick(),
this.overlayRef.detachments() this.overlayRef.detachments()
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
).subscribe(() => { ).subscribe(() => {
this.close(); this.close();
}); });

View File

@@ -88,6 +88,7 @@ export class ViewComponent extends BaseViewComponent {
ngOnInit() { ngOnInit() {
this.inPopout = this.popupUtilsService.inPopout(window); this.inPopout = this.popupUtilsService.inPopout(window);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.cipherId) { if (params.cipherId) {
this.cipherId = params.cipherId; this.cipherId = params.cipherId;

View File

@@ -16,7 +16,7 @@ describe("Browser Utils Service", () => {
let browserPlatformUtilsService: BrowserPlatformUtilsService; let browserPlatformUtilsService: BrowserPlatformUtilsService;
beforeEach(() => { beforeEach(() => {
(window as any).matchMedia = jest.fn().mockReturnValueOnce({}); (window as any).matchMedia = jest.fn().mockReturnValueOnce({});
browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, null); browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
}); });
afterEach(() => { afterEach(() => {

View File

@@ -5,7 +5,6 @@ import { DeviceType } from "@bitwarden/common/enums/deviceType";
import { BrowserApi } from "../browser/browserApi"; import { BrowserApi } from "../browser/browserApi";
import { SafariApp } from "../browser/safariApp"; import { SafariApp } from "../browser/safariApp";
import { StateService } from "../services/abstractions/state.service";
const DialogPromiseExpiration = 600000; // 10 minutes const DialogPromiseExpiration = 600000; // 10 minutes
@@ -19,7 +18,6 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
constructor( constructor(
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService,
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
private biometricCallback: () => Promise<boolean> private biometricCallback: () => Promise<boolean>
) {} ) {}

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject } from "rxjs/internal/BehaviorSubject"; import { BehaviorSubject } from "rxjs";
import { Folder } from "@bitwarden/common/models/domain/folder"; import { Folder } from "@bitwarden/common/models/domain/folder";
import { FolderView } from "@bitwarden/common/models/view/folderView"; import { FolderView } from "@bitwarden/common/models/view/folderView";

View File

@@ -25,6 +25,7 @@ export default class I18nService extends BaseI18nService {
"en-IN", "en-IN",
"es", "es",
"et", "et",
"eu",
"fa", "fa",
"fi", "fi",
"fil", "fil",

View File

@@ -1,27 +0,0 @@
import { FlagName } from "../src/flags";
import { CliUtils } from "../src/utils";
describe("flagEnabled", () => {
it("is true if flag is null", () => {
process.env.FLAGS = JSON.stringify({ test: null });
expect(CliUtils.flagEnabled("test" as FlagName)).toBe(true);
});
it("is true if flag is undefined", () => {
process.env.FLAGS = JSON.stringify({});
expect(CliUtils.flagEnabled("test" as FlagName)).toBe(true);
});
it("is true if flag is true", () => {
process.env.FLAGS = JSON.stringify({ test: true });
expect(CliUtils.flagEnabled("test" as FlagName)).toBe(true);
});
it("is false if flag is false", () => {
process.env.FLAGS = JSON.stringify({ test: false });
expect(CliUtils.flagEnabled("test" as FlagName)).toBe(false);
});
});

View File

@@ -5,6 +5,7 @@ import * as program from "commander";
import * as jsdom from "jsdom"; import * as jsdom from "jsdom";
import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { ClientType } from "@bitwarden/common/enums/clientType"; import { ClientType } from "@bitwarden/common/enums/clientType";
import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions";
import { LogLevelType } from "@bitwarden/common/enums/logLevelType"; import { LogLevelType } from "@bitwarden/common/enums/logLevelType";
@@ -30,6 +31,7 @@ import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.ser
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
import { NoopMessagingService } from "@bitwarden/common/services/noopMessaging.service"; import { NoopMessagingService } from "@bitwarden/common/services/noopMessaging.service";
import { OrganizationService } from "@bitwarden/common/services/organization.service"; import { OrganizationService } from "@bitwarden/common/services/organization.service";
import { OrganizationApiService } from "@bitwarden/common/services/organization/organization-api.service";
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
import { PolicyService } from "@bitwarden/common/services/policy/policy.service"; import { PolicyService } from "@bitwarden/common/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/services/provider.service"; import { ProviderService } from "@bitwarden/common/services/provider.service";
@@ -58,7 +60,7 @@ import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.ser
import { VaultProgram } from "./vault.program"; import { VaultProgram } from "./vault.program";
// Polyfills // Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser; global.DOMParser = new jsdom.JSDOM().window.DOMParser;
// eslint-disable-next-line // eslint-disable-next-line
const packageJson = require("../package.json"); const packageJson = require("../package.json");
@@ -108,6 +110,7 @@ export class Main {
broadcasterService: BroadcasterService; broadcasterService: BroadcasterService;
folderApiService: FolderApiService; folderApiService: FolderApiService;
userVerificationApiService: UserVerificationApiService; userVerificationApiService: UserVerificationApiService;
organizationApiService: OrganizationApiServiceAbstraction;
constructor() { constructor() {
let p = null; let p = null;
@@ -185,6 +188,9 @@ export class Main {
async (expired: boolean) => await this.logout(), async (expired: boolean) => await this.logout(),
customUserAgent customUserAgent
); );
this.organizationApiService = new OrganizationApiService(this.apiService);
this.containerService = new ContainerService(this.cryptoService); this.containerService = new ContainerService(this.cryptoService);
this.settingsService = new SettingsService(this.stateService); this.settingsService = new SettingsService(this.stateService);
@@ -317,7 +323,6 @@ export class Main {
this.apiService, this.apiService,
this.i18nService, this.i18nService,
this.collectionService, this.collectionService,
this.platformUtilsService,
this.cryptoService this.cryptoService
); );
this.exportService = new ExportService( this.exportService = new ExportService(

View File

@@ -1,18 +1,18 @@
import * as inquirer from "inquirer"; import * as inquirer from "inquirer";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { SyncService } from "@bitwarden/common/abstractions/sync.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { Response } from "@bitwarden/node/cli/models/response"; import { Response } from "@bitwarden/node/cli/models/response";
import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse";
export class ConvertToKeyConnectorCommand { export class ConvertToKeyConnectorCommand {
constructor( constructor(
private apiService: ApiService,
private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private syncService: SyncService, private syncService: SyncService,
private organizationApiService: OrganizationApiServiceAbstraction,
private logout: () => Promise<void> private logout: () => Promise<void>
) {} ) {}
@@ -72,7 +72,7 @@ export class ConvertToKeyConnectorCommand {
return Response.success(); return Response.success();
} else if (answer.convert === "leave") { } else if (answer.convert === "leave") {
await this.apiService.postLeaveOrganization(organization.id); await this.organizationApiService.leave(organization.id);
await this.keyConnectorService.removeConvertAccountRequired(); await this.keyConnectorService.removeConvertAccountRequired();
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
return Response.success(); return Response.success();

View File

@@ -121,6 +121,7 @@ export class ServeCommand {
this.main.keyConnectorService, this.main.keyConnectorService,
this.main.environmentService, this.main.environmentService,
this.main.syncService, this.main.syncService,
this.main.organizationApiService,
async () => await this.main.logout() async () => await this.main.logout()
); );

View File

@@ -3,6 +3,7 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose"; import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
@@ -26,6 +27,7 @@ export class UnlockCommand {
private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private syncService: SyncService, private syncService: SyncService,
private organizationApiService: OrganizationApiServiceAbstraction,
private logout: () => Promise<void> private logout: () => Promise<void>
) {} ) {}
@@ -78,10 +80,10 @@ export class UnlockCommand {
if (await this.keyConnectorService.getConvertAccountRequired()) { if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand( const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
this.apiService,
this.keyConnectorService, this.keyConnectorService,
this.environmentService, this.environmentService,
this.syncService, this.syncService,
this.organizationApiService,
this.logout this.logout
); );
const convertResponse = await convertToKeyConnectorCommand.run(); const convertResponse = await convertToKeyConnectorCommand.run();

View File

@@ -1,5 +1,27 @@
// Remove this linter hint if any flags exist import {
// eslint-disable-next-line @typescript-eslint/ban-types flagEnabled as baseFlagEnabled,
export type Flags = {}; devFlagEnabled as baseDevFlagEnabled,
devFlagValue as baseDevFlagValue,
SharedFlags,
SharedDevFlags,
} from "@bitwarden/common/misc/flags";
export type FlagName = keyof Flags; // required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type Flags = {} & SharedFlags;
// required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type DevFlags = {} & SharedDevFlags;
export function flagEnabled(flag: keyof Flags): boolean {
return baseFlagEnabled<Flags>(flag);
}
export function devFlagEnabled(flag: keyof DevFlags) {
return baseDevFlagEnabled<DevFlags>(flag);
}
export function devFlagValue(flag: keyof DevFlags) {
return baseDevFlagValue(flag);
}

View File

@@ -15,7 +15,10 @@
"message": "No Folder" "message": "No Folder"
}, },
"importEncKeyError": { "importEncKeyError": {
"message": "Invalid file password." "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
},
"invalidFilePassword": {
"message": "Invalid file password, please use the password you entered when you created the export file."
}, },
"importPasswordRequired": { "importPasswordRequired": {
"message": "File is password protected, please provide a decryption password." "message": "File is password protected, please provide a decryption password."

View File

@@ -260,6 +260,7 @@ export class Program extends BaseProgram {
this.main.keyConnectorService, this.main.keyConnectorService,
this.main.environmentService, this.main.environmentService,
this.main.syncService, this.main.syncService,
this.main.organizationApiService,
async () => await this.main.logout() async () => await this.main.logout()
); );
const response = await command.run(password, cmd); const response = await command.run(password, cmd);
@@ -534,6 +535,7 @@ export class Program extends BaseProgram {
this.main.keyConnectorService, this.main.keyConnectorService,
this.main.environmentService, this.main.environmentService,
this.main.syncService, this.main.syncService,
this.main.organizationApiService,
this.main.logout this.main.logout
); );
const response = await command.run(null, null); const response = await command.run(null, null);

View File

@@ -13,8 +13,6 @@ import { FolderView } from "@bitwarden/common/models/view/folderView";
import { Response } from "@bitwarden/node/cli/models/response"; import { Response } from "@bitwarden/node/cli/models/response";
import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse";
import { FlagName, Flags } from "./flags";
export class CliUtils { export class CliUtils {
static writeLn(s: string, finalLine = false, error = false) { static writeLn(s: string, finalLine = false, error = false) {
const stream = error ? process.stderr : process.stdout; const stream = error ? process.stderr : process.stdout;
@@ -253,18 +251,4 @@ export class CliUtils {
static convertBooleanOption(optionValue: any) { static convertBooleanOption(optionValue: any) {
return optionValue || optionValue === "" ? true : false; return optionValue || optionValue === "" ? true : false;
} }
static flagEnabled(flag: FlagName) {
return this.flags[flag] == null || this.flags[flag];
}
private static get flags(): Flags {
const envFlags = process.env.FLAGS;
if (typeof envFlags === "string") {
return JSON.parse(envFlags) as Flags;
} else {
return envFlags as Flags;
}
}
} }

View File

@@ -69,6 +69,7 @@ export class LockComponent extends BaseLockComponent {
const forcePasswordReset = await this.stateService.getForcePasswordReset(); const forcePasswordReset = await this.stateService.getForcePasswordReset();
this.successRoute = forcePasswordReset === true ? this.unAuthenicatedUrl : this.authenicatedUrl; this.successRoute = forcePasswordReset === true ? this.unAuthenicatedUrl : this.authenicatedUrl;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.route.queryParams.subscribe((params) => { this.route.queryParams.subscribe((params) => {
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) { if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
setTimeout(async () => { setTimeout(async () => {

View File

@@ -102,13 +102,16 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
this.environmentModal this.environmentModal
); );
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
modal.onShown.subscribe(() => { modal.onShown.subscribe(() => {
this.showingModal = true; this.showingModal = true;
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
modal.onClosed.subscribe(() => { modal.onClosed.subscribe(() => {
this.showingModal = false; this.showingModal = false;
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onSaved.subscribe(() => { childComponent.onSaved.subscribe(() => {
modal.close(); modal.close();
}); });

View File

@@ -7,6 +7,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
@@ -35,7 +36,8 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
route: ActivatedRoute, route: ActivatedRoute,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private ngZone: NgZone,
stateService: StateService stateService: StateService,
organizationApiService: OrganizationApiServiceAbstraction
) { ) {
super( super(
i18nService, i18nService,
@@ -49,13 +51,14 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
apiService, apiService,
syncService, syncService,
route, route,
stateService stateService,
organizationApiService
); );
} }
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message) => {
this.ngZone.run(() => { this.ngZone.run(() => {
switch (message.command) { switch (message.command) {
case "windowHidden": case "windowHidden":

View File

@@ -24,6 +24,7 @@ import { DeleteAccountComponent } from "./delete-account.component";
selector: "app-settings", selector: "app-settings",
templateUrl: "settings.component.html", templateUrl: "settings.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
vaultTimeoutAction: string; vaultTimeoutAction: string;
pin: boolean = null; pin: boolean = null;
@@ -178,6 +179,7 @@ export class SettingsComponent implements OnInit {
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout()); this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction(); this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
this.previousVaultTimeout = this.vaultTimeout.value; this.previousVaultTimeout = this.vaultTimeout.value;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => { this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
this.saveVaultTimeoutOptions(); this.saveVaultTimeoutOptions();
}); });

View File

@@ -21,6 +21,7 @@ import { TwoFactorOptionsComponent } from "./two-factor-options.component";
selector: "app-two-factor", selector: "app-two-factor",
templateUrl: "two-factor.component.html", templateUrl: "two-factor.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class TwoFactorComponent extends BaseTwoFactorComponent { export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef; twoFactorOptionsModal: ViewContainerRef;
@@ -67,18 +68,22 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
this.twoFactorOptionsModal this.twoFactorOptionsModal
); );
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
modal.onShown.subscribe(() => { modal.onShown.subscribe(() => {
this.showingModal = true; this.showingModal = true;
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
modal.onClosed.subscribe(() => { modal.onClosed.subscribe(() => {
this.showingModal = false; this.showingModal = false;
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => { childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close(); modal.close();
this.selectedProviderType = provider; this.selectedProviderType = provider;
await this.init(); await this.init();
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onRecoverSelected.subscribe(() => { childComponent.onRecoverSelected.subscribe(() => {
modal.close(); modal.close();
}); });

View File

@@ -98,7 +98,7 @@ export class AppComponent implements OnInit, OnDestroy {
private isIdle = false; private isIdle = false;
private activeUserId: string = null; private activeUserId: string = null;
private destroy$: Subject<void> = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
@@ -170,12 +170,12 @@ export class AppComponent implements OnInit, OnDestroy {
this.loading = false; this.loading = false;
break; break;
case "lockVault": case "lockVault":
await this.vaultTimeoutService.lock(true, message.userId); await this.vaultTimeoutService.lock(message.userId);
break; break;
case "lockAllVaults": case "lockAllVaults":
for (const userId in this.stateService.accounts.getValue()) { for (const userId in this.stateService.accounts.getValue()) {
if (userId != null) { if (userId != null) {
await this.vaultTimeoutService.lock(true, userId); await this.vaultTimeoutService.lock(userId);
} }
} }
break; break;
@@ -377,10 +377,12 @@ export class AppComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onSaved.subscribe(() => { childComponent.onSaved.subscribe(() => {
this.modal.close(); this.modal.close();
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });
@@ -396,11 +398,13 @@ export class AppComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onSavedFolder.subscribe(async () => { childComponent.onSavedFolder.subscribe(async () => {
this.modal.close(); this.modal.close();
this.syncService.fullSync(false); this.syncService.fullSync(false);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });
@@ -415,6 +419,7 @@ export class AppComponent implements OnInit, OnDestroy {
(comp) => (comp.comingFromAddEdit = false) (comp) => (comp.comingFromAddEdit = false)
); );
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });
@@ -539,6 +544,7 @@ export class AppComponent implements OnInit, OnDestroy {
[this.modal] = await this.modalService.openViewRef(type, ref); [this.modal] = await this.modalService.openViewRef(type, ref);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });
@@ -593,7 +599,7 @@ export class AppComponent implements OnInit, OnDestroy {
if (options[0] === timeout) { if (options[0] === timeout) {
options[1] === "logOut" options[1] === "logOut"
? this.logOut(false, userId) ? this.logOut(false, userId)
: await this.vaultTimeoutService.lock(true, userId); : await this.vaultTimeoutService.lock(userId);
} }
} }
} }

View File

@@ -17,6 +17,7 @@ import localeEnIn from "@angular/common/locales/en-IN";
import localeEo from "@angular/common/locales/eo"; import localeEo from "@angular/common/locales/eo";
import localeEs from "@angular/common/locales/es"; import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et"; import localeEt from "@angular/common/locales/et";
import localeEu from "@angular/common/locales/eu";
import localeFa from "@angular/common/locales/fa"; import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi"; import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil"; import localeFil from "@angular/common/locales/fil";
@@ -117,6 +118,7 @@ registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEo, "eo"); registerLocaleData(localeEo, "eo");
registerLocaleData(localeEs, "es"); registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et"); registerLocaleData(localeEt, "et");
registerLocaleData(localeEu, "eu");
registerLocaleData(localeFa, "fa"); registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi"); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil"); registerLocaleData(localeFil, "fil");

View File

@@ -48,6 +48,7 @@ export class SwitcherAccount extends Account {
]), ]),
], ],
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class AccountSwitcherComponent implements OnInit { export class AccountSwitcherComponent implements OnInit {
isOpen = false; isOpen = false;
accounts: { [userId: string]: SwitcherAccount } = {}; accounts: { [userId: string]: SwitcherAccount } = {};
@@ -84,6 +85,7 @@ export class AccountSwitcherComponent implements OnInit {
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.stateService.accounts.subscribe(async (accounts: { [userId: string]: Account }) => { this.stateService.accounts.subscribe(async (accounts: { [userId: string]: Account }) => {
for (const userId in accounts) { for (const userId in accounts) {
accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus( accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus(

View File

@@ -17,16 +17,19 @@ export class SearchComponent implements OnInit, OnDestroy {
private activeAccountSubscription: Subscription; private activeAccountSubscription: Subscription;
constructor(private searchBarService: SearchBarService, private stateService: StateService) { constructor(private searchBarService: SearchBarService, private stateService: StateService) {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.searchBarService.state$.subscribe((state) => { this.searchBarService.state$.subscribe((state) => {
this.state = state; this.state = state;
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.searchText.valueChanges.subscribe((value) => { this.searchText.valueChanges.subscribe((value) => {
this.searchBarService.setSearchText(value); this.searchBarService.setSearchText(value);
}); });
} }
ngOnInit() { ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.activeAccountSubscription = this.stateService.activeAccount$.subscribe((value) => { this.activeAccountSubscription = this.stateService.activeAccount$.subscribe((value) => {
this.searchBarService.setSearchText(""); this.searchBarService.setSearchText("");
this.searchText.patchValue(""); this.searchText.patchValue("");

View File

@@ -56,6 +56,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
policyService, policyService,
logService logService
); );
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.searchBarService.searchText$.subscribe((searchText) => { this.searchBarService.searchText$.subscribe((searchText) => {
this.searchText = searchText; this.searchText = searchText;
this.searchTextChanged(); this.searchTextChanged();

View File

@@ -5,10 +5,12 @@
<div class="box-content"> <div class="box-content">
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields"> <div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
<div <div
role="group"
class="box-content-row box-content-row-multi box-draggable-row" class="box-content-row box-content-row-multi box-draggable-row"
cdkDrag cdkDrag
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction" *ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
[ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }" [ngClass]="{ 'box-content-row-checkbox': f.type === fieldType.Boolean }"
attr.aria-label="{{ f.name }}"
> >
<button <button
type="button" type="button"
@@ -39,6 +41,7 @@
*ngIf="f.type === fieldType.Text" *ngIf="f.type === fieldType.Text"
placeholder="{{ 'value' | i18n }}" placeholder="{{ 'value' | i18n }}"
appInputVerbatim appInputVerbatim
attr.aria-describedby="fieldName{{ i }}"
/> />
<!-- Password --> <!-- Password -->
<input <input
@@ -51,6 +54,7 @@
placeholder="{{ 'value' | i18n }}" placeholder="{{ 'value' | i18n }}"
[disabled]="!cipher.viewPassword && !f.newField" [disabled]="!cipher.viewPassword && !f.newField"
appInputVerbatim appInputVerbatim
attr.aria-describedby="fieldName{{ i }}"
/> />
<!-- Linked --> <!-- Linked -->
<select <select
@@ -58,6 +62,7 @@
name="Field.Value{{ i }}" name="Field.Value{{ i }}"
[(ngModel)]="f.linkedId" [(ngModel)]="f.linkedId"
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null" *ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
attr.aria-describedby="fieldName{{ i }}"
> >
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option> <option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
@@ -72,6 +77,7 @@
appTrueFalseValue appTrueFalseValue
trueValue="true" trueValue="true"
falseValue="false" falseValue="false"
attr.aria-describedby="fieldName{{ i }}"
/> />
<div <div
class="action-buttons" class="action-buttons"
@@ -84,6 +90,7 @@
appA11yTitle="{{ 'toggleVisibility' | i18n }}" appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="f.showValue" [attr.aria-pressed]="f.showValue"
(click)="toggleFieldValue(f)" (click)="toggleFieldValue(f)"
attr.aria-describedby="fieldName{{ i }}"
> >
<i <i
class="bwi bwi-lg" class="bwi bwi-lg"

View File

@@ -391,9 +391,11 @@
<div class="box-content"> <div class="box-content">
<ng-container *ngIf="cipher.login.hasUris"> <ng-container *ngIf="cipher.login.hasUris">
<div <div
role="group"
class="box-content-row box-content-row-multi" class="box-content-row box-content-row-multi"
appBoxRow appBoxRow
*ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction" *ngFor="let u of cipher.login.uris; let i = index; trackBy: trackByFunction"
attr.aria-label="{{ 'uriPosition' | i18n: i + 1 }}"
> >
<button <button
type="button" type="button"

View File

@@ -10,10 +10,12 @@ import { SearchBarService } from "../layout/search/search-bar.service";
selector: "app-vault-ciphers", selector: "app-vault-ciphers",
templateUrl: "ciphers.component.html", templateUrl: "ciphers.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class CiphersComponent extends BaseCiphersComponent { export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService, searchBarService: SearchBarService) { constructor(searchService: SearchService, searchBarService: SearchBarService) {
super(searchService); super(searchService);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
searchBarService.searchText$.subscribe((searchText) => { searchBarService.searchText$.subscribe((searchText) => {
this.searchText = searchText; this.searchText = searchText;
this.search(200); this.search(200);

View File

@@ -382,6 +382,18 @@
/> />
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
<div class="box-content-row" appBoxRow>
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
<input
id="duckduckgo-apikey"
type="password"
name="DuckDudkGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'"> <ng-container *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label> <label for="anonaddy-accessToken">{{ "apiAccessToken" | i18n }}</label>
@@ -416,6 +428,18 @@
/> />
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'fastmail'">
<div class="box-content-row" appBoxRow>
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="fastmail-apiToken"
type="password"
name="FastmailApiToken"
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
</div> </div>
</div> </div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions"> <div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions">

View File

@@ -216,6 +216,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async load() { async load() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {
if (params.cipherId) { if (params.cipherId) {
const cipherView = new CipherView(); const cipherView = new CipherView();
@@ -458,9 +459,12 @@ export class VaultComponent implements OnInit, OnDestroy {
this.modal = modal; this.modal = modal;
let madeAttachmentChanges = false; let madeAttachmentChanges = false;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true)); childComponent.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true)); childComponent.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
if (madeAttachmentChanges) { if (madeAttachmentChanges) {
@@ -482,11 +486,13 @@ export class VaultComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onSharedCipher.subscribe(async () => { childComponent.onSharedCipher.subscribe(async () => {
this.modal.close(); this.modal.close();
this.viewCipher(cipher); this.viewCipher(cipher);
await this.ciphersComponent.refresh(); await this.ciphersComponent.refresh();
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
}); });
@@ -504,10 +510,12 @@ export class VaultComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onSavedCollections.subscribe(() => { childComponent.onSavedCollections.subscribe(() => {
this.modal.close(); this.modal.close();
this.viewCipher(cipher); this.viewCipher(cipher);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
}); });
@@ -524,6 +532,7 @@ export class VaultComponent implements OnInit, OnDestroy {
(comp) => (comp.cipherId = cipher.id) (comp) => (comp.cipherId = cipher.id)
); );
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.modal.onClosed.subscribe(async () => { this.modal.onClosed.subscribe(async () => {
this.modal = null; this.modal = null;
}); });
@@ -596,6 +605,7 @@ export class VaultComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
childComponent.onSelected.subscribe((value: string) => { childComponent.onSelected.subscribe((value: string) => {
this.modal.close(); this.modal.close();
if (loginType) { if (loginType) {
@@ -608,6 +618,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });
@@ -629,15 +640,18 @@ export class VaultComponent implements OnInit, OnDestroy {
); );
this.modal = modal; this.modal = modal;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
this.modal.close(); this.modal.close();
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => {
this.modal.close(); this.modal.close();
await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter);
}); });
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.modal.onClosed.subscribe(() => { this.modal.onClosed.subscribe(() => {
this.modal = null; this.modal = null;
}); });

27
apps/desktop/src/flags.ts Normal file
View File

@@ -0,0 +1,27 @@
import {
flagEnabled as baseFlagEnabled,
devFlagEnabled as baseDevFlagEnabled,
devFlagValue as baseDevFlagValue,
SharedFlags,
SharedDevFlags,
} from "@bitwarden/common/misc/flags";
// required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type Flags = {} & SharedFlags;
// required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type DevFlags = {} & SharedDevFlags;
export function flagEnabled(flag: keyof Flags): boolean {
return baseFlagEnabled<Flags>(flag);
}
export function devFlagEnabled(flag: keyof DevFlags) {
return baseDevFlagEnabled<DevFlags>(flag);
}
export function devFlagValue(flag: keyof DevFlags) {
return baseDevFlagValue(flag);
}

View File

@@ -1980,16 +1980,16 @@
"message": "API-sleutel" "message": "API-sleutel"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Premie-intekening word vereis"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organisasie is gedeaktiveer." "message": "Organisasie is gedeaktiveer."
}, },
"disabledOrganizationFilterError": { "disabledOrganizationFilterError": {
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." "message": "Items in gedeaktiveerde organisasies is ontoeganklik. Kontak u organisasie-eienaar vir bystand."
}, },
"neverLockWarning": { "neverLockWarning": {
"message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." "message": "Is u seker u wil die “Nooit”-opsie gebruik? Deur u vergrendelopsies op “Nooit” te stel word u kluis se enkripsie op u toestel bewaar. Indien u hierdie opsie gebruik moet u verseker dat u toestel behoorlik beskerm is."
}, },
"cardBrandMir": { "cardBrandMir": {
"message": "Mir" "message": "Mir"

View File

@@ -1394,19 +1394,19 @@
"message": "قفل مع كلمة المرور الرئيسية عند إعادة تشغيل" "message": "قفل مع كلمة المرور الرئيسية عند إعادة تشغيل"
}, },
"deleteAccount": { "deleteAccount": {
"message": "Delete account" "message": "حذف الحساب"
}, },
"deleteAccountDesc": { "deleteAccountDesc": {
"message": "Proceed below to delete your account and all vault data." "message": "تابع أدناه لحذف حسابك وجميع بيانات خزنتك."
}, },
"deleteAccountWarning": { "deleteAccountWarning": {
"message": "Deleting your account is permanent. It cannot be undone." "message": "حذف حسابك دائم. لا يمكن التراجع عنه."
}, },
"accountDeleted": { "accountDeleted": {
"message": "Account deleted" "message": "تم حذف الحساب"
}, },
"accountDeletedDesc": { "accountDeletedDesc": {
"message": "Your account has been closed and all associated data has been deleted." "message": "لقد تم إغلاق حسابك وتم حذف جميع البيانات المرتبطة به."
}, },
"preferences": { "preferences": {
"message": "التفضيلات" "message": "التفضيلات"
@@ -1980,7 +1980,7 @@
"message": "مفتاح API" "message": "مفتاح API"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "الاشتراك المميز مطلوب"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "تم تعطيل المؤسسة." "message": "تم تعطيل المؤسسة."

View File

@@ -361,7 +361,7 @@
"message": "Выдаліць далучэнне" "message": "Выдаліць далучэнне"
}, },
"deleteItemConfirmation": { "deleteItemConfirmation": {
"message": "Вы ўпэўнены, што хочаце выдаліць гэты элемент?" "message": "Вы сапраўды хочаце адправіць элемент у сметніцу?"
}, },
"deletedItem": { "deletedItem": {
"message": "Элемент адпраўлены ў сметніцу" "message": "Элемент адпраўлены ў сметніцу"
@@ -560,7 +560,7 @@
"message": "Няма элементаў для паказу." "message": "Няма элементаў для паказу."
}, },
"sendVerificationCode": { "sendVerificationCode": {
"message": "Адправіць код праверкі на вашу электронную пошту" "message": "Адправіць праверачны код на вашу электронную пошту"
}, },
"sendCode": { "sendCode": {
"message": "Адправіць код" "message": "Адправіць код"
@@ -608,7 +608,7 @@
"message": "Запомніць мяне" "message": "Запомніць мяне"
}, },
"sendVerificationCodeEmailAgain": { "sendVerificationCodeEmailAgain": {
"message": "Адправіць код пацвярджэння зноў" "message": "Адправіць праверачны код яшчэ раз"
}, },
"useAnotherTwoStepMethod": { "useAnotherTwoStepMethod": {
"message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу" "message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу"
@@ -1305,26 +1305,26 @@
"message": "Гатова" "message": "Гатова"
}, },
"accessibilityCookieSaved": { "accessibilityCookieSaved": {
"message": "Accessibility cookie saved!" "message": "Cookie са спецыяльнымі магчымасцямі захаваны!"
}, },
"noAccessibilityCookieSaved": { "noAccessibilityCookieSaved": {
"message": "No accessibility cookie saved" "message": "Адсутнічаюць захаваныя cookie са спецыяльнымі магчымасцямі"
}, },
"warning": { "warning": {
"message": "УВАГА", "message": "УВАГА",
"description": "WARNING (should stay in capitalized letters if the language permits)" "description": "WARNING (should stay in capitalized letters if the language permits)"
}, },
"confirmVaultExport": { "confirmVaultExport": {
"message": "Confirm Vault Export" "message": "Пацвердзіць экспартаванне сховішча"
}, },
"exportWarningDesc": { "exportWarningDesc": {
"message": "Экспартуемы файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць ці адпраўляць па небяспечным каналам (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання." "message": "Экспартуемы файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць ці адпраўляць па небяспечным каналам (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
}, },
"encExportKeyWarningDesc": { "encExportKeyWarningDesc": {
"message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." "message": "Падчас экспартавання даныя шыфруюцца з дапамогай ключа шыфравання ўліковага запісу. Калі вы калі-небудзь зменіце ключ шыфравання ўліковага запісу, вам неабходна будзе экспартаваць даныя паўторна, паколькі вы не зможаце расшыфраваць гэты файл экспартавання."
}, },
"encExportAccountWarningDesc": { "encExportAccountWarningDesc": {
"message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." "message": "Ключы шыфравання з'яўляюцца ўнікальнымі для кожнага ўліковага запісу Bitwarden, таму нельга імпартаваць зашыфраванае сховішча ў іншы ўліковы запіс."
}, },
"noOrganizationsList": { "noOrganizationsList": {
"message": "Вы не з'яўляецеся членам якой-небудзь арганізацыі. Арганізацыі дазваляюць бяспечна абменьвацца элементамі з іншымі карыстальнікамі." "message": "Вы не з'яўляецеся членам якой-небудзь арганізацыі. Арганізацыі дазваляюць бяспечна абменьвацца элементамі з іншымі карыстальнікамі."
@@ -1385,16 +1385,16 @@
"message": "Праверыць на Bitwarden." "message": "Праверыць на Bitwarden."
}, },
"autoPromptWindowsHello": { "autoPromptWindowsHello": {
"message": "Ask for Windows Hello on launch" "message": "Запытваць Windows Hello пры запуску"
}, },
"autoPromptTouchId": { "autoPromptTouchId": {
"message": "Ask for Touch ID on launch" "message": "Запытваць Touch ID пры запуску"
}, },
"lockWithMasterPassOnRestart": { "lockWithMasterPassOnRestart": {
"message": "Блакіраваць асноўным паролем пры перазапуску" "message": "Блакіраваць асноўным паролем пры перазапуску"
}, },
"deleteAccount": { "deleteAccount": {
"message": "Delete account" "message": "Выдаліць уліковы запіс"
}, },
"deleteAccountDesc": { "deleteAccountDesc": {
"message": "Proceed below to delete your account and all vault data." "message": "Proceed below to delete your account and all vault data."
@@ -1613,7 +1613,7 @@
"message": "An organization policy is affecting your ownership options." "message": "An organization policy is affecting your ownership options."
}, },
"allSends": { "allSends": {
"message": "Усе адпраўленні", "message": "Усе Send'ы",
"description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendTypeFile": { "sendTypeFile": {
@@ -1623,11 +1623,11 @@
"message": "Тэкст" "message": "Тэкст"
}, },
"searchSends": { "searchSends": {
"message": "Пошук адпраўленняў", "message": "Пошук Send'аў",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"editSend": { "editSend": {
"message": "Рэдагаваць адпраўленне", "message": "Рэдагаваць Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"myVault": { "myVault": {
@@ -1640,7 +1640,7 @@
"message": "Дата выдалення" "message": "Дата выдалення"
}, },
"deletionDateDesc": { "deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.", "message": "Send будзе незваротна выдалены ў азначаныя дату і час.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"expirationDate": { "expirationDate": {
@@ -1651,7 +1651,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"maxAccessCount": { "maxAccessCount": {
"message": "Maximum Access Count", "message": "Максімальная колькасць доступаў",
"description": "This text will be displayed after a Send has been accessed the maximum amount of times." "description": "This text will be displayed after a Send has been accessed the maximum amount of times."
}, },
"maxAccessCountDesc": { "maxAccessCountDesc": {
@@ -1674,27 +1674,27 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLink": { "sendLink": {
"message": "Даслаць спасылку", "message": "Адправіць спасылку",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLinkLabel": { "sendLinkLabel": {
"message": "Send Link", "message": "Адправіць спасылку",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"textHiddenByDefault": { "textHiddenByDefault": {
"message": "When accessing the Send, hide the text by default", "message": "Прадвызначана хаваць тэкст, калі адбываецца доступ да Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"createdSend": { "createdSend": {
"message": "Created Send", "message": "Створаны Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"editedSend": { "editedSend": {
"message": "Edited Send", "message": "Адрэдагаваны Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"deletedSend": { "deletedSend": {
"message": "Deleted Send", "message": "Выдалены Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"newPassword": { "newPassword": {
@@ -1705,7 +1705,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"createSend": { "createSend": {
"message": "Стварыць адпраўленне", "message": "Стварыць Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendTextDesc": { "sendTextDesc": {
@@ -1734,7 +1734,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"copySendLinkToClipboard": { "copySendLinkToClipboard": {
"message": "Скапіяваць спасылку адпраўлення ў буфер абмену", "message": "Капіяваць спасылку на Send у буфер абмену",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"copySendLinkOnSave": { "copySendLinkOnSave": {

View File

@@ -1980,7 +1980,7 @@
"message": "API-nøgle" "message": "API-nøgle"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Premium-abonnement kræves"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organisationen er deaktiveret." "message": "Organisationen er deaktiveret."

View File

@@ -587,7 +587,7 @@
"message": "Gib den 6-stelligen Verifizierungscode aus deiner Authentifizierungs-App ein." "message": "Gib den 6-stelligen Verifizierungscode aus deiner Authentifizierungs-App ein."
}, },
"enterVerificationCodeEmail": { "enterVerificationCodeEmail": {
"message": "Gib den 6-stelligen Bestätigungscode ein, der an $EMAIL$ gesendet wurde.", "message": "Gib den 6-stelligen Verifizierungscode ein, der an $EMAIL$ gesendet wurde.",
"placeholders": { "placeholders": {
"email": { "email": {
"content": "$1", "content": "$1",
@@ -988,7 +988,7 @@
"description": "Copy to clipboard" "description": "Copy to clipboard"
}, },
"checkForUpdates": { "checkForUpdates": {
"message": "Auf Updates prüfen" "message": "Auf Updates prüfen"
}, },
"version": { "version": {
"message": "Version $VERSION_NUM$", "message": "Version $VERSION_NUM$",
@@ -1803,7 +1803,7 @@
"message": "Minuten" "message": "Minuten"
}, },
"vaultTimeoutPolicyInEffect": { "vaultTimeoutPolicyInEffect": {
"message": "Deine Unternehmensrichtlinien beeinflussen dein Tresor-Timeout. Das maximal zulässige Tresor-Timeout ist $HOURS$ Stunde(n) und $MINUTES$ Minute(n)", "message": "Die Unternehmensrichtlinien beeinflussen deinen Tresor-Timeout. Das maximal zulässige Tresor-Timeout ist $HOURS$ Stunde(n) und $MINUTES$ Minute(n)",
"placeholders": { "placeholders": {
"hours": { "hours": {
"content": "$1", "content": "$1",
@@ -1864,7 +1864,7 @@
"message": "Alle Tresore sperren" "message": "Alle Tresore sperren"
}, },
"accountLimitReached": { "accountLimitReached": {
"message": "Es dürfen nicht mehr als 5 Konten gleichzeitig angemeldet sein." "message": "Es dürfen nicht mehr als fünf Konten gleichzeitig angemeldet sein."
}, },
"accountPreferences": { "accountPreferences": {
"message": "Einstellungen" "message": "Einstellungen"
@@ -1921,7 +1921,7 @@
"message": "Passworttyp" "message": "Passworttyp"
}, },
"regenerateUsername": { "regenerateUsername": {
"message": "Benutzername neu generieren" "message": "Benutzernamen neu generieren"
}, },
"generateUsername": { "generateUsername": {
"message": "Benutzernamen generieren" "message": "Benutzernamen generieren"
@@ -1958,7 +1958,7 @@
"message": "Alle Tresore" "message": "Alle Tresore"
}, },
"searchOrganization": { "searchOrganization": {
"message": "Organisationen durchsuchen" "message": "Organisation durchsuchen"
}, },
"searchMyVault": { "searchMyVault": {
"message": "Meinen Tresor durchsuchen" "message": "Meinen Tresor durchsuchen"

View File

@@ -145,7 +145,7 @@
"message": "Brand" "message": "Brand"
}, },
"expiration": { "expiration": {
"message": "Validoperiodo" "message": "Eksvalidiĝo"
}, },
"securityCode": { "securityCode": {
"message": "Kodo de sekureco" "message": "Kodo de sekureco"

View File

@@ -1980,7 +1980,7 @@
"message": "Clave API" "message": "Clave API"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Se requiere una Suscripción Premium"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "La organización está desactivada." "message": "La organización está desactivada."

View File

@@ -560,7 +560,7 @@
"message": "Ei näytettäviä kohteita." "message": "Ei näytettäviä kohteita."
}, },
"sendVerificationCode": { "sendVerificationCode": {
"message": "Lähetä vahvistuskoodi sähköpostiisi" "message": "Lähetä todennuskoodi sähköpostiisi"
}, },
"sendCode": { "sendCode": {
"message": "Lähetä koodi" "message": "Lähetä koodi"
@@ -578,7 +578,7 @@
"message": "Todennuskoodi vaaditaan." "message": "Todennuskoodi vaaditaan."
}, },
"invalidVerificationCode": { "invalidVerificationCode": {
"message": "Virheellinen vahvistuskoodi" "message": "Virheellinen todennuskoodi"
}, },
"continue": { "continue": {
"message": "Jatka" "message": "Jatka"

View File

@@ -533,13 +533,13 @@
"message": "Adresse e-mail invalide." "message": "Adresse e-mail invalide."
}, },
"masterPasswordRequired": { "masterPasswordRequired": {
"message": "Master password is required." "message": "Le mot de passe maître est requis."
}, },
"confirmMasterPasswordRequired": { "confirmMasterPasswordRequired": {
"message": "Master password retype is required." "message": "Le mot de passe maître doit être entré de nouveau."
}, },
"masterPasswordMinlength": { "masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long." "message": "Le mot de passe maître doit au moins contenir 8 caractères."
}, },
"masterPassDoesntMatch": { "masterPassDoesntMatch": {
"message": "La confirmation du mot de passe maître ne correspond pas." "message": "La confirmation du mot de passe maître ne correspond pas."
@@ -898,10 +898,10 @@
"description": "Clipboard is the operating system thing where you copy/paste data to on your device." "description": "Clipboard is the operating system thing where you copy/paste data to on your device."
}, },
"enableFavicon": { "enableFavicon": {
"message": "Show website icons" "message": "Afficher les icônes du site web"
}, },
"faviconDesc": { "faviconDesc": {
"message": "Show a recognizable image next to each login." "message": "Afficher une image reconnaissable à côté de chaque identifiant."
}, },
"enableMinToTray": { "enableMinToTray": {
"message": "Réduire dans la barre d'outils" "message": "Réduire dans la barre d'outils"
@@ -1394,19 +1394,19 @@
"message": "Verrouiller avec le mot de passe maître lors du redémarrage" "message": "Verrouiller avec le mot de passe maître lors du redémarrage"
}, },
"deleteAccount": { "deleteAccount": {
"message": "Delete account" "message": "Supprimer le compte"
}, },
"deleteAccountDesc": { "deleteAccountDesc": {
"message": "Proceed below to delete your account and all vault data." "message": "Continuez ci-dessous pour supprimer votre compte et toutes les données associées."
}, },
"deleteAccountWarning": { "deleteAccountWarning": {
"message": "Deleting your account is permanent. It cannot be undone." "message": "La suppression de votre compte est définitive. Cette action ne peut pas être annulée."
}, },
"accountDeleted": { "accountDeleted": {
"message": "Account deleted" "message": "Compte supprimé"
}, },
"accountDeletedDesc": { "accountDeletedDesc": {
"message": "Your account has been closed and all associated data has been deleted." "message": "Votre compte a été fermé et toutes les données associées ont été supprimées."
}, },
"preferences": { "preferences": {
"message": "Préférences" "message": "Préférences"
@@ -1562,7 +1562,7 @@
"message": "En cochant cette case, vous acceptez les éléments suivants :" "message": "En cochant cette case, vous acceptez les éléments suivants :"
}, },
"acceptPoliciesRequired": { "acceptPoliciesRequired": {
"message": "Terms of Service and Privacy Policy have not been acknowledged." "message": "Les Conditions d'Utilisation et la Politique de Confidentialité n'ont pas été acceptées."
}, },
"enableBrowserIntegration": { "enableBrowserIntegration": {
"message": "Activer l'intégration avec le navigateur" "message": "Activer l'intégration avec le navigateur"
@@ -1980,16 +1980,16 @@
"message": "Clé d'IPA" "message": "Clé d'IPA"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Abonnement Premium requis"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Organization is disabled." "message": "L'organisation est désactivée."
}, },
"disabledOrganizationFilterError": { "disabledOrganizationFilterError": {
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." "message": "Les éléments des Organisations désactivées ne sont pas accessibles. Contactez le propriétaire de votre Organisation pour obtenir de l'aide."
}, },
"neverLockWarning": { "neverLockWarning": {
"message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." "message": "Êtes-vous sûr de vouloir utiliser l'option \"Jamais\" ? Définir le verrouillage sur \"Jamais\" stocke la clé de chiffrement de votre coffre sur votre appareil. Si vous utilisez cette option, vous devez vous assurer de correctement protéger votre appareil."
}, },
"cardBrandMir": { "cardBrandMir": {
"message": "Mir" "message": "Mir"

View File

@@ -1403,7 +1403,7 @@
"message": "Usunięcie konta jest nieodwracalne. Ta czynność nie może zostać cofnięta." "message": "Usunięcie konta jest nieodwracalne. Ta czynność nie może zostać cofnięta."
}, },
"accountDeleted": { "accountDeleted": {
"message": "Konto usunięte" "message": "Konto zostało usunięte"
}, },
"accountDeletedDesc": { "accountDeletedDesc": {
"message": "Konto zostało zamknięte i wszystkie powiązane z nim dane zostały usunięte." "message": "Konto zostało zamknięte i wszystkie powiązane z nim dane zostały usunięte."

View File

@@ -1980,7 +1980,7 @@
"message": "АПИ Кључ" "message": "АПИ Кључ"
}, },
"premiumSubcriptionRequired": { "premiumSubcriptionRequired": {
"message": "Premium subscription required" "message": "Premium претплата је потребна"
}, },
"organizationIsDisabled": { "organizationIsDisabled": {
"message": "Организација је онемогућена." "message": "Организација је онемогућена."

View File

@@ -12,7 +12,7 @@
"message": "รายการโปรด" "message": "รายการโปรด"
}, },
"types": { "types": {
"message": "Types" "message": "ประเภท"
}, },
"typeLogin": { "typeLogin": {
"message": "เข้าสู่ระบบ" "message": "เข้าสู่ระบบ"
@@ -42,13 +42,13 @@
"message": "แบ่งปัน" "message": "แบ่งปัน"
}, },
"share": { "share": {
"message": "Share" "message": "แชร์"
}, },
"moveToOrganization": { "moveToOrganization": {
"message": "Move to Organization" "message": "ย้ายไปที่องค์กร"
}, },
"movedItemToOrg": { "movedItemToOrg": {
"message": "$ITEMNAME$ moved to $ORGNAME$", "message": "$ITEMNAME$ ย้ายไปที่ $ORGNAME$ แล้ว",
"placeholders": { "placeholders": {
"itemname": { "itemname": {
"content": "$1", "content": "$1",
@@ -61,10 +61,10 @@
} }
}, },
"moveToOrgDesc": { "moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะเป็นการโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้าย"
}, },
"attachments": { "attachments": {
"message": "Attachments" "message": "ไฟล์แนบ"
}, },
"viewItem": { "viewItem": {
"message": "ดูรายการ" "message": "ดูรายการ"
@@ -86,7 +86,7 @@
} }
}, },
"newUri": { "newUri": {
"message": "New URI" "message": "URL ใหม่"
}, },
"username": { "username": {
"message": "ชื่อผู้ใช้" "message": "ชื่อผู้ใช้"
@@ -110,13 +110,13 @@
"message": "เว็บไซต์" "message": "เว็บไซต์"
}, },
"notes": { "notes": {
"message": "Notes" "message": "โน๊ต"
}, },
"customFields": { "customFields": {
"message": "Custom Fields" "message": "Custom Fields"
}, },
"launch": { "launch": {
"message": "Launch" "message": "เริ่ม"
}, },
"copyValue": { "copyValue": {
"message": "คัดลอกค่า", "message": "คัดลอกค่า",
@@ -269,7 +269,7 @@
"message": "นามสกุล" "message": "นามสกุล"
}, },
"fullName": { "fullName": {
"message": "Full Name" "message": "ชื่อเต็ม"
}, },
"address1": { "address1": {
"message": "ที่อยู่ 1" "message": "ที่อยู่ 1"
@@ -401,16 +401,16 @@
"message": "ความยาว" "message": "ความยาว"
}, },
"uppercase": { "uppercase": {
"message": "Uppercase (A-Z)" "message": "ตัวพิมพ์ใหญ่ (A-Z)"
}, },
"lowercase": { "lowercase": {
"message": "Lowercase (a-z)" "message": "ตัวพิมพ์เล็ก (a-z)"
}, },
"numbers": { "numbers": {
"message": "Numbers (0-9)" "message": "หมายเลข (0-9)"
}, },
"specialCharacters": { "specialCharacters": {
"message": "Special Characters (!@#$%^&*)" "message": "อักขระพิเศษ (!@#$%^&*)"
}, },
"numWords": { "numWords": {
"message": "Number of Words" "message": "Number of Words"
@@ -419,7 +419,7 @@
"message": "Word Separator" "message": "Word Separator"
}, },
"capitalize": { "capitalize": {
"message": "Capitalize", "message": "ขึ้นต้นด้วยตัวพิมพ์ใหญ่",
"description": "Make the first letter of a work uppercase." "description": "Make the first letter of a work uppercase."
}, },
"includeNumber": { "includeNumber": {
@@ -563,19 +563,19 @@
"message": "Send a verification code to your email" "message": "Send a verification code to your email"
}, },
"sendCode": { "sendCode": {
"message": "Send Code" "message": "ส่งรหัส"
}, },
"codeSent": { "codeSent": {
"message": "Code Sent" "message": "ส่งรหัสแล้ว"
}, },
"verificationCode": { "verificationCode": {
"message": "รหัสยืนยัน" "message": "รหัสยืนยัน"
}, },
"confirmIdentity": { "confirmIdentity": {
"message": "Confirm your identity to continue." "message": "ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ"
}, },
"verificationCodeRequired": { "verificationCodeRequired": {
"message": "Verification code is required." "message": "จำเป็นต้องมีรหัสการตรวจสอบยืนยัน"
}, },
"invalidVerificationCode": { "invalidVerificationCode": {
"message": "Invalid verification code" "message": "Invalid verification code"
@@ -758,16 +758,16 @@
"message": "ตัวสร้างรหัสผ่าน" "message": "ตัวสร้างรหัสผ่าน"
}, },
"contactUs": { "contactUs": {
"message": "Contact Us" "message": "ติดต่อเรา"
}, },
"getHelp": { "getHelp": {
"message": "Get Help" "message": "ขอความช่วยเหลือ"
}, },
"fileBugReport": { "fileBugReport": {
"message": "File a Bug Report" "message": "File a Bug Report"
}, },
"blog": { "blog": {
"message": "Blog" "message": "บล็อก"
}, },
"followUs": { "followUs": {
"message": "Follow Us" "message": "Follow Us"
@@ -779,7 +779,7 @@
"message": "เปลี่ยนรหัสผ่านหลัก" "message": "เปลี่ยนรหัสผ่านหลัก"
}, },
"changeMasterPasswordConfirmation": { "changeMasterPasswordConfirmation": {
"message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" "message": "คุณสามารถเปลี่ยนรหัสผ่านหลักของคุณได้ที่เว็บ bitwarden.com คุณต้องการไปที่เว็บไซต์ตอนนี้ไหม?"
}, },
"fingerprintPhrase": { "fingerprintPhrase": {
"message": "Fingerprint Phrase", "message": "Fingerprint Phrase",
@@ -799,19 +799,19 @@
"message": "Get Browser Extension" "message": "Get Browser Extension"
}, },
"syncingComplete": { "syncingComplete": {
"message": "Syncing complete" "message": "การซิงก์เสร็จสมบูรณ์"
}, },
"syncingFailed": { "syncingFailed": {
"message": "Syncing failed" "message": "การซิงก์ล้มเหลว"
}, },
"yourVaultIsLocked": { "yourVaultIsLocked": {
"message": "Your vault is locked. Verify your identity to continue." "message": "ตู้เซฟของคุณถูกล็อก ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ"
}, },
"unlock": { "unlock": {
"message": "Unlock" "message": "ปลดล็อค"
}, },
"loggedInAsOn": { "loggedInAsOn": {
"message": "Logged in as $EMAIL$ on $HOSTNAME$.", "message": "ล็อกอินด้วย $EMAIL$ บน $HOSTNAME$",
"placeholders": { "placeholders": {
"email": { "email": {
"content": "$1", "content": "$1",
@@ -824,10 +824,10 @@
} }
}, },
"invalidMasterPassword": { "invalidMasterPassword": {
"message": "Invalid master password" "message": "รหัสผ่านหลักไม่ถูกต้อง"
}, },
"twoStepLoginConfirmation": { "twoStepLoginConfirmation": {
"message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" "message": "การเข้าสู่ระบบแบบสองขั้นตอนทำให้บัญชีของคุณมีความปลอดภัยมากขึ้นด้วยการให้คุณตรวจสอบการเข้าสู่ระบบของคุณกับอุปกรณ์อื่นเช่นคีย์ความปลอดภัย, แอพ authenticator, SMS, โทรศัพท์หรืออีเมล. เข้าสู่ระบบแบบสองขั้นตอนสามารถเปิดใช้งานบน เว็บนิรภัย bitwarden.com คุณต้องการเยี่ยมชมเว็บไซต์เดี๋ยวนี้หรือไม่"
}, },
"twoStepLogin": { "twoStepLogin": {
"message": "เข้าสู่ระบบแบบสองขั้นตอน" "message": "เข้าสู่ระบบแบบสองขั้นตอน"
@@ -842,19 +842,19 @@
"message": "ทันที" "message": "ทันที"
}, },
"tenSeconds": { "tenSeconds": {
"message": "10 seconds" "message": "10 วินาที"
}, },
"twentySeconds": { "twentySeconds": {
"message": "20 seconds" "message": "20 วินาที"
}, },
"thirtySeconds": { "thirtySeconds": {
"message": "30 seconds" "message": "30 วินาที"
}, },
"oneMinute": { "oneMinute": {
"message": "1 นาที" "message": "1 นาที"
}, },
"twoMinutes": { "twoMinutes": {
"message": "2 minutes" "message": "2 นาที"
}, },
"fiveMinutes": { "fiveMinutes": {
"message": "5 นาที" "message": "5 นาที"
@@ -884,13 +884,13 @@
"message": "On Restart" "message": "On Restart"
}, },
"never": { "never": {
"message": "Never" "message": "ไม่อีกเลย"
}, },
"security": { "security": {
"message": "Security" "message": "ความปลอดภัย"
}, },
"clearClipboard": { "clearClipboard": {
"message": "Clear clipboard", "message": "ล้างคลิปบอร์ด",
"description": "Clipboard is the operating system thing where you copy/paste data to on your device." "description": "Clipboard is the operating system thing where you copy/paste data to on your device."
}, },
"clearClipboardDesc": { "clearClipboardDesc": {
@@ -898,7 +898,7 @@
"description": "Clipboard is the operating system thing where you copy/paste data to on your device." "description": "Clipboard is the operating system thing where you copy/paste data to on your device."
}, },
"enableFavicon": { "enableFavicon": {
"message": "Show website icons" "message": "โชว์ไอคอนเว็บไซต์"
}, },
"faviconDesc": { "faviconDesc": {
"message": "Show a recognizable image next to each login." "message": "Show a recognizable image next to each login."
@@ -964,23 +964,23 @@
"message": "Disabling this setting will also disable all other tray related settings." "message": "Disabling this setting will also disable all other tray related settings."
}, },
"language": { "language": {
"message": "Language" "message": "ภาษา"
}, },
"languageDesc": { "languageDesc": {
"message": "Change the language used by the application. Restart is required." "message": "การเปลี่ยนภาษาของแอพพลิเคชั่น จำเป็นต้องรีสตาร์ท"
}, },
"theme": { "theme": {
"message": "Theme" "message": "ธีม"
}, },
"themeDesc": { "themeDesc": {
"message": "Change the application's color theme." "message": "Change the application's color theme."
}, },
"dark": { "dark": {
"message": "Dark", "message": "มืด",
"description": "Dark color" "description": "Dark color"
}, },
"light": { "light": {
"message": "Light", "message": "สว่าง",
"description": "Light color" "description": "Light color"
}, },
"copy": { "copy": {
@@ -1015,7 +1015,7 @@
"message": "Update Available" "message": "Update Available"
}, },
"updateAvailableDesc": { "updateAvailableDesc": {
"message": "An update was found. Do you want to download it now?" "message": "ตรวจพบการอัปเดต คุณต้องการดาวโหลดตอนนี้เลยไหม?"
}, },
"restart": { "restart": {
"message": "เริ่มต้นใหม่" "message": "เริ่มต้นใหม่"
@@ -1030,7 +1030,7 @@
"message": "เกิดข้อผิดพลาดในการอัปเดต" "message": "เกิดข้อผิดพลาดในการอัปเดต"
}, },
"unknown": { "unknown": {
"message": "Unknown" "message": "ไม่ทราบ"
}, },
"copyUsername": { "copyUsername": {
"message": "คัดลอกชื่อผู้ใช้" "message": "คัดลอกชื่อผู้ใช้"
@@ -1056,10 +1056,10 @@
"message": "Refresh Membership" "message": "Refresh Membership"
}, },
"premiumNotCurrentMember": { "premiumNotCurrentMember": {
"message": "You are not currently a premium member." "message": "คุณยังไม่ได้เป็นสมาชิกระดับพรีเมียม"
}, },
"premiumSignUpAndGet": { "premiumSignUpAndGet": {
"message": "Sign up for a premium membership and get:" "message": "สมัครสมาชิกพรีเมี่ยมและรับ:"
}, },
"premiumSignUpStorage": { "premiumSignUpStorage": {
"message": "1 GB of encrypted file storage." "message": "1 GB of encrypted file storage."
@@ -1259,11 +1259,11 @@
"message": "Hide to Tray" "message": "Hide to Tray"
}, },
"alwaysOnTop": { "alwaysOnTop": {
"message": "Always on Top", "message": "อยู่ด้านบนเสมอ",
"description": "Application window should always stay on top of other windows" "description": "Application window should always stay on top of other windows"
}, },
"dateUpdated": { "dateUpdated": {
"message": "Updated", "message": "อัปเดตแล้ว",
"description": "ex. Date this item was updated" "description": "ex. Date this item was updated"
}, },
"datePasswordUpdated": { "datePasswordUpdated": {
@@ -1299,10 +1299,10 @@
"description": "hCaptcha is the name of a website, should not be translated" "description": "hCaptcha is the name of a website, should not be translated"
}, },
"invalidUrl": { "invalidUrl": {
"message": "Invalid Url" "message": "Url ไม่ถูกต้อง"
}, },
"done": { "done": {
"message": "Done" "message": "เสร็จสิ้น"
}, },
"accessibilityCookieSaved": { "accessibilityCookieSaved": {
"message": "Accessibility cookie saved!" "message": "Accessibility cookie saved!"
@@ -1311,7 +1311,7 @@
"message": "No accessibility cookie saved" "message": "No accessibility cookie saved"
}, },
"warning": { "warning": {
"message": "WARNING", "message": "คำเตือน",
"description": "WARNING (should stay in capitalized letters if the language permits)" "description": "WARNING (should stay in capitalized letters if the language permits)"
}, },
"confirmVaultExport": { "confirmVaultExport": {
@@ -1333,21 +1333,21 @@
"message": "There are no collections to list." "message": "There are no collections to list."
}, },
"ownership": { "ownership": {
"message": "Ownership" "message": "ความเป็นเจ้าของ"
}, },
"whoOwnsThisItem": { "whoOwnsThisItem": {
"message": "Who owns this item?" "message": "ใครเป็นเจ้าของรายการนี้?"
}, },
"strong": { "strong": {
"message": "Strong", "message": "แข็งแรง",
"description": "ex. A strong password. Scale: Weak -> Good -> Strong" "description": "ex. A strong password. Scale: Weak -> Good -> Strong"
}, },
"good": { "good": {
"message": "Good", "message": "ไม่เลว",
"description": "ex. A good password. Scale: Weak -> Good -> Strong" "description": "ex. A good password. Scale: Weak -> Good -> Strong"
}, },
"weak": { "weak": {
"message": "Weak", "message": "ง่ายเกินไป",
"description": "ex. A weak password. Scale: Weak -> Good -> Strong" "description": "ex. A weak password. Scale: Weak -> Good -> Strong"
}, },
"weakMasterPassword": { "weakMasterPassword": {
@@ -1361,19 +1361,19 @@
"description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device."
}, },
"unlockWithPin": { "unlockWithPin": {
"message": "Unlock with PIN" "message": "ปลดล็อกด้วย PIN"
}, },
"setYourPinCode": { "setYourPinCode": {
"message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application."
}, },
"pinRequired": { "pinRequired": {
"message": "PIN code is required." "message": "กรุณาใส่รหัส PIN"
}, },
"invalidPin": { "invalidPin": {
"message": "Invalid PIN code." "message": "PIN ไม่ถูกต้อง"
}, },
"unlockWithWindowsHello": { "unlockWithWindowsHello": {
"message": "Unlock with Windows Hello" "message": "ปลดล็อก ด้วย Windows Hello"
}, },
"windowsHelloConsentMessage": { "windowsHelloConsentMessage": {
"message": "Verify for Bitwarden." "message": "Verify for Bitwarden."
@@ -1394,7 +1394,7 @@
"message": "Lock with master password on restart" "message": "Lock with master password on restart"
}, },
"deleteAccount": { "deleteAccount": {
"message": "Delete account" "message": "ลบบัญชีผู้ใช้"
}, },
"deleteAccountDesc": { "deleteAccountDesc": {
"message": "Proceed below to delete your account and all vault data." "message": "Proceed below to delete your account and all vault data."
@@ -1403,13 +1403,13 @@
"message": "Deleting your account is permanent. It cannot be undone." "message": "Deleting your account is permanent. It cannot be undone."
}, },
"accountDeleted": { "accountDeleted": {
"message": "Account deleted" "message": "ลบบัญชีผู้ใช้แล้ว"
}, },
"accountDeletedDesc": { "accountDeletedDesc": {
"message": "Your account has been closed and all associated data has been deleted." "message": "Your account has been closed and all associated data has been deleted."
}, },
"preferences": { "preferences": {
"message": "Preferences" "message": "การตั้งค่า"
}, },
"enableMenuBar": { "enableMenuBar": {
"message": "Show menu bar icon" "message": "Show menu bar icon"
@@ -1437,7 +1437,7 @@
"description": "Noun. As in 'legal documents', like our terms of service and privacy policy." "description": "Noun. As in 'legal documents', like our terms of service and privacy policy."
}, },
"termsOfService": { "termsOfService": {
"message": "Terms of Service" "message": "เงื่อนไขการใช้บริการ"
}, },
"privacyPolicy": { "privacyPolicy": {
"message": "Privacy Policy" "message": "Privacy Policy"
@@ -1505,7 +1505,7 @@
"message": "Enterprise Single Sign-On" "message": "Enterprise Single Sign-On"
}, },
"setMasterPassword": { "setMasterPassword": {
"message": "Set Master Password" "message": "ตั้งรหัสผ่านหลัก"
}, },
"ssoCompleteRegistration": { "ssoCompleteRegistration": {
"message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault."
@@ -1514,7 +1514,7 @@
"message": "New Master Password" "message": "New Master Password"
}, },
"confirmNewMasterPass": { "confirmNewMasterPass": {
"message": "Confirm New Master Password" "message": "ยืนยันรหัสผ่านใหม่"
}, },
"masterPasswordPolicyInEffect": { "masterPasswordPolicyInEffect": {
"message": "One or more organization policies require your master password to meet the following requirements:" "message": "One or more organization policies require your master password to meet the following requirements:"
@@ -1589,7 +1589,7 @@
"message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created." "message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created."
}, },
"approve": { "approve": {
"message": "Approve" "message": "อนุมัติ"
}, },
"verifyBrowserTitle": { "verifyBrowserTitle": {
"message": "Verify browser connection" "message": "Verify browser connection"
@@ -1613,14 +1613,14 @@
"message": "An organization policy is affecting your ownership options." "message": "An organization policy is affecting your ownership options."
}, },
"allSends": { "allSends": {
"message": "All Sends", "message": "ส่งทั้งหมด",
"description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendTypeFile": { "sendTypeFile": {
"message": "File" "message": "ไฟล์"
}, },
"sendTypeText": { "sendTypeText": {
"message": "Text" "message": "ข้อความ"
}, },
"searchSends": { "searchSends": {
"message": "Search Sends", "message": "Search Sends",
@@ -1631,13 +1631,13 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"myVault": { "myVault": {
"message": "My Vault" "message": "ตู้เซฟของฉัน"
}, },
"text": { "text": {
"message": "Text" "message": "ข้อความ"
}, },
"deletionDate": { "deletionDate": {
"message": "Deletion Date" "message": "ถูกลบเมื่อวันที่"
}, },
"deletionDateDesc": { "deletionDateDesc": {
"message": "The Send will be permanently deleted on the specified date and time.", "message": "The Send will be permanently deleted on the specified date and time.",
@@ -1674,7 +1674,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLink": { "sendLink": {
"message": "Send link", "message": "ส่งลิงก์",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
}, },
"sendLinkLabel": { "sendLinkLabel": {
@@ -1715,7 +1715,7 @@
"message": "The file you want to send." "message": "The file you want to send."
}, },
"days": { "days": {
"message": "$DAYS$ days", "message": "$DAYS$ วัน",
"placeholders": { "placeholders": {
"days": { "days": {
"content": "$1", "content": "$1",

View File

@@ -61,7 +61,7 @@
} }
}, },
"moveToOrgDesc": { "moveToOrgDesc": {
"message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. При переміщенні до організації власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення." "message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. Переміщуючи до організації, власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення."
}, },
"attachments": { "attachments": {
"message": "Вкладення" "message": "Вкладення"

View File

@@ -104,7 +104,7 @@ export class Main {
this.updaterMain = new UpdaterMain( this.updaterMain = new UpdaterMain(
this.i18nService, this.i18nService,
this.windowMain, this.windowMain,
"desktop", "clients",
null, null,
null, null,
null, null,

View File

@@ -34,6 +34,7 @@ export class I18nService extends BaseI18nService {
"eo", "eo",
"es", "es",
"et", "et",
"eu",
"fa", "fa",
"fi", "fi",
"fil", "fil",

View File

@@ -5,8 +5,8 @@
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/). The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/bitwarden/web/actions?query=branch:master" target="_blank"> <a href="https://github.com/bitwarden/clients/actions/workflows/build-web.yml?query=branch:master" target="_blank">
<img src="https://github.com/bitwarden/web/actions/workflows/build.yml/badge.svg?branch=master" alt="Github Workflow build on master" /> <img src="https://github.com/bitwarden/clients/actions/workflows/build-web.yml/badge.svg?branch=master" alt="Github Workflow build on master" />
</a> </a>
<a href="https://crowdin.com/project/bitwarden-web" target="_blank"> <a href="https://crowdin.com/project/bitwarden-web" target="_blank">
<img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" /> <img src="https://d322cqt584bo4o.cloudfront.net/bitwarden-web/localized.svg" alt="Crowdin" />

View File

@@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
@@ -34,7 +35,8 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private policyApiService: PolicyApiServiceAbstraction, private policyApiService: PolicyApiServiceAbstraction,
private policyService: PolicyService, private policyService: PolicyService,
private logService: LogService private logService: LogService,
private organizationApiService: OrganizationApiServiceAbstraction
) { ) {
super(router, platformUtilsService, i18nService, route, stateService); super(router, platformUtilsService, i18nService, route, stateService);
} }
@@ -74,7 +76,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
request.token = qParams.token; request.token = qParams.token;
if (await this.performResetPasswordAutoEnroll(qParams)) { if (await this.performResetPasswordAutoEnroll(qParams)) {
const response = await this.apiService.getOrganizationKeys(qParams.organizationId); const response = await this.organizationApiService.getKeys(qParams.organizationId);
if (response == null) { if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
@@ -92,7 +94,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
return request; return request;
} }
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> { private async performResetPasswordAutoEnroll(qParams: Params): Promise<boolean> {
let policyList: Policy[] = null; let policyList: Policy[] = null;
try { try {
const policies = await this.policyApiService.getPoliciesByToken( const policies = await this.policyApiService.getPoliciesByToken(

View File

@@ -26,6 +26,7 @@ import { RouterService, StateService } from "../core";
selector: "app-login", selector: "app-login",
templateUrl: "login.component.html", templateUrl: "login.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class LoginComponent extends BaseLoginComponent { export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false; showResetPasswordAutoEnrollWarning = false;
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
@@ -68,6 +69,7 @@ export class LoginComponent extends BaseLoginComponent {
} }
async ngOnInit() { async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.email != null && qParams.email.indexOf("@") > -1) { if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email; this.email = qParams.email;

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