From 3f7a63b2c6789b263b29421776aa77be07b9e384 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Thu, 18 May 2023 10:32:18 -0700 Subject: [PATCH 01/29] [PM-2102] Implement logic to keep row control enable/disable status in sync with the access item properties whenever the parent control is enabled/disabled (#5433) Angular 15 introduced a breaking change that calls setDisabledState() whenever a CVA is added. This was re-enabling all the internal form group rows (even those that should have remained disabled). --- .../access-selector.component.ts | 73 +++++++++++++------ libs/angular/src/utils/form-selection-list.ts | 14 ++++ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index 7767953b30..d268a6ca5e 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -1,7 +1,14 @@ import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core"; -import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALUE_ACCESSOR, +} from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; +import { ControlsOf } from "@bitwarden/angular/types/controls-of"; import { FormSelectionList } from "@bitwarden/angular/utils/form-selection-list"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { SelectItemView } from "@bitwarden/components/src/multi-select/models/select-item-view"; @@ -47,6 +54,40 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On private notifyOnTouch: () => void; private pauseChangeNotification: boolean; + /** + * Updates the enabled/disabled state of provided row form group based on the item's readonly state. + * If a row is enabled, it also updates the enabled/disabled state of the permission control + * based on the item's accessAllItems state and the current value of `permissionMode`. + * @param controlRow - The form group for the row to update + * @param item - The access item that is represented by the row + */ + private updateRowControlDisableState = ( + controlRow: FormGroup>, + item: AccessItemView + ) => { + // Disable entire row form group if readonly + if (item.readonly) { + controlRow.disable(); + } else { + controlRow.enable(); + + // The enable() above also enables the permission control, so we need to disable it again + // Disable permission control if accessAllItems is enabled or not in Edit mode + if (item.accessAllItems || this.permissionMode != PermissionMode.Edit) { + controlRow.controls.permission.disable(); + } + } + }; + + /** + * Updates the enabled/disabled state of ALL row form groups based on each item's readonly state. + */ + private updateAllRowControlDisableStates = () => { + this.selectionList.forEachControlItem((controlRow, item) => { + this.updateRowControlDisableState(controlRow as FormGroup>, item); + }); + }; + /** * The internal selection list that tracks the value of this form control / component. * It's responsible for keeping items sorted and synced with the rendered form controls @@ -55,21 +96,13 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On protected selectionList = new FormSelectionList((item) => { const permissionControl = this.formBuilder.control(this.initialPermission); - const fg = this.formBuilder.group({ - id: item.id, - type: item.type, + const fg = this.formBuilder.group>({ + id: new FormControl(item.id), + type: new FormControl(item.type), permission: permissionControl, }); - // Disable entire row form group if readonly - if (item.readonly) { - fg.disable(); - } - - // Disable permission control if accessAllItems is enabled - if (item.accessAllItems || this.permissionMode != PermissionMode.Edit) { - permissionControl.disable(); - } + this.updateRowControlDisableState(fg, item); return fg; }, this._itemComparator.bind(this)); @@ -124,14 +157,8 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On set permissionMode(value: PermissionMode) { this._permissionMode = value; - // Toggle any internal permission controls - for (const control of this.selectionList.formArray.controls) { - if (value == PermissionMode.Edit) { - control.get("permission").enable(); - } else { - control.get("permission").disable(); - } - } + // Update any internal permission controls + this.updateAllRowControlDisableStates(); } private _permissionMode: PermissionMode = PermissionMode.Hidden; @@ -189,6 +216,10 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On this.formGroup.disable(); } else { this.formGroup.enable(); + + // The enable() above automatically enables all the row controls, + // so we need to disable the readonly ones again + this.updateAllRowControlDisableStates(); } } diff --git a/libs/angular/src/utils/form-selection-list.ts b/libs/angular/src/utils/form-selection-list.ts index 026ef367a4..aa454e81e4 100644 --- a/libs/angular/src/utils/form-selection-list.ts +++ b/libs/angular/src/utils/form-selection-list.ts @@ -198,4 +198,18 @@ export class FormSelectionList< this.selectItem(selectedItem.id, selectedItem); } } + + /** + * Helper method to iterate over each "selected" form control and its corresponding item + * @param fn - The function to call for each form control and its corresponding item + */ + forEachControlItem( + fn: (control: AbstractControl, TControlValue>, value: TItem) => void + ) { + for (let i = 0; i < this.formArray.length; i++) { + // The selectedItems array and formArray are explicitly kept in sync, + // so we can safely assume the index of the form control and item are the same + fn(this.formArray.at(i), this.selectedItems[i]); + } + } } From 3577b7c1009f8373b55acbb00f28e9f91a9ddb02 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 18 May 2023 13:35:13 -0400 Subject: [PATCH 02/29] [PM-1072] Convert autofill.js to Typescript (#5376) * Rename autofill.js to ts and update webpack * Remove wrapping function * Remove unreachable data-onepassword-title code * Remove unused post-submit logic * Run prettier * Remove unused fake tested code * Add typing * Disable certain eslint rules or fix eslint violations * Update modifications list * Remove unnecessary/confusing types * Checkout autofill.js from master * Add ENV switch for autofill versions * Rename autofill.ts to avoid confusion * Use string union type for FillScriptOp --- apps/browser/package.json | 1 + .../src/autofill/content/autofillv2.ts | 1391 +++++++++++++++++ .../src/autofill/models/autofill-script.ts | 27 +- apps/browser/webpack.config.js | 11 +- 4 files changed, 1423 insertions(+), 7 deletions(-) create mode 100644 apps/browser/src/autofill/content/autofillv2.ts diff --git a/apps/browser/package.json b/apps/browser/package.json index 0057704287..b29ab9c27b 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -6,6 +6,7 @@ "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", "build:watch": "webpack --watch", "build:watch:mv3": "cross-env MANIFEST_VERSION=3 webpack --watch", + "build:watch:autofill": "cross-env AUTOFILL_VERSION=2 webpack --watch", "build:prod": "cross-env NODE_ENV=production webpack", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch", "dist": "npm run build:prod && gulp dist", diff --git a/apps/browser/src/autofill/content/autofillv2.ts b/apps/browser/src/autofill/content/autofillv2.ts new file mode 100644 index 0000000000..8bf16ff879 --- /dev/null +++ b/apps/browser/src/autofill/content/autofillv2.ts @@ -0,0 +1,1391 @@ +/* eslint-disable no-var, no-console, no-prototype-builtins */ +// These eslint rules are disabled because the original JS was not written with them in mind and we don't want to fix +// them all now + +/* + 1Password Extension + + Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. + Copyright (c) 2014 AgileBits. All rights reserved. + + ================================================================================ + + Copyright (c) 2014 AgileBits Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +/* + MODIFICATIONS FROM ORIGINAL + + 1. Populate isFirefox + 2. Remove isChrome and isSafari since they are not used. + 3. Unminify and format to meet Mozilla review requirements. + 4. Remove unnecessary input types from getFormElements query selector and limit number of elements returned. + 5. Remove fakeTested prop. + 6. Rename com.agilebits.* stuff to com.bitwarden.* + 7. Remove "some useful globals" on window + 8. Add ability to autofill span[data-bwautofill] elements + 9. Add new handler, for new command that responds with page details in response callback + 10. Handle sandbox iframe and sandbox rule in CSP + 11. Work on array of saved urls instead of just one to determine if we should autofill non-https sites + 12. Remove setting of attribute com.browser.browser.userEdited on user-inputs + 13. Handle null value URLs in urlNotSecure + 14. Convert to Typescript, add typings and remove dead code (not marked with START/END MODIFICATION) + */ +import AutofillForm from "../models/autofill-form"; +import AutofillPageDetails from "../models/autofill-page-details"; +import AutofillScript, { + AutofillScriptOptions, + FillScript, + FillScriptOp, +} from "../models/autofill-script"; + +/** + * The Document with additional custom properties added by this script + */ +type AutofillDocument = Document & { + elementsByOPID: Record; + elementForOPID: (opId: string) => Element; +}; + +/** + * A HTMLElement (usually a form element) with additional custom properties added by this script + */ +type ElementWithOpId = T & { + opid: string; +}; + +/** + * This script's definition of a Form Element (only a subset of HTML form elements) + * This is defined by getFormElements + */ +type FormElement = HTMLInputElement | HTMLSelectElement | HTMLSpanElement; + +/** + * A Form Element that we can set a value on (fill) + */ +type FillableControl = HTMLInputElement | HTMLSelectElement; + +function collect(document: Document) { + // START MODIFICATION + var isFirefox = + navigator.userAgent.indexOf("Firefox") !== -1 || navigator.userAgent.indexOf("Gecko/") !== -1; + // END MODIFICATION + + (document as AutofillDocument).elementsByOPID = {}; + + function getPageDetails(theDoc: Document, oneShotId: string) { + // start helpers + + /** + * For a given element `el`, returns the value of the attribute `attrName`. + * @param {HTMLElement} el + * @param {string} attrName + * @returns {string} The value of the attribute + */ + function getElementAttrValue(el: any, attrName: string) { + var attrVal = el[attrName]; + if ("string" == typeof attrVal) { + return attrVal; + } + attrVal = el.getAttribute(attrName); + return "string" == typeof attrVal ? attrVal : null; + } + + /** + * Returns the value of the given element. + * @param {HTMLElement} el + * @returns {any} Value of the element + */ + function getElementValue(el: any) { + switch (toLowerString(el.type)) { + case "checkbox": + return el.checked ? "✓" : ""; + + case "hidden": + el = el.value; + if (!el || "number" != typeof el.length) { + return ""; + } + 254 < el.length && (el = el.substr(0, 254) + "...SNIPPED"); + return el; + + default: + // START MODIFICATION + if (!el.type && el.tagName.toLowerCase() === "span") { + return el.innerText; + } + // END MODIFICATION + return el.value; + } + } + + /** + * If `el` is a ` - +
- diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index 9f864fa2ad..361218c6e4 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -1,7 +1,9 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; +import { Router } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; +import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -12,9 +14,12 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service" selector: "app-home", templateUrl: "home.component.html", }) -export class HomeComponent implements OnInit { - loginInitiated = false; +export class HomeComponent implements OnInit, OnDestroy { + @ViewChild(EnvironmentSelectorComponent, { static: true }) + environmentSelector!: EnvironmentSelectorComponent; + private destroyed$: Subject = new Subject(); + loginInitiated = false; formGroup = this.formBuilder.group({ email: ["", [Validators.required, Validators.email]], rememberEmail: [false], @@ -27,9 +32,9 @@ export class HomeComponent implements OnInit { private router: Router, private i18nService: I18nService, private environmentService: EnvironmentService, - private route: ActivatedRoute, private loginService: LoginService ) {} + async ngOnInit(): Promise { let savedEmail = this.loginService.getEmail(); const rememberEmail = this.loginService.getRememberEmail(); @@ -48,6 +53,18 @@ export class HomeComponent implements OnInit { }); } } + + this.environmentSelector.onOpenSelfHostedSettings + .pipe(takeUntil(this.destroyed$)) + .subscribe(() => { + this.setFormValues(); + this.router.navigate(["environment"]); + }); + } + + ngOnDestroy(): void { + this.destroyed$.next(); + this.destroyed$.complete(); } submit() { diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index a0cd5e8887..3b8501be44 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -10,6 +10,7 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; import { BitwardenToastModule } from "@bitwarden/angular/components/toastr.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; @@ -153,6 +154,7 @@ import "./locales"; AboutComponent, HelpAndFeedbackComponent, AutofillComponent, + EnvironmentSelectorComponent, ], providers: [CurrencyPipe, DatePipe], bootstrap: [AppComponent], diff --git a/apps/browser/src/popup/images/eu-flag.png b/apps/browser/src/popup/images/eu-flag.png new file mode 100644 index 0000000000000000000000000000000000000000..592bc95e0ce80551d890e7867335df7c8206ccdb GIT binary patch literal 4549 zcmZ`+2Q*w;)E+@}MsLCB-OLz78H4DeMf4JN^wGsAF;31hbN6@m{=U8UIp?ms){WEGRi_|jAq4;c6q*_;1{ap$Vp0)b zy|~Yz6r2G7a(NeJWqnO$Wsts)r=yEI5&+PM%P=7_z1_=@XOWhwc!NYmQENp9xT>l} zEE*AJ*si8=C5px#6#H>lm2gsw%SPEmmClL`MWEpWoRReFOoZdG$nk3Bv?Nvj_U=`yg}$ zqTzTqnhc{y>97XTx9N_;run3@D7;|+Ay>-^9twcD@EB|l?UEWfK~z;33#P*>7>xRO zT`pLbW8NY5)zc& z=$$8}ca0()O~G9Et^sfRu-3`^Mo#;VoyF+=9AaL?<7{@;@*b**{)P7oG4}1|gj~kc zpcIgk-O-%sQA8W)-N!1AZ~-y3-aVxpR^Y3Zgxhb;ZfONi8>Z58jb`82NK0od+(_vD z)*M0k4!>y`#>75AjEv$el{748$`4I$!gwtt}KBg28t1PZ>VQdjJkHBNpP-FoD zf{Pt1)gziz3G$%@fL|mJ0E^^5$WrS`71_ryY$HN>2Oy&_S^V1%Nb+vvP;@9Vgk-)C zj*D)37s;>B{VOIwjE+6=I$8Y7)7f>R+F41FQW6u_qDb~9YmtxMg6u%hhfLZv?LWM& zr&+0D^e_UtSfVW%>E!K$sOM2q9rhhMH{`xW;wP3Cn5$?C0#;~?iSu4A!-{mP>q;|l(`SCqZ!Xj%O zm>@E#u&hq)_FlhbN?Ek?v>MZc;Fz%O6s;=$$hiT(%Yut#>s-U5m5YYW4OPg2#7s4$V%#*cchb+dG{cZ1{WER|Z^Q?72T&YRKLng@xc6P^=a;ZuL> z%k<9A$Is`A;sNHCyR|3N?)lXY?7me!afLU>a9dkj)pld09^jf(cwg%&Zg7odnr{wq z&sI`TsQ31Y*Hk1RfW#j=vL8kb!JqJg>$Xv#@i9Pxu5b+?8BMz((>>XoZ!{3vempWI zY5}|#Z9)2!bzz$}*FO?bMg)10=Un~Ph8!aGh&b^&@C#=wplZU;rzYU!G>l4K;p~bf zw2P7EWRC^YL`88AYjC9{7IAW^#P=&PaPs!B6kVH&Hso&QQhzYoqgq7Kkl+WB?{QnC zjeqJ|D&-n${;cp8)x-PIPpekwBq(yj8441H>7AL)?{BHD4~?!M_xV00w7qq9@2j}$Rl;6I>z6T!p zG+mobn*2I&TvuD z<)j@KG*UVnn}dzR=A+|3s%`Kbq-*fVr?K_M&}q)6`Dm&ZB^5Onr5CvsiH#7^8`0a- z^Jmy(@X$Zdda70So;`zBXwopREQHT3RkY{MqK0@rJfSJADRNq)MQe)-Q{rs!QULML zRO>2|vpCV;GFc^~(niHv?H^%lgyWRsDX!+Ob*}EUy{-ne^scxu z-Le^@VC7;1(Xz8K9#=7c!rH8$=;PSqaJA<$L(&om6yjBcu{usIxJ=i$Uf-!q&j_p! zHc}`GHEK4Ad}&+OJ~8LoKUz8@UG8mBXq{dmqXsj8S@&6x9u-h2fTZJDrp zwQ25DBfH8plQ>wix00|Tx8k&F^+^RKj7q*6eK+m^vB|KBU=w9y%thyh6EW8_<*P9z+av)KO{JdQeOpVt?XSLeyiV$u;D5OOW$d)5dyH(PZ_MJA z$?HRXj>-D*`~rpBnCdgL(=QJ`aW|{)sxhR%rFZl2Ty5NdD9C3UTswVas4{maP`T=> zS|=>JS&f_+cj9ch5CTH_LCR`ib6?S(aQr6zO4LXIN!m6&bW>s;_yYU1@1& z<~fBbc6(PPR9RjXWTR7iP@QLMg}m1p?Yv-{?o!=bGR{!zU#nC9s#D|uymd^06z%Bh1{q65QH9uF1hF_@XbDRzgk3;*sV-`-K z=Js7lg4af+muu5s(S9iFSdE5_%X|!M@r~RQW|U(L?{mzQtMHrLskmvGZQ0t`fYY3I zoe7#Am`R@zp79KJ4ps>iImFIsekcE)vmMyKSD2Y?QZcFYy0vMu8P5{2ca(LO8NE^M`mFRFTGuXHNce zf_&yZAs`t!i3p`T$p~pYnGjVFojXeogC*@W<2;MF+@hfXr-X=~{|YLZGp4(2c|XaQ z<9dLl?_+aQZPUPy3tpeSG|>xazhBrLw{C^l%2=T4n0kbU?&7DOm|MoTV9!Fc6?1@+ zPwhHJSD?eb&pvby*_XWAhzn$71>(dHLUa6cBq{{&$cOrb`2RYIYKxL2*`!t1%aPmh zc^3K%QpFkfkS@OlR)#ScD*BP$$cNJv%L;LeAAk5ukn#DN-auARNY=5$R*iCR)MC`4 ze6Kb3!L*PEhLpcpyf~$M88z2Dcd|FR6HI`Jkc@CDLzKB0;6_76CQ5!3ofK!59Fk6T z__-f8nd6WsLjmgdQ+eDAc3j zn|cztsNL(*GagO!Q`o?5Rc|C2KHC)EL_0oy3ov^jP%W2Q? z`T+DvwU=(U<-{~qLC-)OkK~5Gub&W!OQCZ^h1fpE0Ibmj|qEi%k9W&vZ69*;<(y7~dKn4<97gF6}g0{jg5OwwjvZ_Xbc8E&uEjOOZ$W^Ls^K`*QAMJ+$7 zwsivMkRbw=;p@M8$qu_-O`m4UrC1<5)>f3K-?}b|b zQrhm^N@3^tfYCbI-`ML17lD!1s1~VEvXcWFP_T_+e}>nRIvzP`Ru*a7x)ng57O$Z5 zgc+GOf5s1vogWlZpHr}X$9C(^G@$f;#s<>|1YsMVz#}ryb-F2P!~{pd;GnWN+9Z8e z7{1=OR&D!nmGF6e*%p()pXR+0SH}S zVgMl?4FLZF;ayw+JQe`qZwvrv;j#XW4e$j1aIOFVF)jdtKOFN5dpR^N=7su?y;5-H zZw5mF{@;7pJiPv z5PTT{L&YHA|B@lmF8`10GV+(~_qzTn2fs`vtLNi_ylD8cEd&()yTJeP{nH-&GK;Lf z3mWNes^Wq|diY*y#32wk_&+26sWkgX35CJ_t^8Nyj}i{P+^2u-+h2$B+k0^;2vRus z?-NFlGJ*A9T)Yoinkq_$($xNjI&QSL=;YHi`Ji#VkBdfC%v)258}&uh$_sE<6U(v} z&o(hYOr(W{0P)sX^SmxEfr1hvp}i4Jg662iL`l-7!b6p0nU?VcP{W0i;ryx%hG`6o#A3qY2M76Bk?nHpu4EVKImNz%i$X~zWMH#A-$)tU_U|!y~Homj%@nwB{L6UWvmD-N=QGKG*G(Bk?!+=r)AQ@ zbR3p#ZihPpl2gB*r+n~!sDk@U%11%AG;3JH9`=C7+5D`KMTuxY>MIFrIuXJ90njsB zBC7yTK{2r2qX&`P)He`B-U}V#?6a0dR$;oWnEstbWHMwKlIi9k>Vm$Ozrcj5ZYZ9c zcc#inj;Hs@xJfn^tnv-vJtV>Cqi<~lz45%4pFk@KXh%(yEysqV=h3>y^8B0OF|e!I z4!@pXuJm=O2Enz(6_*{2q2#U9BwD`(Ix`x|9; zV})+hMCtw|mI^jK9hlfs?frt=V8(K0_zRcExE7wTa(Z1}Wm#%zmB@KW!0V5rP}(I`pV`pV z(MTz=l;PI!`6UD5BULuI5RlvXdk4?cb7rA_>EJ0nmXIk{){1KMG@i9~OxhRar%M$_ ZL?0{(q>lp{lP?@ppeT!2RtK#pA#Ml+ z2kNvat6mGGumVSn4=Uk~39_0i=qXW|5P=<()&+wbAQ9Z$YnCmg^=d$qgXdfQhw0K6 zG9F8JYzNX)q;Ud&@s#RgpP&E<0w13%hF^zradHZ&X##N*03>z*-sEI98zrSDIQn>N zP03z00M^%Ur+RD8C(g~HIn5HGPQdc%&O01SsFe-CaT_i$Q&A9XL3DoY>; zC||D=HcVBk9rl3gC~%rnES1Df1i*_jE@dYH81j7)*~Qo=gpA^s=SD!NfLQ~fmr0+^Cw^2kH zeqg_CC}#7rMmlA<&03#i-t;~~k-gmz6ad=!egMjShPJoEwlay%eJ`^Z0N_}vz{R1& zmN*e1N(SWL5RVbX(3dzuSQfiRXfh>HxnTQUTuC`0L|RGW${@nb&ZIcBUwake%HiH5 zY!;uF;>OQ$v4#e!^u!Mm4p~ux3uGNLxMJc1U*eT3XxQ9)Ib`D~Cnat9S(!EVMTZNi zTcs=&Mb=!`jy6ZjAmU_x9ow>PX*B!1Xm_(bq9^Lh$m7r*@ z>p{o+`{hv*+rh+}wcJXuBVG{cI2Z&CqCyb9Boz)U%dcsqZR9Zw2GXj^GJpWS`8H)L z!SzblGljAMrvy&{i^Tgx2{nYWOkV;Rhj=p%zz@Hsa&0{&$dJd9w93->C4cmd46E-5 z;nHF|4fhhHVu}$bih6T4yM|viE6V?hKo6A{!j!Ta@}eDV2^M-rcdxQ#-_30LCRw;< z0Jlah{-&fv+}1&8VW?QERjazZ^ta917TMPN&lcpQ7s+&&Sig4}&%H<(rY38C43@U; zq_2gSb(v%_(`@3))X57N(bw?v^;t{@+-xB;Q?OTg#GboIZXNJ$|7XT+qtrD}H2>J0 zB^7dqk6Mir3Ic6s6#?CF=243_f%5!umUY-8hPnvGwpAddbfl=cUfF^$>~>v7x9wO~ zjh#0vB|+tgEFi>8_eIaGE`~0qE=W|hv0S5LJl^`sya9!Up^sn^?!}F3oGR@ebRB4S zwEH#LgMj;vW-fF)7kI6;dnPqEO5Kd2od^MV5qQ=P?FEuUaYkKX8qFjaoB+`E&LC9~5k-p*-D4^24+o%N*vFL^i}#}$`I*o;_{8-99YmW&R*#cOew~FyCp2!Er85H8GF*a% zDFQ+f8p<}P%98!qLr;cWWX{hpmZK<>_#@QXa|sXf#-++hLJ1DjW4m z3>9IRlY5`+*^{vB@@1;qBkcJzi%HkH;s4=*G4I49#8Wlw|xi!6!zu2*!y zpkg$U92P|{ucw7mTjlFh>qd*VZuv1fborFWOOO{X+?!?^nA3uh{`e;H)BDK1{?=^l z*th;8&w8A#wkGmd*KvXwTcVp;?y={Q+fYQ^$PY~qWecrv74RhgN*<|*HyE`{I%df# z6e5vUi>-~U&BR2FE3dO3B&o8?Br^7dQ>o1-x~nPW#pX5SCFMEf2@c^?Kcu#z=6Y`a zoSph9A{$Zkk?A=l@0diQ8e?= zS0P_ppy>PyJ4({rCEAmu{I&l0!cPVkaoU6ndz-y#j&8ZFx*&4&;z$iq8% z{tp}ULf%*uwT#Z8`i5V9mMC^J$~8+Wl~fkd7BTX8zr@rZojPO4E^xO5EYLV=S+Zeh zTPd|dHxn~Zu)7?+EWK>IVlt`d#OD0epjI1I{SGNSlsH_e&>CUy#}$joZQ12I`61}&F zTFhfLBbixu9t2dJ8=QT8I?2|cvZG8B50lu*0HVwtKu*vp^ST!55Pt=>HjqO3H{~{w zum)vfwt!sXTu)`L7@wGx&c;sp1mou>0@0_bjw1F#$Brl@ zgRP<6huOVs`i{Of{t?G?gCv8@_vOc!UCAZUr9=L18YY?*nw$IsM)ft7@9vcuTN=1b zIORKZl=GGqm;0EjR~=MjSePImw}sg)SR~n3^c0NHRC!jZ*OZhspu#8LPY`T~O;p9Q z#_ZhUV0b9HTHTN;(+mI9*xA??^bNGBwXC)E)4lR|IdAZ_N+$DJ|KNyFuUq)SnUJAX zXDrXHVTq-xq!P+cMXf7gA|sOH-i;n1yL`0Lv_ZW#$P{z4;Go=hS`@|HN*2_wD8;r|FfB$-Ql=-)InNIK+6xS26I>)mb?f z#ut%|*`{gML59UAyk5K1WkxcOz5TdV-js4@HlXlA28_kzArFW|1r7kur?|*M*Y^e&VTY(biQD|k(id4s(5AclVnlH z+q1Q(h2)*Roiu77^$8ydZ}P*&+4fnqQ^m#7q(GPvqS|kgciP9+sJr?DZ*TF?M*D{M zROV*HtlK$lyjii=>K6aRv1hWK-pS_jO#jH{$cQiT!J9X74tb(7zFb>QGqkmDvqu{r zP5F18C*sTn?lte4B?UBiLf6fQS>hF{!Uah6n6jC9N)=XZM)pSv$7;mc#VQ#Yy()_a z>$baHNSijKY~0_>*A8qy=i=s~$)uAoaD8|2>_}&8rE)l3eK>V9GX=Axyc@doqQb%! zf+d1`Ey31Kdx+q)aVC2oFnftz`M-Ldlh;aXO(l2g*0xj(rwvz2KbSe$?K?U--kf?g zpE_GLJcjc&v##a6+p$f1f3=ZY&E|B5U)>q$g~s8`o4U7~-)Bzz0$OldL*~O7$nG-J z!-{{kEybU0IB#@GUz09{#d-TRC5;`=s|_SUOrkTjGhe_({B>tGzC3=zRa>}!ocF^7 z(hh+i(wr3?+kU^hmK=VubFqe)ZEq~qOVq=8o7^<$Td`KsJ5_N0>Re^(-Rxt3vs1aP zw#|4Z=1&2LqrLUrK1dz}Y39@@=1+8TU=H>*m+gD*`dtM`OqiNV*t})}(4qupwVlu- z6X(ylAQAHeyee~dEIJtPn@$5>J$b%P_X&>M4CH=61Qw^7Acv1KXAKM}2xIh;X8Dj$ zZfpUKaFENJlRQMtWjVDT=b*!%4Bh%B%|Cycu{8Q>39brbF8?PmNPRUMb#(ymCB6Z` z1yTTTE)nqZ0st8RxW6#~fB@e78*2l(|8TAW0O9t4>wh?gm-cF?Ud~JPAA2q9+TRSC zES$eF*%h^)jg9Wo5V)!ucmM!Ilvfi7NX1?{ui@J3=%e-3?@3v?I0;%m zDgX>4bqSr2XbUjL$;*JE12|@*-tZ+gw z7z}f_wvp0SRQ@Bse3D_cL!(`#AP_GvFF`L6K^J#hh>)bDBm^o95f&D>WC(b8JEJWy z0?r<6evUD|dTWw7rWn_$sf3rHdz8hL!cI(BI>)KGF6z|5S4J__M6b1wyVO z5FtS*vQQgd|D}A-5v8&2PF#P>n(c1S3jR6|o z%xYUz%HF z2jzg~#!N$-#VgQJ$InTtNe^CM)IZ|ubcHH|@GNOLw)EqSn!N{3_qOSirG2w%d%F4w zj*akQJB#ejgxY - +
-
+
Bitwarden

{{ "loginOrCreateNewAccount" | i18n }}

@@ -37,9 +25,7 @@ />
- +
+ + +
+ +
+
diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts new file mode 100644 index 0000000000..c708a0af0e --- /dev/null +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -0,0 +1,103 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { ConnectedPosition } from "@angular/cdk/overlay"; +import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; +import { Router } from "@angular/router"; +import { Subject } from "rxjs"; + +import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction"; +import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +@Component({ + selector: "environment-selector", + templateUrl: "environment-selector.component.html", + animations: [ + trigger("transformPanel", [ + state( + "void", + style({ + opacity: 0, + }) + ), + transition( + "void => open", + animate( + "100ms linear", + style({ + opacity: 1, + }) + ) + ), + transition("* => void", animate("100ms linear", style({ opacity: 0 }))), + ]), + ], +}) +export class EnvironmentSelectorComponent implements OnInit, OnDestroy { + @Output() onOpenSelfHostedSettings = new EventEmitter(); + isOpen = false; + showingModal = false; + selectedEnvironment: ServerEnvironment; + ServerEnvironmentType = ServerEnvironment; + euServerFlagEnabled: boolean; + overlayPostition: ConnectedPosition[] = [ + { + originX: "start", + originY: "bottom", + overlayX: "start", + overlayY: "top", + }, + ]; + protected componentDestroyed$: Subject = new Subject(); + + constructor( + protected environmentService: EnvironmentService, + protected configService: ConfigServiceAbstraction, + protected router: Router + ) {} + + async ngOnInit() { + this.euServerFlagEnabled = await this.configService.getFeatureFlagBool( + FeatureFlag.DisplayEuEnvironmentFlag + ); + this.updateEnvironmentInfo(); + } + + ngOnDestroy(): void { + this.componentDestroyed$.next(); + this.componentDestroyed$.complete(); + } + + async toggle(option: ServerEnvironment) { + this.isOpen = !this.isOpen; + if (option === ServerEnvironment.EU) { + await this.environmentService.setUrls({ base: "https://vault.bitwarden.eu" }); + } else if (option === ServerEnvironment.US) { + await this.environmentService.setUrls({ base: "https://vault.bitwarden.com" }); + } else if (option === ServerEnvironment.SelfHosted) { + this.onOpenSelfHostedSettings.emit(); + } + this.updateEnvironmentInfo(); + } + + updateEnvironmentInfo() { + const webvaultUrl = this.environmentService.getWebVaultUrl(); + if (this.environmentService.isSelfHosted()) { + this.selectedEnvironment = ServerEnvironment.SelfHosted; + } else if (webvaultUrl != null && webvaultUrl.includes("bitwarden.eu")) { + this.selectedEnvironment = ServerEnvironment.EU; + } else { + this.selectedEnvironment = ServerEnvironment.US; + } + } + + close() { + this.isOpen = false; + this.updateEnvironmentInfo(); + } +} + +enum ServerEnvironment { + US = "US", + EU = "EU", + SelfHosted = "Self-hosted", +} diff --git a/libs/angular/src/components/environment.component.ts b/libs/angular/src/components/environment.component.ts index 347b5686e7..f47fcf9124 100644 --- a/libs/angular/src/components/environment.component.ts +++ b/libs/angular/src/components/environment.component.ts @@ -4,6 +4,8 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { ModalService } from "../services/modal.service"; + @Directive() export class EnvironmentComponent { @Output() onSaved = new EventEmitter(); @@ -19,7 +21,8 @@ export class EnvironmentComponent { constructor( protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, - protected i18nService: I18nService + protected i18nService: I18nService, + private modalService: ModalService ) { const urls = this.environmentService.getUrls(); @@ -59,5 +62,6 @@ export class EnvironmentComponent { protected saved() { this.onSaved.emit(); + this.modalService.closeAll(); } } diff --git a/libs/common/src/services/environment.service.ts b/libs/common/src/services/environment.service.ts index e093f0891a..2c6df478eb 100644 --- a/libs/common/src/services/environment.service.ts +++ b/libs/common/src/services/environment.service.ts @@ -218,6 +218,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { return ![ "http://vault.bitwarden.com", "https://vault.bitwarden.com", + "http://vault.bitwarden.eu", + "https://vault.bitwarden.eu", "http://vault.qa.bitwarden.pw", "https://vault.qa.bitwarden.pw", ].includes(this.getWebVaultUrl()); From 27094057f82c0221236ad007a6eaa5c1080b2b79 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 11:17:15 +0200 Subject: [PATCH 14/29] chore(deps): update dependency tsconfig-paths-webpack-plugin to v4 (#5481) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 69 +++++++++++++++++++++++++++++++++++++++++++---- package.json | 2 +- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 168de48504..3d5b1b1357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -167,7 +167,7 @@ "tailwindcss": "3.3.2", "ts-jest": "29.1.0", "ts-loader": "9.4.2", - "tsconfig-paths-webpack-plugin": "3.5.2", + "tsconfig-paths-webpack-plugin": "4.0.1", "type-fest": "2.19.0", "typescript": "4.9.5", "url": "0.11.0", @@ -7495,6 +7495,39 @@ "webpack": "*" } }, + "node_modules/@storybook/angular/node_modules/tsconfig-paths-webpack-plugin": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^3.9.0" + } + }, + "node_modules/@storybook/angular/node_modules/tsconfig-paths-webpack-plugin/node_modules/enhanced-resolve": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", + "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@storybook/angular/node_modules/tsconfig-paths-webpack-plugin/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@storybook/api": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", @@ -42523,14 +42556,40 @@ } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", - "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^3.9.0" + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/tsconfig-paths/node_modules/json5": { diff --git a/package.json b/package.json index c8e69c64b3..0bdd5e95ff 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "tailwindcss": "3.3.2", "ts-jest": "29.1.0", "ts-loader": "9.4.2", - "tsconfig-paths-webpack-plugin": "3.5.2", + "tsconfig-paths-webpack-plugin": "4.0.1", "type-fest": "2.19.0", "typescript": "4.9.5", "url": "0.11.0", From 73e03bab8176976d23c21dcbe99358a2b30379ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 11:30:47 +0200 Subject: [PATCH 15/29] Autosync the updated translations (#5490) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ar/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/az/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/be/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/bg/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/bn/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/bs/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ca/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/cs/messages.json | 18 ++++++++++++++++-- apps/desktop/src/locales/cy/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/da/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/de/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/el/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/en_GB/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/en_IN/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/eo/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/es/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/et/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/eu/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/fa/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/fi/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/fil/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/fr/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/gl/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/he/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/hi/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/hr/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/hu/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/id/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/it/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ja/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ka/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/km/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/kn/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ko/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/lv/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/me/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ml/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/my/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/nb/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ne/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/nl/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/nn/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/or/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/pl/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/pt_BR/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/pt_PT/messages.json | 20 +++++++++++++++++--- apps/desktop/src/locales/ro/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/ru/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/si/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/sk/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/sl/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/sr/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/sv/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/te/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/th/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/tr/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/uk/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/vi/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/zh_CN/messages.json | 14 ++++++++++++++ apps/desktop/src/locales/zh_TW/messages.json | 14 ++++++++++++++ 61 files changed, 859 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 3f1b4c73d4..f9843da28b 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index df54e50e2f..7e558adc15 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "تحديث الإعدادات الموصى بها" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 0a4608e4fd..e409110b05 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Tövsiyə edilən Tənzimləmələr Güncəlləməsi" + }, + "region": { + "message": "Bölgə" + }, + "eu": { + "message": "AB", + "description": "European Union" + }, + "us": { + "message": "ABŞ", + "description": "United States" + }, + "selfHosted": { + "message": "Öz-özünə sahiblik edən" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index cdabb7e793..efd52d6444 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Рэкамендаваныя налады абнаўлення" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index f0bac933df..4c89371192 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Препоръчителна промяна на настройките" + }, + "region": { + "message": "Регион" + }, + "eu": { + "message": "ЕС", + "description": "European Union" + }, + "us": { + "message": "САЩ", + "description": "United States" + }, + "selfHosted": { + "message": "Собствен хостинг" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 7d4cfb2eb3..f6972836d9 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 118b5be7ce..e5cc399e20 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index b2905fc5a0..e0d1adf7e5 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Actualització de configuració recomanada" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 391f2311ec..e35a5d78d2 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1985,10 +1985,10 @@ "message": "Vypršel časový limit relace. Vraťte se zpět a zkuste se znovu přihlásit." }, "exportingPersonalVaultTitle": { - "message": "Exportování individuálního trezoru" + "message": "Exportování osobního trezoru" }, "exportingPersonalVaultDescription": { - "message": "Budou exportovány pouze jednotlivé položky trezoru spojené s $EMAIL$ . Nebudou zahrnuty položky trezoru v organizaci.", + "message": "Budou exportovány jen osobní položky trezoru spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.", "placeholders": { "email": { "content": "$1", @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Aktualizace doporučených nastavení" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Vlastní hosting" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 6ea996538e..6d915a97ab 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Anbefalet indstillingsopdatering" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "USA", + "description": "United States" + }, + "selfHosted": { + "message": "Selv-hostet" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 9bde505fee..a757de46dc 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Empfohlene Aktualisierung der Einstellungen" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Selbst gehostet" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 69a2f127d7..bb84afe7d0 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 8fa8ea82d0..fd9426f642 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 4dc29b8f34..f3ee40a6dd 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 1139f03eea..bb86246b2d 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index a7ff6d0541..e8b8fa0b04 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Actualización de ajustes recomendados" + }, + "region": { + "message": "Región" + }, + "eu": { + "message": "Unión Europea", + "description": "European Union" + }, + "us": { + "message": "EE.UU.", + "description": "United States" + }, + "selfHosted": { + "message": "Autoalojado" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 9b1fdfa8a2..6f05a3f625 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 3b3c2d7970..5e1323a74c 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index a2f2511297..220b53cda5 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "به‌روز رسانی تنظیمات توصیه شده" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 3269d10922..7287c306da 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Suositeltava asetusmuutos" + }, + "region": { + "message": "Alue" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Itse ylläpidetty" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 4afa545867..9613f221e9 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 83504d30ba..0fe9681d2d 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Une mise à jour des paramètres est recommandée" + }, + "region": { + "message": "Région" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Auto-hébergé" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index b4fcbf3a68..2cc3461e8a 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index b6c7c6e40d..2324d20019 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index adbf8764a6..dcf0ae8534 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 3fde5beaf8..4016a73db9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Ajánlott beállítások frissítése" + }, + "region": { + "message": "Régió" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Saját kiszolgáló" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 1f163f4841..437838521a 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 5d4cf2decb..90580d12aa 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Aggiornamento delle impostazioni consigliato" + }, + "region": { + "message": "Regione" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 5ea5481360..d1f1235067 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "設定の更新を推奨" + }, + "region": { + "message": "リージョン" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "米国", + "description": "United States" + }, + "selfHosted": { + "message": "自己ホスト型" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 577dcda11e..2f1870a2b4 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index a97d51adeb..b5b4f69d78 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 550ee72fd9..705aa4ba5c 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Ieteicamie iestatījumu atjauninājumi" + }, + "region": { + "message": "Apgabals" + }, + "eu": { + "message": "ES", + "description": "European Union" + }, + "us": { + "message": "ASV", + "description": "United States" + }, + "selfHosted": { + "message": "Pašizvietots" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 6e0f3572f7..c6555d2e8d 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 48b4aa9724..104d697f53 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index f3dca94dd9..545c17156b 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index dc62c1fe20..b431e2a14e 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 66873ef00e..bcd3fd9441 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Aanbevolen instellingen" + }, + "region": { + "message": "Regio" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Zelfgehost" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 48325eab3c..0b7b4ce113 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index b5750b7d07..ede0694974 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 47959f42b3..2dd103dcd0 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Aktualizacja ustawień zalecanych" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Samodzielnie hostowany" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 57761b38c6..5e909f4735 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index d344572628..e7235d1ed9 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -570,7 +570,7 @@ "message": "Não existem itens para listar." }, "sendVerificationCode": { - "message": "Envie um código de verificação para o seu e-mail" + "message": "Enviar um código de verificação para o seu e-mail" }, "sendCode": { "message": "Enviar o código" @@ -1684,7 +1684,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "O meu Cofre" + "message": "O meu cofre" }, "text": { "message": "Texto" @@ -2052,7 +2052,7 @@ "message": "Procurar Organização" }, "searchMyVault": { - "message": "Procurar no meu Cofre" + "message": "Procurar no meu cofre" }, "forwardedEmail": { "message": "Forwarded email alias" @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Região" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "EUA", + "description": "United States" + }, + "selfHosted": { + "message": "Auto-hospedado" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index df5d3af904..c5fc551e18 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index ea290c84c9..e940c97686 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Рекомендуемое обновление настроек" + }, + "region": { + "message": "Регион" + }, + "eu": { + "message": "Европа", + "description": "European Union" + }, + "us": { + "message": "США", + "description": "United States" + }, + "selfHosted": { + "message": "Собственный хостинг" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 7e05312723..e8a79147aa 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index ffafe222cc..773be1cd07 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Odporúčaná aktualizácia nastavenia" + }, + "region": { + "message": "Región" + }, + "eu": { + "message": "EÚ", + "description": "European Union" + }, + "us": { + "message": "USA", + "description": "United States" + }, + "selfHosted": { + "message": "Vlastný hosting" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 6ad123c585..f670e572bc 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 894c3ea59e..fb225b1763 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Препоручено ажурирање поставки" + }, + "region": { + "message": "Регион" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Личан хостинг" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index d1e242eb6a..247f646408 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 13b91e222b..fbc7635f3b 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index fd5b9d7b92..e68d7d13c1 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index d7b4924010..294ab336c3 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Önerilen Ayarlar Güncellemesi" + }, + "region": { + "message": "Bölge" + }, + "eu": { + "message": "AB", + "description": "European Union" + }, + "us": { + "message": "ABD", + "description": "United States" + }, + "selfHosted": { + "message": "Barındırılan" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index d86332f506..4e4c214ed9 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Оновлення рекомендованих налаштувань" + }, + "region": { + "message": "Регіон" + }, + "eu": { + "message": "ЄС", + "description": "European Union" + }, + "us": { + "message": "США", + "description": "United States" + }, + "selfHosted": { + "message": "Власне розміщення" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 8a810b8d84..e4fcbede49 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index d4a028271c..8184e19b76 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "推荐的设置更新" + }, + "region": { + "message": "区域" + }, + "eu": { + "message": "欧盟", + "description": "European Union" + }, + "us": { + "message": "美国", + "description": "United States" + }, + "selfHosted": { + "message": "自托管" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index e248870ab3..a5df7cdde5 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2251,5 +2251,19 @@ }, "windowsBiometricUpdateWarningTitle": { "message": "Recommended Settings Update" + }, + "region": { + "message": "Region" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" + }, + "selfHosted": { + "message": "Self-hosted" } } From 14084dcf59ff92a6e5a31d240dbf0021b5946e89 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 11:37:21 +0200 Subject: [PATCH 16/29] Autosync the updated translations (#5489) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 11 +++++++++++ apps/browser/src/_locales/az/messages.json | 11 +++++++++++ apps/browser/src/_locales/be/messages.json | 11 +++++++++++ apps/browser/src/_locales/bg/messages.json | 11 +++++++++++ apps/browser/src/_locales/bn/messages.json | 11 +++++++++++ apps/browser/src/_locales/bs/messages.json | 11 +++++++++++ apps/browser/src/_locales/ca/messages.json | 11 +++++++++++ apps/browser/src/_locales/cs/messages.json | 15 +++++++++++++-- apps/browser/src/_locales/cy/messages.json | 11 +++++++++++ apps/browser/src/_locales/da/messages.json | 11 +++++++++++ apps/browser/src/_locales/de/messages.json | 11 +++++++++++ apps/browser/src/_locales/el/messages.json | 11 +++++++++++ apps/browser/src/_locales/en_GB/messages.json | 8 ++++++++ apps/browser/src/_locales/en_IN/messages.json | 11 +++++++++++ apps/browser/src/_locales/es/messages.json | 11 +++++++++++ apps/browser/src/_locales/et/messages.json | 11 +++++++++++ apps/browser/src/_locales/eu/messages.json | 11 +++++++++++ apps/browser/src/_locales/fa/messages.json | 11 +++++++++++ apps/browser/src/_locales/fi/messages.json | 11 +++++++++++ apps/browser/src/_locales/fil/messages.json | 11 +++++++++++ apps/browser/src/_locales/fr/messages.json | 11 +++++++++++ apps/browser/src/_locales/gl/messages.json | 11 +++++++++++ apps/browser/src/_locales/he/messages.json | 11 +++++++++++ apps/browser/src/_locales/hi/messages.json | 11 +++++++++++ apps/browser/src/_locales/hr/messages.json | 11 +++++++++++ apps/browser/src/_locales/hu/messages.json | 11 +++++++++++ apps/browser/src/_locales/id/messages.json | 11 +++++++++++ apps/browser/src/_locales/it/messages.json | 11 +++++++++++ apps/browser/src/_locales/ja/messages.json | 11 +++++++++++ apps/browser/src/_locales/ka/messages.json | 11 +++++++++++ apps/browser/src/_locales/km/messages.json | 11 +++++++++++ apps/browser/src/_locales/kn/messages.json | 11 +++++++++++ apps/browser/src/_locales/ko/messages.json | 11 +++++++++++ apps/browser/src/_locales/lt/messages.json | 11 +++++++++++ apps/browser/src/_locales/lv/messages.json | 11 +++++++++++ apps/browser/src/_locales/ml/messages.json | 11 +++++++++++ apps/browser/src/_locales/my/messages.json | 11 +++++++++++ apps/browser/src/_locales/nb/messages.json | 11 +++++++++++ apps/browser/src/_locales/ne/messages.json | 11 +++++++++++ apps/browser/src/_locales/nl/messages.json | 11 +++++++++++ apps/browser/src/_locales/nn/messages.json | 11 +++++++++++ apps/browser/src/_locales/or/messages.json | 11 +++++++++++ apps/browser/src/_locales/pl/messages.json | 11 +++++++++++ apps/browser/src/_locales/pt_BR/messages.json | 11 +++++++++++ apps/browser/src/_locales/pt_PT/messages.json | 17 ++++++++++++++--- apps/browser/src/_locales/ro/messages.json | 11 +++++++++++ apps/browser/src/_locales/ru/messages.json | 11 +++++++++++ apps/browser/src/_locales/si/messages.json | 11 +++++++++++ apps/browser/src/_locales/sk/messages.json | 11 +++++++++++ apps/browser/src/_locales/sl/messages.json | 13 ++++++++++++- apps/browser/src/_locales/sr/messages.json | 11 +++++++++++ apps/browser/src/_locales/sv/messages.json | 11 +++++++++++ apps/browser/src/_locales/te/messages.json | 11 +++++++++++ apps/browser/src/_locales/th/messages.json | 11 +++++++++++ apps/browser/src/_locales/tr/messages.json | 11 +++++++++++ apps/browser/src/_locales/uk/messages.json | 11 +++++++++++ apps/browser/src/_locales/vi/messages.json | 11 +++++++++++ apps/browser/src/_locales/zh_CN/messages.json | 11 +++++++++++ apps/browser/src/_locales/zh_TW/messages.json | 11 +++++++++++ 59 files changed, 652 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 609f6ef39d..71cd3d1a66 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 9ad7b097ab..0c12f845d5 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Bölgə" + }, "opensInANewWindow": { "message": "Yeni bir pəncərədə açılır" + }, + "eu": { + "message": "AB", + "description": "European Union" + }, + "us": { + "message": "ABŞ", + "description": "United States" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 4d0fe3698d..80800f4aa3 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Адкрываць у новым акне" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index bc3feb2469..b17692bc83 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Регион" + }, "opensInANewWindow": { "message": "Отваря се в нов прозорец" + }, + "eu": { + "message": "ЕС", + "description": "European Union" + }, + "us": { + "message": "САЩ", + "description": "United States" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 9ddc1b03ec..76143e7619 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 3f37ef10f7..b3047a9878 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 1028885f50..e117d21790 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "S'obri en una finestra nova" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 7ff5424a05..d83282e08a 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1977,10 +1977,10 @@ "message": "Vypršel časový limit relace. Vraťte se zpět a zkuste se znovu přihlásit." }, "exportingPersonalVaultTitle": { - "message": "Exportování individuálního trezoru" + "message": "Exportování osobního trezoru" }, "exportingPersonalVaultDescription": { - "message": "Budou exportovány pouze položky trezoru spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.", + "message": "Budou exportovány jen osobní položky trezoru spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.", "placeholders": { "email": { "content": "$1", @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Otevře se v novém okně" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 53451d098e..5aebf5dd80 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Åbnes i et nyt vindue" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "USA", + "description": "United States" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index c6f7b90988..c7827d4ddc 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Wird in einem neuen Fenster geöffnet" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index d86d1be57e..e6da9f04c8 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Ανοίγει σε νέο παράθυρο" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 3b54fe0efe..653ff32074 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2226,5 +2226,13 @@ }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index de8b595765..3fe6222cfc 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index ac4b28b7dd..afd3ea4ce8 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Región" + }, "opensInANewWindow": { "message": "Abre en una nueva ventana" + }, + "eu": { + "message": "Unión Europea", + "description": "European Union" + }, + "us": { + "message": "EE.UU.", + "description": "United States" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 8ccfcb5dcc..02ee8d0438 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Piirkond" + }, "opensInANewWindow": { "message": "Avaneb uues aknas" + }, + "eu": { + "message": "EL", + "description": "European Union" + }, + "us": { + "message": "USA", + "description": "United States" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 9d2196ed64..e2189ce6a6 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 6a13d95927..0e4a59e164 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "در پنجره جدید باز می‌شود" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 4edb1b563c..69b77b2612 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Alue" + }, "opensInANewWindow": { "message": "Avautuu uudessa ikkunassa" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index af8050b68a..e98884c200 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index ae4893dd81..8f4e769c69 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Région" + }, "opensInANewWindow": { "message": "S'ouvre dans une nouvelle fenêtre" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index e4e676471f..fc90bade6c 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 456822a3cc..df561ad0e4 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index d668e3e0d2..37ecb3de8c 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index d06a236f69..c5e8214b63 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Régió" + }, "opensInANewWindow": { "message": "Megnyitás új ablakban" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 220af16b5a..6addc264fb 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index c3aab71545..1971331fd9 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Regione" + }, "opensInANewWindow": { "message": "Si apre in una nuova finestra" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index e6eca2c538..630dfb3e21 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "リージョン" + }, "opensInANewWindow": { "message": "新しいウィンドウで開く" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "米国", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index d71e9402df..2f484324f3 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 31b78ceb49..baa61fad17 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 155736462d..962fe7347c 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 078c28a261..e6a9dbdc5c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 317a1d48cc..38c892aaed 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Apgabals" + }, "opensInANewWindow": { "message": "Atver jaunā logā" + }, + "eu": { + "message": "ES", + "description": "European Union" + }, + "us": { + "message": "ASV", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 1975bb0f14..4c4e9936e0 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 0946027aee..8ede2fb938 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 1f76bc82fe..cf3cae05d4 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Regio" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 36f8cc8c94..ca32eb73aa 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Otwiera w nowym oknie" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 8b4fcfad0a..87585d561d 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index ad53f10e41..9950328204 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -56,7 +56,7 @@ "message": "Cofre" }, "myVault": { - "message": "O meu Cofre" + "message": "O meu cofre" }, "allVaults": { "message": "Todos os Cofres" @@ -128,7 +128,7 @@ "message": "Continuar" }, "sendVerificationCode": { - "message": "Envie um código de verificação para o seu e-mail" + "message": "Enviar um código de verificação para o seu e-mail" }, "sendCode": { "message": "Enviar o código" @@ -1744,7 +1744,7 @@ } }, "custom": { - "message": "Custom" + "message": "Personalizado" }, "maximumAccessCount": { "message": "Maximum Access Count" @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Região" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "UE", + "description": "European Union" + }, + "us": { + "message": "EUA", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index bad505607a..47a684aa1a 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 1b15850d27..9da7f861dc 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Регион" + }, "opensInANewWindow": { "message": "Откроется в новом окне" + }, + "eu": { + "message": "Европа", + "description": "European Union" + }, + "us": { + "message": "США", + "description": "United States" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 9b917a74db..94baba9b6d 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 147fe0bbd4..2fa04daf3d 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Región" + }, "opensInANewWindow": { "message": "Otvárať v novom okne" + }, + "eu": { + "message": "EÚ", + "description": "European Union" + }, + "us": { + "message": "USA", + "description": "United States" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 32f3f860fa..91fdc621fd 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2180,7 +2180,7 @@ "message": "Kako uporabljati samodejno izpolnjevanje" }, "autofillSelectInfoWithCommand": { - "message": "Izberite element na tej strani ali pa uporabite bližnjico $COMMAND$", + "message": "Izberite element na tej strani ali pa uporabite bližnjico $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Odpre se v novem oknu" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index a0368285da..952ae90145 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Регион" + }, "opensInANewWindow": { "message": "Отвара се у новом прозору" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index a270aedb9b..79afc8729d 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Öppnas i ett nytt fönster" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 0a69b94887..69d26333a8 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 6a355591f6..1e43987ab0 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 4f904da244..cdc8e109fe 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Bölge" + }, "opensInANewWindow": { "message": "Yeni pencerede açılır" + }, + "eu": { + "message": "AB", + "description": "European Union" + }, + "us": { + "message": "ABD", + "description": "United States" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 336bf3d39a..1969bc3362 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Регіон" + }, "opensInANewWindow": { "message": "Відкривається у новому вікні" + }, + "eu": { + "message": "ЄС", + "description": "European Union" + }, + "us": { + "message": "США", + "description": "United States" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index f7aea9e151..fee4c66f3a 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 0f7d9870e1..45c3c62db0 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "区域" + }, "opensInANewWindow": { "message": "在新窗口中打开" + }, + "eu": { + "message": "欧盟", + "description": "European Union" + }, + "us": { + "message": "美国", + "description": "United States" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 83bd8bd8e3..42e082f136 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2221,7 +2221,18 @@ } } }, + "region": { + "message": "Region" + }, "opensInANewWindow": { "message": "Opens in a new window" + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "us": { + "message": "US", + "description": "United States" } } From b31203d64fbdbc0eaef41a94b00bc85cfe998a18 Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 22 May 2023 17:28:00 +0200 Subject: [PATCH 17/29] Added textarea (#4155) --- apps/browser/src/autofill/content/autofill.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/autofill/content/autofill.js b/apps/browser/src/autofill/content/autofill.js index 661eb8e252..052fd1120f 100644 --- a/apps/browser/src/autofill/content/autofill.js +++ b/apps/browser/src/autofill/content/autofill.js @@ -676,7 +676,7 @@ var els = []; try { var elsList = theDoc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="reset"])' + - ':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, ' + + ':not([type="button"]):not([type="image"]):not([type="file"]):not([data-bwignore]), select, textarea, ' + 'span[data-bwautofill]'); els = Array.prototype.slice.call(elsList); } catch (e) { } @@ -1177,7 +1177,7 @@ } try { // START MODIFICATION - var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, ' + + var elements = Array.prototype.slice.call(selectAllFromDoc('input, select, button, textarea, ' + 'span[data-bwautofill]')); // END MODIFICATION var filteredElements = elements.filter(function (o) { From 62591a8bc8d7dcf754db92d742cb3bfe70b9fbc6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 22 May 2023 20:19:16 +0200 Subject: [PATCH 18/29] [PM-2328] Fix jest deprecations (#5483) --- apps/browser/jest.config.js | 3 ++- apps/cli/jest.config.js | 6 +++-- apps/desktop/jest.config.js | 3 ++- apps/web/jest.config.js | 4 +-- bitwarden_license/bit-web/jest.config.js | 4 +-- jest.config.js | 11 ++------ libs/angular/jest.config.js | 3 ++- libs/common/jest.config.js | 3 ++- libs/components/jest.config.js | 3 ++- libs/exporter/jest.config.js | 3 ++- libs/importer/jest.config.js | 3 ++- libs/node/jest.config.js | 3 ++- libs/shared/jest.config.angular.js | 32 ++++++++++++++++++++++++ libs/shared/jest.config.base.js | 27 -------------------- libs/shared/jest.config.ts.js | 29 +++++++++++++++++++++ 15 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 libs/shared/jest.config.angular.js delete mode 100644 libs/shared/jest.config.base.js create mode 100644 libs/shared/jest.config.ts.js diff --git a/apps/browser/jest.config.js b/apps/browser/jest.config.js index 4f954afa9e..cde02cd995 100644 --- a/apps/browser/jest.config.js +++ b/apps/browser/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "jest-preset-angular", diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index 67116d394b..8765ccc8e4 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,8 +1,10 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig.json"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const { compilerOptions } = require("./tsconfig"); +const sharedConfig = require("../../libs/shared/jest.config.ts"); + +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "ts-jest", diff --git a/apps/desktop/jest.config.js b/apps/desktop/jest.config.js index 4f954afa9e..cde02cd995 100644 --- a/apps/desktop/jest.config.js +++ b/apps/desktop/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "jest-preset-angular", diff --git a/apps/web/jest.config.js b/apps/web/jest.config.js index 707e8960e3..cde02cd995 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "jest-preset-angular", @@ -11,5 +12,4 @@ module.exports = { moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", }), - modulePathIgnorePatterns: ["jslib"], }; diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js index 84c6866a7d..17b7139049 100644 --- a/bitwarden_license/bit-web/jest.config.js +++ b/bitwarden_license/bit-web/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "jest-preset-angular", @@ -11,5 +12,4 @@ module.exports = { moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", }), - modulePathIgnorePatterns: ["jslib"], }; diff --git a/jest.config.js b/jest.config.js index ec58fe0c01..8b54a82658 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,12 +2,14 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); +/** @type {import('jest').Config} */ module.exports = { reporters: ["default", "jest-junit"], collectCoverage: true, coverageReporters: ["html", "lcov"], coverageDirectory: "coverage", + moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", }), @@ -30,13 +32,4 @@ module.exports = { // https://github.com/facebook/jest/issues/9430#issuecomment-1149882002 // Also anecdotally improves performance when run locally maxWorkers: 3, - globals: { - "ts-jest": { - // Further workaround for memory leak, recommended here: - // https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014 - // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code - // See https://bitwarden.atlassian.net/browse/EC-497 for more info - isolatedModules: true, - }, - }, }; diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index 3be0f66db5..e294e4ff47 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../shared/tsconfig.libs"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, displayName: "libs/angular tests", diff --git a/libs/common/jest.config.js b/libs/common/jest.config.js index 29309a7830..d7f78abbf3 100644 --- a/libs/common/jest.config.js +++ b/libs/common/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../shared/tsconfig.libs"); -const sharedConfig = require("../shared/jest.config.base"); +const sharedConfig = require("../shared/jest.config.ts"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, displayName: "libs/common tests", diff --git a/libs/components/jest.config.js b/libs/components/jest.config.js index e90c663ce9..2f4b1ed15d 100644 --- a/libs/components/jest.config.js +++ b/libs/components/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("./tsconfig"); -const sharedConfig = require("../../libs/shared/jest.config.base"); +const sharedConfig = require("../../libs/shared/jest.config.angular"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, displayName: "libs/components tests", diff --git a/libs/exporter/jest.config.js b/libs/exporter/jest.config.js index 2eeeefad62..68daba3d40 100644 --- a/libs/exporter/jest.config.js +++ b/libs/exporter/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../shared/tsconfig.libs"); -const sharedConfig = require("../shared/jest.config.base"); +const sharedConfig = require("../shared/jest.config.ts"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "ts-jest", diff --git a/libs/importer/jest.config.js b/libs/importer/jest.config.js index 2eeeefad62..68daba3d40 100644 --- a/libs/importer/jest.config.js +++ b/libs/importer/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../shared/tsconfig.libs"); -const sharedConfig = require("../shared/jest.config.base"); +const sharedConfig = require("../shared/jest.config.ts"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "ts-jest", diff --git a/libs/node/jest.config.js b/libs/node/jest.config.js index 833d03cfa5..fd7d580fda 100644 --- a/libs/node/jest.config.js +++ b/libs/node/jest.config.js @@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../shared/tsconfig.libs"); -const sharedConfig = require("../shared/jest.config.base"); +const sharedConfig = require("../shared/jest.config.ts"); +/** @type {import('jest').Config} */ module.exports = { ...sharedConfig, preset: "ts-jest", diff --git a/libs/shared/jest.config.angular.js b/libs/shared/jest.config.angular.js new file mode 100644 index 0000000000..a0dcc27516 --- /dev/null +++ b/libs/shared/jest.config.angular.js @@ -0,0 +1,32 @@ +/* eslint-env node */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { defaultTransformerOptions } = require("jest-preset-angular/presets"); + +/** @type {import('jest').Config} */ +module.exports = { + testMatch: ["**/+(*.)+(spec).+(ts)"], + + // Workaround for a memory leak that crashes tests in CI: + // https://github.com/facebook/jest/issues/9430#issuecomment-1149882002 + // Also anecdotally improves performance when run locally + maxWorkers: 3, + + transform: { + "^.+\\.(ts|js|mjs|svg)$": [ + "jest-preset-angular", + { + ...defaultTransformerOptions, + // Jest does not use tsconfig.spec.json by default + tsconfig: "/tsconfig.spec.json", + // Further workaround for memory leak, recommended here: + // https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014 + // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code + // See https://bitwarden.atlassian.net/browse/EC-497 for more info + isolatedModules: true, + astTransformers: { + before: ["/../../libs/shared/es2020-transformer.ts"], + }, + }, + ], + }, +}; diff --git a/libs/shared/jest.config.base.js b/libs/shared/jest.config.base.js deleted file mode 100644 index e0eda1c4ca..0000000000 --- a/libs/shared/jest.config.base.js +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-env node */ -module.exports = { - testMatch: ["**/+(*.)+(spec).+(ts)"], - collectCoverage: true, - coverageReporters: ["html", "lcov"], - coverageDirectory: "coverage", - - // Workaround for a memory leak that crashes tests in CI: - // https://github.com/facebook/jest/issues/9430#issuecomment-1149882002 - // Also anecdotally improves performance when run locally - maxWorkers: 3, - - globals: { - "ts-jest": { - // Jest does not use tsconfig.spec.json by default - tsconfig: "/tsconfig.spec.json", - // Further workaround for memory leak, recommended here: - // https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014 - // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code - // See https://bitwarden.atlassian.net/browse/EC-497 for more info - isolatedModules: true, - astTransformers: { - before: ["/../../libs/shared/es2020-transformer.ts"], - }, - }, - }, -}; diff --git a/libs/shared/jest.config.ts.js b/libs/shared/jest.config.ts.js new file mode 100644 index 0000000000..04ab80859b --- /dev/null +++ b/libs/shared/jest.config.ts.js @@ -0,0 +1,29 @@ +/* eslint-env node */ + +/** @type {import('jest').Config} */ +module.exports = { + testMatch: ["**/+(*.)+(spec).+(ts)"], + + // Workaround for a memory leak that crashes tests in CI: + // https://github.com/facebook/jest/issues/9430#issuecomment-1149882002 + // Also anecdotally improves performance when run locally + maxWorkers: 3, + + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + // Jest does not use tsconfig.spec.json by default + tsconfig: "/tsconfig.spec.json", + // Further workaround for memory leak, recommended here: + // https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014 + // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code + // See https://bitwarden.atlassian.net/browse/EC-497 for more info + isolatedModules: true, + astTransformers: { + before: ["/../../libs/shared/es2020-transformer.ts"], + }, + }, + ], + }, +}; From 3a1ae46c37496b87ba6cd64a8150e11cb342f2ee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 13:13:42 -0600 Subject: [PATCH 19/29] chore(deps): update crowdin/github-action action to v1.8.1 (#5484) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-browser.yml | 2 +- .github/workflows/build-desktop.yml | 2 +- .github/workflows/build-web.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 468e8b3b54..7270cc9203 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -360,7 +360,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@3cabba4ddfd0579a1236b3fb68405236dc489ccc # v1.8.0 + uses: crowdin/github-action@102b5aa21783a64027193ef802a616140a1ca102 # v1.8.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 6c614df483..7935e2a8ec 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1196,7 +1196,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@3cabba4ddfd0579a1236b3fb68405236dc489ccc # v1.8.0 + uses: crowdin/github-action@102b5aa21783a64027193ef802a616140a1ca102 # v1.8.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 8fed2825a9..9151650a7b 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -297,7 +297,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@3cabba4ddfd0579a1236b3fb68405236dc489ccc # v1.8.0 + uses: crowdin/github-action@102b5aa21783a64027193ef802a616140a1ca102 # v1.8.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} From 946d254b0fa159dfa4926796340f7a2dd2ac5db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bispo?= Date: Mon, 22 May 2023 23:04:50 +0100 Subject: [PATCH 20/29] [PM-2347] Fix EU env flag (#5495) --- .../src/auth/components/environment-selector.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/angular/src/auth/components/environment-selector.component.html b/libs/angular/src/auth/components/environment-selector.component.html index 53c177ee1d..e64d09da06 100644 --- a/libs/angular/src/auth/components/environment-selector.component.html +++ b/libs/angular/src/auth/components/environment-selector.component.html @@ -48,6 +48,7 @@ type="button" class="environment-selector-dialog-item" (click)="toggle(ServerEnvironmentType.EU)" + *ngIf="euServerFlagEnabled" > {{ "eu" | i18n }} -
+
- diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 0072e1f3ae..473ce88f2d 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -1,6 +1,8 @@ import { Component, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; +import { lastValueFrom } from "rxjs"; +import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -15,7 +17,7 @@ import { OrganizationResponse } from "@bitwarden/common/admin-console/models/res import { ApiKeyComponent } from "../../../settings/api-key.component"; import { PurgeVaultComponent } from "../../../settings/purge-vault.component"; -import { DeleteOrganizationComponent } from "./delete-organization.component"; +import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./components"; @Component({ selector: "app-org-account", @@ -23,8 +25,6 @@ import { DeleteOrganizationComponent } from "./delete-organization.component"; }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil export class AccountComponent { - @ViewChild("deleteOrganizationTemplate", { read: ViewContainerRef, static: true }) - deleteModalRef: ViewContainerRef; @ViewChild("purgeOrganizationTemplate", { read: ViewContainerRef, static: true }) purgeModalRef: ViewContainerRef; @ViewChild("apiKeyTemplate", { read: ViewContainerRef, static: true }) @@ -51,7 +51,8 @@ export class AccountComponent { private logService: LogService, private router: Router, private organizationService: OrganizationService, - private organizationApiService: OrganizationApiServiceAbstraction + private organizationApiService: OrganizationApiServiceAbstraction, + private dialogService: DialogServiceAbstraction ) {} async ngOnInit() { @@ -100,17 +101,18 @@ export class AccountComponent { } async deleteOrganization() { - await this.modalService.openViewRef( - DeleteOrganizationComponent, - this.deleteModalRef, - (comp) => { - comp.organizationId = this.organizationId; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - comp.onSuccess.subscribe(() => { - this.router.navigate(["/"]); - }); - } - ); + const dialog = openDeleteOrganizationDialog(this.dialogService, { + data: { + organizationId: this.organizationId, + requestType: "RegularDelete", + }, + }); + + const result = await lastValueFrom(dialog.closed); + + if (result === DeleteOrganizationDialogResult.Deleted) { + this.router.navigate(["/"]); + } } async purgeVault() { diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.html b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.html new file mode 100644 index 0000000000..f963d27b8d --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.html @@ -0,0 +1,40 @@ + + + {{ "deleteOrganization" | i18n }} +
+ {{ + "deletingOrganizationIsPermanentWarning" | i18n : organization?.name + }} +

+ + {{ "orgCreatedSponsorshipInvalid" | i18n }} + + + + {{ "deletingOrganizationContentWarning" | i18n : organization?.name }} +

    +
  • + {{ type.count }} {{ type.localizationKey | i18n }} +
  • +
+ {{ "deletingOrganizationActiveUserAccountsWarning" | i18n }} + + +

+ +
+
+ + +
+
+ diff --git a/apps/web/src/app/admin-console/organizations/settings/delete-organization.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts similarity index 57% rename from apps/web/src/app/admin-console/organizations/settings/delete-organization.component.ts rename to apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index cec096955a..e99a0d2910 100644 --- a/apps/web/src/app/admin-console/organizations/settings/delete-organization.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -1,17 +1,25 @@ -import { Component, EventEmitter, OnInit, Output } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, FormControl, Validators } from "@angular/forms"; +import { combineLatest, Subject, takeUntil } from "rxjs"; +import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Utils } from "@bitwarden/common/misc/utils"; import { Verification } from "@bitwarden/common/types/verification"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { UserVerificationModule } from "../../../../shared/components/user-verification"; +import { SharedModule } from "../../../../shared/shared.module"; + class CountBasedLocalizationKey { singular: string; plural: string; @@ -43,63 +51,90 @@ class OrganizationContentSummary { itemCountByType: OrganizationContentSummaryItem[] = []; } +export interface DeleteOrganizationDialogParams { + organizationId: string; + + requestType: "InvalidFamiliesForEnterprise" | "RegularDelete"; +} + +export enum DeleteOrganizationDialogResult { + Deleted = "deleted", + Canceled = "canceled", +} + @Component({ selector: "app-delete-organization", - templateUrl: "delete-organization.component.html", + standalone: true, + imports: [SharedModule, UserVerificationModule], + templateUrl: "delete-organization-dialog.component.html", }) -export class DeleteOrganizationComponent implements OnInit { - organizationId: string; +export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + loaded: boolean; deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete"; - organizationName: string; + organization: Organization; organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary(); - @Output() onSuccess: EventEmitter = new EventEmitter(); + secret: Verification; - masterPassword: Verification; + protected formGroup = this.formBuilder.group({ + secret: new FormControl(null, [Validators.required]), + }); formPromise: Promise; constructor( + @Inject(DIALOG_DATA) private params: DeleteOrganizationDialogParams, + private dialogRef: DialogRef, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private userVerificationService: UserVerificationService, private logService: LogService, private cipherService: CipherService, private organizationService: OrganizationService, - private organizationApiService: OrganizationApiServiceAbstraction + private organizationApiService: OrganizationApiServiceAbstraction, + private formBuilder: FormBuilder ) {} - async ngOnInit(): Promise { - await this.load(); + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } - async submit() { + async ngOnInit(): Promise { + this.deleteOrganizationRequestType = this.params.requestType; + + combineLatest([ + this.organizationService.get$(this.params.organizationId), + this.cipherService.getAllFromApiForOrganization(this.params.organizationId), + ]) + .pipe(takeUntil(this.destroy$)) + .subscribe(([organization, ciphers]) => { + this.organization = organization; + this.organizationContentSummary = this.buildOrganizationContentSummary(ciphers); + this.loaded = true; + }); + } + + protected submit = async () => { try { this.formPromise = this.userVerificationService - .buildRequest(this.masterPassword) - .then((request) => this.organizationApiService.delete(this.organizationId, request)); + .buildRequest(this.formGroup.value.secret) + .then((request) => this.organizationApiService.delete(this.organization.id, request)); await this.formPromise; this.platformUtilsService.showToast( "success", this.i18nService.t("organizationDeleted"), this.i18nService.t("organizationDeletedDesc") ); - this.onSuccess.emit(); + this.dialogRef.close(DeleteOrganizationDialogResult.Deleted); } catch (e) { this.logService.error(e); } - } + }; - private async load() { - this.organizationName = (await this.organizationService.get(this.organizationId)).name; - this.organizationContentSummary = await this.buildOrganizationContentSummary(); - this.loaded = true; - } - - private async buildOrganizationContentSummary(): Promise { + private buildOrganizationContentSummary(ciphers: CipherView[]): OrganizationContentSummary { const organizationContentSummary = new OrganizationContentSummary(); - const organizationItems = ( - await this.cipherService.getAllFromApiForOrganization(this.organizationId) - ).filter((item) => item.deletedDate == null); + const organizationItems = ciphers.filter((item) => item.deletedDate == null); if (organizationItems.length < 1) { return organizationContentSummary; @@ -129,3 +164,18 @@ export class DeleteOrganizationComponent implements OnInit { return new CountBasedLocalizationKey(`type${type}`, `type${type}Plural`); } } + +/** + * Strongly typed helper to open a Delete Organization dialog + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ +export function openDeleteOrganizationDialog( + dialogService: DialogServiceAbstraction, + config: DialogConfig +) { + return dialogService.open( + DeleteOrganizationDialogComponent, + config + ); +} diff --git a/apps/web/src/app/admin-console/organizations/settings/components/index.ts b/apps/web/src/app/admin-console/organizations/settings/components/index.ts new file mode 100644 index 0000000000..ae4b74837d --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/settings/components/index.ts @@ -0,0 +1 @@ +export * from "./delete-organization-dialog.component"; diff --git a/apps/web/src/app/admin-console/organizations/settings/delete-organization.component.html b/apps/web/src/app/admin-console/organizations/settings/delete-organization.component.html deleted file mode 100644 index ef4ec15b7e..0000000000 --- a/apps/web/src/app/admin-console/organizations/settings/delete-organization.component.html +++ /dev/null @@ -1,61 +0,0 @@ - diff --git a/apps/web/src/app/admin-console/organizations/settings/index.ts b/apps/web/src/app/admin-console/organizations/settings/index.ts index 289bf3d632..74b356100a 100644 --- a/apps/web/src/app/admin-console/organizations/settings/index.ts +++ b/apps/web/src/app/admin-console/organizations/settings/index.ts @@ -1,2 +1,2 @@ export * from "./organization-settings.module"; -export { DeleteOrganizationComponent } from "./delete-organization.component"; +export { DeleteOrganizationDialogComponent } from "./components/delete-organization-dialog.component"; diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts index 0b22c66d1e..c5500c6a94 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts @@ -4,18 +4,12 @@ import { LooseComponentsModule, SharedModule } from "../../../shared"; import { PoliciesModule } from "../../organizations/policies"; import { AccountComponent } from "./account.component"; -import { DeleteOrganizationComponent } from "./delete-organization.component"; import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module"; import { SettingsComponent } from "./settings.component"; import { TwoFactorSetupComponent } from "./two-factor-setup.component"; @NgModule({ imports: [SharedModule, LooseComponentsModule, PoliciesModule, OrganizationSettingsRoutingModule], - declarations: [ - SettingsComponent, - AccountComponent, - DeleteOrganizationComponent, - TwoFactorSetupComponent, - ], + declarations: [SettingsComponent, AccountComponent, TwoFactorSetupComponent], }) export class OrganizationSettingsModule {} diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.html b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.html index 055cdddb38..34a8f21123 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.html +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.html @@ -50,4 +50,3 @@ - diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index 9d632f56c5..1406f08024 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -1,9 +1,9 @@ -import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Observable, Subject } from "rxjs"; +import { lastValueFrom, Observable, Subject } from "rxjs"; import { first, map, takeUntil } from "rxjs/operators"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -11,12 +11,15 @@ import { ValidationService } from "@bitwarden/common/abstractions/validation.ser import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-sponsorship-redeem.request"; -import { PlanType, PlanSponsorshipType } from "@bitwarden/common/billing/enums"; +import { PlanSponsorshipType, PlanType } from "@bitwarden/common/billing/enums"; import { ProductType } from "@bitwarden/common/enums"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { OrganizationPlansComponent } from "../../../billing/settings/organization-plans.component"; -import { DeleteOrganizationComponent } from "../../organizations/settings"; +import { + DeleteOrganizationDialogResult, + openDeleteOrganizationDialog, +} from "../settings/components"; @Component({ selector: "families-for-enterprise-setup", @@ -36,9 +39,6 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { value.onSuccess.subscribe(this.onOrganizationCreateSuccess.bind(this)); } - @ViewChild("deleteOrganizationTemplate", { read: ViewContainerRef, static: true }) - deleteModalRef: ViewContainerRef; - loading = true; badToken = false; formPromise: Promise; @@ -62,7 +62,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { private syncService: SyncService, private validationService: ValidationService, private organizationService: OrganizationService, - private modalService: ModalService + private dialogService: DialogServiceAbstraction ) {} async ngOnInit() { @@ -136,18 +136,18 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { this.router.navigate(["/"]); } catch (e) { if (this.showNewOrganization) { - await this.modalService.openViewRef( - DeleteOrganizationComponent, - this.deleteModalRef, - (comp) => { - comp.organizationId = organizationId; - comp.deleteOrganizationRequestType = "InvalidFamiliesForEnterprise"; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - comp.onSuccess.subscribe(() => { - this.router.navigate(["/"]); - }); - } - ); + const dialog = openDeleteOrganizationDialog(this.dialogService, { + data: { + organizationId: organizationId, + requestType: "InvalidFamiliesForEnterprise", + }, + }); + + const result = await lastValueFrom(dialog.closed); + + if (result === DeleteOrganizationDialogResult.Deleted) { + this.router.navigate(["/"]); + } } this.validationService.showError(this.i18nService.t("sponsorshipTokenHasExpired")); } diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 6a7ae61aa8..955e9b0da9 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -9,7 +9,6 @@ import { SubscriptionRoutingModule } from "../app/billing/settings/subscription- import { flagEnabled, Flags } from "../utils/flags"; import { TrialInitiationComponent } from "./accounts/trial-initiation/trial-initiation.component"; -import { OrganizationModule } from "./admin-console/organizations/organization.module"; import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; @@ -249,7 +248,8 @@ const routes: Routes = [ }, { path: "organizations", - loadChildren: () => OrganizationModule, + loadChildren: () => + import("./admin-console/organizations/organization.module").then((m) => m.OrganizationModule), }, ]; diff --git a/apps/web/src/app/shared/components/user-verification/index.ts b/apps/web/src/app/shared/components/user-verification/index.ts new file mode 100644 index 0000000000..9fe21e309c --- /dev/null +++ b/apps/web/src/app/shared/components/user-verification/index.ts @@ -0,0 +1,3 @@ +export * from "./user-verification.module"; +export * from "./user-verification-prompt.component"; +export * from "./user-verification.component"; diff --git a/apps/web/src/app/components/user-verification-prompt.component.html b/apps/web/src/app/shared/components/user-verification/user-verification-prompt.component.html similarity index 100% rename from apps/web/src/app/components/user-verification-prompt.component.html rename to apps/web/src/app/shared/components/user-verification/user-verification-prompt.component.html diff --git a/apps/web/src/app/components/user-verification-prompt.component.ts b/apps/web/src/app/shared/components/user-verification/user-verification-prompt.component.ts similarity index 100% rename from apps/web/src/app/components/user-verification-prompt.component.ts rename to apps/web/src/app/shared/components/user-verification/user-verification-prompt.component.ts diff --git a/apps/web/src/app/components/user-verification.component.html b/apps/web/src/app/shared/components/user-verification/user-verification.component.html similarity index 100% rename from apps/web/src/app/components/user-verification.component.html rename to apps/web/src/app/shared/components/user-verification/user-verification.component.html diff --git a/apps/web/src/app/components/user-verification.component.ts b/apps/web/src/app/shared/components/user-verification/user-verification.component.ts similarity index 100% rename from apps/web/src/app/components/user-verification.component.ts rename to apps/web/src/app/shared/components/user-verification/user-verification.component.ts diff --git a/apps/web/src/app/shared/components/user-verification/user-verification.module.ts b/apps/web/src/app/shared/components/user-verification/user-verification.module.ts new file mode 100644 index 0000000000..de6b32f7fe --- /dev/null +++ b/apps/web/src/app/shared/components/user-verification/user-verification.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../../shared.module"; + +import { UserVerificationPromptComponent } from "./user-verification-prompt.component"; +import { UserVerificationComponent } from "./user-verification.component"; + +@NgModule({ + imports: [SharedModule], + declarations: [UserVerificationComponent, UserVerificationPromptComponent], + exports: [UserVerificationComponent, UserVerificationPromptComponent], +}) +export class UserVerificationModule {} diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index a1ba393866..c9f115a273 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -64,8 +64,6 @@ import { TaxInfoComponent } from "../billing/settings/tax-info.component"; import { UserSubscriptionComponent } from "../billing/settings/user-subscription.component"; import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; import { SelectableAvatarComponent } from "../components/selectable-avatar.component"; -import { UserVerificationPromptComponent } from "../components/user-verification-prompt.component"; -import { UserVerificationComponent } from "../components/user-verification.component"; import { FooterComponent } from "../layouts/footer.component"; import { FrontendLayoutComponent } from "../layouts/frontend-layout.component"; import { NavbarComponent } from "../layouts/navbar.component"; @@ -110,6 +108,7 @@ import { AddEditComponent as OrgAddEditComponent } from "../vault/org-vault/add- import { AttachmentsComponent as OrgAttachmentsComponent } from "../vault/org-vault/attachments.component"; import { CollectionsComponent as OrgCollectionsComponent } from "../vault/org-vault/collections.component"; +import { UserVerificationModule } from "./components/user-verification"; import { SharedModule } from "./shared.module"; // Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left. @@ -120,6 +119,7 @@ import { SharedModule } from "./shared.module"; OrganizationCreateModule, RegisterFormModule, ProductSwitcherModule, + UserVerificationModule, ChangeKdfModule, DynamicAvatarComponent, ], @@ -178,7 +178,6 @@ import { SharedModule } from "./shared.module"; GeneratorComponent, PasswordGeneratorHistoryComponent, PasswordRepromptComponent, - UserVerificationPromptComponent, PaymentComponent, PaymentMethodComponent, PreferencesComponent, @@ -224,7 +223,6 @@ import { SharedModule } from "./shared.module"; BillingHistoryViewComponent, UserLayoutComponent, UserSubscriptionComponent, - UserVerificationComponent, VaultTimeoutInputComponent, VerifyEmailComponent, VerifyEmailTokenComponent, @@ -330,7 +328,6 @@ import { SharedModule } from "./shared.module"; BillingHistoryViewComponent, UserLayoutComponent, UserSubscriptionComponent, - UserVerificationComponent, VaultTimeoutInputComponent, VerifyEmailComponent, VerifyEmailTokenComponent, diff --git a/apps/web/src/app/tools/import-export/export.component.ts b/apps/web/src/app/tools/import-export/export.component.ts index 0b3982dcb7..a615eed07d 100644 --- a/apps/web/src/app/tools/import-export/export.component.ts +++ b/apps/web/src/app/tools/import-export/export.component.ts @@ -15,7 +15,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { EncryptedExportType } from "@bitwarden/common/enums"; import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export"; -import { UserVerificationPromptComponent } from "../../components/user-verification-prompt.component"; +import { UserVerificationPromptComponent } from "../../shared/components/user-verification"; @Component({ selector: "app-export", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts index 2355f3049f..e6eadd9e92 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -6,7 +6,7 @@ import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/components/user-verification-prompt.component"; +import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/shared/components/user-verification"; import { AccessTokenView } from "../models/view/access-token.view"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 74d90c52a6..ee90b4fcb5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/components/user-verification-prompt.component"; +import { UserVerificationPromptComponent } from "@bitwarden/web-vault/app/shared/components/user-verification"; import { SecretsManagerPortingApiService } from "../services/sm-porting-api.service"; import { SecretsManagerPortingService } from "../services/sm-porting.service"; From b9d3b0aff7b21024784d1bd59ce2df91f56b46f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bispo?= Date: Thu, 25 May 2023 18:32:26 +0100 Subject: [PATCH 28/29] [PM-2398] Fix firefox extension environments bug (#5514) --- .../src/services/config/config.service.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libs/common/src/services/config/config.service.ts b/libs/common/src/services/config/config.service.ts index f17ded9e6c..dba5d1ca09 100644 --- a/libs/common/src/services/config/config.service.ts +++ b/libs/common/src/services/config/config.service.ts @@ -1,4 +1,5 @@ -import { BehaviorSubject, concatMap, from, timer } from "rxjs"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject, Subject, concatMap, from, takeUntil, timer } from "rxjs"; import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction"; @@ -10,9 +11,11 @@ import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { FeatureFlag } from "../../enums/feature-flag.enum"; import { ServerConfigData } from "../../models/data/server-config.data"; -export class ConfigService implements ConfigServiceAbstraction { +@Injectable() +export class ConfigService implements ConfigServiceAbstraction, OnDestroy { protected _serverConfig = new BehaviorSubject(null); serverConfig$ = this._serverConfig.asObservable(); + private destroy$ = new Subject(); constructor( private stateService: StateService, @@ -27,11 +30,14 @@ export class ConfigService implements ConfigServiceAbstraction { this._serverConfig.next(serverConfig); }); - this.environmentService.urls - .pipe(concatMap(() => from(this.fetchServerConfig()))) - .subscribe((serverConfig) => { - this._serverConfig.next(serverConfig); - }); + this.environmentService.urls.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.fetchServerConfig(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); } async fetchServerConfig(): Promise { From 0fcfe883b5f7eedf98f4d4295d119dab003a0886 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 25 May 2023 14:17:19 -0400 Subject: [PATCH 29/29] Feature/[PM-1378] - Trusted Device Encryption - Establish trust logic for all clients (#5339) * PM1378 - (1) Create state service methods for securely storing a device symmetric key while following existing pattern of DuckDuckGoKey generation (2) Create makeDeviceKey method on crypto service which leverages the new state service methods for storing the device key. * PM-1378 - Document CSPRNG types w/ comments explaining what they are and when they should be used. * PM-1378 - TODO to add tests for makeDeviceKey method * PM-1378 - Create Devices API service for creating and updating device encrypted master keys + move models according to latest code standards ( I think) * PM-1378 - TODO clean up - DeviceResponse properly moved next to device api service abstraction per ADR 0013 * PM-1378 - CryptoService makeDeviceKey test written * PM-1378 - Tweak crypto service makeDeviceKey test to leverage a describe for the function to better group related code. * PM-1378 - Move known devices call out of API service and into new devices-api.service and update all references. All clients building. * PM-1378 - Comment clean up * PM-1378 - Refactor out master key naming as that is a reserved specific key generated from the MP key derivation process + use same property on request object as back end. * PM-1378 - Missed a use of master key * PM-1378 - More abstraction updates to remove master key. * PM-1378 - Convert crypto service makeDeviceKey into getDeviceKey method to consolidate service logic based on PR feedback * PM-1378- Updating makeDeviceKey --> getDeviceKey tests to match updated code * PM-1378 - Current work on updating establish trusted device logic in light of new encryption mechanisms (introduction of a device asymmetric key pair in order to allow for key rotation while maintaining trusted devices) * PM-1378 - (1) CryptoService.TrustDevice() naming refactors (2) Lots of test additions and tweaks for trustDevice() * PM-1378 - Updated TrustedDeviceKeysRequest names to be consistent across the client side board. * PM-1378 - Move trusted device crypto service methods out of crypto service into new DeviceCryptoService for better single responsibility design * PM-1378 - (1) Add getDeviceByIdentifier endpoint to devices api as will need it later (2) Update TrustedDeviceKeysRequest and DeviceResponse models to match latest server side generic encrypted key names * PM-1378 - PR feedback fix - use JSDOC comments and move from abstraction to implementation * PM-1378 - Per PR feedback, makeDeviceKey should be private - updated tests with workaround. * PM-1378- Per PR feedback, refactored deviceKey to use partialKey dict so we can associate userId with specific device keys. * PM-1378 - Replace deviceId with deviceIdentifier per PR feedback * PM-1378 - Remove unnecessary createTrustedDeviceKey methods * PM-1378 - Update device crypto service to leverage updateTrustedDeviceKeys + update tests * PM-1378 - Update trustDevice logic - (1) Use getEncKey to get user symmetric key as it's the correct method and (2) Attempt to retrieve the userSymKey earlier on and short circuit if it is not found. * PM-1378 - Replace deviceId with deviceIdentifier because they are not the same thing * PM-1378 - Per PR feedback, (1) on web/browser extension, store device key in local storage under account.keys existing structure (2) on desktop, store deviceKey in secure storage. (3) Exempt account.keys.deviceKey from being cleared on account reset * PM-1378 - Desktop testing revealed that I forgot to add userId existence and options reconciliation checks back * PM-1378 - Per discussion with Jake, create DeviceKey custom type which is really just an opaque so we can more easily differentiate between key types. * PM-1378 - Update symmetric-crypto-key.ts opaque DeviceKey to properly setup Opaque type. * PM-1378 - Fix wrong return type for getDeviceKey on DeviceCryptoServiceAbstraction per PR feedback --- .../browser/src/auth/popup/login.component.ts | 6 +- .../desktop/src/auth/login/login.component.ts | 6 +- .../src/services/electron-state.service.ts | 32 ++ .../web/src/app/auth/login/login.component.ts | 6 +- .../src/auth/components/login.component.ts | 6 +- .../src/services/jslib-services.module.ts | 23 ++ libs/common/src/abstractions/api.service.ts | 1 - .../device-crypto.service.abstraction.ts | 8 + .../devices-api.service.abstraction.ts | 14 + .../devices/responses}/device.response.ts | 6 + libs/common/src/abstractions/state.service.ts | 4 +- libs/common/src/models/domain/account.ts | 3 +- .../src/models/domain/symmetric-crypto-key.ts | 5 +- libs/common/src/services/api.service.ts | 8 - .../device-crypto.service.implementation.ts | 85 +++++ .../services/device-crypto.service.spec.ts | 317 ++++++++++++++++++ .../devices-api.service.implementation.ts | 64 ++++ .../requests/trusted-device-keys.request.ts | 7 + libs/common/src/services/state.service.ts | 35 +- libs/common/src/types/csprng.d.ts | 4 + 20 files changed, 613 insertions(+), 27 deletions(-) create mode 100644 libs/common/src/abstractions/device-crypto.service.abstraction.ts create mode 100644 libs/common/src/abstractions/devices/devices-api.service.abstraction.ts rename libs/common/src/{auth/models/response => abstractions/devices/responses}/device.response.ts (65%) create mode 100644 libs/common/src/services/device-crypto.service.implementation.ts create mode 100644 libs/common/src/services/device-crypto.service.spec.ts create mode 100644 libs/common/src/services/devices/devices-api.service.implementation.ts create mode 100644 libs/common/src/services/devices/requests/trusted-device-keys.request.ts diff --git a/apps/browser/src/auth/popup/login.component.ts b/apps/browser/src/auth/popup/login.component.ts index cc48a75e4e..0652070a4d 100644 --- a/apps/browser/src/auth/popup/login.component.ts +++ b/apps/browser/src/auth/popup/login.component.ts @@ -3,9 +3,9 @@ import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -27,7 +27,7 @@ import { flagEnabled } from "../../flags"; export class LoginComponent extends BaseLoginComponent { showPasswordless = false; constructor( - apiService: ApiService, + devicesApiService: DevicesApiServiceAbstraction, appIdService: AppIdService, authService: AuthService, router: Router, @@ -46,7 +46,7 @@ export class LoginComponent extends BaseLoginComponent { loginService: LoginService ) { super( - apiService, + devicesApiService, appIdService, authService, router, diff --git a/apps/desktop/src/auth/login/login.component.ts b/apps/desktop/src/auth/login/login.component.ts index efe09efe73..bd7ff1e73b 100644 --- a/apps/desktop/src/auth/login/login.component.ts +++ b/apps/desktop/src/auth/login/login.component.ts @@ -6,10 +6,10 @@ import { Subject, takeUntil } from "rxjs"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -52,7 +52,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { } constructor( - apiService: ApiService, + devicesApiService: DevicesApiServiceAbstraction, appIdService: AppIdService, authService: AuthService, router: Router, @@ -74,7 +74,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { loginService: LoginService ) { super( - apiService, + devicesApiService, appIdService, authService, router, diff --git a/apps/desktop/src/services/electron-state.service.ts b/apps/desktop/src/services/electron-state.service.ts index b290866dd0..137302c801 100644 --- a/apps/desktop/src/services/electron-state.service.ts +++ b/apps/desktop/src/services/electron-state.service.ts @@ -1,6 +1,11 @@ +import { Utils } from "@bitwarden/common/misc/utils"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { StorageOptions } from "@bitwarden/common/models/domain/storage-options"; +import { + DeviceKey, + SymmetricCryptoKey, +} from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { StateService as BaseStateService } from "@bitwarden/common/services/state.service"; import { Account } from "../models/account"; @@ -11,6 +16,10 @@ export class ElectronStateService extends BaseStateService implements ElectronStateServiceAbstraction { + private partialKeys = { + deviceKey: "_deviceKey", + }; + async addAccount(account: Account) { // Apply desktop overides to default account values account = new Account(account); @@ -77,4 +86,27 @@ export class ElectronStateService this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } + + override async getDeviceKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + + const b64DeviceKey = await this.secureStorageService.get( + `${options.userId}${this.partialKeys.deviceKey}`, + options + ); + + return new SymmetricCryptoKey(Utils.fromB64ToArray(b64DeviceKey).buffer) as DeviceKey; + } + + override async setDeviceKey(value: DeviceKey, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + + await this.saveSecureStorageKey(this.partialKeys.deviceKey, value.keyB64, options); + } } diff --git a/apps/web/src/app/auth/login/login.component.ts b/apps/web/src/app/auth/login/login.component.ts index 54f9bd3646..fa5a96cc37 100644 --- a/apps/web/src/app/auth/login/login.component.ts +++ b/apps/web/src/app/auth/login/login.component.ts @@ -5,9 +5,9 @@ import { Subject, takeUntil } from "rxjs"; import { first } from "rxjs/operators"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -41,7 +41,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest private destroy$ = new Subject(); constructor( - apiService: ApiService, + devicesApiService: DevicesApiServiceAbstraction, appIdService: AppIdService, authService: AuthService, router: Router, @@ -63,7 +63,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest loginService: LoginService ) { super( - apiService, + devicesApiService, appIdService, authService, router, diff --git a/libs/angular/src/auth/components/login.component.ts b/libs/angular/src/auth/components/login.component.ts index 0d7c594e69..e882d93894 100644 --- a/libs/angular/src/auth/components/login.component.ts +++ b/libs/angular/src/auth/components/login.component.ts @@ -3,9 +3,9 @@ import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { take } from "rxjs/operators"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { AllValidationErrors, @@ -55,7 +55,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit } constructor( - protected apiService: ApiService, + protected devicesApiService: DevicesApiServiceAbstraction, protected appIdService: AppIdService, protected authService: AuthService, protected router: Router, @@ -295,7 +295,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit async getLoginWithDevice(email: string) { try { const deviceIdentifier = await this.appIdService.getAppId(); - const res = await this.apiService.getKnownDevice(email, deviceIdentifier); + const res = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); //ensure the application is not self-hosted this.showLoginWithDevice = res && !this.selfHosted; } catch (e) { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 7cf75712a0..7ae54c9213 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -10,6 +10,8 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/conf import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { DeviceCryptoServiceAbstraction } from "@bitwarden/common/abstractions/device-crypto.service.abstraction"; +import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction"; import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -90,6 +92,8 @@ import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service import { CryptoService } from "@bitwarden/common/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; +import { DeviceCryptoService } from "@bitwarden/common/services/device-crypto.service.implementation"; +import { DevicesApiServiceImplementation } from "@bitwarden/common/services/devices/devices-api.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; @@ -351,6 +355,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; PlatformUtilsServiceAbstraction, LogService, StateServiceAbstraction, + AppIdServiceAbstraction, + DevicesApiServiceAbstraction, ], }, { @@ -656,6 +662,23 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; useClass: OrgDomainApiService, deps: [OrgDomainServiceAbstraction, ApiServiceAbstraction], }, + { + provide: DevicesApiServiceAbstraction, + useClass: DevicesApiServiceImplementation, + deps: [ApiServiceAbstraction], + }, + { + provide: DeviceCryptoServiceAbstraction, + useClass: DeviceCryptoService, + deps: [ + CryptoFunctionServiceAbstraction, + CryptoServiceAbstraction, + EncryptService, + StateServiceAbstraction, + AppIdServiceAbstraction, + DevicesApiServiceAbstraction, + ], + }, ], }) export class JslibServicesModule {} diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 5670a5dc36..2273b29019 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -361,7 +361,6 @@ export abstract class ApiService { putDeviceVerificationSettings: ( request: DeviceVerificationRequest ) => Promise; - getKnownDevice: (email: string, deviceIdentifier: string) => Promise; getEmergencyAccessTrusted: () => Promise>; getEmergencyAccessGranted: () => Promise>; diff --git a/libs/common/src/abstractions/device-crypto.service.abstraction.ts b/libs/common/src/abstractions/device-crypto.service.abstraction.ts new file mode 100644 index 0000000000..23b3be967f --- /dev/null +++ b/libs/common/src/abstractions/device-crypto.service.abstraction.ts @@ -0,0 +1,8 @@ +import { DeviceKey } from "../models/domain/symmetric-crypto-key"; + +import { DeviceResponse } from "./devices/responses/device.response"; + +export abstract class DeviceCryptoServiceAbstraction { + trustDevice: () => Promise; + getDeviceKey: () => Promise; +} diff --git a/libs/common/src/abstractions/devices/devices-api.service.abstraction.ts b/libs/common/src/abstractions/devices/devices-api.service.abstraction.ts new file mode 100644 index 0000000000..345b728977 --- /dev/null +++ b/libs/common/src/abstractions/devices/devices-api.service.abstraction.ts @@ -0,0 +1,14 @@ +import { DeviceResponse } from "./responses/device.response"; + +export abstract class DevicesApiServiceAbstraction { + getKnownDevice: (email: string, deviceIdentifier: string) => Promise; + + getDeviceByIdentifier: (deviceIdentifier: string) => Promise; + + updateTrustedDeviceKeys: ( + deviceIdentifier: string, + devicePublicKeyEncryptedUserSymKey: string, + userSymKeyEncryptedDevicePublicKey: string, + deviceKeyEncryptedDevicePrivateKey: string + ) => Promise; +} diff --git a/libs/common/src/auth/models/response/device.response.ts b/libs/common/src/abstractions/devices/responses/device.response.ts similarity index 65% rename from libs/common/src/auth/models/response/device.response.ts rename to libs/common/src/abstractions/devices/responses/device.response.ts index 2770499e81..331df2e16c 100644 --- a/libs/common/src/auth/models/response/device.response.ts +++ b/libs/common/src/abstractions/devices/responses/device.response.ts @@ -7,6 +7,9 @@ export class DeviceResponse extends BaseResponse { identifier: string; type: DeviceType; creationDate: string; + encryptedUserKey: string; + encryptedPublicKey: string; + encryptedPrivateKey: string; constructor(response: any) { super(response); @@ -15,5 +18,8 @@ export class DeviceResponse extends BaseResponse { this.identifier = this.getResponseProperty("Identifier"); this.type = this.getResponseProperty("Type"); this.creationDate = this.getResponseProperty("CreationDate"); + this.encryptedUserKey = this.getResponseProperty("EncryptedUserKey"); + this.encryptedPublicKey = this.getResponseProperty("EncryptedPublicKey"); + this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey"); } } diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index ac5bb70596..04cfb609fe 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -17,7 +17,7 @@ import { ServerConfigData } from "../models/data/server-config.data"; import { Account, AccountSettingsSettings } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; import { StorageOptions } from "../models/domain/storage-options"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WindowState } from "../models/domain/window-state"; import { GeneratedPasswordHistory } from "../tools/generator/password"; import { SendData } from "../tools/send/models/data/send.data"; @@ -163,6 +163,8 @@ export abstract class StateService { setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise; getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise; setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise; + getDeviceKey: (options?: StorageOptions) => Promise; + setDeviceKey: (value: DeviceKey, options?: StorageOptions) => Promise; getEmail: (options?: StorageOptions) => Promise; setEmail: (value: string, options?: StorageOptions) => Promise; getEmailVerified: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts index a7988d41d7..db98a17b42 100644 --- a/libs/common/src/models/domain/account.ts +++ b/libs/common/src/models/domain/account.ts @@ -23,7 +23,7 @@ import { EventData } from "../data/event.data"; import { ServerConfigData } from "../data/server-config.data"; import { EncString } from "./enc-string"; -import { SymmetricCryptoKey } from "./symmetric-crypto-key"; +import { DeviceKey, SymmetricCryptoKey } from "./symmetric-crypto-key"; export class EncryptionPair { encrypted?: TEncrypted; @@ -107,6 +107,7 @@ export class AccountKeys { string, SymmetricCryptoKey >(); + deviceKey?: DeviceKey; organizationKeys?: EncryptionPair< { [orgId: string]: EncryptedOrganizationKeyData }, Record diff --git a/libs/common/src/models/domain/symmetric-crypto-key.ts b/libs/common/src/models/domain/symmetric-crypto-key.ts index 3bd329dd21..8c9920d131 100644 --- a/libs/common/src/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/models/domain/symmetric-crypto-key.ts @@ -1,4 +1,4 @@ -import { Jsonify } from "type-fest"; +import { Jsonify, Opaque } from "type-fest"; import { EncryptionType } from "../../enums"; import { Utils } from "../../misc/utils"; @@ -75,3 +75,6 @@ export class SymmetricCryptoKey { return SymmetricCryptoKey.fromString(obj?.keyB64); } } + +// Setup all separate key types as opaque types +export type DeviceKey = Opaque; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index e02bfb743f..59e3755a04 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1110,14 +1110,6 @@ export class ApiService implements ApiServiceAbstraction { return new DeviceVerificationResponse(r); } - async getKnownDevice(email: string, deviceIdentifier: string): Promise { - const r = await this.send("GET", "/devices/knowndevice", null, false, true, null, (headers) => { - headers.set("X-Device-Identifier", deviceIdentifier); - headers.set("X-Request-Email", Utils.fromUtf8ToUrlB64(email)); - }); - return r as boolean; - } - // Emergency Access APIs async getEmergencyAccessTrusted(): Promise> { diff --git a/libs/common/src/services/device-crypto.service.implementation.ts b/libs/common/src/services/device-crypto.service.implementation.ts new file mode 100644 index 0000000000..ba50e300b4 --- /dev/null +++ b/libs/common/src/services/device-crypto.service.implementation.ts @@ -0,0 +1,85 @@ +import { AppIdService } from "../abstractions/appId.service"; +import { CryptoService } from "../abstractions/crypto.service"; +import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; +import { DeviceCryptoServiceAbstraction } from "../abstractions/device-crypto.service.abstraction"; +import { DevicesApiServiceAbstraction } from "../abstractions/devices/devices-api.service.abstraction"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; +import { EncryptService } from "../abstractions/encrypt.service"; +import { StateService } from "../abstractions/state.service"; +import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../types/csprng"; + +export class DeviceCryptoService implements DeviceCryptoServiceAbstraction { + constructor( + protected cryptoFunctionService: CryptoFunctionService, + protected cryptoService: CryptoService, + protected encryptService: EncryptService, + protected stateService: StateService, + protected appIdService: AppIdService, + protected devicesApiService: DevicesApiServiceAbstraction + ) {} + + async trustDevice(): Promise { + // Attempt to get user symmetric key + const userSymKey: SymmetricCryptoKey = await this.cryptoService.getEncKey(); + + // If user symmetric key is not found, throw error + if (!userSymKey) { + throw new Error("User symmetric key not found"); + } + + // Generate deviceKey + const deviceKey = await this.makeDeviceKey(); + + // Generate asymmetric RSA key pair: devicePrivateKey, devicePublicKey + const [devicePublicKey, devicePrivateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair( + 2048 + ); + + const [ + devicePublicKeyEncryptedUserSymKey, + userSymKeyEncryptedDevicePublicKey, + deviceKeyEncryptedDevicePrivateKey, + ] = await Promise.all([ + // Encrypt user symmetric key with the DevicePublicKey + this.cryptoService.rsaEncrypt(userSymKey.encKey, devicePublicKey), + + // Encrypt devicePublicKey with user symmetric key + this.encryptService.encrypt(devicePublicKey, userSymKey), + + // Encrypt devicePrivateKey with deviceKey + this.encryptService.encrypt(devicePrivateKey, deviceKey), + ]); + + // Send encrypted keys to server + const deviceIdentifier = await this.appIdService.getAppId(); + return this.devicesApiService.updateTrustedDeviceKeys( + deviceIdentifier, + devicePublicKeyEncryptedUserSymKey.encryptedString, + userSymKeyEncryptedDevicePublicKey.encryptedString, + deviceKeyEncryptedDevicePrivateKey.encryptedString + ); + } + + async getDeviceKey(): Promise { + // Check if device key is already stored + const existingDeviceKey = await this.stateService.getDeviceKey(); + + if (existingDeviceKey != null) { + return existingDeviceKey; + } else { + return this.makeDeviceKey(); + } + } + + private async makeDeviceKey(): Promise { + // Create 512-bit device key + const randomBytes: CsprngArray = await this.cryptoFunctionService.randomBytes(64); + const deviceKey = new SymmetricCryptoKey(randomBytes) as DeviceKey; + + // Save device key in secure storage + await this.stateService.setDeviceKey(deviceKey); + + return deviceKey; + } +} diff --git a/libs/common/src/services/device-crypto.service.spec.ts b/libs/common/src/services/device-crypto.service.spec.ts new file mode 100644 index 0000000000..7e14961cc2 --- /dev/null +++ b/libs/common/src/services/device-crypto.service.spec.ts @@ -0,0 +1,317 @@ +import { mock, mockReset } from "jest-mock-extended"; + +import { AppIdService } from "../abstractions/appId.service"; +import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; +import { DevicesApiServiceAbstraction } from "../abstractions/devices/devices-api.service.abstraction"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; +import { EncryptService } from "../abstractions/encrypt.service"; +import { StateService } from "../abstractions/state.service"; +import { EncryptionType } from "../enums/encryption-type.enum"; +import { EncString } from "../models/domain/enc-string"; +import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { CryptoService } from "../services/crypto.service"; +import { CsprngArray } from "../types/csprng"; + +import { DeviceCryptoService } from "./device-crypto.service.implementation"; + +describe("deviceCryptoService", () => { + let deviceCryptoService: DeviceCryptoService; + + const cryptoFunctionService = mock(); + const cryptoService = mock(); + const encryptService = mock(); + const stateService = mock(); + const appIdService = mock(); + const devicesApiService = mock(); + + beforeEach(() => { + mockReset(cryptoFunctionService); + mockReset(encryptService); + mockReset(stateService); + mockReset(appIdService); + mockReset(devicesApiService); + + deviceCryptoService = new DeviceCryptoService( + cryptoFunctionService, + cryptoService, + encryptService, + stateService, + appIdService, + devicesApiService + ); + }); + + it("instantiates", () => { + expect(deviceCryptoService).not.toBeFalsy(); + }); + + describe("Trusted Device Encryption", () => { + const deviceKeyBytesLength = 64; + const userSymKeyBytesLength = 64; + + describe("getDeviceKey", () => { + let mockRandomBytes: CsprngArray; + let mockDeviceKey: SymmetricCryptoKey; + let existingDeviceKey: DeviceKey; + let stateSvcGetDeviceKeySpy: jest.SpyInstance; + let makeDeviceKeySpy: jest.SpyInstance; + + beforeEach(() => { + mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray; + mockDeviceKey = new SymmetricCryptoKey(mockRandomBytes); + existingDeviceKey = new SymmetricCryptoKey( + new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray + ) as DeviceKey; + + stateSvcGetDeviceKeySpy = jest.spyOn(stateService, "getDeviceKey"); + makeDeviceKeySpy = jest.spyOn(deviceCryptoService as any, "makeDeviceKey"); + }); + + it("gets a device key when there is not an existing device key", async () => { + stateSvcGetDeviceKeySpy.mockResolvedValue(null); + makeDeviceKeySpy.mockResolvedValue(mockDeviceKey); + + const deviceKey = await deviceCryptoService.getDeviceKey(); + + expect(stateSvcGetDeviceKeySpy).toHaveBeenCalledTimes(1); + expect(makeDeviceKeySpy).toHaveBeenCalledTimes(1); + + expect(deviceKey).not.toBeNull(); + expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey); + expect(deviceKey).toEqual(mockDeviceKey); + }); + + it("returns the existing device key without creating a new one when there is an existing device key", async () => { + stateSvcGetDeviceKeySpy.mockResolvedValue(existingDeviceKey); + + const deviceKey = await deviceCryptoService.getDeviceKey(); + + expect(stateSvcGetDeviceKeySpy).toHaveBeenCalledTimes(1); + expect(makeDeviceKeySpy).not.toHaveBeenCalled(); + + expect(deviceKey).not.toBeNull(); + expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey); + expect(deviceKey).toEqual(existingDeviceKey); + }); + }); + + describe("makeDeviceKey", () => { + it("creates a new non-null 64 byte device key, securely stores it, and returns it", async () => { + const mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray; + + const cryptoFuncSvcRandomBytesSpy = jest + .spyOn(cryptoFunctionService, "randomBytes") + .mockResolvedValue(mockRandomBytes); + + const stateSvcSetDeviceKeySpy = jest.spyOn(stateService, "setDeviceKey"); + + // TypeScript will allow calling private methods if the object is of type 'any' + // This is a hacky workaround, but it allows for cleaner tests + const deviceKey = await (deviceCryptoService as any).makeDeviceKey(); + + expect(cryptoFuncSvcRandomBytesSpy).toHaveBeenCalledTimes(1); + expect(cryptoFuncSvcRandomBytesSpy).toHaveBeenCalledWith(deviceKeyBytesLength); + + expect(deviceKey).not.toBeNull(); + expect(deviceKey).toBeInstanceOf(SymmetricCryptoKey); + + expect(stateSvcSetDeviceKeySpy).toHaveBeenCalledTimes(1); + expect(stateSvcSetDeviceKeySpy).toHaveBeenCalledWith(deviceKey); + }); + }); + + describe("trustDevice", () => { + let mockDeviceKeyRandomBytes: CsprngArray; + let mockDeviceKey: DeviceKey; + + let mockUserSymKeyRandomBytes: CsprngArray; + let mockUserSymKey: SymmetricCryptoKey; + + const deviceRsaKeyLength = 2048; + let mockDeviceRsaKeyPair: [ArrayBuffer, ArrayBuffer]; + let mockDevicePrivateKey: ArrayBuffer; + let mockDevicePublicKey: ArrayBuffer; + let mockDevicePublicKeyEncryptedUserSymKey: EncString; + let mockUserSymKeyEncryptedDevicePublicKey: EncString; + let mockDeviceKeyEncryptedDevicePrivateKey: EncString; + + const mockDeviceResponse: DeviceResponse = new DeviceResponse({ + Id: "mockId", + Name: "mockName", + Identifier: "mockIdentifier", + Type: "mockType", + CreationDate: "mockCreationDate", + }); + + const mockDeviceId = "mockDeviceId"; + + let makeDeviceKeySpy: jest.SpyInstance; + let rsaGenerateKeyPairSpy: jest.SpyInstance; + let cryptoSvcGetEncKeySpy: jest.SpyInstance; + let cryptoSvcRsaEncryptSpy: jest.SpyInstance; + let encryptServiceEncryptSpy: jest.SpyInstance; + let appIdServiceGetAppIdSpy: jest.SpyInstance; + let devicesApiServiceUpdateTrustedDeviceKeysSpy: jest.SpyInstance; + + beforeEach(() => { + // Setup all spies and default return values for the happy path + + mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray; + mockDeviceKey = new SymmetricCryptoKey(mockDeviceKeyRandomBytes) as DeviceKey; + + mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength).buffer as CsprngArray; + mockUserSymKey = new SymmetricCryptoKey(mockUserSymKeyRandomBytes); + + mockDeviceRsaKeyPair = [ + new ArrayBuffer(deviceRsaKeyLength), + new ArrayBuffer(deviceRsaKeyLength), + ]; + + mockDevicePublicKey = mockDeviceRsaKeyPair[0]; + mockDevicePrivateKey = mockDeviceRsaKeyPair[1]; + + mockDevicePublicKeyEncryptedUserSymKey = new EncString( + EncryptionType.Rsa2048_OaepSha1_B64, + "mockDevicePublicKeyEncryptedUserSymKey" + ); + + mockUserSymKeyEncryptedDevicePublicKey = new EncString( + EncryptionType.AesCbc256_HmacSha256_B64, + "mockUserSymKeyEncryptedDevicePublicKey" + ); + + mockDeviceKeyEncryptedDevicePrivateKey = new EncString( + EncryptionType.AesCbc256_HmacSha256_B64, + "mockDeviceKeyEncryptedDevicePrivateKey" + ); + + // TypeScript will allow calling private methods if the object is of type 'any' + makeDeviceKeySpy = jest + .spyOn(deviceCryptoService as any, "makeDeviceKey") + .mockResolvedValue(mockDeviceKey); + + rsaGenerateKeyPairSpy = jest + .spyOn(cryptoFunctionService, "rsaGenerateKeyPair") + .mockResolvedValue(mockDeviceRsaKeyPair); + + cryptoSvcGetEncKeySpy = jest + .spyOn(cryptoService, "getEncKey") + .mockResolvedValue(mockUserSymKey); + + cryptoSvcRsaEncryptSpy = jest + .spyOn(cryptoService, "rsaEncrypt") + .mockResolvedValue(mockDevicePublicKeyEncryptedUserSymKey); + + encryptServiceEncryptSpy = jest + .spyOn(encryptService, "encrypt") + .mockImplementation((plainValue, key) => { + if (plainValue === mockDevicePublicKey && key === mockUserSymKey) { + return Promise.resolve(mockUserSymKeyEncryptedDevicePublicKey); + } + if (plainValue === mockDevicePrivateKey && key === mockDeviceKey) { + return Promise.resolve(mockDeviceKeyEncryptedDevicePrivateKey); + } + }); + + appIdServiceGetAppIdSpy = jest + .spyOn(appIdService, "getAppId") + .mockResolvedValue(mockDeviceId); + + devicesApiServiceUpdateTrustedDeviceKeysSpy = jest + .spyOn(devicesApiService, "updateTrustedDeviceKeys") + .mockResolvedValue(mockDeviceResponse); + }); + + it("calls the required methods with the correct arguments and returns a DeviceResponse", async () => { + const response = await deviceCryptoService.trustDevice(); + + expect(makeDeviceKeySpy).toHaveBeenCalledTimes(1); + expect(rsaGenerateKeyPairSpy).toHaveBeenCalledTimes(1); + expect(cryptoSvcGetEncKeySpy).toHaveBeenCalledTimes(1); + + expect(cryptoSvcRsaEncryptSpy).toHaveBeenCalledTimes(1); + expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2); + + expect(appIdServiceGetAppIdSpy).toHaveBeenCalledTimes(1); + expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledTimes(1); + expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledWith( + mockDeviceId, + mockDevicePublicKeyEncryptedUserSymKey.encryptedString, + mockUserSymKeyEncryptedDevicePublicKey.encryptedString, + mockDeviceKeyEncryptedDevicePrivateKey.encryptedString + ); + + expect(response).toBeInstanceOf(DeviceResponse); + expect(response).toEqual(mockDeviceResponse); + }); + + it("throws specific error if user symmetric key is not found", async () => { + // setup the spy to return null + cryptoSvcGetEncKeySpy.mockResolvedValue(null); + // check if the expected error is thrown + await expect(deviceCryptoService.trustDevice()).rejects.toThrow( + "User symmetric key not found" + ); + + // reset the spy + cryptoSvcGetEncKeySpy.mockReset(); + + // setup the spy to return undefined + cryptoSvcGetEncKeySpy.mockResolvedValue(undefined); + // check if the expected error is thrown + await expect(deviceCryptoService.trustDevice()).rejects.toThrow( + "User symmetric key not found" + ); + }); + + const methodsToTestForErrorsOrInvalidReturns = [ + { + method: "makeDeviceKey", + spy: () => makeDeviceKeySpy, + errorText: "makeDeviceKey error", + }, + { + method: "rsaGenerateKeyPair", + spy: () => rsaGenerateKeyPairSpy, + errorText: "rsaGenerateKeyPair error", + }, + { + method: "getEncKey", + spy: () => cryptoSvcGetEncKeySpy, + errorText: "getEncKey error", + }, + { + method: "rsaEncrypt", + spy: () => cryptoSvcRsaEncryptSpy, + errorText: "rsaEncrypt error", + }, + { + method: "encryptService.encrypt", + spy: () => encryptServiceEncryptSpy, + errorText: "encryptService.encrypt error", + }, + ]; + + describe.each(methodsToTestForErrorsOrInvalidReturns)( + "trustDevice error handling and invalid return testing", + ({ method, spy, errorText }) => { + // ensures that error propagation works correctly + it(`throws an error if ${method} fails`, async () => { + const methodSpy = spy(); + methodSpy.mockRejectedValue(new Error(errorText)); + await expect(deviceCryptoService.trustDevice()).rejects.toThrow(errorText); + }); + + test.each([null, undefined])( + `throws an error if ${method} returns %s`, + async (invalidValue) => { + const methodSpy = spy(); + methodSpy.mockResolvedValue(invalidValue); + await expect(deviceCryptoService.trustDevice()).rejects.toThrow(); + } + ); + } + ); + }); + }); +}); diff --git a/libs/common/src/services/devices/devices-api.service.implementation.ts b/libs/common/src/services/devices/devices-api.service.implementation.ts new file mode 100644 index 0000000000..aa0d0f0c29 --- /dev/null +++ b/libs/common/src/services/devices/devices-api.service.implementation.ts @@ -0,0 +1,64 @@ +import { DevicesApiServiceAbstraction } from "../../abstractions/devices/devices-api.service.abstraction"; +import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; +import { Utils } from "../../misc/utils"; +import { ApiService } from "../api.service"; + +import { TrustedDeviceKeysRequest } from "./requests/trusted-device-keys.request"; + +export class DevicesApiServiceImplementation implements DevicesApiServiceAbstraction { + constructor(private apiService: ApiService) {} + + async getKnownDevice(email: string, deviceIdentifier: string): Promise { + const r = await this.apiService.send( + "GET", + "/devices/knowndevice", + null, + false, + true, + null, + (headers) => { + headers.set("X-Device-Identifier", deviceIdentifier); + headers.set("X-Request-Email", Utils.fromUtf8ToUrlB64(email)); + } + ); + return r as boolean; + } + + /** + * Get device by identifier + * @param deviceIdentifier - client generated id (not device id in DB) + */ + async getDeviceByIdentifier(deviceIdentifier: string): Promise { + const r = await this.apiService.send( + "GET", + `/devices/identifier/${deviceIdentifier}`, + null, + true, + true + ); + return new DeviceResponse(r); + } + + async updateTrustedDeviceKeys( + deviceIdentifier: string, + devicePublicKeyEncryptedUserSymKey: string, + userSymKeyEncryptedDevicePublicKey: string, + deviceKeyEncryptedDevicePrivateKey: string + ): Promise { + const request = new TrustedDeviceKeysRequest( + devicePublicKeyEncryptedUserSymKey, + userSymKeyEncryptedDevicePublicKey, + deviceKeyEncryptedDevicePrivateKey + ); + + const result = await this.apiService.send( + "PUT", + `/devices/${deviceIdentifier}/keys`, + request, + true, + true + ); + + return new DeviceResponse(result); + } +} diff --git a/libs/common/src/services/devices/requests/trusted-device-keys.request.ts b/libs/common/src/services/devices/requests/trusted-device-keys.request.ts new file mode 100644 index 0000000000..da89de975b --- /dev/null +++ b/libs/common/src/services/devices/requests/trusted-device-keys.request.ts @@ -0,0 +1,7 @@ +export class TrustedDeviceKeysRequest { + constructor( + public encryptedUserKey: string, + public encryptedPublicKey: string, + public encryptedPrivateKey: string + ) {} +} diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index 4a1592e733..dc32c688e3 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -35,7 +35,7 @@ import { EncString } from "../models/domain/enc-string"; import { GlobalState } from "../models/domain/global-state"; import { State } from "../models/domain/state"; import { StorageOptions } from "../models/domain/storage-options"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WindowState } from "../models/domain/window-state"; import { GeneratedPasswordHistory } from "../tools/generator/password"; import { SendData } from "../tools/send/models/data/send.data"; @@ -1054,6 +1054,32 @@ export class StateService< : await this.secureStorageService.save(DDG_SHARED_KEY, value, options); } + async getDeviceKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()); + + if (options?.userId == null) { + return null; + } + + const account = await this.getAccount(options); + + return account?.keys?.deviceKey as DeviceKey; + } + + async setDeviceKey(value: DeviceKey, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()); + + if (options?.userId == null) { + return; + } + + const account = await this.getAccount(options); + + account.keys.deviceKey = value; + + await this.saveAccount(account, options); + } + async getEmail(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) @@ -2751,7 +2777,10 @@ export class StateService< // settings persist even on reset, and are not effected by this method protected resetAccount(account: TAccount) { - const persistentAccountInformation = { settings: account.settings }; + const persistentAccountInformation = { + settings: account.settings, + keys: { deviceKey: account.keys.deviceKey }, + }; return Object.assign(this.createAccount(), persistentAccountInformation); } @@ -2830,7 +2859,7 @@ export class StateService< return this.reconcileOptions(options, defaultOptions); } - private async saveSecureStorageKey( + protected async saveSecureStorageKey( key: string, value: T, options?: StorageOptions diff --git a/libs/common/src/types/csprng.d.ts b/libs/common/src/types/csprng.d.ts index b62f8b37a6..ec0a31a9f7 100644 --- a/libs/common/src/types/csprng.d.ts +++ b/libs/common/src/types/csprng.d.ts @@ -1,5 +1,9 @@ import { Opaque } from "type-fest"; +// You would typically use these types when you want to create a type that +// represents an array or string value generated from a +// cryptographic secure pseudorandom number generator (CSPRNG) + type CsprngArray = Opaque; type CsprngString = Opaque;