diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts
index 61daf7c82cb..6e1c4e569ef 100644
--- a/apps/web/src/app/oss-routing.module.ts
+++ b/apps/web/src/app/oss-routing.module.ts
@@ -11,7 +11,8 @@ import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component"
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
import { HintComponent } from "./accounts/hint.component";
import { LockComponent } from "./accounts/lock.component";
-import { LoginComponent } from "./accounts/login.component";
+import { LoginWithDeviceComponent } from "./accounts/login/login-with-device.component";
+import { LoginComponent } from "./accounts/login/login.component";
import { RecoverDeleteComponent } from "./accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "./accounts/recover-two-factor.component";
import { RegisterComponent } from "./accounts/register.component";
@@ -60,6 +61,11 @@ const routes: Routes = [
canActivate: [HomeGuard], // Redirects either to vault, login or lock page.
},
{ path: "login", component: LoginComponent, canActivate: [UnauthGuard] },
+ {
+ path: "login-with-device",
+ component: LoginWithDeviceComponent,
+ data: { titleId: "loginWithDevice" },
+ },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{
path: "register",
diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts
index 0885d7d5d77..457200a0e9b 100644
--- a/apps/web/src/app/oss.module.ts
+++ b/apps/web/src/app/oss.module.ts
@@ -1,5 +1,6 @@
import { NgModule } from "@angular/core";
+import { LoginModule } from "./accounts/login/login.module";
import { TrialInitiationModule } from "./accounts/trial-initiation/trial-initiation.module";
import { OrganizationCreateModule } from "./organizations/create/organization-create.module";
import { OrganizationManageModule } from "./organizations/manage/organization-manage.module";
@@ -18,6 +19,7 @@ import { VaultFilterModule } from "./vault/vault-filter/vault-filter.module";
OrganizationManageModule,
OrganizationUserModule,
OrganizationCreateModule,
+ LoginModule,
],
exports: [
SharedModule,
@@ -25,6 +27,7 @@ import { VaultFilterModule } from "./vault/vault-filter/vault-filter.module";
TrialInitiationModule,
VaultFilterModule,
OrganizationBadgeModule,
+ LoginModule,
],
bootstrap: [],
})
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index 128b0585ee1..e122aa18522 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -6,7 +6,6 @@ import { AcceptEmergencyComponent } from "../accounts/accept-emergency.component
import { AcceptOrganizationComponent } from "../accounts/accept-organization.component";
import { HintComponent } from "../accounts/hint.component";
import { LockComponent } from "../accounts/lock.component";
-import { LoginComponent } from "../accounts/login.component";
import { RecoverDeleteComponent } from "../accounts/recover-delete.component";
import { RecoverTwoFactorComponent } from "../accounts/recover-two-factor.component";
import { RegisterFormModule } from "../accounts/register-form/register-form.module";
@@ -207,7 +206,6 @@ import { SharedModule } from ".";
FrontendLayoutComponent,
HintComponent,
LockComponent,
- LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
@@ -351,7 +349,6 @@ import { SharedModule } from ".";
FrontendLayoutComponent,
HintComponent,
LockComponent,
- LoginComponent,
MasterPasswordPolicyComponent,
NavbarComponent,
NestedCheckboxComponent,
diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json
index 3ab3434cfb3..6a52acd58d7 100644
--- a/apps/web/src/locales/af/messages.json
+++ b/apps/web/src/locales/af/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Toestemmings"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json
index 2b3457e9bde..473e312b25e 100644
--- a/apps/web/src/locales/ar/messages.json
+++ b/apps/web/src/locales/ar/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json
index 1bd2459880a..86ebc3a5878 100644
--- a/apps/web/src/locales/az/messages.json
+++ b/apps/web/src/locales/az/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "İcazələr"
},
+ "managerPermissions": {
+ "message": "Menecer icazələri"
+ },
+ "adminPermissions": {
+ "message": "Admin icazələri"
+ },
"accessEventLogs": {
"message": "Tədbir jurnalına müraciət"
},
diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json
index b0ce295735f..20a80249cc7 100644
--- a/apps/web/src/locales/be/messages.json
+++ b/apps/web/src/locales/be/messages.json
@@ -10,7 +10,7 @@
}
},
"whatTypeOfItem": {
- "message": "Выберыце тып элемента"
+ "message": "Які гэта элемент запісу?"
},
"name": {
"message": "Назва"
@@ -65,7 +65,7 @@
"message": "Код бяспекі (CVV)"
},
"identityName": {
- "message": "Імя"
+ "message": "Імя пасведчання"
},
"company": {
"message": "Кампанія"
@@ -137,10 +137,10 @@
"message": "Доктар"
},
"expirationMonth": {
- "message": "Месяц заканчэння"
+ "message": "Месяц завяршэння"
},
"expirationYear": {
- "message": "Год заканчэння"
+ "message": "Год завяршэння"
},
"authenticatorKeyTotp": {
"message": "Ключ аўтэнтыфікацыі (TOTP)"
@@ -227,13 +227,13 @@
"description": "Toggling an expand/collapse state."
},
"generatePassword": {
- "message": "Згенерыраваць пароль"
+ "message": "Генерыраваць пароль"
},
"checkPassword": {
"message": "Праверце, ці не скампраметаваны пароль."
},
"passwordExposed": {
- "message": "Гэты пароль быў скампраметаваны $VALUE$ раз(-ы/-оў). Вы павінны змяніць яго.",
+ "message": "Гэты пароль быў скампраметаваны наступную колькасць разоў: $VALUE$. Вы павінны змяніць яго.",
"placeholders": {
"value": {
"content": "$1",
@@ -294,7 +294,7 @@
"message": "Тыпы"
},
"typeLogin": {
- "message": "Імя карыстальніка"
+ "message": "Лагін"
},
"typeCard": {
"message": "Картка"
@@ -315,7 +315,7 @@
"message": "Пасведчанні"
},
"typeSecureNotePlural": {
- "message": "Бяспечныя нататкі"
+ "message": "Абароненыя нататкі"
},
"folders": {
"message": "Папкі"
@@ -369,10 +369,10 @@
"message": "Дадаць элемент"
},
"editItem": {
- "message": "Рэдагаванне элемента"
+ "message": "Рэдагаваць элемент"
},
"viewItem": {
- "message": "Прагляд элемента"
+ "message": "Прагледзець элемент"
},
"ex": {
"message": "напр.",
@@ -398,27 +398,27 @@
}
},
"copyValue": {
- "message": "Капіяваць значэнне",
+ "message": "Скапіяваць значэнне",
"description": "Copy value to clipboard"
},
"copyPassword": {
- "message": "Капіяваць пароль",
+ "message": "Скапіяваць пароль",
"description": "Copy password to clipboard"
},
"copyUsername": {
- "message": "Капіяваць імя карыстальніка",
+ "message": "Скапіяваць імя карыстальніка",
"description": "Copy username to clipboard"
},
"copyNumber": {
- "message": "Капіяваць нумар",
+ "message": "Скапіяваць нумар",
"description": "Copy credit card number"
},
"copySecurityCode": {
- "message": "Капіяваць код бяспекі",
+ "message": "Скапіяваць код бяспекі",
"description": "Copy credit card security code (CVV)"
},
"copyUri": {
- "message": "Капіяваць URI",
+ "message": "Скапіяваць URI",
"description": "Copy URI to clipboard"
},
"me": {
@@ -549,7 +549,7 @@
"message": "Вы выйшлі"
},
"loginExpired": {
- "message": "Скончыўся тэрмін дзеяння вашага сеансу."
+ "message": "Тэрмін дзеяння вашага сеансу завяршыўся."
},
"logOutConfirmation": {
"message": "Вы ўпэўнены, што хочаце выйсці?"
@@ -621,7 +621,7 @@
"message": "Увядзіце адрас электроннай пошты ўліковага запісу для атрымання падказкі да асноўнага пароля."
},
"getMasterPasswordHint": {
- "message": "Атрымаць падказку для асноўнага пароля"
+ "message": "Атрымаць падказку да асноўнага пароля"
},
"emailRequired": {
"message": "Патрабуецца адрас электроннай пошты."
@@ -648,7 +648,7 @@
"message": "Уліковы запіс паспяхова створаны."
},
"masterPassSent": {
- "message": "Мы адправілі вам на электронную пошту падказку для асноўнага пароля."
+ "message": "Мы адправілі вам на электронную пошту падказку да асноўнага пароля."
},
"unexpectedError": {
"message": "Адбылася нечаканая памылка."
@@ -663,7 +663,7 @@
"message": "Разблакіраваць"
},
"loggedInAsEmailOn": {
- "message": "Вы ўвайшлі як $HOSTNAME$ у $EMAIL$.",
+ "message": "Вы ўвайшлі як $EMAIL$ у $HOSTNAME$.",
"placeholders": {
"email": {
"content": "$1",
@@ -718,7 +718,7 @@
"message": "Увядзіце 6 лічбаў праверачнага кода з вашай праграмы аўтэнтыфікацыі."
},
"enterVerificationCodeEmail": {
- "message": "Увядзіце 6 лічбаў кода праверкі, які быў адпраўлены на $EMAIL$.",
+ "message": "Увядзіце 6 лічбаў праверачнага кода, які быў адпраўлены на $EMAIL$.",
"placeholders": {
"email": {
"content": "$1",
@@ -782,11 +782,11 @@
"message": "Выкарыстоўвайце YubiKey для доступу да вашага ўліковага запісу. Працуе з прыладамі YubiKey серый 4, 5 і NEO."
},
"duoDesc": {
- "message": "Пацвярдзіце з дапамогай Duo Security, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі.",
+ "message": "Праверка з дапамогай Duo Security, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі U2F.",
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"duoOrganizationDesc": {
- "message": "Пацвярдзіце з дапамогай Duo Security для вашай арганізацыі, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі.",
+ "message": "Праверка з дапамогай Duo Security для вашай арганізацыі, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі U2F.",
"description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated."
},
"u2fDesc": {
@@ -829,7 +829,7 @@
"message": "Рэдагуйце калекцыі, з якімі гэты элемент знаходзіцца ў агульным доступе. Толькі карыстальнікі арганізацыі з доступам да гэтых калекцый змогуць бачыць гэты элемент."
},
"deleteSelectedItemsDesc": {
- "message": "Вы выбралі наступную колькасць запісаў для выдалення: $COUNT$. Вы ўпэўнены, што хочаце выдаліць гэтыя элементы?",
+ "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы ўпэўнены, што хочаце выдаліць іх?",
"placeholders": {
"count": {
"content": "$1",
@@ -847,7 +847,7 @@
}
},
"moveSelectedItemsCountDesc": {
- "message": "Вы выбралі наступную колькасць элементаў: $COUNT$. Наступная колькасць колькасць будзе перамешчана: $MOVEABLE_COUNT$. Застануцца без перамяшчэння: $NONMOVEABLE_COUNT$.",
+ "message": "Вы выбралі наступную колькасць элементаў: $COUNT$ шт. З іх будуць перамешчаны ў арганізацыю: $MOVEABLE_COUNT$ шт. Не перамешчанымі застануцца: $NONMOVEABLE_COUNT$ шт.",
"placeholders": {
"count": {
"content": "$1",
@@ -867,7 +867,7 @@
"message": "Праверачны код (TOTP)"
},
"copyVerificationCode": {
- "message": "Капіяваць праверачны код"
+ "message": "Скапіяваць праверачны код"
},
"warning": {
"message": "Папярэджанне"
@@ -876,10 +876,10 @@
"message": "Пацвердзіць экспартаванне сховішча"
},
"exportWarningDesc": {
- "message": "Файл, які экспартуецца ўтрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць або адпраўляць па неабароненых каналах (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
+ "message": "Пры экспартаванні файл утрымлівае даныя вашага сховішча ў незашыфраваным фармаце. Яго не варта захоўваць або адпраўляць па неабароненых каналах (напрыклад, па электроннай пошце). Выдаліце яго адразу пасля выкарыстання."
},
"encExportKeyWarningDesc": {
- "message": "Пры экспарце даныя шыфруюцца з дапамогай ключа шыфравання ўліковага запісу. Калі вы калі-небудзь зменіце ключ шыфравання ўліковага запісу, вам неабходна будзе экспартаваць даныя паўторна, паколькі вы не зможаце расшыфраваць гэты файл экспартавання."
+ "message": "Пры экспартаванні даныя шыфруюцца з дапамогай ключа шыфравання ўліковага запісу. Калі вы калі-небудзь зменіце ключ шыфравання ўліковага запісу, вам неабходна будзе экспартаваць даныя паўторна, паколькі вы не зможаце расшыфраваць гэты файл экспартавання."
},
"encExportAccountWarningDesc": {
"message": "Ключы шыфравання з'яўляюцца ўнікальнымі для кожнага ўліковага запісу Bitwarden, таму нельга імпартаваць зашыфраванае сховішча ў іншы ўліковы запіс."
@@ -955,7 +955,7 @@
"message": "Пазбягаць неадназначных сімвалаў"
},
"regeneratePassword": {
- "message": "Згенерыраваць новы пароль"
+ "message": "Паўторна генерыраваць пароль"
},
"length": {
"message": "Даўжыня"
@@ -1013,7 +1013,7 @@
"message": "Код"
},
"changeEmailDesc": {
- "message": "Мы адправілі праверачны код на $EMAIL$. Праверце сваю пошту на наяўнасць гэтага кода і ўвядзіце яго ніжэй, каб завяршыць змяненне адраса электроннай пошты.",
+ "message": "На ваш паштовы адрас $EMAIL$ адпраўлены ліст з праверачным кодам. Праверце сваю пошту на наяўнасць гэтага кода і ўвядзіце яго ніжэй, каб завяршыць змяненне адраса электроннай пошты.",
"placeholders": {
"email": {
"content": "$1",
@@ -1028,10 +1028,10 @@
"message": "Электронная пошта зменена"
},
"logBackIn": {
- "message": "Калі ласка, увайдзіце зноў."
+ "message": "Калі ласка, увайдзіце паўторна."
},
"logBackInOthersToo": {
- "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце зноў."
+ "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце яшчэ раз."
},
"changeMasterPassword": {
"message": "Змяніць асноўны пароль"
@@ -1067,7 +1067,7 @@
}
},
"kdfIterationsWarning": {
- "message": "Занадта вялікае значэнне ітэрацый KDF можа істотна запаволіць уваход (і разблакіроўку) на прыладах з састарэлымі працэсарамі. Мы рэкамендуем паслядоўна павялічваць значэнне з крокам $INCREMENT$ і правяраць вынік на ўсіх вашых прыладах.",
+ "message": "Занадта вялікае значэнне ітэрацый KDF можа істотна запаволіць уваход (і разблакіроўку) на прыладах, якія маюць састарэлы працэсар. Мы рэкамендуем паслядоўна павялічваць значэнне з крокам $INCREMENT$ і правяраць вынік на ўсіх вашых прыладах.",
"placeholders": {
"increment": {
"content": "$1",
@@ -1148,7 +1148,7 @@
"message": "Памылка імпартавання"
},
"importErrorDesc": {
- "message": "Адбылася праблема з данымі, якія вы спрабуеце імпартаваць. Калі ласка, выпраўце памылкі, якія пералічаны ў вашым зыходным файле і паспрабуйце яшчэ раз."
+ "message": "Адбылася праблема з данымі, якія вы спрабуеце імпартаваць. Калі ласка, выпраўце памылкі, якія пералічаны ніжэй у вашым зыходным файле і паспрабуйце яшчэ раз."
},
"importSuccess": {
"message": "Даныя былі паспяхова імпартаваны ў ваша сховішча."
@@ -1212,7 +1212,7 @@
"message": "Паказваць значкі вэб-сайтаў"
},
"faviconDesc": {
- "message": "Паказваць распазнавальны відарыс побач з кожным з кожным лагінам."
+ "message": "Паказваць распазнавальны відарыс побач з кожным лагінам."
},
"enableGravatars": {
"message": "Паказваць Gravatars",
@@ -1235,7 +1235,7 @@
"message": "Правілы дамена"
},
"domainRulesDesc": {
- "message": "Калі ў вас ёсць аднолькавы лагін на некалькіх розных даменах вэб-сайта, то вы можаце пазначыць вэб-сайт як \"эквівалентны\". \"Глабальныя\" - гэта дамены, які створаны для вас Bitwarden."
+ "message": "Калі ў вас ёсць аднолькавы лагін на некалькіх розных даменах вэб-сайта, вы можаце пазначыць вэб-сайт як \"эквівалентны\". \"Глабальныя\" - гэта дамены, якія стварыў для вас Bitwarden."
},
"globalEqDomains": {
"message": "Глабальныя эквівалентныя дамены"
@@ -1280,7 +1280,7 @@
"message": "Патрабаваць двухэтапны ўваход для карыстальнікаў вашай арганізацыі, які сканфігураваны на ўзроўні арганізацыі."
},
"twoStepLoginRecoveryWarning": {
- "message": "Уключэнне двухэтапнага ўваходу можа цалкам заблакіраваць доступ да ўліковага запісу Bitwarden. Код аднаўлення дае магчымасць атрымаць доступ да вашага ўліковага запісу ў выпадку, калі вы не можаце скарыстацца звычайным спосабам пастаўшчыка двухэтапнага ўваходу (напрыклад, вы згубілі сваю прыладу). Падтрымка Bitwarden не зможа вам дапамагчы, калі вы згубіце доступ да свайго ўліковага запіс. Мы рэкамендуем вам запісаць або раздрукаваць код аднаўлення і захоўваць яго ў надзейным месцы."
+ "message": "Уключэнне двухэтапнага ўваходу можа цалкам заблакіраваць доступ да ўліковага запісу Bitwarden. Код аднаўлення дае магчымасць атрымаць доступ да вашага ўліковага запісу ў выпадку, калі вы не можаце скарыстацца звычайным спосабам пастаўшчыка двухэтапнага ўваходу (напрыклад, вы згубілі сваю прыладу). Падтрымка Bitwarden не зможа вам дапамагчы, калі вы згубіце доступ да свайго ўліковага запісу. Мы рэкамендуем вам запісаць або раздрукаваць код аднаўлення і захоўваць яго ў надзейным месцы."
},
"viewRecoveryCode": {
"message": "Паглядзець код аднаўлення"
@@ -1299,26 +1299,26 @@
"message": "Аднавіць доступ"
},
"premium": {
- "message": "Прэміяльны статус",
+ "message": "Прэміум",
"description": "Premium Membership"
},
"premiumMembership": {
- "message": "Прэміяльны статус"
+ "message": "Прэміяльны ўдзельнік"
},
"premiumRequired": {
- "message": "Патрабуецца прэміяльны статус"
+ "message": "Патрабуецца прэміум"
},
"premiumRequiredDesc": {
"message": "Для выкарыстання гэтай функцыі патрабуецца прэміяльны статус."
},
"youHavePremiumAccess": {
- "message": "У вас прэміяльын статус"
+ "message": "У вас прэміяльны доступ"
},
"alreadyPremiumFromOrg": {
"message": "У вас ужо ёсць доступ да прэміяльных функцый, таму што вы з'яўляецеся ўдзельнікам арганізацыі, якая іх мае."
},
"manage": {
- "message": "Кіраваць"
+ "message": "Кіраванне"
},
"disable": {
"message": "Адключыць"
@@ -1354,7 +1354,7 @@
"message": "Гэтыя праграмы з'яўляюцца рэкамендаванымі. Звярніце ўвагу, што іншыя праграмы таксама будуць працаваць."
},
"twoStepAuthenticatorScanCode": {
- "message": "Скануйце гэты QR-код з дапамогай праграмы аўтэнтыфікацыі"
+ "message": "Адскануйце гэты QR-код з дапамогай праграмы аўтэнтыфікацыі"
},
"key": {
"message": "Ключ"
@@ -1387,13 +1387,13 @@
"message": "Захаваць форму."
},
"twoFactorYubikeyWarning": {
- "message": "З-за абмежаванняў платформы, YubiKey нельга выкарыстоўваць ва ўсіх праграмах Bitwarden. Калі YubiKey немагчыма выкарыстоўваць, вы павінны актываваць пастаўшчыка двухэтапнага ўваходу для атрымання доступу да свайго ўліковага запісу. Платформы, якія падтрымліваюцца:"
+ "message": "У сувязі з абмежаваннямі платформы, YubiKey нельга выкарыстоўваць ва ўсіх праграмах Bitwarden. Калі YubiKey немагчыма выкарыстоўваць, вы павінны актываваць пастаўшчыка двухэтапнага ўваходу для атрымання доступу да свайго ўліковага запісу. Платформы, якія падтрымліваюцца:"
},
"twoFactorYubikeySupportUsb": {
"message": "Вэб-сховішча, праграма для камп'ютара, інтэрфейс каманднага радка (CLI) і ўсе пашырэнні браўзера на прыладах з партом USB, якія сумяшчальныя з YubiKey."
},
"twoFactorYubikeySupportMobile": {
- "message": "Мабільныя праграмы на прыладах з NFC або портам USB, якія сумяшчальныя з YubiKey."
+ "message": "Мабільныя праграмы на прыладах з NFC або партом USB, якія сумяшчальны з YubiKey."
},
"yubikeyX": {
"message": "YubiKey $INDEX$",
@@ -1489,13 +1489,13 @@
"message": "Захаваць форму."
},
"twoFactorU2fWarning": {
- "message": "З-за абмежаванняў платформы, FIDO U2F можна выкарыстоўваць не ва ўсіх праграмах Bitwarden. Калі FIDO U2F не атрымліваецца выкарыстоўваць, то вы павінны ўключыць другога пастаўшчыка двухэтапнага ўваходу, каб атрымаць доступ да вашага ўліковага запісу. Платформы, якія падтрымліваюцца:"
+ "message": "У сувязі з абмежаваннямі платформы, FIDO U2F можна выкарыстоўваць не ва ўсіх праграмах Bitwarden. Калі FIDO U2F не атрымліваецца выкарыстоўваць, то вы павінны ўключыць другога пастаўшчыка двухэтапнага ўваходу, каб атрымаць доступ да вашага ўліковага запісу. Платформы, якія падтрымліваюцца:"
},
"twoFactorU2fSupportWeb": {
"message": "Вэб-сховішча і пашырэнні браўзера на камп'ютары/ноўтбуку з браўзерам, які падтрымлівае U2F (Chrome, Opera, Vivaldi або Firefox з уключаным FIDO U2F)."
},
"twoFactorU2fWaiting": {
- "message": "Чаканне націску кнопкі на ключы бяспекі"
+ "message": "Чаканне націску кнопкі на вашым ключы бяспекі"
},
"twoFactorU2fClickSave": {
"message": "Націсніце кнопку \"Захаваць\" ніжэй, каб уключыць гэты ключ бяспекі для двухэтапнага ўваходу."
@@ -1504,7 +1504,7 @@
"message": "Праблема чытання ключа бяспекі. Паспрабуйце яшчэ раз."
},
"twoFactorWebAuthnWarning": {
- "message": "З-за абмежавання платформы, WebAuthn немагчыма выкарыстоўваць ва ўсіх праграмах Bitwarden. Вам неабходна актываваць іншага пастаўшчыка двухэтапнага ўваходу, каб вы маглі атрымаць доступ да свайго ўліковага запісу, калі немагчыма скарыстацца WebAuthn. Платформы, які падтрымліваюцца:"
+ "message": "У сувязі з абмежаваннямі платформы, WebAuthn немагчыма выкарыстоўваць ва ўсіх праграмах Bitwarden. Вам неабходна актываваць іншага пастаўшчыка двухэтапнага ўваходу, каб вы маглі атрымаць доступ да свайго ўліковага запісу, калі немагчыма скарыстацца WebAuthn. Платформы, які падтрымліваюцца:"
},
"twoFactorWebAuthnSupportWeb": {
"message": "Вэб-сховішча і пашырэнні браўзера на камп'ютары/ноўтбуку з браўзерам, які падтрымлівае WebAuthn (Chrome, Opera, Vivaldi або Firefox з уключаным FIDO U2F)."
@@ -1523,7 +1523,7 @@
"message": "Справаздачы"
},
"reportsDesc": {
- "message": "Выявіце і выпраўце недахопы ў бяспецы вашых уліковых запісах, націснуўшы на справаздачы ніжэй.",
+ "message": "Выявіце і выпраўце недахопы ў бяспецы вашых уліковых запісаў, націснуўшы на справаздачы ніжэй.",
"description": "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault."
},
"unsecuredWebsitesReport": {
@@ -1536,7 +1536,7 @@
"message": "Знойдзены неабароненыя вэб-сайты"
},
"unsecuredWebsitesFoundDesc": {
- "message": "Мы знайшлі наступную колькасць з неабароненымі URI у вашым сховішчы: $COUNT$. Вам неабходна змяніць іх схему URI на https://, калі вэб-сайт дазваляе гэта зрабіць.",
+ "message": "У сховішчы ёсць элементы ($COUNT$ шт.) з неабароненымі URI. Вам неабходна змяніць іх схему URI на https://, калі вэб-сайт дазваляе гэта зрабіць.",
"placeholders": {
"count": {
"content": "$1",
@@ -1557,7 +1557,7 @@
"message": "Знойдзены лагіны без 2ФА"
},
"inactive2faFoundDesc": {
- "message": "У сховішчы выяўлены вэб-сайты ($COUNT$ шт.), якія могуць быць не наладжаны для двухэтапнай аўтэнтыфікацыі (згодна з 2fa.directory). Для дадатковай абароны гэтых уліковых запісаў уключыце двухэтапную аўтэнтыфікацыю.",
+ "message": "У сховішчы ёсць вэб-сайты ($COUNT$ шт.), якія могуць быць не наладжаны для двухэтапнай аўтэнтыфікацыі (згодна з 2fa.directory). Для дадатковай абароны гэтых уліковых запісаў уключыце двухэтапную аўтэнтыфікацыю.",
"placeholders": {
"count": {
"content": "$1",
@@ -1605,16 +1605,16 @@
}
},
"weakPasswordsReport": {
- "message": "Слабыя паролі"
+ "message": "Ненадзейныя паролі"
},
"weakPasswordsReportDesc": {
- "message": "Слабыя паролі могуць быць лёгка падабраныя зламыснікамі. Змяніце гэтыя паролі на надзейныя з дапамогай генератара пароляў."
+ "message": "Ненадзейныя паролі могуць быць лёгка падабраныя зламыснікамі. Замяніце гэтыя паролі на надзейныя з дапамогай генератара пароляў."
},
"weakPasswordsFound": {
- "message": "Знойдзены слабыя паролі"
+ "message": "Знойдзены ненадзейныя паролі"
},
"weakPasswordsFoundDesc": {
- "message": "У сховішчы ёсць элементы ($COUNT$ шт.) з ненадзейнымі паролямі. Вам неабходна змяніць іх на больш складаныя.",
+ "message": "У сховішчы ёсць элементы ($COUNT$ шт.) з ненадзейнымі паролямі. Вам неабходна замяніць іх на больш надзейныя.",
"placeholders": {
"count": {
"content": "$1",
@@ -1635,7 +1635,7 @@
"message": "Знойдзены паўторныя паролі"
},
"reusedPasswordsFoundDesc": {
- "message": "У сховішчы знойдзены паўторныя паролі ($COUNT$ шт.). Змяніце іх на ўнікальныя.",
+ "message": "У сховішчы ёсць паўторныя паролі ($COUNT$ шт.). Вам неабходна згенерыраваць унікальныя паролі і замяніць іх.",
"placeholders": {
"count": {
"content": "$1",
@@ -1743,7 +1743,7 @@
"message": "Дададзеныя крэдыты з'явяцца на вашым рахунку пасля поўнай апрацоўкі плацяжу. Некаторыя спосабы аплаты адбываюцца з затрымкай і могуць заняць больш часу, чым астатнія."
},
"makeSureEnoughCredit": {
- "message": "Пераканайцеся, што на вашым рахунку дастаткова крэдытаў для ажыццяўлення гэтай пакупкі. Калі сродкаў на вашым рахунку не хапае, то для кампенсацыі нястачы будзе выкарыстаны ваш прадвызначаны метад аплаты. Вы можаце дадаць крэдыты на свой рахунак на старонцы аплаты."
+ "message": "Пераканайцеся, што на вашым рахунку дастаткова крэдытаў для ажыццяўлення гэтай пакупкі. Калі сродкаў на вашым рахунку не хапае, то для кампенсацыі нястачы будзе выкарыстаны ваш прадвызначаны спосаб аплаты. Вы можаце дадаць крэдыты на свой рахунак на старонцы аплаты."
},
"creditAppliedDesc": {
"message": "Крэдыт вашага рахунку можа выкарыстоўвацца для здзяйснення купляў. Любыя даступныя крэдыты будуць аўтаматычна ўжыты для рахункаў, якія згенерыраваны для гэтага ўліковага запісу."
@@ -1753,7 +1753,7 @@
"description": "Another way of saying \"Get a premium membership\""
},
"premiumUpdated": {
- "message": "Вы абнавіліся да прэміяльнага статусу."
+ "message": "Вы абнавіліся да прэміяльнай версіі."
},
"premiumUpgradeUnlockFeatures": {
"message": "Абнавіце свой уліковы запіс да платнай версіі і разблакіруйце некаторыя цудоўныя дадатковыя функцыі."
@@ -1850,10 +1850,10 @@
"description": "Short abbreviation for 'month'"
},
"paymentChargedAnnually": {
- "message": "У вас адразу будзе спагнана плата згодна з абраным спосабам плацяжу, а інтэрвал такіх плацяжоў будзе ажыццяўляцца штогод. Вы можаце скасаваць іх у любы момант."
+ "message": "У вас адразу будзе спагнана плата згодна з выбраным спосабам аплаты, а інтэрвал такіх плацяжоў будзе ажыццяўляцца кожны год. Вы можаце скасаваць іх у любы момант."
},
"paymentCharged": {
- "message": "У вас адразу будзе спагнана плата згодна з абраным спосабам плацяжу, а інтэрвал такіх плацяжоў будзе ажыццяўляцца $INTERVAL$. Вы можаце скасаваць яго ў любы момант.",
+ "message": "У вас адразу будзе спагнана плата згодна з выбраным спосабам аплаты, а інтэрвал такіх плацяжоў будзе ажыццяўляцца кожны $INTERVAL$. Вы можаце скасаваць яго ў любы момант.",
"placeholders": {
"interval": {
"content": "$1",
@@ -1862,7 +1862,7 @@
}
},
"paymentChargedWithTrial": {
- "message": "У ваш тарыфны план уключаны выпрабавальны перыяд на 7 дзён. У вас не будзе спагнана плата згодна з абраным спосабам плацяжу пакуль выпрабавальны перыяд не скончыцца. Вы можаце скасаваць яго ў любы момант."
+ "message": "У ваш тарыфны план уключаны выпрабавальны перыяд на 7 дзён. У вас не будзе спагнана плата згодна з выбраным спосабам аплаты пакуль не завяршыцца выпрабавальны перыяд. Вы можаце скасаваць яго ў любы момант."
},
"paymentInformation": {
"message": "Плацежная інфармацыя"
@@ -1871,7 +1871,7 @@
"message": "Плацежная інфармацыя"
},
"billingTrialSubLabel": {
- "message": "У вас не будзе спаганяцца плата абраным метадам плацяжу на працягу 7 дзён выпрабавальнага перыяду."
+ "message": "У вас не будзе спаганяцца плата згодна з выбраным спосабам аплаты на працягу 7 дзён выпрабавальнага перыяду."
},
"creditCard": {
"message": "Крэдытная картка"
@@ -1928,7 +1928,7 @@
"message": "Ліцэнзія абноўлена"
},
"manageSubscription": {
- "message": "Кіраваць падпіскай"
+ "message": "Кіраванне падпіскай"
},
"storage": {
"message": "Сховішча"
@@ -1956,7 +1956,7 @@
"message": "Спосаб аплаты"
},
"noPaymentMethod": {
- "message": "Файл не змяшчае метаду аплаты."
+ "message": "Файл не змяшчае спосабу аплаты."
},
"addPaymentMethod": {
"message": "Дадаць спосаб аплаты"
@@ -1986,7 +1986,7 @@
"message": "Няма трансакцый."
},
"chargeNoun": {
- "message": "Спісанне",
+ "message": "Спагнанне",
"description": "Noun. A charge from a payment method."
},
"refundNoun": {
@@ -2003,13 +2003,13 @@
}
},
"gbStorageAdd": {
- "message": "ГБ сховішча для дадавання"
+ "message": "ГБ сховішча для дабаўлення"
},
"gbStorageRemove": {
"message": "ГБ сховішча для выдалення"
},
"storageAddNote": {
- "message": "Дабаўленне сховішча прывядзе да карэкціроўкі ў вашым выніковым рахунку і адразу будзе спагнана аплата згодна з азначаным спосабам. Першы плацеж будзе прапарцыйны астачы бягучага плацежнага перыяду."
+ "message": "Дабаўленне сховішча прывядзе да карэкціроўкі ў вашым выніковым рахунку і адразу будзе спагнаны плацеж згодна з азначаным у файле спосабам аплаты. Першы плацеж будзе прапарцыйны астачы бягучага плацежнага перыяду."
},
"storageRemoveNote": {
"message": "Выдаленне сховішча прывядзе да карэкціроўкі вашага выніковага рахунку, які будзе прапарцыйна раздзелены ў выглядзе крэдытаў за наступны плацежны перыяд."
@@ -2030,7 +2030,7 @@
"message": "Спосаб аплаты абноўлены."
},
"purchasePremium": {
- "message": "Купіць прэміяльны статус"
+ "message": "Купіць прэміум"
},
"licenseFile": {
"message": "Файл ліцэнзіі"
@@ -2054,7 +2054,7 @@
"message": "Электронная пошта вашага ўліковага запісу павінна быць пацверджана."
},
"newOrganizationDesc": {
- "message": "Арганізацыі дазваляюць дзяліцца элементамі вашага сховішча з іншымі, а таксама кіраваць звязанымі карыстальнікамі для вызначанага аб'екта. Гэта можа быць сям'я, невялікая каманда або вялікая кампанія."
+ "message": "Арганізацыі дазваляюць абагуляць элементы вашага сховішча з іншымі, а таксама кіраваць звязанымі карыстальнікамі для вызначанага аб'екта. Гэта можа быць сям'я, невялікая каманда або вялікая кампанія."
},
"generalInformation": {
"message": "Агульная інфармацыя"
@@ -2087,7 +2087,7 @@
"message": "# карыстальніцкіх месцаў"
},
"userSeatsAdditionalDesc": {
- "message": "Ваш тарыфны план мае наступную колькасць карыстальніцкіх месцаў: $BASE_SEATS$. Вы можаце дадаць дадатковую колькасць карыстальнікаў па кошце $SEAT_PRICE$ за карыстальніка ў месяц.",
+ "message": "Ваш тарыфны план мае наступную колькасць карыстальніцкіх месцаў: $BASE_SEATS$ шт. Вы можаце дадаць дадатковую колькасць карыстальнікаў па кошце $SEAT_PRICE$ за карыстальніка ў месяц.",
"placeholders": {
"base_seats": {
"content": "$1",
@@ -2203,7 +2203,7 @@
"message": "Лакальны хостынг (неабавязкова)"
},
"usersGetPremium": {
- "message": "Карыстальнікі атрымліваюць доступ да прэміяльных функцый"
+ "message": "Карыстальнікі атрымаюць доступ да прэміяльных функцый"
},
"controlAccessWithGroups": {
"message": "Кантроль доступу карыстальнікаў з дапамогай груп"
@@ -2272,10 +2272,10 @@
"message": "Ваша арганізацыя была абноўлена."
},
"leave": {
- "message": "Пакінуць"
+ "message": "Выйсці"
},
"leaveOrganizationConfirmation": {
- "message": "Вы ўпэўнены, што хочаце пакінуць гэту арганізацыю?"
+ "message": "Вы ўпэўнены, што хочаце выйсці з гэтай арганізацыі?"
},
"leftOrganization": {
"message": "Вы пакінулі арганізацыю."
@@ -2284,7 +2284,7 @@
"message": "Прадвызначаная калекцыя"
},
"getHelp": {
- "message": "Атрымаць дапамогу"
+ "message": "Атрымаць даведку"
},
"getApps": {
"message": "Атрымаць праграмы"
@@ -2332,13 +2332,13 @@
"message": "Пасля таго, як удзельнік адкліканы, ён больш не зможа атрымаць доступ да даных арганізацыі. Для хуткага аднаўлення доступу ўдзельніка, перайдзіце ва ўкладку \"Адкліканыя\"."
},
"removeUserConfirmationKeyConnector": {
- "message": "Увага! Гэтаму карыстальніку патрабуецца Key Connector для таго, каб кіраваць шыфраваннем. Выдаленне гэтага карыстальніка з вашай арганізацыі канчаткова адключыць яго ўліковы запіс. Гэта дзеянне нельга будзе адрабіць. Вы сапраўды хочаце працягнуць?"
+ "message": "Папярэджанне! Гэтаму карыстальніку патрабуецца Key Connector для таго, каб кіраваць шыфраваннем. Выдаленне гэтага карыстальніка з вашай арганізацыі канчаткова адключыць яго ўліковы запіс. Гэта дзеянне нельга будзе адрабіць. Вы сапраўды хочаце працягнуць?"
},
"externalId": {
"message": "Знешні ідэнтыфікатар"
},
"externalIdDesc": {
- "message": "Знешні ідэнтыфікатара можа быць выкарыстаны ў якасці спасылкі для сувязі гэтага рэсурсу са знешняй сістэмай, такой як каталог карыстальніка."
+ "message": "Знешні ідэнтыфікатар можа быць выкарыстаны ў якасці спасылкі для сувязі гэтага рэсурсу са знешняй сістэмай, такой як каталог карыстальніка."
},
"accessControl": {
"message": "Кантроль доступу"
@@ -2371,7 +2371,7 @@
"message": "Запрасіць карыстальніка"
},
"inviteUserDesc": {
- "message": "Запрасіце новага карыстальніка ў вашу арганізацыю ўвёўшы яго электронную пошту ўліковага запісу Bitwarden. Калі ў яго пакуль няма ўліковага запісу Bitwarden, яму будзе прапанавана стварыць яго."
+ "message": "Запрасіць новага карыстальніка ў вашу арганізацыю, увёўшы яго электронную пошту ўліковага запісу Bitwarden. Калі ён не мае ўліковага запісу, то ён атрымае запыт на яго стварэнне."
},
"inviteMultipleEmailDesc": {
"message": "Вы можаце запрасіць да $COUNT$ карыстальнікаў за раз, падзяляючы адрасы электроннай пошты ў спісе коскамі.",
@@ -2467,7 +2467,7 @@
"message": "Вэб-сховішча"
},
"loggedIn": {
- "message": "Выкананы ўваход."
+ "message": "Вы ўвайшлі."
},
"changedPassword": {
"message": "Пароль уліковага запісу зменены."
@@ -2506,7 +2506,7 @@
}
},
"editedItemId": {
- "message": "Адрэдагаваны элемент $ID$.",
+ "message": "Элемент $ID$ адрэдагаваны.",
"placeholders": {
"id": {
"content": "$1",
@@ -2614,7 +2614,7 @@
}
},
"editedCollectionId": {
- "message": "Адрэдагавана калекцыя $ID$.",
+ "message": "Калекцыя $ID$ адрэдагавана.",
"placeholders": {
"id": {
"content": "$1",
@@ -2632,7 +2632,7 @@
}
},
"editedPolicyId": {
- "message": "Адрэдагаваная палітыка $ID$.",
+ "message": "Палітыка $ID$ адрэдагавана.",
"placeholders": {
"id": {
"content": "$1",
@@ -2650,7 +2650,7 @@
}
},
"editedGroupId": {
- "message": "Адрэдагавана група $ID$.",
+ "message": "Група $ID$ адрэдагавана.",
"placeholders": {
"id": {
"content": "$1",
@@ -2731,7 +2731,7 @@
}
},
"editedCollectionsForItem": {
- "message": "Адрэдагаваныя калекцыі для элемента $ID$.",
+ "message": "Калекцыі для элемента $ID$ адрэдагаваны.",
"placeholders": {
"id": {
"content": "$1",
@@ -2758,7 +2758,7 @@
}
},
"editedUserId": {
- "message": "Адрэдагаваны карыстальнік $ID$.",
+ "message": "Карыстальнік $ID$ адрэдагаваны.",
"placeholders": {
"id": {
"content": "$1",
@@ -2767,7 +2767,7 @@
}
},
"editedGroupsForUser": {
- "message": "Адрэдагаваныя групы для карыстальніка $ID$.",
+ "message": "Групы для карыстальніка $ID$ адрэдагаваны.",
"placeholders": {
"id": {
"content": "$1",
@@ -2776,7 +2776,7 @@
}
},
"unlinkedSsoUser": {
- "message": "Нязвязныя SSO для карыстальніка $ID$.",
+ "message": "Адлучаныя SSO для карыстальніка $ID$.",
"placeholders": {
"id": {
"content": "$1",
@@ -2881,7 +2881,7 @@
"message": "Пацвердзіць карыстальнікаў"
},
"usersNeedConfirmed": {
- "message": "У вас ёсць карыстальнікі, які прынялі запрашэнне, але патрабуюць пацвярджэння. Гэтыя карыстальнікі не будуць мець доступу да арганізацыі, пакуль не будуць пацверджаны."
+ "message": "У вас ёсць карыстальнікі, якія прынялі запрашэнне, але патрабуюць пацвярджэння. Гэтыя карыстальнікі не будуць мець доступу да арганізацыі, пакуль не будуць пацверджаны."
},
"startDate": {
"message": "Дата пачатку"
@@ -2893,7 +2893,7 @@
"message": "Праверыць пошту"
},
"verifyEmailDesc": {
- "message": "Параверце ваш адрас электроннай пошты для разблакіроўкі доступу да ўсіх функцый."
+ "message": "Праверце ваш адрас электроннай пошты для разблакіроўкі доступу да ўсіх функцый."
},
"verifyEmailFirst": {
"message": "Спачатку неабходна праверыць адрас электроннай пошты вашага ўліковага запісу."
@@ -2929,7 +2929,7 @@
"message": "Запрашэнне прынята"
},
"inviteAcceptedDesc": {
- "message": "Вы можаце атрымаць доступ да гэтага арганізацыі адразу пасля таго, як будзе пацверджаны ваш удзел. Мы апавясцім вас, калі гэта адбудзецца."
+ "message": "Вы можаце атрымаць доступ да гэтай арганізацыі адразу пасля таго, як будзе пацверджаны ваш удзел. Мы апавясцім вас, калі гэта адбудзецца."
},
"inviteAcceptFailed": {
"message": "Немагчыма прыняць запрашэнне. Папрасіце адміністратара арганізацыі адправіць яго яшчэ раз."
@@ -3014,7 +3014,7 @@
"description": "A billing plan/package. For example: families, teams, enterprise, etc."
},
"changeBillingPlan": {
- "message": "Палепшыць план",
+ "message": "Палепшыць тарыфны план",
"description": "A billing plan/package. For example: families, teams, enterprise, etc."
},
"changeBillingPlanUpgrade": {
@@ -3041,10 +3041,10 @@
"message": "Праверыць банкаўскі рахунак"
},
"verifyBankAccountDesc": {
- "message": "Мы зрабілі два сімвалічныя ўклады на ваш банкаўскі рахунак (ён адлюструецца ў спісе на працягу 1-2 працоўных дзён). Увядзіце гэту суму для праверкі банкаўскага рахунку."
+ "message": "Мы зрабілі два сімвалічныя ўклады на ваш банкаўскі рахунак (ён адлюструецца ў спісе на працягу некалькіх наступных працоўных дзён). Увядзіце гэту суму для праверкі банкаўскага рахунку."
},
"verifyBankAccountInitialDesc": {
- "message": "Плацеж праз банкаўскі рахунак даступны толькі кліентам, якія пражываюць у ЗША. Вам неабходна будзе пацвердзіць свой банкаўскі рахунак. Мы зробім два сімвалічныя ўклады на працягу наступных 1-2 дзён. Увядзіце гэтыя сумы на старонцы аплаты арганізацыі для праверкі банкаўскага рахунку."
+ "message": "Плацеж праз банкаўскі рахунак даступны толькі кліентам, якія пражываюць у ЗША. Вам неабходна будзе пацвердзіць свой банкаўскі рахунак. Мы зробім два сімвалічныя ўклады на працягу наступных некалькіх дзён. Увядзіце гэтыя сумы на старонцы аплаты арганізацыі для праверкі банкаўскага рахунку."
},
"verifyBankAccountFailureWarning": {
"message": "Невыкананне праверкі банкаўскага рахунку прывядзе да збою аплаты і адключэння падпіскі."
@@ -3109,7 +3109,7 @@
"message": "Карэкціроўка вашай падпіскі прывядзе да прапарцыйнага змянення ў вашым выніковым рахунку. Калі колькасць карыстальнікаў перавысіць колькасць месцаў у вашай падпісцы, вы адразу атрымаеце прапарцыйную плату за дадатковых карыстальнікаў."
},
"subscriptionUserSeats": {
- "message": "Агульная колькасць карыстальнікаў для вашай падпіскі: $COUNT$.",
+ "message": "Агульная колькасць карыстальнікаў для вашай падпіскі: $COUNT$ шт.",
"placeholders": {
"count": {
"content": "$1",
@@ -3163,7 +3163,7 @@
}
},
"subscriptionSponsoredFamiliesPlan": {
- "message": "Колькасць карыстальнікаў для вашай падпіскі: $COUNT$. Ваш тарыфны план мае спонсарскую падтрымку і аплачваецца знешняй арганізацыяй.",
+ "message": "Колькасць карыстальнікаў для вашай падпіскі: $COUNT$ шт. Ваш тарыфны план мае спонсарскую падтрымку і аплачваецца знешняй арганізацыяй.",
"placeholders": {
"count": {
"content": "$1",
@@ -3187,7 +3187,7 @@
"message": "Месцаў для выдалення"
},
"seatsAddNote": {
- "message": "Дадаванне карыстальніцкіх месцаў прывядзе да карэкціроўкі ў вашым выніковым рахунку і адразу будзе спагнана аплата згодна з азначаным спосабам. Першы плацеж будзе прапарцыйны астачы бягучага плацежнага перыяду."
+ "message": "Дабаўленне карыстальніцкіх месцаў прывядзе да карэкціроўкі ў вашым выніковым рахунку і адразу будзе спагнаны плацеж згодна з азначаным у файле спосабам аплаты. Першы плацеж будзе прапарцыйны астачы бягучага плацежнага перыяду."
},
"seatsRemoveNote": {
"message": "Выдаленне карыстальніцкіх месцаў прывядзе да карэкціроўкі вашага выніковага рахунку, які будзе прапарцыйна раздзелены ў выглядзе крэдытаў за наступны плацежны перыяд."
@@ -3214,7 +3214,7 @@
"message": "Зараз вы выкарыстоўваеце састарэлую схему шыфравання."
},
"updateEncryptionKeyDesc": {
- "message": "Мы перайшлі на больш складаныя ключы шыфравання, якія забяспечваюць лепшую бяспеку і доступ да самых новых функцый. Абнаўленне вашы ключоў шыфравання адбываецца хутка і лёгка. Проста ўвядзіце свой асноўны пароль знізу. Гэта абнаўленне ўрэшце стане абавязковым."
+ "message": "Мы перайшлі на больш складаныя ключы шыфравання, якія забяспечваюць лепшую бяспеку і доступ да самых новых функцый. Абнаўленне вашых ключоў шыфравання адбудзецца хутка і лёгка. Проста ўвядзіце свой асноўны пароль знізу. Гэта абнаўленне ўрэшце стане абавязковым."
},
"updateEncryptionKeyWarning": {
"message": "Пасля абнаўлення вашага ключа шыфравання вам неабходна выйсці з сістэмы, а потым выканаць паўторны ўваход ва ўсе праграмы Bitwarden, якія вы зараз выкарыстоўваеце (напрыклад, мабільныя праграмы або пашырэнні для браўзераў). Збой пры выхадзе і паўторным уваходзе (пры гэтым спампоўваецца ваш новы ключ шыфравання) можа стаць прычынай пашкоджання даных. Мы паспрабуем аўтаматычна ажыццявіць завяршэнне ўсіх вашых сеансаў, але гэта можа адбывацца з затрымкай."
@@ -3247,7 +3247,7 @@
"message": "Вернута"
},
"nothingSelected": {
- "message": "Вы нічога не выбралі."
+ "message": "Вы пакуль нічога не выбралі."
},
"acceptPolicies": {
"message": "Ставячы гэты сцяжок, вы пагаджаецеся з наступным:"
@@ -3321,7 +3321,7 @@
"message": "Хто валодае гэтым элементам?"
},
"strong": {
- "message": "Моцны",
+ "message": "Надзейны",
"description": "ex. A strong password. Scale: Very Weak -> Weak -> Good -> Strong"
},
"good": {
@@ -3329,15 +3329,15 @@
"description": "ex. A good password. Scale: Very Weak -> Weak -> Good -> Strong"
},
"weak": {
- "message": "Слабы",
+ "message": "Ненадзейны",
"description": "ex. A weak password. Scale: Very Weak -> Weak -> Good -> Strong"
},
"veryWeak": {
- "message": "Вельмі слабы",
+ "message": "Вельмі ненадзейны",
"description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong"
},
"weakMasterPassword": {
- "message": "Слабы асноўны пароль"
+ "message": "Ненадзейны асноўны пароль"
},
"weakMasterPasswordDesc": {
"message": "Асноўны пароль, які вы выбралі з'яўляецца ненадзейным. Для належнай абароны ўліковага запісу Bitwarden, вы павінны выкарыстоўваць надзейны асноўны пароль (або парольную фразу). Вы ўпэўнены, што хочаце выкарыстоўваць гэты асноўны пароль?"
@@ -3365,7 +3365,7 @@
"message": "У вашым сховішчы ёсць старыя далучаныя файлы, якія неабходна выправіць перад тым, як змяніць ключ шыфравання ўліковага запісу."
},
"yourAccountsFingerprint": {
- "message": "Фраза адбітку вашага ўліковага запісу",
+ "message": "Фраза адбітку пальца вашага ўліковага запісу",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
"fingerprintEnsureIntegrityVerify": {
@@ -3384,7 +3384,7 @@
"message": "Ключ API"
},
"apiKeyDesc": {
- "message": "Ваш ключ API можа быць выкарыстаны для аўтэнтыфікацыі публічнага API праграмы."
+ "message": "Ваш ключ API можа быць выкарыстаны для аўтэнтыфікацыі публічнага API праграмы Bitwarden."
},
"apiKeyRotateDesc": {
"message": "Змяненне ключа API анулюе папярэдні ключ. Вы можаце змяніць ваш ключ API, калі ёсць любыя сумневы адносна бяспекі бягучага ключа."
@@ -3418,7 +3418,7 @@
"message": "Купля ў праграме"
},
"cannotPerformInAppPurchase": {
- "message": "Вы не можаце выканаць гэта дзеянне падчас выкарыстання аплаты ў праграме."
+ "message": "Вы не можаце выканаць гэта дзеянне падчас выкарыстання спосабу аплаты ў праграме."
},
"manageSubscriptionFromStore": {
"message": "Вы павінны кіраваць сваёй падпіскай з той крамы, у якой вы ажыццяўлялі куплю праграмы."
@@ -3433,16 +3433,16 @@
"message": "Патрабуецца асноўны пароль"
},
"masterPassPolicyDesc": {
- "message": "Задайце мінімальныя патрабаванні да надзейнасці асноўнага пароля."
+ "message": "Прызначце мінімальныя патрабаванні да надзейнасці асноўнага пароля."
},
"twoStepLoginPolicyTitle": {
"message": "Патрабуецца двухэтапны ўваход"
},
"twoStepLoginPolicyDesc": {
- "message": "Патрабаваць удзельнікаў наладу двуэтапнага ўваходу."
+ "message": "Патрабаваць ад удзельнікаў наладу двуэтапнага ўваходу."
},
"twoStepLoginPolicyWarning": {
- "message": "Удзельнікі арганізацыі, якія не з'яўляюцца ўладальнікамі або адміністратарамі і якія не ўключылі двухэтапны ўваход у сваіх уліковых запісаць будуць выдалены з арганізацыі і атрымаюць адпаведнае апавяшчэнне па электроннай пошце."
+ "message": "Удзельнікі арганізацыі, якія не з'яўляюцца ўладальнікамі або адміністратарамі і якія не ўключылі двухэтапны ўваход у сваіх уліковых запісах будуць выдалены з арганізацыі і атрымаюць адпаведнае апавяшчэнне па электроннай пошце."
},
"twoStepLoginPolicyUserWarning": {
"message": "Вы з'яўляецеся ўдзельнікам арганізацыі, якая патрабуе выкарыстанне двухэтапнага ўваходу для вашага ўліковага запісу. Калі вы адключыце ўсіх пастаўшчыкоў двухэтапнага ўваходу, вы будзеце аўтаматычна выдалены з гэтай арганізацыі."
@@ -3451,10 +3451,10 @@
"message": "Прызначыць патрабаванні для генератара пароляў."
},
"passwordGeneratorPolicyInEffect": {
- "message": "На налады генератара ўплываюць адна або некалькі палітык арганізацый."
+ "message": "Адна або больш палітык арганізацыі ўплывае на налады генератара."
},
"masterPasswordPolicyInEffect": {
- "message": "Згодна з адной або некалькімі палітыкамі арганізацыі асноўны пароль павінен адпавядаць наступным патрабаванням:"
+ "message": "Адна або больш палітык арганізацыі патрабуе, каб ваш асноўны пароль адпавядаў наступным патрабаванням:"
},
"policyInEffectMinComplexity": {
"message": "Мінімальны ўзровень складанасці $SCORE$",
@@ -3505,7 +3505,7 @@
"message": "Параметры карыстальніка"
},
"vaultTimeoutAction": {
- "message": "Дзеянне пасля заканчэння часу сховішча"
+ "message": "Дзеянне пасля заканчэння часу чакання сховішча"
},
"vaultTimeoutActionLockDesc": {
"message": "Для атрымання доступу да заблакіраванага сховішча, вам неабходна ўвесці асноўны пароль або скарыстацца іншым метадам разблакіроўкі."
@@ -3525,7 +3525,7 @@
"message": "Пошук у сметніцы"
},
"permanentlyDelete": {
- "message": "Выдаліць канчаткова"
+ "message": "Выдаліць назаўсёды"
},
"permanentlyDeleteSelected": {
"message": "Назаўсёды выдаліць выбраныя"
@@ -3534,7 +3534,7 @@
"message": "Назаўсёды выдаліць элементы"
},
"permanentlyDeleteItemConfirmation": {
- "message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэтыя элементы?"
+ "message": "Вы ўпэўнены, што хочаце назаўсёды выдаліць гэты элемент?"
},
"permanentlyDeletedItem": {
"message": "Элемент выдалены назаўсёды"
@@ -3543,7 +3543,7 @@
"message": "Элементы выдалены назаўсёды"
},
"permanentlyDeleteSelectedItemsDesc": {
- "message": "Вы выбралі наступную колькасць запісаў для выдалення: $COUNT$ шт. Вы ўпэўнены, што хочаце назаўсёды выдаліць гэтыя элементы?",
+ "message": "Вы выбралі наступную колькасць элементаў для выдалення: $COUNT$ шт. Вы ўпэўнены, што хочаце назаўсёды выдаліць іх?",
"placeholders": {
"count": {
"content": "$1",
@@ -3582,7 +3582,7 @@
"message": "Аднавіць элементы"
},
"restoreSelectedItemsDesc": {
- "message": "Вы выбралі наступную колькасць запісаў для аднаўлення: $COUNT$. Вы ўпэўнены, што хочаце аднавіць гэтыя элементы?",
+ "message": "Вы выбралі наступную колькасць элементаў для аднаўлення: $COUNT$. Вы ўпэўнены, што хочаце аднавіць іх?",
"placeholders": {
"count": {
"content": "$1",
@@ -3600,7 +3600,7 @@
}
},
"vaultTimeoutLogOutConfirmation": {
- "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыі праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?"
+ "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы ўпэўнены, што хочаце выкарыстоўваць гэты параметр?"
},
"vaultTimeoutLogOutConfirmationTitle": {
"message": "Пацвярджэнне дзеяння часу чакання"
@@ -3621,7 +3621,7 @@
"message": "Падатковая інфармацыя абноўлена."
},
"setMasterPassword": {
- "message": "Задаць асноўны пароль"
+ "message": "Прызначыць асноўны пароль"
},
"ssoCompleteRegistration": {
"message": "Для завяршэння працэсу ўваходу з дапамогай SSO, прызначце асноўны пароль для доступу да вашага сховішча і яго абароны."
@@ -3636,7 +3636,7 @@
"message": "Уваходзьце з выкарыстаннем партала адзінага ўваходу вашай арганізацыі. Калі ласка, увядзіце ідэнтыфікатар вашай арганізацыі для пачатку працы."
},
"enterpriseSingleSignOn": {
- "message": "Адзіны карпаратыўны ўваход (SSO)"
+ "message": "Адзіны ўваход прадпрыемства (SSO)"
},
"ssoHandOff": {
"message": "Цяпер вы можаце закрыць гэту ўкладку і працягнуць у пашырэнні."
@@ -3663,10 +3663,10 @@
"message": "Вы ўпэўнены, што хочаце адлучыць SSO для гэтай арганізацыі?"
},
"linkSso": {
- "message": "Падлучыць SSO"
+ "message": "Звязаць з SSO"
},
"singleOrg": {
- "message": "Адна арганізацыя"
+ "message": "Адзіная арганізацыя"
},
"singleOrgDesc": {
"message": "Забараніць удзельнікам далучацца да іншых арганізацый."
@@ -3681,13 +3681,13 @@
"message": "Патрабаваць аўтэнтыфікацыю праз адзіны ўваход (SSO)"
},
"requireSsoPolicyDesc": {
- "message": "Патрабаваць ад удзельнікаў уваходзіць праз адзіны карпаратыўны ўваход (SSO)."
+ "message": "Патрабаваць ад удзельнікаў уваходзіць праз адзіны ўваход прадпрыемства (SSO)."
},
"prerequisite": {
"message": "Перадумова"
},
"requireSsoPolicyReq": {
- "message": "Перад актывацыяй гэтай палітыкі неабходна ўключыць палітыку адзінага ўваходу (SSO)."
+ "message": "Перад актывацыяй гэтай палітыкі неабходна ўключыць палітыку адзінага ўваходу (SSO) прадпрыемства."
},
"requireSsoPolicyReqError": {
"message": "Палітыка адзінай арганізацыі не ўключана."
@@ -3714,7 +3714,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"editedSend": {
- "message": "Адрэдагаваны Send",
+ "message": "Send адрэдагаваны",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletedSend": {
@@ -3730,7 +3730,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"whatTypeOfSend": {
- "message": "Які гэта тып Send?",
+ "message": "Які гэта тып Send'a?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletionDate": {
@@ -3744,14 +3744,14 @@
"message": "Дата завяршэння"
},
"expirationDateDesc": {
- "message": "Калі зададзена, то доступ да гэтага Send міне ў азначаную дату і час.",
+ "message": "Калі прызначана, то доступ да гэтага Send міне ў азначаную дату і час.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"maxAccessCount": {
"message": "Максімальная колькасць доступаў"
},
"maxAccessCountDesc": {
- "message": "Калі зададзена, то карыстальнікі больш не змогуць атрымаць доступ да гэтага Send пасля таго, як будзе дасягнута максімальная колькасць зваротаў.",
+ "message": "Калі прызначана, то карыстальнікі больш не змогуць атрымаць доступ да гэтага Send пасля таго, як будзе дасягнута максімальная колькасць зваротаў.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"currentAccessCount": {
@@ -3772,11 +3772,11 @@
"message": "Адклікана"
},
"sendLink": {
- "message": "Адправіць спасылку",
+ "message": "Спасылка на Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"copySendLink": {
- "message": "Капіяваць спасылку на Send",
+ "message": "Скапіяваць спасылку на Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"removePassword": {
@@ -3809,7 +3809,7 @@
"message": "Пратэрмінавана"
},
"searchSends": {
- "message": "Шукаць адпраўленні",
+ "message": "Пошук у Send'ах",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendProtectedPassword": {
@@ -3817,7 +3817,7 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendProtectedPasswordDontKnow": {
- "message": "Не ведаеце пароль? Спытайце ў адпраўніка пароль, які неабходны для доступу да гэтага Send'а.",
+ "message": "Не ведаеце пароль? Спытайце ў адпраўніка пароль, які неабходны для доступу да гэтага Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendHiddenByDefault": {
@@ -3836,14 +3836,14 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"noSendsInList": {
- "message": "У спісе адсутнічае Send.",
+ "message": "У спісе адсутнічаюць Send'ы.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"emergencyAccess": {
"message": "Экстранны доступ"
},
"emergencyAccessDesc": {
- "message": "Забяспечце экстранны доступ давераным кантактам. Давераныя кантакты могуць атрымаць доступ для прагляду або перадачы валодання вашым уліковы запісам у экстранных сітуацыях. Наведайце нашу старонку дапамогі для падрабязнага азнаямлення, як працуе супольны доступ нулявога ўзроўню."
+ "message": "Дайце экстранны доступ давераным кантактам. Давераныя кантакты могуць атрымаць доступ для прагляду або перадачы валодання вашым уліковы запісам у экстранных сітуацыях. Наведайце нашу старонку дапамогі для падрабязнага азнаямлення, як працуе супольны доступ нулявога ўзроўню."
},
"emergencyAccessOwnerWarning": {
"message": "Вы ўладальнік адной або некалькіх арганізацый. Калі перадаць доступ валодання экстранным кантактам, то ён будзе мець усе дазволы ўладальніка пасля перадачы."
@@ -3861,7 +3861,7 @@
"message": "Прызначаны ў якасці экстраннага кантакту"
},
"noGrantedAccess": {
- "message": "Вы нікога пакуль не прызначылі ў якасці экстраннага кантакту."
+ "message": "У вас пакуль няма экстранных кантактаў."
},
"inviteEmergencyContact": {
"message": "Запрасіць экстранны кантакт"
@@ -3939,7 +3939,7 @@
}
},
"requestSent": {
- "message": "Экстранны доступ запрошаны для $USER$. Вы апавясцім вас па электроннай пошце, калі можна будзе працягнуць.",
+ "message": "Экстранны доступ запрошаны для $USER$. Мы апавясцім вас па электроннай пошце, калі можна будзе працягнуць.",
"placeholders": {
"user": {
"content": "$1",
@@ -3973,7 +3973,7 @@
"message": "Экстранны доступ адхілены"
},
"passwordResetFor": {
- "message": "Пароль скінуты для $USER$. Цяпер вы ўвайсці з дапамогай новага пароля.",
+ "message": "Пароль скінуты для $USER$. Цяпер вы можаце ўвайсці з дапамогай новага пароля.",
"placeholders": {
"user": {
"content": "$1",
@@ -3991,13 +3991,13 @@
"message": "На ўладальнікаў арганізацыі і адміністратараў гэта палітыка не аказвае ўплыву."
},
"personalOwnershipSubmitError": {
- "message": "У адпаведнасці з карпаратыўнай палітыкай вам забаронена захоўваць элементы ў асабістым сховішчы. Змяніце параметры ўласнасці на арганізацыю і выберыце з даступных калекцый."
+ "message": "У адпаведнасці з палітыкай прадпрыемства вам забаронена захоўваць элементы ў асабістым сховішчы. Змяніце параметры ўласнасці на арганізацыю і выберыце з даступных калекцый."
},
"disableSend": {
"message": "Выдаліць Send"
},
"disableSendPolicyDesc": {
- "message": "Не дазваляць удзельнікам ствараць або рэдагаваць Send'ы.",
+ "message": "Не дазваляць удзельнікам ствараць або рэдагаваць Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"disableSendExemption": {
@@ -4008,11 +4008,11 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendDisabledWarning": {
- "message": "У адпаведнасці з карпаратыўнай палітыкай, вы можаце выдаліць толькі бягучы Send.",
+ "message": "У адпаведнасці з палітыкай прадпрыемства, вы можаце выдаліць толькі бягучы Send.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendOptions": {
- "message": "Параметры адпраўкі",
+ "message": "Параметры Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendOptionsPolicyDesc": {
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Дазволы"
},
+ "managerPermissions": {
+ "message": "Дазволы менеджара"
+ },
+ "adminPermissions": {
+ "message": "Дазволы адміністратара"
+ },
"accessEventLogs": {
"message": "Доступ да журналаў з падзеямі"
},
@@ -4070,7 +4076,7 @@
"message": "У вас недастаткова правоў для выканання гэтага дзеяння."
},
"manageAllCollections": {
- "message": "Кіраваць усімі калекцыямі"
+ "message": "Кіраванне ўсімі калекцыямі"
},
"createNewCollections": {
"message": "Старыць новыя калекцыі"
@@ -4082,7 +4088,7 @@
"message": "Выдаліць любую калекцыю"
},
"manageAssignedCollections": {
- "message": "Кіраваць прызначанымі калекцыямі"
+ "message": "Кіраванне прызначанымі калекцыямі"
},
"editAssignedCollections": {
"message": "Рэдагаваць прызначаныя калекцыі"
@@ -4141,7 +4147,7 @@
"message": "Скапіяваць спасылку ў буфер абмену пасля захавання, каб абагуліць гэты Send."
},
"sendLinkLabel": {
- "message": "Адправіць спасылку",
+ "message": "Спасылка на Send",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"send": {
@@ -4250,7 +4256,7 @@
"message": "Адмова паспяхова ўжыта!"
},
"eventEnrollPasswordReset": {
- "message": "Карыстальнік $ID$ зарэгістраваўся, каб атрымаць дапамогу ў скіданні пароля.",
+ "message": "Карыстальнік $ID$ падаў запыт, каб атрымаць дапамогу ў скіданні пароля.",
"placeholders": {
"id": {
"content": "$1",
@@ -4310,7 +4316,7 @@
"message": "гэты карыстальнік"
},
"resetPasswordMasterPasswordPolicyInEffect": {
- "message": "Згодна з адной або некалькімі палітыкамі арганізацыі асноўны пароль павінен адпавядаць наступным патрабаванням:"
+ "message": "Адна або больш палітык арганізацыі патрабуе, каб асноўны пароль адпавядаў наступным патрабаванням:"
},
"resetPasswordSuccess": {
"message": "Пароль паспяхова скінуты!"
@@ -4340,7 +4346,7 @@
"message": "Аўтаматычна рэгістраваць новых карыстальнікаў"
},
"resetPasswordAutoEnrollInviteWarning": {
- "message": "Гэта арганізацыя мае карпаратыўную палітыку, якая аўтаматычна зарэгіструе ваша скіданне пароля. Рэгістрацыя дазволіць адміністратарам арганізацыі змяняць ваш асноўны пароль."
+ "message": "Гэта арганізацыя мае палітыку прадпрыемства, якая аўтаматычна зарэгіструе ваша скіданне пароля. Рэгістрацыя дазволіць адміністратарам арганізацыі змяняць ваш асноўны пароль."
},
"resetPasswordOrgKeysError": {
"message": "Пусты адказ на ключы арганізацыі"
@@ -4352,7 +4358,7 @@
"message": "Элементы, якія знаходзяцца ў сметніцы больш чым 30 дзён будуць выдаляцца аўтаматычна."
},
"trashCleanupWarningSelfHosted": {
- "message": "Элементы, якія будуць знаходзіцца ў сметніцы пэўны час будуць выдаляцца аўтаматычна."
+ "message": "Элементы, якія знаходзіцца ў сметніцы пэўны час будуць выдаляцца аўтаматычна."
},
"passwordPrompt": {
"message": "Паўторны запыт асноўнага пароля"
@@ -4373,10 +4379,10 @@
"message": "Вы ўпэўнены, што хочаце выдаліць наступных карыстальнікаў? Для завяршэння працэсу можа спатрэбіцца некалькі секунд і гэта дзеянне нельга перарваць або скасаваць."
},
"removeOrgUsersConfirmation": {
- "message": "Пасля выдалення ўдзельніка(-ў) яны згубяць доступ да даных арганізацыі і гэта дзеянне з'яўляецца незваротным. Для паўторнага дабаўлення ўдзельніка ў арганізацыю, яго неабходна будзе зноў запрасіць туды. Працэс можа заняць некалькі секунд і яго немагчыма перарваць або скасаваць."
+ "message": "Пасля выдалення ўдзельніка(-ў) ён згубіць доступ да даных арганізацыі і гэта дзеянне з'яўляецца незваротным. Для паўторнага дабаўлення ўдзельніка ў арганізацыю, яго неабходна будзе зноў запрасіць туды. Працэс можа заняць некалькі секунд і яго немагчыма перарваць або скасаваць."
},
"revokeUsersWarning": {
- "message": "Калі ўдзельнік(-і) адкліканы, яны больш не змогуць атрымаць доступ да даных арганізацыі. Для хуткага аднаўлення доступу ўдзельніка, перайдзіце ва ўкладку \"Адкліканыя\". Для завяршэння працэсу можа спатрэбіцца некалькі секунд і гэта дзеянне не можа быць перарвана або скасавана."
+ "message": "Калі ўдзельнік(-і) адкліканы, ён больш не зможа атрымаць доступ да даных арганізацыі. Для хуткага аднаўлення доступу ўдзельніка, перайдзіце ва ўкладку \"Адкліканыя\". Для завяршэння працэсу можа спатрэбіцца некалькі секунд і гэта дзеянне не можа быць перарвана або скасавана."
},
"theme": {
"message": "Тэма"
@@ -4439,7 +4445,7 @@
"message": "Налады пастаўшчыка"
},
"setupProviderLoginDesc": {
- "message": "Вы былі запрошаны для наладжвання новага пастаўшчыка. Для працягу вам неабходна ўвайсці або стврыць новы ўліковы запіс Bitwarden."
+ "message": "Вы былі запрошаны для наладжвання новага пастаўшчыка. Для працягу вам неабходна ўвайсці або стварыць новы ўліковы запіс Bitwarden."
},
"setupProviderDesc": {
"message": "Калі ласка, увядзіце дэталі ніжэй для завяршэння наладжвання правайдара. Вы можаце звязацца са службай падтрымкі, калі ў вас узніклі пытанні."
@@ -4472,7 +4478,7 @@
"message": "Далучыцца да пастаўшчыка"
},
"joinProviderDesc": {
- "message": "Вас запрасілі далучыцца да азначанага вышэй пастаўшчыка. Для таго, каб пацвердзіць запрашэнне, вам неабходна ўвайсці ў ваш уліковы запіс Bitwarden або стварыць яго."
+ "message": "Вас запрасілі далучыцца да азначанага вышэй пастаўшчыка. Для таго, каб пацвердзіць запрашэнне, вам неабходна ўвайсці або стварыць новы ўліковы запіс Bitwarden."
},
"providerInviteAcceptFailed": {
"message": "Немагчыма прыняць запрашэнне. Папрасіце адміністратара пастаўшчыка адправіць яго яшчэ раз."
@@ -4481,7 +4487,7 @@
"message": "Вы можаце атрымаць доступ да гэтага пастаўшчыка адразу пасля таго, як будзе пацверджаны ваш удзел. Мы апавясцім вас, калі гэта адбудзецца."
},
"providerUsersNeedConfirmed": {
- "message": "У вас ёсць карыстальнікі, якія прынялі запрашэнне, але яшчэ не былі пацверджаны. Яны не будуць мець доступу да пастаўшчыка пакуль не будзе пацверджаны іх удзел."
+ "message": "У вас ёсць карыстальнікі, якія прынялі запрашэнне, але яшчэ не былі пацверджаны. Яны не будуць мець доступу да пастаўшчыка пакуль не будуць пацверджаны."
},
"provider": {
"message": "Пастаўшчык"
@@ -4560,10 +4566,10 @@
"message": "Абнавіць асноўны пароль"
},
"updateMasterPasswordWarning": {
- "message": "Ваш асноўны пароль быў нядаўна зменены адміністратарам вашай арганізацыі. Для атрымання доступу да сховішча, вы павінны абнавіць асноўны пароль. Працягваючы, вы выйдзіце з бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны."
+ "message": "Ваш асноўны пароль быў нядаўна зменены адміністратарам вашай арганізацыі. Для атрымання доступу да сховішча, вы павінны абнавіць яго. Працягваючы, вы выйдзіце з бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны."
},
"masterPasswordInvalidWarning": {
- "message": "Ваш асноўны пароль не адпавядае патрабаванням гэтай арганізацыі. Каб далучыцца да арганізацыі, вам неабходна абнавіць свой асноўны пароль зараз. Працягнуўшы, вы выйдзіце з бягучага сеанса і вам спатрэбіцца ўвайсці зноў. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу гадзіны."
+ "message": "Ваш асноўны пароль не адпавядае патрабаванням гэтай арганізацыі. Каб далучыцца да арганізацыі, вам неабходна абнавіць яго зараз. Працягнуўшы, вы выйдзіце з бягучага сеанса і вам спатрэбіцца ўвайсці зноў. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу гадзіны."
},
"maximumVaultTimeout": {
"message": "Час чакання сховішча"
@@ -4584,7 +4590,7 @@
"message": "Хвіліны"
},
"vaultTimeoutPolicyInEffect": {
- "message": "Палітыка вашай арганізацыі ўплывае на час чакання сховішча. Максімальны дазволены час чакання сховішча $HOURS$ гадз. і $MINUTES$ хв.",
+ "message": "Палітыка вашай арганізацыі ўплывае на час чакання сховішча. Максімальны дазволены час чакання сховішча складае $HOURS$ гадз. і $MINUTES$ хв.",
"placeholders": {
"hours": {
"content": "$1",
@@ -4756,7 +4762,7 @@
"message": "Спасылка больш не дзейнічае. Калі ласка, папрасіце спонсара паўторна адправіць прапанову."
},
"reclaimedFreePlan": {
- "message": "Бясплатны план адноўлены"
+ "message": "Бясплатны тарыфны план адноўлены"
},
"redeem": {
"message": "Актываваць"
@@ -4780,7 +4786,7 @@
"message": "Вам прапанаваны бясплатны тарыфны план Bitwarden Families ад арганізацыі. Для працягу, вам неабходна ўвайсці ва ўліковы запіс з якога вы атрымалі прапанову."
},
"sponsoredFamiliesAcceptFailed": {
- "message": "Немагчыма праняць прапанову. Калі ласка, адпраўце прапанову на электронную пошту паўторна са свайго карпаратыўнага ўліковага запісу."
+ "message": "Немагчыма праняць прапанову. Калі ласка, адпраўце прапанову на электронную пошту паўторна са свайго ўліковага запісу прадпрыемства."
},
"sponsoredFamiliesAcceptFailedShort": {
"message": "Немагчыма праняць прапанову. $DESCRIPTION$",
@@ -4813,7 +4819,7 @@
}
},
"resendEmailLabel": {
- "message": "Паўторна адправіць ліст пра спансаванне карыстальніку $NAME$",
+ "message": "Паўторна адправіць ліст пра спансіраванне карыстальніку $NAME$",
"placeholders": {
"name": {
"content": "$1",
@@ -4822,7 +4828,7 @@
}
},
"freeFamiliesPlan": {
- "message": "Бясплатны сямейны план"
+ "message": "Бясплатны тарыфны план Bitwarden Families"
},
"redeemNow": {
"message": "Актываваць зараз"
@@ -4831,22 +4837,22 @@
"message": "Атрымальнік"
},
"removeSponsorship": {
- "message": "Выдаліць спансаванне"
+ "message": "Выдаліць спансіраванне"
},
"removeSponsorshipConfirmation": {
"message": "Пасля выдалення спонсарства, вы будзеце адказваць за гэту падпіску і звязаныя рахункі. Вы ўпэўнены, што хочаце працягнуць?"
},
"sponsorshipCreated": {
- "message": "Спансаванне створана"
+ "message": "Спансіраванне створана"
},
"emailSent": {
"message": "Ліст адпраўлены"
},
"revokeSponsorshipConfirmation": {
- "message": "Пасля выдалення гэтага ўліковага запісу спонсарства тарыфнага плана Bitwarden Families завяршыцца ў канцы плацежнага перыяду. У вас не будзе магчымасці скарыстацца новай спонсарскай прапановай, пакуль не завяршыцца тэрмін бягучай прапановы. Вы ўпэўнены, што хочаце працягнуць?"
+ "message": "Пасля выдалення гэтага ўліковага запісу, спонсарства тарыфнага плана Bitwarden Families завяршыцца ў канцы плацежнага перыяду. У вас не будзе магчымасці скарыстацца новай спонсарскай прапановай, пакуль не завяршыцца тэрмін бягучай прапановы. Вы ўпэўнены, што хочаце працягнуць?"
},
"removeSponsorshipSuccess": {
- "message": "Спансаванне выдалена"
+ "message": "Спансіраванне выдалена"
},
"ssoKeyConnectorError": {
"message": "Памылка Key Connector: пераканайцеся, што Key Connector даступны і карэктна працуе."
@@ -4885,7 +4891,7 @@
}
},
"leaveOrganization": {
- "message": "Пакінуць арганізацыю"
+ "message": "Выйсці з арганізацыі"
},
"removeMasterPassword": {
"message": "Выдаліць асноўны пароль"
@@ -4942,7 +4948,7 @@
"message": "Key Connector адключаны"
},
"keyConnectorWarning": {
- "message": "Як толькі ўдзельнікі пачнуць выкарыстоўваць Key Connector, ваша арганізацыя не зможа вярнуцца да расшыфроўкі з асноўным паролем. Працягвайце толькі калі вы гатовы разгарнуць сервер ключоў і кіраваць ім."
+ "message": "Пасля таго, як удзельнікі пачнуць выкарыстоўваць Key Connector, ваша арганізацыя не зможа вярнуцца да расшыфроўкі з асноўным паролем. Працягвайце толькі калі вы гатовы разгарнуць сервер ключоў і кіраваць ім."
},
"migratedKeyConnector": {
"message": "Выканана міграцыя на Key Connector"
@@ -4978,7 +4984,7 @@
"message": "БЯСПЛАТНА дзякуючы спонсарству"
},
"viewBillingSyncToken": {
- "message": "Паглядзець плацежны токен сінхранізацыі"
+ "message": "Прагледзець плацежны токен сінхранізацыі"
},
"generateBillingSyncToken": {
"message": "Генерыраваць плацежны токен сінхранізацыі"
@@ -4990,7 +4996,7 @@
"message": "Ваш токен плацежнай сінхранізацыі можа атрымаць доступ і адрэдагаваць налады падпіскі гэтай арганізацыі."
},
"manageBillingSync": {
- "message": "Кіраваць плацежнай сінхранізацыяй"
+ "message": "Кіраванне плацежнай сінхранізацыяй"
},
"setUpBillingSync": {
"message": "Наладзіць плацежную сінхранізацыю"
@@ -5011,7 +5017,7 @@
"message": "Уласнае размяшчэнне"
},
"selfHostingEnterpriseOrganizationSectionCopy": {
- "message": "Для наладжвання арганізацыі на вашым уласным серверы, вам неабходна будзе загрузіць файл ліцэнзіі. Для падтрымкі тарыфных планаў Bitwarden Families і пашыраных магчымасцяў выстаўлення рахункаў для вашай арганізацыі, вам неабходна наладзіць плацежную сінхранізацыю."
+ "message": "Для наладжвання арганізацыі на вашым уласным серверы, вам неабходна будзе загрузіць файл з ліцэнзіяй. Для падтрымкі тарыфных планаў Bitwarden Families і пашыраных магчымасцей выстаўлення рахункаў для вашай арганізацыі, вам неабходна наладзіць плацежную сінхранізацыю."
},
"billingSyncApiKeyRotated": {
"message": "Токен зменены."
@@ -5068,7 +5074,7 @@
}
},
"required": {
- "message": "абавязкова"
+ "message": "патрабуецца"
},
"idpSingleSignOnServiceUrlRequired": {
"message": "Патрабуецца, калі ідэнтыфікатар аб'екта не з'яўляецца URL-адрасам."
@@ -5077,19 +5083,19 @@
"message": "Дадатковыя дапасаванні"
},
"openIdAuthorityRequired": {
- "message": "Абавязкова, калі ўстанова не дзейнічае."
+ "message": "Патрабуецца, калі ўстанова не дзейнічае."
},
"separateMultipleWithComma": {
- "message": "Некалькі значэнняў, якія раздзелыны коскай."
+ "message": "Некалькі значэнняў, якія раздзелены коскай."
},
"sessionTimeout": {
- "message": "Час чакання вашай сесіі завяршыўся. Калі ласка, увайдзіце паўторна."
+ "message": "Час чакання вашага сеанса завяршыўся. Калі ласка, увайдзіце паўторна."
},
"exportingPersonalVaultTitle": {
"message": "Экспартаванне асабістага сховішча"
},
"exportingOrganizationVaultTitle": {
- "message": "Экспартавання сховішча арганізацыі"
+ "message": "Экспартаванне сховішча арганізацыі"
},
"exportingPersonalVaultDescription": {
"message": "Будуць экспартаваны толькі асабістыя элементы сховішча, якія звязаны з $EMAIL$. Элементы сховішча арганізацыі не будуць уключаны.",
@@ -5101,7 +5107,7 @@
}
},
"exportingOrganizationVaultDescription": {
- "message": "Будуць экспартаваны толькі запісы сховішча арганізацыі, які звязаны з $ORGANIZATION$. Элементы асабістага сховішча і элементы з іншых арганізацый не будуць уключаны.",
+ "message": "Будуць экспартаваны толькі запісы сховішча арганізацыі, якія звязаны з $ORGANIZATION$. Элементы асабістага сховішча і элементы з іншых арганізацый не будуць уключаны.",
"placeholders": {
"organization": {
"content": "$1",
@@ -5160,13 +5166,13 @@
"description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com"
},
"plusAddressedEmailDesc": {
- "message": "Выкарыстоўваць магчымасці пададрасацыі вашага пастаўшчыка паслуг электроннай пошты."
+ "message": "Выкарыстоўвайце магчымасці пададрасацыі вашага пастаўшчыка паслуг электроннай пошты."
},
"catchallEmail": {
"message": "Адрас для ўсёй пошты дамена"
},
"catchallEmailDesc": {
- "message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсё пошты дамена."
+ "message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсёй пошты дамена."
},
"random": {
"message": "Выпадкова",
@@ -5182,7 +5188,7 @@
"message": "Невядомы элемент. Магчыма, што вам неабходна запытаць дазвол на доступ да гэтага элемента."
},
"cannotSponsorSelf": {
- "message": "Вы не можаце актываваць актыўны ўліковы запіс. Увядзіце іншы адрас электроннай пошты."
+ "message": "Вы не можаце актываваць бягучы ўліковы запіс. Увядзіце іншы адрас электроннай пошты."
},
"revokeWhenExpired": {
"message": "Міне $DATE$",
@@ -5216,7 +5222,7 @@
"Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\""
},
"sponsorshipsSynced": {
- "message": "Уласнае размяшчэнне спансавання сінхранізавана."
+ "message": "Уласнае размяшчэнне спансіравання сінхранізавана."
},
"billingManagedByProvider": {
"message": "Кіруецца $PROVIDER$",
@@ -5288,7 +5294,7 @@
"message": "Гэты ключ API мае доступ да кіравання карыстальнікаў у межах вашай арганізацыі. Яго неабходна захоўваць у надзейным месцы."
},
"copyScimKey": {
- "message": "Капіяваць ключ API для SCIM у буфер абмену",
+ "message": "Скапіяваць ключ API для SCIM у буфер абмену",
"description": "the text, 'SCIM' and 'API', are acronymns and should not be translated."
},
"rotateScimKey": {
diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json
index 419ace835ea..200a343abaf 100644
--- a/apps/web/src/locales/bg/messages.json
+++ b/apps/web/src/locales/bg/messages.json
@@ -2257,7 +2257,7 @@
"message": "Годишно"
},
"annual": {
- "message": "Annual"
+ "message": "Годишно"
},
"basePrice": {
"message": "Базова цена"
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Права"
},
+ "managerPermissions": {
+ "message": "Права на управителя"
+ },
+ "adminPermissions": {
+ "message": "Права на администратора"
+ },
"accessEventLogs": {
"message": "Достъп до журналите"
},
diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json
index d3e7a5f6a1d..837c9cf7190 100644
--- a/apps/web/src/locales/bn/messages.json
+++ b/apps/web/src/locales/bn/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json
index fbc629ef7fe..2f14bb380f0 100644
--- a/apps/web/src/locales/bs/messages.json
+++ b/apps/web/src/locales/bs/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json
index 08cc2b165f2..953e79fbf13 100644
--- a/apps/web/src/locales/ca/messages.json
+++ b/apps/web/src/locales/ca/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permisos"
},
+ "managerPermissions": {
+ "message": "Permisos de l'administrador"
+ },
+ "adminPermissions": {
+ "message": "Permisos de l'administrador"
+ },
"accessEventLogs": {
"message": "Registres d'esdeveniments dels accessos"
},
diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json
index 2e18e2c5923..3bba318dd47 100644
--- a/apps/web/src/locales/cs/messages.json
+++ b/apps/web/src/locales/cs/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Oprávnění"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Přístup k logům"
},
diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json
index 9631152699d..67fdbc44188 100644
--- a/apps/web/src/locales/da/messages.json
+++ b/apps/web/src/locales/da/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Tilladelser"
},
+ "managerPermissions": {
+ "message": "Håndtér rettigheder"
+ },
+ "adminPermissions": {
+ "message": "Admin-tilladelser"
+ },
"accessEventLogs": {
"message": "Tilgå begivenhedslogger"
},
diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json
index d12b19e9a4e..a00359ce98c 100644
--- a/apps/web/src/locales/de/messages.json
+++ b/apps/web/src/locales/de/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Berechtigungen"
},
+ "managerPermissions": {
+ "message": "Manager-Berechtigungen"
+ },
+ "adminPermissions": {
+ "message": "Administrator-Berechtigungen"
+ },
"accessEventLogs": {
"message": "Zugriff auf Ereignisprotokolle"
},
diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json
index a8324937f85..b8ac7e6c385 100644
--- a/apps/web/src/locales/el/messages.json
+++ b/apps/web/src/locales/el/messages.json
@@ -918,7 +918,7 @@
"message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption."
},
"fileTypeHeading": {
- "message": "File Type"
+ "message": "Τύπος αρχείου"
},
"accountBackup": {
"message": "Account Backup"
@@ -1721,7 +1721,7 @@
"message": "Billing Plan"
},
"paymentType": {
- "message": "Payment Type"
+ "message": "Τύπος πληρωμής"
},
"accountCredit": {
"message": "Λογαριασμός Πίστωσης",
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Άδειες"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Αρχείο Καταγραφής Πρόσβασης"
},
@@ -4424,10 +4430,10 @@
"message": "Αφαίρεση Χρηστών"
},
"revokeUsers": {
- "message": "Revoke Users"
+ "message": "Ανάκληση χρηστών"
},
"restoreUsers": {
- "message": "Restore Users"
+ "message": "Επαναφορά χρηστών"
},
"error": {
"message": "Σφάλμα"
@@ -5116,13 +5122,13 @@
"message": "Master Password"
},
"security": {
- "message": "Security"
+ "message": "Ασφάλεια"
},
"keys": {
- "message": "Keys"
+ "message": "Κλειδιά"
},
"billingHistory": {
- "message": "Billing History"
+ "message": "Ιστορικό χρέωσης"
},
"backToReports": {
"message": "Επιστροφή στις Αναφορές"
@@ -5135,7 +5141,7 @@
"description": "This is used by screen readers to indicate the organization that is currently being shown to the user."
},
"accountSettings": {
- "message": "Account Settings"
+ "message": "Ρυθμίσεις λογαριασμού"
},
"generator": {
"message": "Γεννήτρια"
@@ -5303,7 +5309,7 @@
"message": "Rotate Key"
},
"scimApiKey": {
- "message": "SCIM API Key",
+ "message": "Κλειδί API SCIM",
"description": "the text, 'SCIM' and 'API', are acronymns and should not be translated."
},
"copyScimUrl": {
@@ -5347,7 +5353,7 @@
}
},
"turnOn": {
- "message": "Turn on"
+ "message": "Ενεργοποίηση"
},
"on": {
"message": "On"
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 68ff1271c8a..ca591013d3b 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -569,15 +569,27 @@
"loginOrCreateNewAccount": {
"message": "Log in or create a new account to access your secure vault."
},
+ "loginWithDevice" : {
+ "message": "Log in with device"
+ },
+ "loginWithDevciceEnabledInfo": {
+ "message": "Log in with device must be enabled in the settings of the Biwarden mobile app. Need another option?"
+ },
"createAccount": {
"message": "Create Account"
},
+ "newAroundHere": {
+ "message": "New around here?"
+ },
"startTrial": {
"message": "Start Trial"
},
"logIn": {
"message": "Log In"
},
+ "logInInitiated": {
+ "message": "Log in initiated"
+ },
"submit": {
"message": "Submit"
},
@@ -635,7 +647,7 @@
"confirmMasterPasswordRequired": {
"message": "Master password retype is required."
},
- "masterPasswordMinLength": {
+ "masterPasswordMinlength": {
"message": "Master password must be at least 8 characters long."
},
"masterPassDoesntMatch": {
@@ -705,6 +717,9 @@
"noOrganizationsList": {
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
},
+ "notificationSentDevice":{
+ "message": "A notification has been sent to your device."
+ },
"versionNumber": {
"message": "Version $VERSION_NUMBER$",
"placeholders": {
@@ -2536,6 +2551,9 @@
}
}
},
+ "viewAllLoginOptions": {
+ "message": "View all log in options"
+ },
"viewedItemId": {
"message": "Viewed item $ID$.",
"placeholders": {
@@ -3379,6 +3397,12 @@
"message": "To ensure the integrity of your encryption keys, please verify the user's fingerprint phrase before continuing.",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
},
+ "fingerprintMatchInfo": {
+ "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device."
+ },
+ "fingerprintPhraseHeader": {
+ "message": "Fingerprint phrase"
+ },
"dontAskFingerprintAgain": {
"message": "Never prompt to verify fingerprint phrases for invited users (Not recommended)",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
@@ -4064,6 +4088,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
@@ -4373,6 +4403,9 @@
"reinviteSelected": {
"message": "Resend Invitations"
},
+ "resendNotification": {
+ "message": "Resend notification"
+ },
"noSelectedUsersApplicable": {
"message": "This action is not applicable to any of the selected users."
},
diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json
index eac57249883..3f7beb72d16 100644
--- a/apps/web/src/locales/en_GB/messages.json
+++ b/apps/web/src/locales/en_GB/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json
index 14a07c48e2d..939d41b015d 100644
--- a/apps/web/src/locales/en_IN/messages.json
+++ b/apps/web/src/locales/en_IN/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json
index 327bc12f086..960cb3250e7 100644
--- a/apps/web/src/locales/eo/messages.json
+++ b/apps/web/src/locales/eo/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permesoj"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Aliri Eventajn Registrojn"
},
diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json
index 686980e052c..062e7326340 100644
--- a/apps/web/src/locales/es/messages.json
+++ b/apps/web/src/locales/es/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permisos"
},
+ "managerPermissions": {
+ "message": "Permisos del Administrador"
+ },
+ "adminPermissions": {
+ "message": "Permisos del administrador"
+ },
"accessEventLogs": {
"message": "Acceder a los registros de eventos"
},
diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json
index 542914b221d..f4242fe946d 100644
--- a/apps/web/src/locales/et/messages.json
+++ b/apps/web/src/locales/et/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Õigused"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Ligipääs sündmuste logile"
},
diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json
index c685a9ffb08..467f257db3d 100644
--- a/apps/web/src/locales/eu/messages.json
+++ b/apps/web/src/locales/eu/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Baimenak"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Sarreren erregistroak"
},
diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json
index b064002d08e..5bd081806c6 100644
--- a/apps/web/src/locales/fi/messages.json
+++ b/apps/web/src/locales/fi/messages.json
@@ -2248,7 +2248,7 @@
}
},
"trialConfirmationEmail": {
- "message": "Olemme lähettäneet vahvistusviestin tiimisi laskutussähköpostiosoitteeseen "
+ "message": "Olemme lähettäneet sähköpostivahvistuksen tiimisi laskutusoitteeseen "
},
"monthly": {
"message": "Kuukausittainen"
@@ -2326,10 +2326,10 @@
"message": "Haluatko varmasti poistaa käyttäjän?"
},
"removeOrgUserConfirmation": {
- "message": "Kun jäsen poistetaan, hänellä ei ole enää pääsyä organisaation tietoihin, eikä poistoa ole mahdollista perua. Jos haluat lisätä jäsenen takaisin organisaatioon, hänet on kutsuttava ja perehdytettävä uudelleen."
+ "message": "Poisteltulla jäsenellä ei ole enää organisaation tietojen käyttöoikeutta, eikä poiston peruminen ole mahdollista. Jos jäsen halutaan palauttaa organisaatioon, hänet on kutsuttava ja määritettävä uudelleen."
},
"revokeUserConfirmation": {
- "message": "Kun jäsenen käyttöoikeus perutaan, hänellä ei ole enää pääsyä organisaation tietoihin. Voit palauttaa käyttöoikeuden nopeasti Perutut-välilehdeltä."
+ "message": "Perutulla jäsenellä ei ole enää organisaation tietojen käyttöoikeutta. Käyttöoikeus voidaan palauttaa nopeasti Perutut-välilehdeltä."
},
"removeUserConfirmationKeyConnector": {
"message": "Varoitus! Tämä käyttäjä tarvitsee salauksensa hallintaan Key Connectoria. Käyttäjän poistaminen organisaatiostasi poistaa heidän tilinsä käytöstä pysvästi. Toimenpide on pysyvä, eikä sen peruminen ole mahdollista. Haluatko jatkaa?"
@@ -2395,10 +2395,10 @@
"message": "Hae"
},
"invited": {
- "message": "Kutsuttu"
+ "message": "Kutsutut"
},
"accepted": {
- "message": "Hyväksytty"
+ "message": "Hyväksytyt"
},
"confirmed": {
"message": "Vahvistettu"
@@ -2686,7 +2686,7 @@
}
},
"revokedUserId": {
- "message": "Jäsenen $ID$ organisaation käyttöoikeus peruttiin.",
+ "message": "Käyttäjän $ID$ organisaation käyttöoikeus on peruttu.",
"placeholders": {
"id": {
"content": "$1",
@@ -2704,7 +2704,7 @@
}
},
"revokeUserId": {
- "message": "Peru jäsenen $ID$ käyttöoikeus",
+ "message": "Peru käyttäjän $ID$ käyttöoikeus",
"placeholders": {
"id": {
"content": "$1",
@@ -4023,7 +4023,7 @@
"message": "Käytäntöä ei pakoteta käyttöön niille organisaation käyttäjille, joilla on organisaation käytäntöjen hallintaoikeudet."
},
"disableHideEmail": {
- "message": "Näytä jäsenen sähköpostiosoite aina vastaanottajien kanssa, kun luodaan tai muokataan Sendiä.",
+ "message": "Näytä jäsenen sähköpostiosoite aina vastaanottajien ohessa, kun Send luodaan tai sitä muokataan.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendOptionsPolicyInEffect": {
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Käyttöoikeudet"
},
+ "managerPermissions": {
+ "message": "Valvojan oikeudet"
+ },
+ "adminPermissions": {
+ "message": "Ylläpitäjän oikeudet"
+ },
"accessEventLogs": {
"message": "Tapahtumalokien käyttö"
},
@@ -4370,13 +4376,13 @@
"message": "Toiminto ei koske valittuja käyttäjiä."
},
"removeUsersWarning": {
- "message": "Haluatko varmasti poistaa seuraavat käyttäjät? Toiminto saattaa kestää muutamia sekunteja, eikä sen keskeytys tai peruminen ole mahdollista."
+ "message": "Haluatko varmasti poistaa seuraavat käyttäjät? Käsittely voi kestää muutamia sekunteja, eikä sen keskeytys tai peruminen ole mahdollista."
},
"removeOrgUsersConfirmation": {
- "message": "Kun jäseniä poistetaan, heillä ei ole enää pääsyä organisaation tietoihin, eikä poistoa ole mahdollista perua. Jos haluat lisätä jäsenen takaisin organisaatioon, hänet on kutsuttava ja perehdytettävä uudelleen. Käsittely voi kestää muutamia sekunteja, eikä sitä ole mahdollista keskeyttää tai perua."
+ "message": "Poisteltuilla jäsenillä ei ole enää organisaation tietojen käyttöoikeutta, eikä poiston peruminen ole mahdollista. Jos jäsniä halutaan palauttaa organisaatioon, heidät on kutsuttava ja määritettävä uudelleen. Käsittely voi kestää muutamia sekunteja, eikä sen keskeytys tai peruminen ole mahdollista."
},
"revokeUsersWarning": {
- "message": "Kun jäsenien käyttöoikeudet perutaan, heillä ei ole enää pääsyä organisaation tietoihin. Voit palauttaa käyttöoikeudet nopeasti Perutut-välilehdeltä. Käsittely voi kestää muutamia sekunteja, eikä sitä ole mahdollista keskeyttää tai perua."
+ "message": "Perutuilla jäsenillä ei ole enää organisaation tietojen käyttöoikeutta. Käyttöoikeudet voidaan palauttaa nopeasti Perutut-välilehdeltä. Käsittely voi kestää muutamia sekunteja, eikä sen keskeytys tai peruminen ole mahdollista."
},
"theme": {
"message": "Teema"
diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json
index 37f65c83d49..874faf9c552 100644
--- a/apps/web/src/locales/fil/messages.json
+++ b/apps/web/src/locales/fil/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json
index 51dd966dea1..ce1b38ac01d 100644
--- a/apps/web/src/locales/fr/messages.json
+++ b/apps/web/src/locales/fr/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Permissions du Gestionnaire"
+ },
+ "adminPermissions": {
+ "message": "Permissions de l'Administrateur"
+ },
"accessEventLogs": {
"message": "Accéder aux journaux d'événements"
},
diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json
index fce1aa9d41e..7097f742461 100644
--- a/apps/web/src/locales/he/messages.json
+++ b/apps/web/src/locales/he/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json
index 63abb103a71..ebdb4cd0ac2 100644
--- a/apps/web/src/locales/hi/messages.json
+++ b/apps/web/src/locales/hi/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json
index 0207bb4e595..5ae0ed2b8a7 100644
--- a/apps/web/src/locales/hr/messages.json
+++ b/apps/web/src/locales/hr/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Dozvole"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Pristup zapisnicima događaja"
},
diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json
index c4c76ad6095..2d673d6e654 100644
--- a/apps/web/src/locales/hu/messages.json
+++ b/apps/web/src/locales/hu/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Jogosultságok"
},
+ "managerPermissions": {
+ "message": "Jogosultságok kezelése"
+ },
+ "adminPermissions": {
+ "message": "Adminisztrátori jogosultságok"
+ },
"accessEventLogs": {
"message": "Eseménynapló elérése"
},
diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json
index 46f191e077b..07e2c67bdf0 100644
--- a/apps/web/src/locales/id/messages.json
+++ b/apps/web/src/locales/id/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Izin"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Akses Log Peristiwa"
},
diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json
index b9ba2942153..f92c5c349e6 100644
--- a/apps/web/src/locales/it/messages.json
+++ b/apps/web/src/locales/it/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permessi"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Accedi ai log degli eventi"
},
diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json
index dd3ab6b97a9..e415d837534 100644
--- a/apps/web/src/locales/ja/messages.json
+++ b/apps/web/src/locales/ja/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "権限"
},
+ "managerPermissions": {
+ "message": "権限の管理"
+ },
+ "adminPermissions": {
+ "message": "管理者権限"
+ },
"accessEventLogs": {
"message": "イベントログにアクセス"
},
diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json
index 5ca96affb7a..80755183b9f 100644
--- a/apps/web/src/locales/ka/messages.json
+++ b/apps/web/src/locales/ka/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json
index 5ca96affb7a..80755183b9f 100644
--- a/apps/web/src/locales/km/messages.json
+++ b/apps/web/src/locales/km/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json
index e49c178c6be..0f57d99a739 100644
--- a/apps/web/src/locales/kn/messages.json
+++ b/apps/web/src/locales/kn/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "ಅನುಮತಿಗಳು"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "ಈವೆಂಟ್ ಲಾಗ್ಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"
},
diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json
index 5e9ec49e03a..9ea99f04446 100644
--- a/apps/web/src/locales/ko/messages.json
+++ b/apps/web/src/locales/ko/messages.json
@@ -306,7 +306,7 @@
"message": "보안 메모"
},
"typeLoginPlural": {
- "message": "Logins"
+ "message": "로그인"
},
"typeCardPlural": {
"message": "카드"
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "권한"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "이벤트 로그 접근"
},
diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json
index f1d428388a6..8b5956c696c 100644
--- a/apps/web/src/locales/lv/messages.json
+++ b/apps/web/src/locales/lv/messages.json
@@ -679,7 +679,7 @@
"message": "Nederīga galvenā parole"
},
"invalidFilePassword": {
- "message": "Invalid file password, please use the password you entered when you created the export file."
+ "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izdošanas datnes izveidošanas brīdī."
},
"lockNow": {
"message": "Aizslēgt"
@@ -894,46 +894,46 @@
"message": "Datnes veids"
},
"fileEncryptedExportWarningDesc": {
- "message": "This file export will be password protected and require the file password to decrypt."
+ "message": "Šī datņu izdošana būs aizsargāta ar paroli, un būs nepieciešama datnes parole, lai to atšifrētu."
},
"exportPasswordDescription": {
- "message": "This password will be used to export and import this file"
+ "message": "Šī parole tiks izmantota, lai izdotu un ievietotu šo datni"
},
"confirmMasterPassword": {
- "message": "Confirm Master Password"
+ "message": "Apstiprināt galveno paroli"
},
"confirmFormat": {
- "message": "Confirm Format"
+ "message": "Apstiprināt veidolu"
},
"filePassword": {
- "message": "File Password"
+ "message": "Datnes parole"
},
"confirmFilePassword": {
- "message": "Confirm File Password"
+ "message": "Apstiprināt datnes paroli"
},
"accountBackupOptionDescription": {
- "message": "Use your account encryption key to encrypt the export and restrict import to only the current Bitwarden account."
+ "message": "Jāizmanto konta šifrēšanas atslēga, lai šifrētu izdošanu un ierobežotu ievietošanu tikai pašreizējā Bitwarden kontā."
},
"passwordProtectedOptionDescription": {
- "message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption."
+ "message": "Uzstādīt paroli, lai šifrētu izdošanu un tad to ievietotu jebkurā Bitwarden kontā, izmantojot atšifrēšanas paroli."
},
"fileTypeHeading": {
- "message": "File Type"
+ "message": "Datnes veids"
},
"accountBackup": {
- "message": "Account Backup"
+ "message": "Konta rezerves kopija"
},
"passwordProtected": {
- "message": "Password Protected"
+ "message": "Aizsargāts ar paroli"
},
"filePasswordAndConfirmFilePasswordDoNotMatch": {
- "message": "“File password” and “Confirm File Password“ do not match."
+ "message": "\"Datnes parole\" un \"Apstiprināt datnes paroli\" vērtības nesakrīt."
},
"confirmVaultImport": {
- "message": "Confirm Vault Import"
+ "message": "Apstiprināt glabātavas satura ievietošanu"
},
"confirmVaultImportDesc": {
- "message": "This file is password-protected. Please enter the file password to import data."
+ "message": "Šī datne ir aizsargāta ar paroli. Lūgums ievadīt datnes paroli, lai ievietotu datus."
},
"exportSuccess": {
"message": "Glabātavas saturs ir izgūts."
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Atļaujas"
},
+ "managerPermissions": {
+ "message": "Pārvaldnieka atļaujas"
+ },
+ "adminPermissions": {
+ "message": "Administratora atļaujas"
+ },
"accessEventLogs": {
"message": "Piekļūt notikumu žurnāla ierakstiem"
},
diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json
index 3daff3c6492..ea6689a9b80 100644
--- a/apps/web/src/locales/ml/messages.json
+++ b/apps/web/src/locales/ml/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "അനുമതികൾ"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json
index 3f8bec3fd68..372bd618e02 100644
--- a/apps/web/src/locales/nb/messages.json
+++ b/apps/web/src/locales/nb/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Rettigheter"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Få tilgang til hendelseslogger"
},
diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json
index 456d2253c6c..002abcd8852 100644
--- a/apps/web/src/locales/nl/messages.json
+++ b/apps/web/src/locales/nl/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Rechten"
},
+ "managerPermissions": {
+ "message": "Managersrechten"
+ },
+ "adminPermissions": {
+ "message": "Beheerdersrechten"
+ },
"accessEventLogs": {
"message": "Eventlogs"
},
diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json
index 1b27527fc3a..fa3ea5e5272 100644
--- a/apps/web/src/locales/nn/messages.json
+++ b/apps/web/src/locales/nn/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json
index fc3f0ff1b83..ea9e0cba533 100644
--- a/apps/web/src/locales/pl/messages.json
+++ b/apps/web/src/locales/pl/messages.json
@@ -645,7 +645,7 @@
"message": "Konto zostało utworzone! Teraz możesz się zalogować."
},
"trialAccountCreated": {
- "message": "Konto zostało pomyślnie utworzone."
+ "message": "Konto zostało utworzone."
},
"masterPassSent": {
"message": "Wysłaliśmy Tobie wiadomość e-mail z podpowiedzią do hasła głównego."
@@ -918,7 +918,7 @@
"message": "Utwórz hasło wygenerowane przez użytkownika, aby chronić eksport. Użyj tego, aby utworzyć eksport, który może być używany na innych kontach."
},
"fileTypeHeading": {
- "message": "Typ Pliku"
+ "message": "Rodzaj pliku"
},
"accountBackup": {
"message": "Kopia zapasowa konta"
@@ -1324,7 +1324,7 @@
"message": "Wyłącz"
},
"revokeAccess": {
- "message": "Odwołaj dostęp"
+ "message": "Unieważnij dostęp"
},
"twoStepLoginProviderEnabled": {
"message": "Ten dostawca logowania dwustopniowego jest już włączony na koncie."
@@ -1840,7 +1840,7 @@
"message": "rok"
},
"yr": {
- "message": "r."
+ "message": "rok"
},
"month": {
"message": "miesiąc"
@@ -2257,7 +2257,7 @@
"message": "Rocznie"
},
"annual": {
- "message": "Raz w roku"
+ "message": "Rocznie"
},
"basePrice": {
"message": "Cena netto"
@@ -2329,7 +2329,7 @@
"message": "Gdy członek zostanie usunięty, nie ma już dostępu do danych organizacji, a ta czynność jest nieodwracalna. Aby ponownie dodać członka do organizacji, należy go zaprosić i ponownie wprowadzić."
},
"revokeUserConfirmation": {
- "message": "Gdy członek zostanie odwołany, nie ma już dostępu do danych organizacji. Aby szybko przywrócić dostęp członków, przejdź do karty Odwołani."
+ "message": "Jeśli użytkownik zostanie unieważniony, nie będzie miał dostępu do danych organizacji. Aby przywrócić dostęp użytkownika, przejdź do karty Unieważnieni."
},
"removeUserConfirmationKeyConnector": {
"message": "Ostrzeżenie! Ten użytkownik wymaga serwera Key Connector do zarządzania szyfrowaniem. Usunięcie tego użytkownika z organizacji spowoduje trwałe wyłączenie jego konta. Nie możesz cofnąć tej akcji. Czy chcesz kontynuować?"
@@ -2686,7 +2686,7 @@
}
},
"revokedUserId": {
- "message": "Odwołano dostęp organizacji dla $ID$.",
+ "message": "Dostęp do organizacji dla użytkownika $ID$ został unieważniony.",
"placeholders": {
"id": {
"content": "$1",
@@ -2704,7 +2704,7 @@
}
},
"revokeUserId": {
- "message": "Odwołano dostęp $ID$",
+ "message": "Unieważnij dostęp dla użytkownika $ID$",
"placeholders": {
"id": {
"content": "$1",
@@ -3769,7 +3769,7 @@
"message": "Wyłączone"
},
"revoked": {
- "message": "Odwołani"
+ "message": "Unieważnieni"
},
"sendLink": {
"message": "Link wysyłki",
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Uprawnienia"
},
+ "managerPermissions": {
+ "message": "Menedżer uprawnień"
+ },
+ "adminPermissions": {
+ "message": "Administrator uprawnień"
+ },
"accessEventLogs": {
"message": "Dostęp do dziennika zdarzeń"
},
@@ -4376,7 +4382,7 @@
"message": "Kiedy członek (członkowie) zostaje usunięty, nie ma już dostępu do danych organizacji i działanie to jest nieodwracalne. Aby dodać członka z powrotem do organizacji, musi on zostać ponownie zaproszony i włączony do organizacji. Proces ten może trwać kilka sekund i nie może być przerwany ani anulowany."
},
"revokeUsersWarning": {
- "message": "Gdy członek (członkowie) zostanie odwołany, nie ma już dostępu do danych organizacji. Aby szybko przywrócić dostęp do danych członka, przejdź do zakładki Odwołano. Proces ten może potrwać kilka sekund i nie można go przerwać ani anulować."
+ "message": "Jeśli użytkownicy zostaną unieważnieni, nie będą mieli dostępu do danych organizacji. Aby przywrócić dostęp użytkownika, przejdź do karty Unieważnieni. Proces może potrwać kilka sekund i nie można go przerwać lub anulować."
},
"theme": {
"message": "Motyw"
@@ -4409,7 +4415,7 @@
"message": "Usunięto"
},
"bulkRevokedMessage": {
- "message": "Odwołano dostęp do organizacji"
+ "message": "Dostęp do organizacji został unieważniony"
},
"bulkRestoredMessage": {
"message": "Przywrócono dostęp do organizacji"
@@ -4424,7 +4430,7 @@
"message": "Usuń użytkowników"
},
"revokeUsers": {
- "message": "Odwołaj użytkowników"
+ "message": "Unieważnij użytkowników"
},
"restoreUsers": {
"message": "Przywróć użytkowników"
diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json
index 6944ffec979..fcb778e6d9b 100644
--- a/apps/web/src/locales/pt_BR/messages.json
+++ b/apps/web/src/locales/pt_BR/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissões"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Acessar Registro de Eventos"
},
diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json
index bdf34e78aec..38025cfcab3 100644
--- a/apps/web/src/locales/pt_PT/messages.json
+++ b/apps/web/src/locales/pt_PT/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissões"
},
+ "managerPermissions": {
+ "message": "Gestão de permissões"
+ },
+ "adminPermissions": {
+ "message": "Permissões de Administrador"
+ },
"accessEventLogs": {
"message": "Aceder ao Registro de Eventos"
},
diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json
index ac5b652a525..79ec219b22e 100644
--- a/apps/web/src/locales/ro/messages.json
+++ b/apps/web/src/locales/ro/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permisiuni"
},
+ "managerPermissions": {
+ "message": "Permisiuni manager"
+ },
+ "adminPermissions": {
+ "message": "Permisiuni Admin"
+ },
"accessEventLogs": {
"message": "Acces la jurnalele de evenimente"
},
diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json
index 9039b0e70c8..e6a40602e3b 100644
--- a/apps/web/src/locales/ru/messages.json
+++ b/apps/web/src/locales/ru/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Разрешения"
},
+ "managerPermissions": {
+ "message": "Права менеджера"
+ },
+ "adminPermissions": {
+ "message": "Права администратора"
+ },
"accessEventLogs": {
"message": "Доступ к журналам событий"
},
diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json
index ae91788759d..26e5893ffce 100644
--- a/apps/web/src/locales/si/messages.json
+++ b/apps/web/src/locales/si/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json
index a90a35f0973..0a76deafa2c 100644
--- a/apps/web/src/locales/sk/messages.json
+++ b/apps/web/src/locales/sk/messages.json
@@ -1523,14 +1523,14 @@
"message": "Reporty"
},
"reportsDesc": {
- "message": "Identify and close security gaps in your online accounts by clicking the reports below.",
+ "message": "Identifikujte a opravte bezpečnostné nedostatky kliknutím na reporty nižšie.",
"description": "Vault Health Reports can be used to evaluate the security of your Bitwarden Personal or Organization Vault."
},
"unsecuredWebsitesReport": {
"message": "Nezabezpečené stránky"
},
"unsecuredWebsitesReportDesc": {
- "message": "URLs that start with http:// don’t use the best available encryption. Change the Login URIs for these accounts to https:// for safer browsing."
+ "message": "URL, ktoré začínajú s http:// nepoužívajú najlepšie dostupné šifrovanie. Upravte URL prihlásenia pre tieto kontá na https:// pre bezpečnejšie surfovanie na internete."
},
"unsecuredWebsitesFound": {
"message": "Našli sa nezabezpečené stránky"
@@ -1575,7 +1575,7 @@
"message": "Uniknuté heslá"
},
"exposedPasswordsReportDesc": {
- "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins."
+ "message": "Heslá odhalené pri úniku dát sú pre útočníkov jednoduchým cieľom. Zmeňte tieto heslá aby ste sa vyhli možnému vlámaniu."
},
"exposedPasswordsFound": {
"message": "Našli sme uniknuté heslá"
@@ -1608,7 +1608,7 @@
"message": "Slabé heslá"
},
"weakPasswordsReportDesc": {
- "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the Password Generator."
+ "message": "Jednoduché heslá môže útočník ľahko uhádnuť. Vymeňte tieto heslá za silné s použitím Generátora Hesiel."
},
"weakPasswordsFound": {
"message": "Našli sa slabé heslá"
@@ -1629,7 +1629,7 @@
"message": "Viacnásobne použité heslá"
},
"reusedPasswordsReportDesc": {
- "message": "Reusing passwords makes it easier for attackers to break into multiple accounts. Change these passwords so that each is unique."
+ "message": "Opakované používanie hesiel uľahčuje útočníkom vlámať sa do viacerých účtov. Zmeňte si tieto heslá tak aby bolo každé unikátne."
},
"reusedPasswordsFound": {
"message": "Našli sa viacnásobne použité heslá"
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Povolenia"
},
+ "managerPermissions": {
+ "message": "Oprávnenia manažéra"
+ },
+ "adminPermissions": {
+ "message": "Oprávnenia správcu"
+ },
"accessEventLogs": {
"message": "Prístup k protokolom udalostí"
},
diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json
index b98db470a0c..2290771cdf4 100644
--- a/apps/web/src/locales/sl/messages.json
+++ b/apps/web/src/locales/sl/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json
index 53726c7c596..77530eb1340 100644
--- a/apps/web/src/locales/sr/messages.json
+++ b/apps/web/src/locales/sr/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Дозволе"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Приступе извештаја догађаја"
},
diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json
index ad332f42106..73b53363e46 100644
--- a/apps/web/src/locales/sr_CS/messages.json
+++ b/apps/web/src/locales/sr_CS/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json
index 9b9f40897cc..2812a6047f9 100644
--- a/apps/web/src/locales/sv/messages.json
+++ b/apps/web/src/locales/sv/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Behörigheter"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Åtkomst till händelseloggar"
},
diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json
index e186527ed53..e40011655b0 100644
--- a/apps/web/src/locales/tr/messages.json
+++ b/apps/web/src/locales/tr/messages.json
@@ -2428,7 +2428,7 @@
"message": "Kuruluşunuzda atanmış koleksiyonlara erişimi olan normal bir kullanıcı."
},
"manager": {
- "message": "Yönetici"
+ "message": "Yetkili"
},
"managerDesc": {
"message": "Yetkililer kuruluşunuzdaki kendilerine atanmış koleksiyonlara erişebilir ve onları yönetebilir."
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "İzinler"
},
+ "managerPermissions": {
+ "message": "Yetkili İzinleri"
+ },
+ "adminPermissions": {
+ "message": "Yönetici İzinleri"
+ },
"accessEventLogs": {
"message": "Olay günlüklerine erişme"
},
diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json
index 464af5736fa..b774c28e298 100644
--- a/apps/web/src/locales/uk/messages.json
+++ b/apps/web/src/locales/uk/messages.json
@@ -912,10 +912,10 @@
"message": "Підтвердьте пароль файлу"
},
"accountBackupOptionDescription": {
- "message": "Завжди використовує шифрування облікового запису Bitwarden, а не головний пароль для захисту експорту. Цей експорт можна імпортувати лише в поточний обліковий запис. Використовуйте його для створення резервної копії, яку не можна використовувати в іншому місці."
+ "message": "Використовуйте ключ шифрування облікового запису, щоб зашифрувати експортований файл і дозволити його імпорт лише до поточного облікового запису Bitwarden."
},
"passwordProtectedOptionDescription": {
- "message": "Створити користувацький пароль для захисту експорту. Використовуйте це для створення експорту, який може бути використаний в інших облікових записах."
+ "message": "Встановіть пароль, щоб зашифрувати експортований файл та дозволити імпорт до будь-якого облікового запису Bitwarden за допомогою цього пароля."
},
"fileTypeHeading": {
"message": "Тип файлу"
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Дозволи"
},
+ "managerPermissions": {
+ "message": "Дозволи менеджера"
+ },
+ "adminPermissions": {
+ "message": "Дозволи адміністратора"
+ },
"accessEventLogs": {
"message": "Доступ до журналів подій"
},
diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json
index 7842e65b5fe..dfad84612e8 100644
--- a/apps/web/src/locales/vi/messages.json
+++ b/apps/web/src/locales/vi/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "Permissions"
},
+ "managerPermissions": {
+ "message": "Manager Permissions"
+ },
+ "adminPermissions": {
+ "message": "Admin Permissions"
+ },
"accessEventLogs": {
"message": "Access Event Logs"
},
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index 2fec98de3fb..3e7d64fa465 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "权限"
},
+ "managerPermissions": {
+ "message": "经理权限"
+ },
+ "adminPermissions": {
+ "message": "管理员权限"
+ },
"accessEventLogs": {
"message": "访问事件日志"
},
diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json
index b9ba520df1e..a125fe17477 100644
--- a/apps/web/src/locales/zh_TW/messages.json
+++ b/apps/web/src/locales/zh_TW/messages.json
@@ -4057,6 +4057,12 @@
"permissions": {
"message": "權限"
},
+ "managerPermissions": {
+ "message": "權限管理"
+ },
+ "adminPermissions": {
+ "message": "管理員權限"
+ },
"accessEventLogs": {
"message": "存取事件紀錄"
},
diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts
index 5cc3b930bb4..195bc8e5f54 100644
--- a/apps/web/src/utils/flags.ts
+++ b/apps/web/src/utils/flags.ts
@@ -10,6 +10,7 @@ import {
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type Flags = {
showTrial?: boolean;
+ showPasswordless?: boolean;
} & SharedFlags;
// required to avoid linting errors when there are no flags
diff --git a/libs/angular/src/components/login.component.ts b/libs/angular/src/components/login.component.ts
index 1c7a8c2332d..1bc2e8ed871 100644
--- a/libs/angular/src/components/login.component.ts
+++ b/libs/angular/src/components/login.component.ts
@@ -1,10 +1,15 @@
-import { Directive, Input, NgZone, OnInit } from "@angular/core";
+import { Directive, NgZone, OnInit } from "@angular/core";
+import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { take } from "rxjs/operators";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
+import {
+ AllValidationErrors,
+ FormValidationErrorsService,
+} from "@bitwarden/common/abstractions/formValidationErrors.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
@@ -18,16 +23,19 @@ import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
- @Input() email = "";
- @Input() rememberEmail = true;
-
- masterPassword = "";
showPassword = false;
formPromise: Promise
;
onSuccessfulLogin: () => Promise;
onSuccessfulLoginNavigate: () => Promise;
onSuccessfulLoginTwoFactorNavigate: () => Promise;
onSuccessfulLoginForceResetNavigate: () => Promise;
+ selfHosted = false;
+
+ formGroup = this.formBuilder.group({
+ email: ["", [Validators.required, Validators.email]],
+ masterPassword: ["", [Validators.required, Validators.minLength(8)]],
+ rememberEmail: [true],
+ });
protected twoFactorRoute = "2fa";
protected successRoute = "vault";
@@ -44,9 +52,12 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
protected logService: LogService,
- protected ngZone: NgZone
+ protected ngZone: NgZone,
+ protected formBuilder: FormBuilder,
+ protected formValidationErrorService: FormValidationErrorsService
) {
super(environmentService, i18nService, platformUtilsService);
+ this.selfHosted = platformUtilsService.isSelfHost();
}
get selfHostedDomain() {
@@ -54,59 +65,53 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
}
async ngOnInit() {
- if (this.email == null || this.email === "") {
- this.email = await this.stateService.getRememberedEmail();
- if (this.email == null) {
- this.email = "";
+ let email = this.formGroup.get("email")?.value;
+ if (email == null || email === "") {
+ email = await this.stateService.getRememberedEmail();
+ this.formGroup.get("email")?.setValue(email);
+
+ if (email == null) {
+ this.formGroup.get("email")?.setValue("");
}
}
if (!this.alwaysRememberEmail) {
- this.rememberEmail = (await this.stateService.getRememberedEmail()) != null;
- }
- if (Utils.isBrowser && !Utils.isNode) {
- this.focusInput();
+ const rememberEmail = (await this.stateService.getRememberedEmail()) != null;
+ this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
}
}
- async submit() {
+ async submit(showToast = true) {
+ const email = this.formGroup.get("email")?.value;
+ const masterPassword = this.formGroup.get("masterPassword")?.value;
+ const rememberEmail = this.formGroup.get("rememberEmail")?.value;
+
await this.setupCaptcha();
- if (this.email == null || this.email === "") {
- this.platformUtilsService.showToast(
- "error",
- this.i18nService.t("errorOccurred"),
- this.i18nService.t("emailRequired")
- );
+ this.formGroup.markAllAsTouched();
+
+ //web
+ if (this.formGroup.invalid && !showToast) {
return;
}
- if (this.email.indexOf("@") === -1) {
- this.platformUtilsService.showToast(
- "error",
- this.i18nService.t("errorOccurred"),
- this.i18nService.t("invalidEmail")
- );
- return;
- }
- if (this.masterPassword == null || this.masterPassword === "") {
- this.platformUtilsService.showToast(
- "error",
- this.i18nService.t("errorOccurred"),
- this.i18nService.t("masterPasswordRequired")
- );
+
+ //desktop, browser; This should be removed once all clients use reactive forms
+ if (this.formGroup.invalid && showToast) {
+ const errorText = this.getErrorToastMessage();
+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
return;
}
try {
const credentials = new PasswordLogInCredentials(
- this.email,
- this.masterPassword,
+ email,
+ masterPassword,
this.captchaToken,
null
);
this.formPromise = this.authService.logIn(credentials);
const response = await this.formPromise;
- if (this.rememberEmail || this.alwaysRememberEmail) {
- await this.stateService.setRememberedEmail(this.email);
+ if (rememberEmail || this.alwaysRememberEmail) {
+ await this.stateService.setRememberedEmail(email);
} else {
await this.stateService.setRememberedEmail(null);
}
@@ -188,9 +193,30 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
);
}
+ private getErrorToastMessage() {
+ const error: AllValidationErrors = this.formValidationErrorService
+ .getFormValidationErrors(this.formGroup.controls)
+ .shift();
+
+ if (error) {
+ switch (error.errorName) {
+ case "email":
+ return this.i18nService.t("invalidEmail");
+ default:
+ return this.i18nService.t(this.errorTag(error));
+ }
+ }
+
+ return;
+ }
+
+ private errorTag(error: AllValidationErrors): string {
+ const name = error.errorName.charAt(0).toUpperCase() + error.errorName.slice(1);
+ return `${error.controlName}${name}`;
+ }
+
protected focusInput() {
- document
- .getElementById(this.email == null || this.email === "" ? "email" : "masterPassword")
- .focus();
+ const email = this.formGroup.get("email")?.value;
+ document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus();
}
}
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index b76ad9a5903..b53aac0866a 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -5,6 +5,7 @@ import { AbstractThemingService } from "@bitwarden/angular/services/theming/them
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/abstractions/account/account.service.abstraction";
+import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/abstractions/anonymousHub.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
@@ -62,6 +63,7 @@ import { Account } from "@bitwarden/common/models/domain/account";
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
import { AccountApiService } from "@bitwarden/common/services/account/account-api.service";
import { AccountService } from "@bitwarden/common/services/account/account.service";
+import { AnonymousHubService } from "@bitwarden/common/services/anonymousHub.service";
import { ApiService } from "@bitwarden/common/services/api.service";
import { AppIdService } from "@bitwarden/common/services/appId.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
@@ -544,6 +546,11 @@ import { ValidationService } from "./validation.service";
useClass: ConfigApiService,
deps: [ApiServiceAbstraction],
},
+ {
+ provide: AnonymousHubServiceAbstraction,
+ useClass: AnonymousHubService,
+ deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService],
+ },
],
})
export class JslibServicesModule {}
diff --git a/libs/common/spec/misc/utils.spec.ts b/libs/common/spec/misc/utils.spec.ts
index d4dbac958a8..3978f9cfed8 100644
--- a/libs/common/spec/misc/utils.spec.ts
+++ b/libs/common/spec/misc/utils.spec.ts
@@ -70,4 +70,10 @@ describe("Utils Service", () => {
expect(Utils.newGuid()).toMatch(validGuid);
});
});
+
+ describe("fromByteStringToArray", () => {
+ it("should handle null", () => {
+ expect(Utils.fromByteStringToArray(null)).toEqual(null);
+ });
+ });
});
diff --git a/libs/common/spec/services/state.service.spec.ts b/libs/common/spec/services/state.service.spec.ts
deleted file mode 100644
index a6f76ab4fc0..00000000000
--- a/libs/common/spec/services/state.service.spec.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
-
-import { LogService } from "@bitwarden/common/abstractions/log.service";
-import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
-import { StateFactory } from "@bitwarden/common/factories/stateFactory";
-import { Account } from "@bitwarden/common/models/domain/account";
-import { GlobalState } from "@bitwarden/common/models/domain/globalState";
-import { State } from "@bitwarden/common/models/domain/state";
-import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
-import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
-import { StateService } from "@bitwarden/common/services/state.service";
-import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
-
-describe("Browser State Service backed by chrome.storage api", () => {
- let secureStorageService: SubstituteOf;
- let diskStorageService: SubstituteOf;
- let memoryStorageService: SubstituteOf;
- let logService: SubstituteOf;
- let stateMigrationService: SubstituteOf;
- let stateFactory: SubstituteOf>;
- let useAccountCache: boolean;
-
- let state: State;
- const userId = "userId";
-
- let sut: StateService;
-
- beforeEach(() => {
- secureStorageService = Substitute.for();
- diskStorageService = Substitute.for();
- memoryStorageService = Substitute.for();
- logService = Substitute.for();
- stateMigrationService = Substitute.for();
- stateFactory = Substitute.for();
- useAccountCache = true;
-
- state = new State(new GlobalState());
- const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state)));
- memoryStorageService.get("state").mimicks(stateGetter);
- memoryStorageService
- .save("state", Arg.any(), Arg.any())
- .mimicks((key: string, obj: any, options: StorageOptions) => {
- return new Promise(() => {
- state = obj;
- });
- });
-
- sut = new StateService(
- diskStorageService,
- secureStorageService,
- memoryStorageService,
- logService,
- stateMigrationService,
- stateFactory,
- useAccountCache
- );
- });
-
- describe("account state getters", () => {
- beforeEach(() => {
- state.accounts[userId] = createAccount(userId);
- state.activeUserId = userId;
- });
-
- describe("getCryptoMasterKey", () => {
- it("should return the stored SymmetricCryptoKey", async () => {
- const key = new SymmetricCryptoKey(new Uint8Array(32).buffer);
- state.accounts[userId].keys.cryptoMasterKey = key;
-
- const actual = await sut.getCryptoMasterKey();
- expect(actual).toBeInstanceOf(SymmetricCryptoKey);
- expect(actual).toMatchObject(key);
- });
- });
- });
-
- function createAccount(userId: string): Account {
- return new Account({
- profile: { userId: userId },
- });
- }
-});
diff --git a/libs/common/spec/services/stateMigration.service.spec.ts b/libs/common/spec/services/stateMigration.service.spec.ts
index e306e64e29b..b0188abacce 100644
--- a/libs/common/spec/services/stateMigration.service.spec.ts
+++ b/libs/common/spec/services/stateMigration.service.spec.ts
@@ -116,8 +116,8 @@ describe("State Migration Service", () => {
key: "orgThreeEncKey",
},
},
- },
- },
+ } as any,
+ } as any,
});
const migratedAccount = await (stateMigrationService as any).migrateAccountFrom4To5(
diff --git a/libs/common/src/abstractions/anonymousHub.service.ts b/libs/common/src/abstractions/anonymousHub.service.ts
new file mode 100644
index 00000000000..43bdabd512c
--- /dev/null
+++ b/libs/common/src/abstractions/anonymousHub.service.ts
@@ -0,0 +1,4 @@
+export abstract class AnonymousHubService {
+ createHubConnection: (token: string) => void;
+ stopHubConnection: () => void;
+}
diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts
index f08e5c34af8..06d3f5b4eba 100644
--- a/libs/common/src/abstractions/api.service.ts
+++ b/libs/common/src/abstractions/api.service.ts
@@ -46,6 +46,7 @@ import { OrganizationUserUpdateGroupsRequest } from "../models/request/organizat
import { OrganizationUserUpdateRequest } from "../models/request/organizationUserUpdateRequest";
import { PasswordHintRequest } from "../models/request/passwordHintRequest";
import { PasswordRequest } from "../models/request/passwordRequest";
+import { PasswordlessCreateAuthRequest } from "../models/request/passwordlessCreateAuthRequest";
import { PaymentRequest } from "../models/request/paymentRequest";
import { PreloginRequest } from "../models/request/preloginRequest";
import { ProviderAddOrganizationRequest } from "../models/request/provider/providerAddOrganizationRequest";
@@ -84,6 +85,7 @@ import { VerifyEmailRequest } from "../models/request/verifyEmailRequest";
import { ApiKeyResponse } from "../models/response/apiKeyResponse";
import { AttachmentResponse } from "../models/response/attachmentResponse";
import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse";
+import { AuthRequestResponse } from "../models/response/authRequestResponse";
import { RegisterResponse } from "../models/response/authentication/registerResponse";
import { BillingHistoryResponse } from "../models/response/billingHistoryResponse";
import { BillingPaymentResponse } from "../models/response/billingPaymentResponse";
@@ -210,6 +212,9 @@ export abstract class ApiService {
postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise;
putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise;
postConvertToKeyConnector: () => Promise;
+ //passwordless
+ postAuthRequest: (request: PasswordlessCreateAuthRequest) => Promise;
+ getAuthResponse: (id: string, accessCode: string) => Promise;
getUserBillingHistory: () => Promise;
getUserBillingPayment: () => Promise;
diff --git a/libs/common/src/abstractions/auth.service.ts b/libs/common/src/abstractions/auth.service.ts
index 4947f21708b..bbe1c01bf29 100644
--- a/libs/common/src/abstractions/auth.service.ts
+++ b/libs/common/src/abstractions/auth.service.ts
@@ -1,18 +1,26 @@
+import { Observable } from "rxjs";
+
import { AuthenticationStatus } from "../enums/authenticationStatus";
import { AuthResult } from "../models/domain/authResult";
import {
ApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
+ PasswordlessLogInCredentials,
} from "../models/domain/logInCredentials";
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { TokenRequestTwoFactor } from "../models/request/identityToken/tokenRequestTwoFactor";
+import { AuthRequestPushNotification } from "../models/response/notificationResponse";
export abstract class AuthService {
masterPasswordHash: string;
email: string;
logIn: (
- credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
+ credentials:
+ | ApiLogInCredentials
+ | PasswordLogInCredentials
+ | SsoLogInCredentials
+ | PasswordlessLogInCredentials
) => Promise;
logInTwoFactor: (
twoFactor: TokenRequestTwoFactor,
@@ -24,4 +32,7 @@ export abstract class AuthService {
authingWithSso: () => boolean;
authingWithPassword: () => boolean;
getAuthStatus: (userId?: string) => Promise;
+ authResponsePushNotifiction: (notification: AuthRequestPushNotification) => Promise;
+
+ getPushNotifcationObs$: () => Observable;
}
diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts
index 238dfe199dd..03ccfe0abf4 100644
--- a/libs/common/src/abstractions/state.service.ts
+++ b/libs/common/src/abstractions/state.service.ts
@@ -78,8 +78,6 @@ export abstract class StateService {
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise;
hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise;
setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise;
- getDecodedToken: (options?: StorageOptions) => Promise;
- setDecodedToken: (value: any, options?: StorageOptions) => Promise;
getDecryptedCiphers: (options?: StorageOptions) => Promise;
setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise;
getDecryptedCollections: (options?: StorageOptions) => Promise;
@@ -141,6 +139,8 @@ export abstract class StateService {
setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise;
getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise;
setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise;
+ getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise;
+ setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise;
getEmail: (options?: StorageOptions) => Promise;
setEmail: (value: string, options?: StorageOptions) => Promise;
getEmailVerified: (options?: StorageOptions) => Promise;
@@ -160,6 +160,11 @@ export abstract class StateService {
) => Promise;
getEnableCloseToTray: (options?: StorageOptions) => Promise;
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise;
+ getEnableDuckDuckGoBrowserIntegration: (options?: StorageOptions) => Promise;
+ setEnableDuckDuckGoBrowserIntegration: (
+ value: boolean,
+ options?: StorageOptions
+ ) => Promise;
getEnableFullWidth: (options?: StorageOptions) => Promise;
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise;
getEnableGravitars: (options?: StorageOptions) => Promise;
diff --git a/libs/common/src/abstractions/storage.service.ts b/libs/common/src/abstractions/storage.service.ts
index 5cff9fdac60..31506f4302b 100644
--- a/libs/common/src/abstractions/storage.service.ts
+++ b/libs/common/src/abstractions/storage.service.ts
@@ -1,4 +1,4 @@
-import { StorageOptions } from "../models/domain/storageOptions";
+import { MemoryStorageOptions, StorageOptions } from "../models/domain/storageOptions";
export abstract class AbstractStorageService {
abstract get(key: string, options?: StorageOptions): Promise;
@@ -8,5 +8,9 @@ export abstract class AbstractStorageService {
}
export abstract class AbstractCachedStorageService extends AbstractStorageService {
- abstract getBypassCache(key: string, options?: StorageOptions): Promise;
+ abstract getBypassCache(key: string, options?: MemoryStorageOptions): Promise;
+}
+
+export interface MemoryStorageServiceInterface {
+ get(key: string, options?: MemoryStorageOptions): Promise;
}
diff --git a/libs/common/src/enums/authRequestType.ts b/libs/common/src/enums/authRequestType.ts
new file mode 100644
index 00000000000..4edfa5b8889
--- /dev/null
+++ b/libs/common/src/enums/authRequestType.ts
@@ -0,0 +1,4 @@
+export enum AuthRequestType {
+ AuthenticateAndUnlock = 0,
+ Unlock = 1,
+}
diff --git a/libs/common/src/enums/authenticationType.ts b/libs/common/src/enums/authenticationType.ts
index ed7375c8085..5133c4f648e 100644
--- a/libs/common/src/enums/authenticationType.ts
+++ b/libs/common/src/enums/authenticationType.ts
@@ -2,4 +2,5 @@ export enum AuthenticationType {
Password = 0,
Sso = 1,
Api = 2,
+ Passwordless = 3,
}
diff --git a/libs/common/src/enums/nativeMessagingVersion.ts b/libs/common/src/enums/nativeMessagingVersion.ts
new file mode 100644
index 00000000000..f7cf411a40a
--- /dev/null
+++ b/libs/common/src/enums/nativeMessagingVersion.ts
@@ -0,0 +1,4 @@
+export enum NativeMessagingVersion {
+ One = 1, // Original implementation
+ Latest = One,
+}
diff --git a/libs/common/src/enums/notificationType.ts b/libs/common/src/enums/notificationType.ts
index 77ebde01fc5..457ad174cad 100644
--- a/libs/common/src/enums/notificationType.ts
+++ b/libs/common/src/enums/notificationType.ts
@@ -17,4 +17,7 @@ export enum NotificationType {
SyncSendCreate = 12,
SyncSendUpdate = 13,
SyncSendDelete = 14,
+
+ AuthRequest = 15,
+ AuthRequestResponse = 16,
}
diff --git a/libs/common/src/misc/logInStrategies/logIn.strategy.ts b/libs/common/src/misc/logInStrategies/logIn.strategy.ts
index 8615700681b..577130156f7 100644
--- a/libs/common/src/misc/logInStrategies/logIn.strategy.ts
+++ b/libs/common/src/misc/logInStrategies/logIn.strategy.ts
@@ -14,6 +14,7 @@ import {
ApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
+ PasswordlessLogInCredentials,
} from "../../models/domain/logInCredentials";
import { DeviceRequest } from "../../models/request/deviceRequest";
import { ApiTokenRequest } from "../../models/request/identityToken/apiTokenRequest";
@@ -42,7 +43,11 @@ export abstract class LogInStrategy {
) {}
abstract logIn(
- credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
+ credentials:
+ | ApiLogInCredentials
+ | PasswordLogInCredentials
+ | SsoLogInCredentials
+ | PasswordlessLogInCredentials
): Promise;
async logInTwoFactor(
diff --git a/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts b/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts
new file mode 100644
index 00000000000..0acc4a49f00
--- /dev/null
+++ b/libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts
@@ -0,0 +1,86 @@
+import { ApiService } from "../../abstractions/api.service";
+import { AppIdService } from "../../abstractions/appId.service";
+import { AuthService } from "../../abstractions/auth.service";
+import { CryptoService } from "../../abstractions/crypto.service";
+import { LogService } from "../../abstractions/log.service";
+import { MessagingService } from "../../abstractions/messaging.service";
+import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
+import { StateService } from "../../abstractions/state.service";
+import { TokenService } from "../../abstractions/token.service";
+import { TwoFactorService } from "../../abstractions/twoFactor.service";
+import { AuthResult } from "../../models/domain/authResult";
+import { PasswordlessLogInCredentials } from "../../models/domain/logInCredentials";
+import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
+import { PasswordTokenRequest } from "../../models/request/identityToken/passwordTokenRequest";
+import { TokenRequestTwoFactor } from "../../models/request/identityToken/tokenRequestTwoFactor";
+
+import { LogInStrategy } from "./logIn.strategy";
+
+export class PasswordlessLogInStrategy extends LogInStrategy {
+ get email() {
+ return this.tokenRequest.email;
+ }
+
+ get masterPasswordHash() {
+ return this.tokenRequest.masterPasswordHash;
+ }
+
+ tokenRequest: PasswordTokenRequest;
+
+ private localHashedPassword: string;
+ private key: SymmetricCryptoKey;
+
+ constructor(
+ cryptoService: CryptoService,
+ apiService: ApiService,
+ tokenService: TokenService,
+ appIdService: AppIdService,
+ platformUtilsService: PlatformUtilsService,
+ messagingService: MessagingService,
+ logService: LogService,
+ stateService: StateService,
+ twoFactorService: TwoFactorService,
+ private authService: AuthService
+ ) {
+ super(
+ cryptoService,
+ apiService,
+ tokenService,
+ appIdService,
+ platformUtilsService,
+ messagingService,
+ logService,
+ stateService,
+ twoFactorService
+ );
+ }
+
+ async onSuccessfulLogin() {
+ await this.cryptoService.setKey(this.key);
+ await this.cryptoService.setKeyHash(this.localHashedPassword);
+ }
+
+ async logInTwoFactor(
+ twoFactor: TokenRequestTwoFactor,
+ captchaResponse: string
+ ): Promise {
+ this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
+ return super.logInTwoFactor(twoFactor);
+ }
+
+ async logIn(credentials: PasswordlessLogInCredentials) {
+ this.localHashedPassword = credentials.localPasswordHash;
+ this.key = credentials.decKey;
+
+ this.tokenRequest = new PasswordTokenRequest(
+ credentials.email,
+ credentials.accessCode,
+ null,
+ await this.buildTwoFactor(credentials.twoFactor),
+ await this.buildDeviceRequest()
+ );
+
+ this.tokenRequest.setPasswordlessAccessCode(credentials.authRequestId);
+ return this.startLogIn();
+ }
+}
diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts
index f66124bc47e..454def36551 100644
--- a/libs/common/src/misc/utils.ts
+++ b/libs/common/src/misc/utils.ts
@@ -99,6 +99,9 @@ export class Utils {
}
static fromByteStringToArray(str: string): Uint8Array {
+ if (str == null) {
+ return null;
+ }
const arr = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
arr[i] = str.charCodeAt(i);
diff --git a/libs/common/src/models/data/server-config.data.spec.ts b/libs/common/src/models/data/server-config.data.spec.ts
new file mode 100644
index 00000000000..1c4e890ab80
--- /dev/null
+++ b/libs/common/src/models/data/server-config.data.spec.ts
@@ -0,0 +1,55 @@
+import {
+ EnvironmentServerConfigData,
+ ServerConfigData,
+ ThirdPartyServerConfigData,
+} from "./server-config.data";
+
+describe("ServerConfigData", () => {
+ describe("fromJSON", () => {
+ it("should create a ServerConfigData from a JSON object", () => {
+ const serverConfigData = ServerConfigData.fromJSON({
+ version: "1.0.0",
+ gitHash: "1234567890",
+ server: {
+ name: "test",
+ url: "https://test.com",
+ },
+ environment: {
+ vault: "https://vault.com",
+ api: "https://api.com",
+ identity: "https://identity.com",
+ notifications: "https://notifications.com",
+ sso: "https://sso.com",
+ },
+ utcDate: "2020-01-01T00:00:00.000Z",
+ });
+
+ expect(serverConfigData.version).toEqual("1.0.0");
+ expect(serverConfigData.gitHash).toEqual("1234567890");
+ expect(serverConfigData.server.name).toEqual("test");
+ expect(serverConfigData.server.url).toEqual("https://test.com");
+ expect(serverConfigData.environment.vault).toEqual("https://vault.com");
+ expect(serverConfigData.environment.api).toEqual("https://api.com");
+ expect(serverConfigData.environment.identity).toEqual("https://identity.com");
+ expect(serverConfigData.environment.notifications).toEqual("https://notifications.com");
+ expect(serverConfigData.environment.sso).toEqual("https://sso.com");
+ expect(serverConfigData.utcDate).toEqual("2020-01-01T00:00:00.000Z");
+ });
+
+ it("should be an instance of ServerConfigData", () => {
+ const serverConfigData = ServerConfigData.fromJSON({} as any);
+
+ expect(serverConfigData).toBeInstanceOf(ServerConfigData);
+ });
+
+ it("should deserialize sub objects", () => {
+ const serverConfigData = ServerConfigData.fromJSON({
+ server: {},
+ environment: {},
+ } as any);
+
+ expect(serverConfigData.server).toBeInstanceOf(ThirdPartyServerConfigData);
+ expect(serverConfigData.environment).toBeInstanceOf(EnvironmentServerConfigData);
+ });
+ });
+});
diff --git a/libs/common/src/models/data/server-config.data.ts b/libs/common/src/models/data/server-config.data.ts
index 62744ecb621..30043a0b028 100644
--- a/libs/common/src/models/data/server-config.data.ts
+++ b/libs/common/src/models/data/server-config.data.ts
@@ -1,3 +1,5 @@
+import { Jsonify } from "type-fest";
+
import {
ServerConfigResponse,
ThirdPartyServerConfigResponse,
@@ -11,27 +13,38 @@ export class ServerConfigData {
environment?: EnvironmentServerConfigData;
utcDate: string;
- constructor(serverConfigReponse: ServerConfigResponse) {
- this.version = serverConfigReponse?.version;
- this.gitHash = serverConfigReponse?.gitHash;
- this.server = serverConfigReponse?.server
- ? new ThirdPartyServerConfigData(serverConfigReponse.server)
+ constructor(serverConfigResponse: Partial) {
+ this.version = serverConfigResponse?.version;
+ this.gitHash = serverConfigResponse?.gitHash;
+ this.server = serverConfigResponse?.server
+ ? new ThirdPartyServerConfigData(serverConfigResponse.server)
: null;
this.utcDate = new Date().toISOString();
- this.environment = serverConfigReponse?.environment
- ? new EnvironmentServerConfigData(serverConfigReponse.environment)
+ this.environment = serverConfigResponse?.environment
+ ? new EnvironmentServerConfigData(serverConfigResponse.environment)
: null;
}
+
+ static fromJSON(obj: Jsonify): ServerConfigData {
+ return Object.assign(new ServerConfigData({}), obj, {
+ server: obj?.server ? ThirdPartyServerConfigData.fromJSON(obj.server) : null,
+ environment: obj?.environment ? EnvironmentServerConfigData.fromJSON(obj.environment) : null,
+ });
+ }
}
export class ThirdPartyServerConfigData {
name: string;
url: string;
- constructor(response: ThirdPartyServerConfigResponse) {
+ constructor(response: Partial) {
this.name = response.name;
this.url = response.url;
}
+
+ static fromJSON(obj: Jsonify): ThirdPartyServerConfigData {
+ return Object.assign(new ThirdPartyServerConfigData({}), obj);
+ }
}
export class EnvironmentServerConfigData {
@@ -41,11 +54,15 @@ export class EnvironmentServerConfigData {
notifications: string;
sso: string;
- constructor(response: EnvironmentServerConfigResponse) {
+ constructor(response: Partial) {
this.vault = response.vault;
this.api = response.api;
this.identity = response.identity;
this.notifications = response.notifications;
this.sso = response.sso;
}
+
+ static fromJSON(obj: Jsonify): EnvironmentServerConfigData {
+ return Object.assign(new EnvironmentServerConfigData({}), obj);
+ }
}
diff --git a/libs/common/src/models/domain/account-keys.spec.ts b/libs/common/src/models/domain/account-keys.spec.ts
new file mode 100644
index 00000000000..bf8348e15a2
--- /dev/null
+++ b/libs/common/src/models/domain/account-keys.spec.ts
@@ -0,0 +1,62 @@
+import { Utils } from "@bitwarden/common/misc/utils";
+
+import { makeStaticByteArray } from "../../../spec/utils";
+
+import { AccountKeys, EncryptionPair } from "./account";
+import { SymmetricCryptoKey } from "./symmetricCryptoKey";
+
+describe("AccountKeys", () => {
+ describe("toJSON", () => {
+ it("should serialize itself", () => {
+ const keys = new AccountKeys();
+ const buffer = makeStaticByteArray(64).buffer;
+ keys.publicKey = buffer;
+
+ const bufferSpy = jest.spyOn(Utils, "fromBufferToByteString");
+ keys.toJSON();
+ expect(bufferSpy).toHaveBeenCalledWith(buffer);
+ });
+
+ it("should serialize public key as a string", () => {
+ const keys = new AccountKeys();
+ keys.publicKey = Utils.fromByteStringToArray("hello").buffer;
+ const json = JSON.stringify(keys);
+ expect(json).toContain('"publicKey":"hello"');
+ });
+ });
+
+ describe("fromJSON", () => {
+ it("should deserialize public key to a buffer", () => {
+ const keys = AccountKeys.fromJSON({
+ publicKey: "hello",
+ });
+ expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello").buffer);
+ });
+
+ it("should deserialize cryptoMasterKey", () => {
+ const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON");
+ AccountKeys.fromJSON({} as any);
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it("should deserialize organizationKeys", () => {
+ const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON");
+ AccountKeys.fromJSON({ organizationKeys: [{ orgId: "keyJSON" }] } as any);
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it("should deserialize providerKeys", () => {
+ const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON");
+ AccountKeys.fromJSON({ providerKeys: [{ providerId: "keyJSON" }] } as any);
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it("should deserialize privateKey", () => {
+ const spy = jest.spyOn(EncryptionPair, "fromJSON");
+ AccountKeys.fromJSON({
+ privateKey: { encrypted: "encrypted", decrypted: "decrypted" },
+ } as any);
+ expect(spy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/account-profile.spec.ts b/libs/common/src/models/domain/account-profile.spec.ts
new file mode 100644
index 00000000000..7c6deda34eb
--- /dev/null
+++ b/libs/common/src/models/domain/account-profile.spec.ts
@@ -0,0 +1,9 @@
+import { AccountProfile } from "./account";
+
+describe("AccountProfile", () => {
+ describe("fromJSON", () => {
+ it("should deserialize to an instance of itself", () => {
+ expect(AccountProfile.fromJSON({})).toBeInstanceOf(AccountProfile);
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/account-settings.spec.ts b/libs/common/src/models/domain/account-settings.spec.ts
new file mode 100644
index 00000000000..544d591b323
--- /dev/null
+++ b/libs/common/src/models/domain/account-settings.spec.ts
@@ -0,0 +1,24 @@
+import { AccountSettings, EncryptionPair } from "./account";
+import { EncString } from "./encString";
+
+describe("AccountSettings", () => {
+ describe("fromJSON", () => {
+ it("should deserialize to an instance of itself", () => {
+ expect(AccountSettings.fromJSON(JSON.parse("{}"))).toBeInstanceOf(AccountSettings);
+ });
+
+ it("should deserialize pinProtected", () => {
+ const accountSettings = new AccountSettings();
+ accountSettings.pinProtected = EncryptionPair.fromJSON({
+ encrypted: "encrypted",
+ decrypted: "3.data",
+ });
+ const jsonObj = JSON.parse(JSON.stringify(accountSettings));
+ const actual = AccountSettings.fromJSON(jsonObj);
+
+ expect(actual.pinProtected).toBeInstanceOf(EncryptionPair);
+ expect(actual.pinProtected.encrypted).toEqual("encrypted");
+ expect(actual.pinProtected.decrypted.encryptedString).toEqual("3.data");
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/account-tokens.spec.ts b/libs/common/src/models/domain/account-tokens.spec.ts
new file mode 100644
index 00000000000..733b3908e9a
--- /dev/null
+++ b/libs/common/src/models/domain/account-tokens.spec.ts
@@ -0,0 +1,9 @@
+import { AccountTokens } from "./account";
+
+describe("AccountTokens", () => {
+ describe("fromJSON", () => {
+ it("should deserialize to an instance of itself", () => {
+ expect(AccountTokens.fromJSON({})).toBeInstanceOf(AccountTokens);
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/account.spec.ts b/libs/common/src/models/domain/account.spec.ts
new file mode 100644
index 00000000000..0c76c16cc2d
--- /dev/null
+++ b/libs/common/src/models/domain/account.spec.ts
@@ -0,0 +1,23 @@
+import { Account, AccountKeys, AccountProfile, AccountSettings, AccountTokens } from "./account";
+
+describe("Account", () => {
+ describe("fromJSON", () => {
+ it("should deserialize to an instance of itself", () => {
+ expect(Account.fromJSON({})).toBeInstanceOf(Account);
+ });
+
+ it("should call all the sub-fromJSONs", () => {
+ const keysSpy = jest.spyOn(AccountKeys, "fromJSON");
+ const profileSpy = jest.spyOn(AccountProfile, "fromJSON");
+ const settingsSpy = jest.spyOn(AccountSettings, "fromJSON");
+ const tokensSpy = jest.spyOn(AccountTokens, "fromJSON");
+
+ Account.fromJSON({});
+
+ expect(keysSpy).toHaveBeenCalled();
+ expect(profileSpy).toHaveBeenCalled();
+ expect(settingsSpy).toHaveBeenCalled();
+ expect(tokensSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts
index b7bbcb431c9..5822af35c03 100644
--- a/libs/common/src/models/domain/account.ts
+++ b/libs/common/src/models/domain/account.ts
@@ -1,3 +1,8 @@
+import { Except, Jsonify } from "type-fest";
+
+import { Utils } from "@bitwarden/common/misc/utils";
+import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify";
+
import { AuthenticationStatus } from "../../enums/authenticationStatus";
import { KdfType } from "../../enums/kdfType";
import { UriMatchType } from "../../enums/uriMatchType";
@@ -24,7 +29,39 @@ import { SymmetricCryptoKey } from "./symmetricCryptoKey";
export class EncryptionPair {
encrypted?: TEncrypted;
decrypted?: TDecrypted;
- decryptedSerialized?: string;
+
+ toJSON() {
+ return {
+ encrypted: this.encrypted,
+ decrypted:
+ this.decrypted instanceof ArrayBuffer
+ ? Utils.fromBufferToByteString(this.decrypted)
+ : this.decrypted,
+ };
+ }
+
+ static fromJSON(
+ obj: Jsonify, Jsonify>>,
+ decryptedFromJson?: (decObj: Jsonify | string) => TDecrypted,
+ encryptedFromJson?: (encObj: Jsonify) => TEncrypted
+ ) {
+ if (obj == null) {
+ return null;
+ }
+
+ const pair = new EncryptionPair();
+ if (obj?.encrypted != null) {
+ pair.encrypted = encryptedFromJson
+ ? encryptedFromJson(obj.encrypted)
+ : (obj.encrypted as TEncrypted);
+ }
+ if (obj?.decrypted != null) {
+ pair.decrypted = decryptedFromJson
+ ? decryptedFromJson(obj.decrypted)
+ : (obj.decrypted as TDecrypted);
+ }
+ return pair;
+ }
}
export class DataEncryptionPair {
@@ -73,19 +110,66 @@ export class AccountKeys {
>();
organizationKeys?: EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyData },
- Map
+ Record
> = new EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyData },
- Map
+ Record
>();
- providerKeys?: EncryptionPair> = new EncryptionPair<
+ providerKeys?: EncryptionPair> = new EncryptionPair<
any,
- Map
+ Record
>();
privateKey?: EncryptionPair = new EncryptionPair();
publicKey?: ArrayBuffer;
- publicKeySerialized?: string;
apiKeyClientSecret?: string;
+
+ toJSON() {
+ return Object.assign(this as Except, {
+ publicKey: Utils.fromBufferToByteString(this.publicKey),
+ });
+ }
+
+ static fromJSON(obj: DeepJsonify): AccountKeys {
+ if (obj == null) {
+ return null;
+ }
+
+ return Object.assign(
+ new AccountKeys(),
+ { cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey) },
+ {
+ cryptoSymmetricKey: EncryptionPair.fromJSON(
+ obj?.cryptoSymmetricKey,
+ SymmetricCryptoKey.fromJSON
+ ),
+ },
+ { organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys) },
+ { providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys) },
+ {
+ privateKey: EncryptionPair.fromJSON(
+ obj?.privateKey,
+ (decObj: string) => Utils.fromByteStringToArray(decObj).buffer
+ ),
+ },
+ {
+ publicKey: Utils.fromByteStringToArray(obj?.publicKey)?.buffer,
+ }
+ );
+ }
+
+ static initRecordEncryptionPairsFromJSON(obj: any) {
+ return EncryptionPair.fromJSON(obj, (decObj: any) => {
+ if (obj == null) {
+ return null;
+ }
+
+ const record: Record = {};
+ for (const id in decObj) {
+ record[id] = SymmetricCryptoKey.fromJSON(decObj[id]);
+ }
+ return record;
+ });
+ }
}
export class AccountProfile {
@@ -106,6 +190,14 @@ export class AccountProfile {
keyHash?: string;
kdfIterations?: number;
kdfType?: KdfType;
+
+ static fromJSON(obj: Jsonify): AccountProfile {
+ if (obj == null) {
+ return null;
+ }
+
+ return Object.assign(new AccountProfile(), obj);
+ }
}
export class AccountSettings {
@@ -142,6 +234,21 @@ export class AccountSettings {
vaultTimeout?: number;
vaultTimeoutAction?: string = "lock";
serverConfig?: ServerConfigData;
+
+ static fromJSON(obj: Jsonify): AccountSettings {
+ if (obj == null) {
+ return null;
+ }
+
+ return Object.assign(new AccountSettings(), obj, {
+ environmentUrls: EnvironmentUrls.fromJSON(obj?.environmentUrls),
+ pinProtected: EncryptionPair.fromJSON(
+ obj?.pinProtected,
+ EncString.fromJSON
+ ),
+ serverConfig: ServerConfigData.fromJSON(obj?.serverConfig),
+ });
+ }
}
export type AccountSettingsSettings = {
@@ -150,9 +257,16 @@ export type AccountSettingsSettings = {
export class AccountTokens {
accessToken?: string;
- decodedToken?: any;
refreshToken?: string;
securityStamp?: string;
+
+ static fromJSON(obj: Jsonify): AccountTokens {
+ if (obj == null) {
+ return null;
+ }
+
+ return Object.assign(new AccountTokens(), obj);
+ }
}
export class Account {
@@ -186,4 +300,17 @@ export class Account {
},
});
}
+
+ static fromJSON(json: Jsonify): Account {
+ if (json == null) {
+ return null;
+ }
+
+ return Object.assign(new Account({}), json, {
+ keys: AccountKeys.fromJSON(json?.keys),
+ profile: AccountProfile.fromJSON(json?.profile),
+ settings: AccountSettings.fromJSON(json?.settings),
+ tokens: AccountTokens.fromJSON(json?.tokens),
+ });
+ }
}
diff --git a/libs/common/src/models/domain/encryption-pair.spec.ts b/libs/common/src/models/domain/encryption-pair.spec.ts
new file mode 100644
index 00000000000..55fad76db03
--- /dev/null
+++ b/libs/common/src/models/domain/encryption-pair.spec.ts
@@ -0,0 +1,34 @@
+import { Utils } from "@bitwarden/common/misc/utils";
+
+import { EncryptionPair } from "./account";
+
+describe("EncryptionPair", () => {
+ describe("toJSON", () => {
+ it("should populate decryptedSerialized for buffer arrays", () => {
+ const pair = new EncryptionPair();
+ pair.decrypted = Utils.fromByteStringToArray("hello").buffer;
+ const json = pair.toJSON();
+ expect(json.decrypted).toEqual("hello");
+ });
+
+ it("should serialize encrypted and decrypted", () => {
+ const pair = new EncryptionPair();
+ pair.encrypted = "hello";
+ pair.decrypted = "world";
+ const json = pair.toJSON();
+ expect(json.encrypted).toEqual("hello");
+ expect(json.decrypted).toEqual("world");
+ });
+ });
+
+ describe("fromJSON", () => {
+ it("should deserialize encrypted and decrypted", () => {
+ const pair = EncryptionPair.fromJSON({
+ encrypted: "hello",
+ decrypted: "world",
+ });
+ expect(pair.encrypted).toEqual("hello");
+ expect(pair.decrypted).toEqual("world");
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/environmentUrls.ts b/libs/common/src/models/domain/environmentUrls.ts
index d4fd173c83a..c78768bdc29 100644
--- a/libs/common/src/models/domain/environmentUrls.ts
+++ b/libs/common/src/models/domain/environmentUrls.ts
@@ -1,3 +1,5 @@
+import { Jsonify } from "type-fest";
+
export class EnvironmentUrls {
base: string = null;
api: string = null;
@@ -7,4 +9,8 @@ export class EnvironmentUrls {
events: string = null;
webVault: string = null;
keyConnector: string = null;
+
+ static fromJSON(obj: Jsonify): EnvironmentUrls {
+ return Object.assign(new EnvironmentUrls(), obj);
+ }
}
diff --git a/libs/common/src/models/domain/globalState.ts b/libs/common/src/models/domain/globalState.ts
index 422293fceda..c907d3ecb5c 100644
--- a/libs/common/src/models/domain/globalState.ts
+++ b/libs/common/src/models/domain/globalState.ts
@@ -37,4 +37,5 @@ export class GlobalState {
alwaysShowDock?: boolean;
enableBrowserIntegration?: boolean;
enableBrowserIntegrationFingerprint?: boolean;
+ enableDuckDuckGoBrowserIntegration?: boolean;
}
diff --git a/libs/common/src/models/domain/logInCredentials.ts b/libs/common/src/models/domain/logInCredentials.ts
index c1e23610e47..5f2035fd156 100644
--- a/libs/common/src/models/domain/logInCredentials.ts
+++ b/libs/common/src/models/domain/logInCredentials.ts
@@ -1,3 +1,5 @@
+import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
+
import { AuthenticationType } from "../../enums/authenticationType";
import { TokenRequestTwoFactor } from "../request/identityToken/tokenRequestTwoFactor";
@@ -29,3 +31,16 @@ export class ApiLogInCredentials {
constructor(public clientId: string, public clientSecret: string) {}
}
+
+export class PasswordlessLogInCredentials {
+ readonly type = AuthenticationType.Passwordless;
+
+ constructor(
+ public email: string,
+ public accessCode: string,
+ public authRequestId: string,
+ public decKey: SymmetricCryptoKey,
+ public localPasswordHash: string,
+ public twoFactor?: TokenRequestTwoFactor
+ ) {}
+}
diff --git a/libs/common/src/models/domain/state.spec.ts b/libs/common/src/models/domain/state.spec.ts
new file mode 100644
index 00000000000..64e71d7cb2c
--- /dev/null
+++ b/libs/common/src/models/domain/state.spec.ts
@@ -0,0 +1,28 @@
+import { Account } from "./account";
+import { State } from "./state";
+
+describe("state", () => {
+ describe("fromJSON", () => {
+ it("should deserialize to an instance of itself", () => {
+ expect(State.fromJSON({})).toBeInstanceOf(State);
+ });
+
+ it("should always assign an object to accounts", () => {
+ const state = State.fromJSON({});
+ expect(state.accounts).not.toBeNull();
+ expect(state.accounts).toEqual({});
+ });
+
+ it("should build an account map", () => {
+ const accountsSpy = jest.spyOn(Account, "fromJSON");
+ const state = State.fromJSON({
+ accounts: {
+ userId: {},
+ },
+ });
+
+ expect(state.accounts["userId"]).toBeInstanceOf(Account);
+ expect(accountsSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/libs/common/src/models/domain/state.ts b/libs/common/src/models/domain/state.ts
index f5a2c046b54..5450325d25c 100644
--- a/libs/common/src/models/domain/state.ts
+++ b/libs/common/src/models/domain/state.ts
@@ -1,3 +1,5 @@
+import { Jsonify } from "type-fest";
+
import { Account } from "./account";
import { GlobalState } from "./globalState";
@@ -14,4 +16,30 @@ export class State<
constructor(globals: TGlobalState) {
this.globals = globals;
}
+
+ // TODO, make Jsonify work. It currently doesn't because Globals doesn't implement Jsonify.
+ static fromJSON(
+ obj: any
+ ): State {
+ if (obj == null) {
+ return null;
+ }
+
+ return Object.assign(new State(null), obj, {
+ accounts: State.buildAccountMapFromJSON(obj?.accounts),
+ });
+ }
+
+ private static buildAccountMapFromJSON(
+ jsonAccounts: Jsonify<{ [userId: string]: Jsonify }>
+ ) {
+ if (!jsonAccounts) {
+ return {};
+ }
+ const accounts: { [userId: string]: Account } = {};
+ for (const userId in jsonAccounts) {
+ accounts[userId] = Account.fromJSON(jsonAccounts[userId]);
+ }
+ return accounts;
+ }
}
diff --git a/libs/common/src/models/domain/storageOptions.ts b/libs/common/src/models/domain/storageOptions.ts
index 2db7e0ccf58..7e21b194332 100644
--- a/libs/common/src/models/domain/storageOptions.ts
+++ b/libs/common/src/models/domain/storageOptions.ts
@@ -1,3 +1,5 @@
+import { Jsonify } from "type-fest";
+
import { HtmlStorageLocation } from "../../enums/htmlStorageLocation";
import { StorageLocation } from "../../enums/storageLocation";
@@ -8,3 +10,5 @@ export type StorageOptions = {
htmlStorageLocation?: HtmlStorageLocation;
keySuffix?: string;
};
+
+export type MemoryStorageOptions = StorageOptions & { deserializer?: (obj: Jsonify) => T };
diff --git a/libs/common/src/models/request/identityToken/tokenRequest.ts b/libs/common/src/models/request/identityToken/tokenRequest.ts
index 82a4a394c5d..5e38d2069b0 100644
--- a/libs/common/src/models/request/identityToken/tokenRequest.ts
+++ b/libs/common/src/models/request/identityToken/tokenRequest.ts
@@ -4,6 +4,7 @@ import { TokenRequestTwoFactor } from "./tokenRequestTwoFactor";
export abstract class TokenRequest {
protected device?: DeviceRequest;
+ protected passwordlessAuthRequest: string;
constructor(protected twoFactor: TokenRequestTwoFactor, device?: DeviceRequest) {
this.device = device != null ? device : null;
@@ -18,6 +19,10 @@ export abstract class TokenRequest {
this.twoFactor = twoFactor;
}
+ setPasswordlessAccessCode(accessCode: string) {
+ this.passwordlessAuthRequest = accessCode;
+ }
+
protected toIdentityToken(clientId: string) {
const obj: any = {
scope: "api offline_access",
@@ -32,6 +37,11 @@ export abstract class TokenRequest {
// obj.devicePushToken = this.device.pushToken;
}
+ //passswordless login
+ if (this.passwordlessAuthRequest) {
+ obj.authRequest = this.passwordlessAuthRequest;
+ }
+
if (this.twoFactor.token && this.twoFactor.provider != null) {
obj.twoFactorToken = this.twoFactor.token;
obj.twoFactorProvider = this.twoFactor.provider;
diff --git a/libs/common/src/models/request/passwordlessCreateAuthRequest.ts b/libs/common/src/models/request/passwordlessCreateAuthRequest.ts
new file mode 100644
index 00000000000..df83c547775
--- /dev/null
+++ b/libs/common/src/models/request/passwordlessCreateAuthRequest.ts
@@ -0,0 +1,12 @@
+import { AuthRequestType } from "../../enums/authRequestType";
+
+export class PasswordlessCreateAuthRequest {
+ constructor(
+ readonly email: string,
+ readonly deviceIdentifier: string,
+ readonly publicKey: string,
+ readonly type: AuthRequestType,
+ readonly accessCode: string,
+ readonly fingerprintPhrase: string
+ ) {}
+}
diff --git a/libs/common/src/models/response/authRequestResponse.ts b/libs/common/src/models/response/authRequestResponse.ts
new file mode 100644
index 00000000000..1a29a3da856
--- /dev/null
+++ b/libs/common/src/models/response/authRequestResponse.ts
@@ -0,0 +1,26 @@
+import { DeviceType } from "@bitwarden/common/enums/deviceType";
+
+import { BaseResponse } from "./baseResponse";
+
+export class AuthRequestResponse extends BaseResponse {
+ id: string;
+ publicKey: string;
+ requestDeviceType: DeviceType;
+ requestIpAddress: string;
+ key: string;
+ masterPasswordHash: string;
+ creationDate: string;
+ requestApproved: boolean;
+
+ constructor(response: any) {
+ super(response);
+ this.id = this.getResponseProperty("Id");
+ this.publicKey = this.getResponseProperty("PublicKey");
+ this.requestDeviceType = this.getResponseProperty("RequestDeviceType");
+ this.requestIpAddress = this.getResponseProperty("RequestIpAddress");
+ this.key = this.getResponseProperty("Key");
+ this.masterPasswordHash = this.getResponseProperty("MasterPasswordHash");
+ this.creationDate = this.getResponseProperty("CreationDate");
+ this.requestApproved = this.getResponseProperty("RequestApproved");
+ }
+}
diff --git a/libs/common/src/models/response/notificationResponse.ts b/libs/common/src/models/response/notificationResponse.ts
index f23de8fe8be..1e2a5045063 100644
--- a/libs/common/src/models/response/notificationResponse.ts
+++ b/libs/common/src/models/response/notificationResponse.ts
@@ -37,6 +37,10 @@ export class NotificationResponse extends BaseResponse {
case NotificationType.SyncSendDelete:
this.payload = new SyncSendNotification(payload);
break;
+ case NotificationType.AuthRequest:
+ case NotificationType.AuthRequestResponse:
+ this.payload = new AuthRequestPushNotification(payload);
+ break;
default:
break;
}
@@ -96,3 +100,14 @@ export class SyncSendNotification extends BaseResponse {
this.revisionDate = new Date(this.getResponseProperty("RevisionDate"));
}
}
+
+export class AuthRequestPushNotification extends BaseResponse {
+ id: string;
+ userId: string;
+
+ constructor(response: any) {
+ super(response);
+ this.id = this.getResponseProperty("Id");
+ this.userId = this.getResponseProperty("UserId");
+ }
+}
diff --git a/libs/common/src/services/anonymousHub.service.ts b/libs/common/src/services/anonymousHub.service.ts
new file mode 100644
index 00000000000..13b5898b18b
--- /dev/null
+++ b/libs/common/src/services/anonymousHub.service.ts
@@ -0,0 +1,60 @@
+import { Injectable } from "@angular/core";
+import {
+ HttpTransportType,
+ HubConnection,
+ HubConnectionBuilder,
+ IHubProtocol,
+} from "@microsoft/signalr";
+import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack";
+
+import { AnonymousHubService as AnonymousHubServiceAbstraction } from "../abstractions/anonymousHub.service";
+import { AuthService } from "../abstractions/auth.service";
+import { EnvironmentService } from "../abstractions/environment.service";
+import { LogService } from "../abstractions/log.service";
+
+import {
+ AuthRequestPushNotification,
+ NotificationResponse,
+} from "./../models/response/notificationResponse";
+
+@Injectable()
+export class AnonymousHubService implements AnonymousHubServiceAbstraction {
+ private anonHubConnection: HubConnection;
+ private url: string;
+
+ constructor(
+ private environmentService: EnvironmentService,
+ private authService: AuthService,
+ private logService: LogService
+ ) {}
+
+ async createHubConnection(token: string) {
+ this.url = this.environmentService.getNotificationsUrl();
+
+ this.anonHubConnection = new HubConnectionBuilder()
+ .withUrl(this.url + "/anonymousHub?Token=" + token, {
+ skipNegotiation: true,
+ transport: HttpTransportType.WebSockets,
+ })
+ .withHubProtocol(new MessagePackHubProtocol() as IHubProtocol)
+ .build();
+
+ this.anonHubConnection.start().catch((error) => this.logService.error(error));
+
+ this.anonHubConnection.on("AuthRequestResponseRecieved", (data: any) => {
+ this.ProcessNotification(new NotificationResponse(data));
+ });
+ }
+
+ stopHubConnection() {
+ if (this.anonHubConnection) {
+ this.anonHubConnection.stop();
+ }
+ }
+
+ private async ProcessNotification(notification: NotificationResponse) {
+ await this.authService.authResponsePushNotifiction(
+ notification.payload as AuthRequestPushNotification
+ );
+ }
+}
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index dbc67f57272..a4648e25045 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -54,6 +54,7 @@ import { OrganizationUserUpdateGroupsRequest } from "../models/request/organizat
import { OrganizationUserUpdateRequest } from "../models/request/organizationUserUpdateRequest";
import { PasswordHintRequest } from "../models/request/passwordHintRequest";
import { PasswordRequest } from "../models/request/passwordRequest";
+import { PasswordlessCreateAuthRequest } from "../models/request/passwordlessCreateAuthRequest";
import { PaymentRequest } from "../models/request/paymentRequest";
import { PreloginRequest } from "../models/request/preloginRequest";
import { ProviderAddOrganizationRequest } from "../models/request/provider/providerAddOrganizationRequest";
@@ -92,6 +93,7 @@ import { VerifyEmailRequest } from "../models/request/verifyEmailRequest";
import { ApiKeyResponse } from "../models/response/apiKeyResponse";
import { AttachmentResponse } from "../models/response/attachmentResponse";
import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse";
+import { AuthRequestResponse } from "../models/response/authRequestResponse";
import { RegisterResponse } from "../models/response/authentication/registerResponse";
import { BillingHistoryResponse } from "../models/response/billingHistoryResponse";
import { BillingPaymentResponse } from "../models/response/billingPaymentResponse";
@@ -265,6 +267,17 @@ export class ApiService implements ApiServiceAbstraction {
}
}
+ async postAuthRequest(request: PasswordlessCreateAuthRequest): Promise {
+ const r = await this.send("POST", "/auth-requests/", request, false, true);
+ return new AuthRequestResponse(r);
+ }
+
+ async getAuthResponse(id: string, accessCode: string): Promise {
+ const path = `/auth-requests/${id}/response?code=${accessCode}`;
+ const r = await this.send("GET", path, null, false, true);
+ return new AuthRequestResponse(r);
+ }
+
// Account APIs
async getProfile(): Promise {
@@ -2323,7 +2336,9 @@ export class ApiService implements ApiServiceAbstraction {
requestInit.headers = headers;
const response = await this.fetch(new Request(requestUrl, requestInit));
- if (hasResponse && response.status === 200) {
+ const responseType = response.headers.get("content-type");
+ const responseIsJson = responseType != null && responseType.indexOf("application/json") !== -1;
+ if (hasResponse && response.status === 200 && responseIsJson) {
const responseJson = await response.json();
return responseJson;
} else if (response.status !== 200) {
diff --git a/libs/common/src/services/auth.service.ts b/libs/common/src/services/auth.service.ts
index 6f77bca20b3..3807eee3d69 100644
--- a/libs/common/src/services/auth.service.ts
+++ b/libs/common/src/services/auth.service.ts
@@ -1,3 +1,5 @@
+import { Observable, Subject } from "rxjs";
+
import { ApiService } from "../abstractions/api.service";
import { AppIdService } from "../abstractions/appId.service";
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
@@ -17,17 +19,20 @@ import { KdfType } from "../enums/kdfType";
import { KeySuffixOptions } from "../enums/keySuffixOptions";
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
+import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy";
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
import { AuthResult } from "../models/domain/authResult";
import {
ApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
+ PasswordlessLogInCredentials,
} from "../models/domain/logInCredentials";
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { TokenRequestTwoFactor } from "../models/request/identityToken/tokenRequestTwoFactor";
import { PreloginRequest } from "../models/request/preloginRequest";
import { ErrorResponse } from "../models/response/errorResponse";
+import { AuthRequestPushNotification } from "../models/response/notificationResponse";
const sessionTimeoutLength = 2 * 60 * 1000; // 2 minutes
@@ -42,9 +47,15 @@ export class AuthService implements AuthServiceAbstraction {
: null;
}
- private logInStrategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy;
+ private logInStrategy:
+ | ApiLogInStrategy
+ | PasswordLogInStrategy
+ | SsoLogInStrategy
+ | PasswordlessLogInStrategy;
private sessionTimeout: any;
+ private pushNotificationSubject = new Subject();
+
constructor(
protected cryptoService: CryptoService,
protected apiService: ApiService,
@@ -61,52 +72,78 @@ export class AuthService implements AuthServiceAbstraction {
) {}
async logIn(
- credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
+ credentials:
+ | ApiLogInCredentials
+ | PasswordLogInCredentials
+ | SsoLogInCredentials
+ | PasswordlessLogInCredentials
): Promise {
this.clearState();
- let strategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy;
+ let strategy:
+ | ApiLogInStrategy
+ | PasswordLogInStrategy
+ | SsoLogInStrategy
+ | PasswordlessLogInStrategy;
- if (credentials.type === AuthenticationType.Password) {
- strategy = new PasswordLogInStrategy(
- this.cryptoService,
- this.apiService,
- this.tokenService,
- this.appIdService,
- this.platformUtilsService,
- this.messagingService,
- this.logService,
- this.stateService,
- this.twoFactorService,
- this
- );
- } else if (credentials.type === AuthenticationType.Sso) {
- strategy = new SsoLogInStrategy(
- this.cryptoService,
- this.apiService,
- this.tokenService,
- this.appIdService,
- this.platformUtilsService,
- this.messagingService,
- this.logService,
- this.stateService,
- this.twoFactorService,
- this.keyConnectorService
- );
- } else if (credentials.type === AuthenticationType.Api) {
- strategy = new ApiLogInStrategy(
- this.cryptoService,
- this.apiService,
- this.tokenService,
- this.appIdService,
- this.platformUtilsService,
- this.messagingService,
- this.logService,
- this.stateService,
- this.twoFactorService,
- this.environmentService,
- this.keyConnectorService
- );
+ switch (credentials.type) {
+ case AuthenticationType.Password:
+ strategy = new PasswordLogInStrategy(
+ this.cryptoService,
+ this.apiService,
+ this.tokenService,
+ this.appIdService,
+ this.platformUtilsService,
+ this.messagingService,
+ this.logService,
+ this.stateService,
+ this.twoFactorService,
+ this
+ );
+ break;
+ case AuthenticationType.Sso:
+ strategy = new SsoLogInStrategy(
+ this.cryptoService,
+ this.apiService,
+ this.tokenService,
+ this.appIdService,
+ this.platformUtilsService,
+ this.messagingService,
+ this.logService,
+ this.stateService,
+ this.twoFactorService,
+ this.keyConnectorService
+ );
+ break;
+ case AuthenticationType.Api:
+ strategy = new ApiLogInStrategy(
+ this.cryptoService,
+ this.apiService,
+ this.tokenService,
+ this.appIdService,
+ this.platformUtilsService,
+ this.messagingService,
+ this.logService,
+ this.stateService,
+ this.twoFactorService,
+ this.environmentService,
+ this.keyConnectorService
+ );
+ break;
+ case AuthenticationType.Passwordless:
+ strategy = new PasswordlessLogInStrategy(
+ this.cryptoService,
+ this.apiService,
+ this.tokenService,
+ this.appIdService,
+ this.platformUtilsService,
+ this.messagingService,
+ this.logService,
+ this.stateService,
+ this.twoFactorService,
+ this
+ );
+ break;
}
const result = await strategy.logIn(credentials as any);
@@ -202,7 +239,21 @@ export class AuthService implements AuthServiceAbstraction {
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
}
- private saveState(strategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy) {
+ async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise {
+ this.pushNotificationSubject.next(notification.id);
+ }
+
+ getPushNotifcationObs$(): Observable {
+ return this.pushNotificationSubject.asObservable();
+ }
+
+ private saveState(
+ strategy:
+ | ApiLogInStrategy
+ | PasswordLogInStrategy
+ | SsoLogInStrategy
+ | PasswordlessLogInStrategy
+ ) {
this.logInStrategy = strategy;
this.startSessionTimeout();
}
diff --git a/libs/common/src/services/cipher.service.ts b/libs/common/src/services/cipher.service.ts
index 40440ea8bf7..62d849d4176 100644
--- a/libs/common/src/services/cipher.service.ts
+++ b/libs/common/src/services/cipher.service.ts
@@ -393,7 +393,7 @@ export class CipherService implements CipherServiceAbstraction {
: firstValueFrom(this.settingsService.settings$).then(
(settings: AccountSettingsSettings) => {
let matches: any[] = [];
- settings.equivalentDomains.forEach((eqDomain: any) => {
+ settings.equivalentDomains?.forEach((eqDomain: any) => {
if (eqDomain.length && eqDomain.indexOf(domain) >= 0) {
matches = matches.concat(eqDomain);
}
diff --git a/libs/common/src/services/encrypt.service.ts b/libs/common/src/services/encrypt.service.ts
index 0b3f9731ca7..fef33a3f9c3 100644
--- a/libs/common/src/services/encrypt.service.ts
+++ b/libs/common/src/services/encrypt.service.ts
@@ -98,7 +98,7 @@ export class EncryptService implements AbstractEncryptService {
}
}
- return this.cryptoFunctionService.aesDecryptFast(fastParams);
+ return await this.cryptoFunctionService.aesDecryptFast(fastParams);
}
async decryptToBytes(encThing: IEncrypted, key: SymmetricCryptoKey): Promise {
diff --git a/libs/common/src/services/memoryStorage.service.ts b/libs/common/src/services/memoryStorage.service.ts
index d1616c6029f..1634b67b051 100644
--- a/libs/common/src/services/memoryStorage.service.ts
+++ b/libs/common/src/services/memoryStorage.service.ts
@@ -1,6 +1,12 @@
-import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
+import {
+ AbstractStorageService,
+ MemoryStorageServiceInterface,
+} from "@bitwarden/common/abstractions/storage.service";
-export class MemoryStorageService implements AbstractStorageService {
+export class MemoryStorageService
+ extends AbstractStorageService
+ implements MemoryStorageServiceInterface
+{
private store = new Map();
get(key: string): Promise {
diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts
index d5aabb3490a..1f52a27ad67 100644
--- a/libs/common/src/services/state.service.ts
+++ b/libs/common/src/services/state.service.ts
@@ -3,14 +3,16 @@ import { BehaviorSubject, concatMap } from "rxjs";
import { LogService } from "../abstractions/log.service";
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
import { StateMigrationService } from "../abstractions/stateMigration.service";
-import { AbstractStorageService } from "../abstractions/storage.service";
+import {
+ MemoryStorageServiceInterface,
+ AbstractStorageService,
+} from "../abstractions/storage.service";
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
import { KdfType } from "../enums/kdfType";
import { StorageLocation } from "../enums/storageLocation";
import { ThemeType } from "../enums/themeType";
import { UriMatchType } from "../enums/uriMatchType";
import { StateFactory } from "../factories/stateFactory";
-import { Utils } from "../misc/utils";
import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData";
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
@@ -56,6 +58,8 @@ const partialKeys = {
masterKey: "_masterkey",
};
+const DDG_SHARED_KEY = "DuckDuckGoSharedKey";
+
export class StateService<
TGlobalState extends GlobalState = GlobalState,
TAccount extends Account = Account
@@ -76,7 +80,7 @@ export class StateService<
constructor(
protected storageService: AbstractStorageService,
protected secureStorageService: AbstractStorageService,
- protected memoryStorageService: AbstractStorageService,
+ protected memoryStorageService: AbstractStorageService & MemoryStorageServiceInterface,
protected logService: LogService,
protected stateMigrationService: StateMigrationService,
protected stateFactory: StateFactory,
@@ -150,6 +154,9 @@ export class StateService<
return;
}
await this.updateState(async (state) => {
+ if (state.accounts == null) {
+ state.accounts = {};
+ }
state.accounts[userId] = this.createAccount();
const diskAccount = await this.getAccountFromDisk({ userId: userId });
state.accounts[userId].profile = diskAccount.profile;
@@ -494,11 +501,11 @@ export class StateService<
);
}
- @withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getCryptoMasterKey(options?: StorageOptions): Promise {
- return (
- await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
- )?.keys?.cryptoMasterKey;
+ const account = await this.getAccount(
+ this.reconcileOptions(options, await this.defaultInMemoryOptions())
+ );
+ return account?.keys?.cryptoMasterKey;
}
async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise {
@@ -604,23 +611,6 @@ export class StateService<
await this.saveSecureStorageKey(partialKeys.biometricKey, value, options);
}
- async getDecodedToken(options?: StorageOptions): Promise {
- return (
- await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
- )?.tokens?.decodedToken;
- }
-
- async setDecodedToken(value: any, options?: StorageOptions): Promise {
- const account = await this.getAccount(
- this.reconcileOptions(options, await this.defaultInMemoryOptions())
- );
- account.tokens.decodedToken = value;
- await this.saveAccount(
- account,
- this.reconcileOptions(options, await this.defaultInMemoryOptions())
- );
- }
-
@withPrototypeForArrayMembers(CipherView, CipherView.fromJSON)
async getDecryptedCiphers(options?: StorageOptions): Promise {
return (
@@ -657,11 +647,11 @@ export class StateService<
);
}
- @withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise {
- return (
- await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
- )?.keys?.cryptoSymmetricKey?.decrypted;
+ const account = await this.getAccount(
+ this.reconcileOptions(options, await this.defaultInMemoryOptions())
+ );
+ return account?.keys?.cryptoSymmetricKey?.decrypted;
}
async setDecryptedCryptoSymmetricKey(
@@ -678,14 +668,13 @@ export class StateService<
);
}
- @withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getDecryptedOrganizationKeys(
options?: StorageOptions
): Promise