From cf1d82ac66b0c5c6e9989dc18c6ba06225c0868f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 16 Dec 2021 14:10:11 +0100 Subject: [PATCH 01/10] Add .git-blame-ignore-revs and prettier instructions (#585) --- .git-blame-ignore-revs | 1 + README.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..b2c2a6a4 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +193434461dbd9c48fe5dcbad95693470aec422ac diff --git a/README.md b/README.md index fed8186b..95529e3f 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,23 @@ Common code referenced across Bitwarden JavaScript projects. - _Microsoft Build Tools 2015_ in Visual Studio Installer - [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) either by downloading it seperately or through the Visual Studio Installer. + +## Prettier + +We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps: + +1. Check out your local Branch +2. Run `git merge 8b2dfc6cdcb8ff5b604364c2ea6d343473aee7cd` +3. Resolve any merge conflicts, commit. +4. Run `npm prettier` +5. Commit +6. Run `git merge -Xours 193434461dbd9c48fe5dcbad95693470aec422ac` +7. Push + +### Git diff + +We also recommend that you configure git to ignore the prettier revision using: + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` From 36b3aea758eec7bf3e85aaa81fff3d207bc671cb Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Thu, 16 Dec 2021 14:38:11 +0100 Subject: [PATCH 02/10] Update README.md (#586) * Update README.md * Replace header Git diff with Git blame --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95529e3f..ce5cb1f1 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ We recently migrated to using Prettier as code formatter. All previous branches 1. Check out your local Branch 2. Run `git merge 8b2dfc6cdcb8ff5b604364c2ea6d343473aee7cd` 3. Resolve any merge conflicts, commit. -4. Run `npm prettier` +4. Run `npm run prettier` 5. Commit 6. Run `git merge -Xours 193434461dbd9c48fe5dcbad95693470aec422ac` 7. Push -### Git diff +### Git blame We also recommend that you configure git to ignore the prettier revision using: From e9666458c483d4b895e01b241d0207e6047fb00c Mon Sep 17 00:00:00 2001 From: Linus Aarnio <42450444+linusaarnio@users.noreply.github.com> Date: Thu, 16 Dec 2021 18:41:37 +0100 Subject: [PATCH 03/10] Select an image to display for credit cards based on the brand. (#537) Co-authored-by: Hinton --- angular/src/components/icon.component.ts | 23 +++++++++ angular/src/images/cards/amex-dark.png | Bin 0 -> 773 bytes angular/src/images/cards/amex-light.png | Bin 0 -> 773 bytes angular/src/images/cards/diners_club-dark.png | Bin 0 -> 783 bytes .../src/images/cards/diners_club-light.png | Bin 0 -> 713 bytes angular/src/images/cards/discover-dark.png | Bin 0 -> 808 bytes angular/src/images/cards/discover-light.png | Bin 0 -> 830 bytes angular/src/images/cards/jcb-dark.png | Bin 0 -> 836 bytes angular/src/images/cards/jcb-light.png | Bin 0 -> 798 bytes angular/src/images/cards/maestro-dark.png | Bin 0 -> 752 bytes angular/src/images/cards/maestro-light.png | Bin 0 -> 820 bytes angular/src/images/cards/mastercard-dark.png | Bin 0 -> 737 bytes angular/src/images/cards/mastercard-light.png | Bin 0 -> 757 bytes angular/src/images/cards/union_pay-dark.png | Bin 0 -> 1255 bytes angular/src/images/cards/union_pay-light.png | Bin 0 -> 1245 bytes angular/src/images/cards/visa-dark.png | Bin 0 -> 548 bytes angular/src/images/cards/visa-light.png | Bin 0 -> 590 bytes angular/src/scss/icons.scss | 44 ++++++++++++++++++ 18 files changed, 67 insertions(+) create mode 100644 angular/src/images/cards/amex-dark.png create mode 100644 angular/src/images/cards/amex-light.png create mode 100644 angular/src/images/cards/diners_club-dark.png create mode 100644 angular/src/images/cards/diners_club-light.png create mode 100644 angular/src/images/cards/discover-dark.png create mode 100644 angular/src/images/cards/discover-light.png create mode 100644 angular/src/images/cards/jcb-dark.png create mode 100644 angular/src/images/cards/jcb-light.png create mode 100644 angular/src/images/cards/maestro-dark.png create mode 100644 angular/src/images/cards/maestro-light.png create mode 100644 angular/src/images/cards/mastercard-dark.png create mode 100644 angular/src/images/cards/mastercard-light.png create mode 100644 angular/src/images/cards/union_pay-dark.png create mode 100644 angular/src/images/cards/union_pay-light.png create mode 100644 angular/src/images/cards/visa-dark.png create mode 100644 angular/src/images/cards/visa-light.png create mode 100644 angular/src/scss/icons.scss diff --git a/angular/src/components/icon.component.ts b/angular/src/components/icon.component.ts index a32948cb..d30ce20e 100644 --- a/angular/src/components/icon.component.ts +++ b/angular/src/components/icon.component.ts @@ -18,6 +18,21 @@ const IconMap: any = { "fa-apple": String.fromCharCode(0xf179), }; +/** + * Provides a mapping from supported card brands to + * the filenames of icon that should be present in images/cards folder of clients. + */ +const cardIcons: Record = { + Visa: "card-visa", + Mastercard: "card-mastercard", + Amex: "card-amex", + Discover: "card-discover", + "Diners Club": "card-diners-club", + JCB: "card-jcb", + Maestro: "card-maestro", + UnionPay: "card-union-pay", +}; + @Component({ selector: "app-vault-icon", templateUrl: "icon.component.html", @@ -59,6 +74,7 @@ export class IconComponent implements OnChanges { break; case CipherType.Card: this.icon = "fa-credit-card"; + this.setCardIcon(); break; case CipherType.Identity: this.icon = "fa-id-card-o"; @@ -102,4 +118,11 @@ export class IconComponent implements OnChanges { this.image = null; } } + + private setCardIcon() { + const brand = this.cipher.card.brand; + if (this.imageEnabled && brand in cardIcons) { + this.icon = "credit-card-icon " + cardIcons[brand]; + } + } } diff --git a/angular/src/images/cards/amex-dark.png b/angular/src/images/cards/amex-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1b075975dbcdcd2f24465c3718497d2999758f GIT binary patch literal 773 zcmV+g1N!`lP)>;Q4j|NsABrr;oh)PT6=-|hK8l-j@C z^B{uM5PH$?_WYp9?Vrl*&*b(ni`Rd+=Wws&kiqG%)9_H8-A|p}&*k-x!0H5^x&i~zSRde&X?s-=3?091tkTsX)KDa=@Si@{V$T40&LuDFL=X zo~sLB;(Bg)3WWIq?#>oIbYoQ2)YbQNHoD-%APS-*sVC1(&xPd(8>}sJ1{!6eLZ3SeOQ5b@feB@j=lru%v)@Ma^kHhP{m>#!QtLmhL-IbQ2{yRfxkICV1qz`*b2x1&dwRJ z=2YsF94mz@VmbH$6!4j-59YoTlp@CgcU|UB5q4A_lzx!9JU82UT_QyCqMTvkEvXG)qRPCMiV9_MQ4V&2c}khGNQ zMfZu?_JW@KI8k+KlIiqLyZNEINZTjX;k|!D@BM!O8;ZSS7+?&300000NkvXXu0mjf D##w7Z literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/amex-light.png b/angular/src/images/cards/amex-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ac1b075975dbcdcd2f24465c3718497d2999758f GIT binary patch literal 773 zcmV+g1N!`lP)>;Q4j|NsABrr;oh)PT6=-|hK8l-j@C z^B{uM5PH$?_WYp9?Vrl*&*b(ni`Rd+=Wws&kiqG%)9_H8-A|p}&*k-x!0H5^x&i~zSRde&X?s-=3?091tkTsX)KDa=@Si@{V$T40&LuDFL=X zo~sLB;(Bg)3WWIq?#>oIbYoQ2)YbQNHoD-%APS-*sVC1(&xPd(8>}sJ1{!6eLZ3SeOQ5b@feB@j=lru%v)@Ma^kHhP{m>#!QtLmhL-IbQ2{yRfxkICV1qz`*b2x1&dwRJ z=2YsF94mz@VmbH$6!4j-59YoTlp@CgcU|UB5q4A_lzx!9JU82UT_QyCqMTvkEvXG)qRPCMiV9_MQ4V&2c}khGNQ zMfZu?_JW@KI8k+KlIiqLyZNEINZTjX;k|!D@BM!O8;ZSS7+?&300000NkvXXu0mjf D##w7Z literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/diners_club-dark.png b/angular/src/images/cards/diners_club-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d23233eb868ab2853671392b7aabcd55d6a0b9 GIT binary patch literal 783 zcmV+q1MvKbP)?x` z5oetiUye1B%E8|6qRi)myxvu!(kDSxqRr!V_ec7GI5Yv)ZT^a|59>N7}NDvq&D< zY8hzFoQ3iM&45xKy~ZVA%KSCq?}m8P7{NtKc{>I~i_i6Yp!(1#+0IodLP0E~tWZ*d zXcU@ngo>+)Py~>Slb|yxho%sudlR4tgy#;%BT{6A5(v=gO9APf1*!qQcYX2O zPrs9Q2$i)NdIRaU@g*q01F1;&gwnwXaS&cNAP9Be`lNH*1iisebPz%*BPAK2wEMGw zzacoPk^sZUAr41+VG6;%Z5$Cw#-Rwp_PU}6VM!tB+c+bN9#Nvaz-qGgslf>gyqSE>L`_Sg|veoOM(@A3h000Pd zQchC<-_O5*Umy^g&-vK^00Iq3L_t(o!|m5=bE_Z_24I>?;}W?DqN$g*|NmDWJ44tV zP)QD%IWuSaeoG8bc2N--W0%vB7v*qfKY4%S#d&*QpvAdei*j3}3p;cESrz7!^AkJp zTUDGJKQhj*<71flS)B+ZFENP$BKA(VPj(helU;1(!^!z5tD zHuMb*`C|%&bI>3(Y(vhURoO-mmNb#xwxPzCt098$lK_m|gj#=0twA5jBzMvFyOdqB-jcn7!D21@OZcCc!T9k!k2VLkcIuO#yJqQQb6-l(D9{JdA#HD)| z=^%PuXCU0fC=qSDLOG(!Krv7s?P^CsQ~xjf?0*LxU-Q0p vV5ir-L7v&=HSe@9e9K;PK9n)X(}l4=&6Si7S+2pm00000NkvXXu0mjfGeTH0 literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/discover-dark.png b/angular/src/images/cards/discover-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed6f6f942264a62e11813df4e2ed85411705f81 GIT binary patch literal 808 zcmV+@1K0eCP)u#OXKj6ehL@S1%goNPv$pc`^GQxsUPLxYO;tQWNxr|rzGW$nRWMdsVLLcG z;&LR|+1qwaGn!d0)@~-t%+HF9l1Mx`aCCX5T`Z)gs%l9!f>AQEVJymOC~HYIN0}H2 z0000HbW%=J0N=lrU?33h*Ux~@uYi9*5C%cQNdN!>DoI2^R9M69*I9GgKnw@)4JG90 zDQhp*HU?9h`$+ozzgs1F92aIN>8u}`8U7E9VLiW<iXpDDP!fh=-#3ZT*C8YyGqgZv6GOC$?$=ouQ*Dson!<(6^_E#!@@Fr*CaNzB(N z_(6=rj(7x!Z#2X00nwpD6oKbU+!I7mwl?7=#M-7r$2kjuBp@j)UGDwY8g}6O~ zuz?`A8bgkufsTJd4hw0yt0-V)-~^K18KM|6LvsmP2i4SY(A;u2BO5|_3YmbGAxqa= z8kdvv02ZdLvz+sQWFj48*3@bT>7t^RWkI9%p&2UrB52sOkL_1et8F0f{a!loxbGm! z%0hP_r5G0w`7CUvQcnh)N&}@kkcJQ3x`ED9ni85A!yr+0oxT0^q2B)g3Yzx>Wy@Zm z`7TLR56tXm(g$?llOGRd?W?HbnRrN;c&H{O9+D>&4`mBtqM`Ybdy0mnm}n@I$;3jN z*)+MF2ld`sUX#D(Q4Ow7_ds*GmQU*WN-5IN^?X;OyNi`%$7x&2)q*bl%ih!rLEi01 mhEHe4%Rd+ok6*t2Amlgxq9NYlwX%u;0000P)X8-^I3v^OWQvlz;AP~=hU|-LFw=t%^0006pNkl8nPdRNEhFD|LcS z&JERB#cS1V5UmNv)Y zbB7y#_Xryky@H~QtrTgOWYQ()GNt(8b_-4A0n(D4W~XfTpz%1bw?AjCS=uPupJ|%b z&Oq4BR&KW+7a4c|gqU55S=PU<$gW`S|OP;Vri@r8P`8!X8es^{Vf6$9zZyHMlJ^V-u03wl2<>LO?zIzje| z(HzJ}vQX+OXs*w+`+5BK{raQ%@q#n$5l`93<%J*9Xf=c2Ck1=YrAl7PApigX07*qo IM6N<$g0_C7tN;K2 literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/jcb-dark.png b/angular/src/images/cards/jcb-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb484d91e5a7302b07365ebbfe47e8cd06f4633 GIT binary patch literal 836 zcmV-K1H1f*P)u$J(B_rB+}PRMcB#|L%+5)T z!?Clr=c}`qnVyP`lG0^#V`ptVK}o7<>d^oI01I?dPE!Ei&%a*~fFK}%e^WtB#sB~T z{z*hZR9M69*Vl5JKoo%CwTe?X0z-rWv9>}-y~~bUdQaZ}b+Sv5V82=x|NGm@)Pg8+p&bqsbplcXx#CjubyPYgV9UcfYk%8_y zdB~J_pjYsPfpCX`W}J}6Kob_~azY{lsZY=Yf;JjyOd&T^EsSw*;{S9Av^`4;xF8Kk zeHkXo3U>7fl*wF{fTC8)!dZkqPN0Bn{gw{1k`PY!|2&3H$&qY2oRx&;owPkPqk~-% zav4b1^)-D(H&P`iZKMZ0(EAK@chAx65gE1|&=5$MG34I^N$Y4ThK!9zoj%ay7y>0i z+$)87BIp)Fq_>7P0NSM}gN5mYC#q`gK1h=U(Qx2snh_%~{4kAoZEislR`OrAZXbYT zQS{{LRQ57U>xA>GdAM++cNBD!Zpnfih7NE4skG|}suh~DkpGX+n1y-_bjt~84CDX< z1@M~(l7WE~2C~g}0tX}y8AyRVv|PjW-yyF*fE={=203VXW5QRV2r|-!5QTe57EU%b z%^bnSkHWv&lTtmfANKzOYS!a4&~ZIb3$^RH)7E#(rnMT?nC6Q%LVo~LqTQs{{F>_k O0000#@5ju-+3QJ-!_HS{hO^jkymjaF=)x{Rq{iWJymsO2_4niE zh{ufc_W1kp^7GZ%x8Az*`t;M1&40KXXQvlz;AP|7hU|-MAf0kZ9oB#j;>`6pHR9M69*I9F_FciS?XtnLV z_e4=-5f@qn#9iywy}et%|3~VHk)~z_3ylx$wEq_d9)6QB90mv>gXnsAZbwlx@+>2)Po(>qRR~5t>F( z)K0sitK_ZmS6h!st7J&$4i`rJ(21Ay_zJQByHAFoK6La8#{4B+XsX$t+=VL?Sb^Srr*>= z-_VDI92qhs=qV*Ml%cK!wNgT!1g$}W{-%SPunV~z2^zy*NI1LDx(mCI-%Ex@PTa5# z*sKw^ybb{yx8iQB&-Z!!5uo5OP@eYHp#eQ6#l!qTo{~b2(m_R~pigP;aDUmXlVdd| cUn~&v3^hLOTPN|%xBvhE07*qoM6N<$f=djcb^rhX literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/maestro-dark.png b/angular/src/images/cards/maestro-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4879b56fc47f68985a2e8e49a4cccbce928393 GIT binary patch literal 752 zcmV> z001vDJuomkFfc$bG(0afJuWmnFEc$aGd(ahIxjRmFfu(YGCeRbIxjRm>i`=7q2G3M zz(y@O29wLY4I?8@Wzz;91De$=J4fIH9!rDEG>y?aiqHm=%5QeU35~!Heyo#MmmOVs zDMC?!9WK-d9}k<`9hKKuf5;4mwh?%u6mggrYK|IXgrZ1-sX%wo8bCTQJ6000kkQchDq0N)@G&%c20pD^!WU@)amw738O0kug)K~z}7?bnA= z!Y}~F@un0I9O=fQGHe0avWJL!y#EtP^`I5L){c0dcl;mVcOjz*93etFMdOsrBnTlK zN$@moeAb3|8o$E?;f^VbQiMxVCJ7v6QNnKb0po-17%PCY>rSGZcPmrRCyFE+0H4R@#$pNP;UjRw90Oadj z%QBV>Z)gUbSK00yNM%oG1e`nhvtkWM3lB&UL38=5I)F|M3{8O3lh<7Vsf{2!pqmYJ zkA?)HA)KL2MG2%MXbPY=2ZFz?g`gSSLr!Q1ln^9>E?v+Wnj`=zQ6MQO^x%T(aiDnw zs1LVMggK(s2&>ir5IiBb;r9{V+4(3tyPrqv?>j{WeEP?}r@t2X^iu^6$!|bbh%aQ% z=L>0iezP^SaGq=5r20#kTcqo8ixdnt_gcvcf73pNxM`g#+k?{H>9VkW#ySCBBg({0 zX#;9!mfTF+2=zY7SKaTUWv6UoYPqHV=cnHu`W-061`@b56DLDBfoI?xB-s4RJ~$yO ih>)XXEQU`DE93*8%LLh0NtU<(0000+1?I1V(-{bt>;_yIJzvuVw`TY>5;Y)+d><}sZ{Qcka`}OwspxpE8?(pK~=}^Au zFtg<}jnO-b&~J9a_K=&CSC^|kb^h=1kk;`)xaSX>+Z~nH`oG5YfQ_O^g7j~H@lRs! zFhwo zuuN$}hfD2)nsLA)hVx?n@xp;VYbe-0g0bXAh1T6I@*z8S?FlVzEe zT1H5yP){0rcTG_}|VDgy-G~Tmg9mNg#VJXblY#fRe!wNLc9A1)cLi zoe7``wkJqyT9VQ`_X~a#848J71l@4-| y2vMu2O>iV*_q5aLvghqCn~>!gU$edx?IB;CRP+I%fo)a*0000> z001vDJuomkFfc$bGd(XfJuovmFEl(aGd(UeJuWgmFElX#ixEjV=`Fq3I4 zpl~YQkss3rAKwEV?F<+ChY|Wm2mNmb{Zj+}Spi{AG=^d>xqBtB5GC;$6I4ewpKmJC ziXrnR5c+}-_A(6ocnkMC3T;&~Y9un57b?gKA?<_}ZZap!&177zwSmyIh+GW;f}L zH?e;zXKKJK&n!e;#7#D%6Zeud1V)pS2iKFawaBssz%5sH5Bsn TcbISa00000NkvXXu0mjfUKKLh literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/mastercard-light.png b/angular/src/images/cards/mastercard-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f87ae25911d375f66a2f00dd473a6392abfee7fa GIT binary patch literal 757 zcmVh16MpsDtdoArQ=_^LPUAUEs~Dfc!D`bP)-ZwCES1O8b7{pZH~)tmdyjqy-o`nzHC zDHib=6aMYg^?!}|utxlN3;p4^_^-J3kDL3?jr4GT^JZ`FFh=^2AqhP(q5uE@3UpFV zQvl!3Umy^_fWLpPic@C*00GfSL_t(o!|m7EQo=A42H*e!N_%L!XbZJPcEN>Ra9`g4 zp=_A}g#HOgxX>B?%UpbubJC;EY3-al#@D%pvbA$iYjSDQkG?iss+VV ziSjH>je>IObjpZ|>QIg7Fi`Ob{Q)TOTF%IF+Ara1jF-y3Y5K#yy))!FU2PGoIUlkI zroN2d-w6sWZ7rRMDpmBgn0!0b>S~+$80(1)w?S%;4fo5#)&`6z54#rZ0dT07adaObJ5)a2{eq*rke% zAbDlDwr2>n@!BtI15g=2xJ5l<6#)4VvH)brA#*QCylm>FjUn`oKBIk_-IOgq-nS(P zxd56-KS-Se2zUER+UrNWMeu1PuM9&$7i>03W!J*<=oL*#Iw1_v7a#uq{xOY+8fs}MMoYuq z++Hm;z8)q{otfe8?IMMMB{N47FkSHW_R{9%z{=Q(y}LzXjkVU)n8m|YZp!b=a9$#R?8!e{I%%;xEK$Vdv zOM4S`b@Xm?3`}koEn_26Rf!=#J2E?imb3V$s&tB~4_|WfQC3hVQSm}aD?de!B`%sH zEdB8C`_t5Ht*Rr1fE{pdB`jYpIYI9wEu5ARxcJ zu$I~Y00T2gL_t(o!|j(>bJIW+Mg>VIS?OD|uo9@tE$#+vV_FhQ=)L#ed-?wlteAmo z%Zn)X3k+w*^4l5j@m{TD7{wi>ozG7d8RQf81suWVa`}T8_@=1 zaYpun zNGH0j7aboJ9mt6G=tTSVqHUw1x8t5$?AMoe&>Cf5HjxgBJzb&#iKa`mGb7R^I`Yej zNJNQ_R;%Z&!_}+0L>1xLBI0ABb0iv1)I~MD4Ar)tNE9OqZ1hGwXJ|w+iK^(Pm!p%3 zZZHAK0z{LENJNQF;IO(}wC=vTo|s8#)hs5VX%3R`Ib#5GY%FokxWgsm6}25nlt~1f zSC=P;p}5lVYe~HUKZN3xgezRc0bIDJVfPU*wnn1yMAvZAvW6HA@wAN4N3B#cQA)5} zh791f0x8j}Krq{mY{vFv?njiUt3>B7;_eEr`zY4yA(}NbFQ-mNK&?d;m!}Xg4_IQX zkP%U$Ye4k`u39JY28|m08ZI?aYbdA3TsTU!8?giz_IcU4`2aVf3n%J!9n10ZT;Xr*}I8yc*mz?*d z{Pu$i8BzYAJ%(TIz(ZPBxE@>^?Fs_gjuRsNy{J)NUcSOQO6)#bq=*YGuMxE8C5Jp$~S#QkEMl^3M%;ueThHu%5`6=IPH_t5?#uwCV?}T=F Ra!&vN002ovPDHLkV1lCmMkoLP literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/union_pay-light.png b/angular/src/images/cards/union_pay-light.png new file mode 100644 index 0000000000000000000000000000000000000000..2d75aa9070de5b32bc5efc83b242ddec1ba135af GIT binary patch literal 1245 zcmV<31S0#1P)n4C6Ngl#2A?h15}y>)UmzK`ozYPqrCaH zxK(eK5o&Y_J!|t{WwaeJ{_yeq)YVmQm-T#s6L@!~9yXJsy@|fNWPG4SVvRD5iD_7S z5M*;RDq4dnNc`dA-pJ6Sy2p~Fyy~g0N`RC1i;uc^|GR>6WO5& zRs9d0394O><<+%h=wve1u~2O~C-URAmdLHgB%RsgAnu5yoclx)PYykbJ2$k{1<9bM z9J(2Yo}{3AGN>qrw&Kv86!iGdKTTfIixX5t3IfVhQ5OQrRFNKoGF4C z$SUek9@j}glSxQaRK?3^nUhsi1g$d^5)hJ*sAvWo(K7XWMbAkIMUEvPT@F=qxfOhl zmig(e!|hfjXlSi`Uei8px3*1EfWxt}sT0FA9b)PxnN32XqIuvOU)JWq)9457%EAKt z7I^FqKpE()Wmzy7I`9Z_rBE%Go5I6rnVZ7fr|5;)^if&H9>NC7O|X)!cR;&fgu;VC z2unhuq8$!J746_dh&yvvair%b-Vb5ojS|APz z7V3ziA5E-)hF8=^(B4D@*`y4;4#TL!F-R0k3dt+l^RXN>P^NWTClsz#aKR%gia|pw z>icN714dw3P6uusHgATC%`pxQe~Oj^tp%@xV)RAZM%e*{K7ia5G_;}$u&@af0Iy4V zQ5J*EsR6`NkhCHpRVrG=FipcGJceQNp3TAMeWai>Zu+E|hE6wVBAyi5kX0mJ*Rm9( z&`uosev+-R3n}zI4jrBZ(&f;@`FKZPPtcJohfu3diF`T8C-P2Dr#bl-{yqL@pmB9E zJG7*ZD5K|dg1n@R@iXnxM5ZkAGwnH^vU#qKWMjrh#}wr!|Gozv9!)d500000NkvXX Hu0mjfHGyZg literal 0 HcmV?d00001 diff --git a/angular/src/images/cards/visa-dark.png b/angular/src/images/cards/visa-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0705b68e7d47f7c94d982aa8ba2d26eab61b4054 GIT binary patch literal 548 zcmV+<0^9wGP)u!+K}pNZ&#|+%V`pvh^7D#} zlBA}pmzkb@fQ8uE+rGcUeSn5mT48W>d2n-i*xA~Ghql`Q000SeQchC<-@kxgAP~>b zB|4OJ0004VNklBM(ojF65jiDU+) zRNppqNsms6?l-!u+nqt+TqgCZqate6vka(FR7e$6K^0U%ML||W6vaD>1&?0aHrn(c z2U_=mW#BjRZlh@!0*AT*vVxg-GiFOW1G~+#^GviFcN`0uXooG^Bg<}H665+pb~&f> zVGpp2d>%v&nUPr2!fRqfb2?iNw8mZ-J_M)^hxm^Dg&iJw(1>adw3>Kb5X|9%g5C1{ zR~{sTnkCi<6d@WUbH>8G6ujg?@Wt$7LnK^dRFfqQ@`185AVlx6)f70IpqIhWJsY$> zVa|f|kS4sR)Tna73l(){L1+>hj=8U7y==V{dkjg(8z3}fPXm+{GBSe6vH0rW03q*| zUa9quqvHrz3le2PmU9O9JI9I7#hJ;3nUTi){-Akd|J5H`1yxW5eGsTA!+EH-45(9m mnQz*1U)iGWc@5LFy;462ua(aA6>O;h0000iOwztgq`TC|okMaNj z01R|ePE!EizaS9LfM3slU|=QdAB6w_0gFjQK~z}7?U&1zgCGos8Ef0QfaO+Et7zZ< zB`5i5-Lk;VoZ(+E5XqN>Fg*t$MEljKBAu^-b^1`n`Zy7YIMc026Omf+t<+N%>Z#O4 ze5y$nYf_C+>Q)ZADOEugR6!MVD9GHQ*NSEa13qmb^k)B%13i|Gq#ZAMnguy@U6j(< z4q}oUXwqj(TO(~7dFF{|99HBD>12m3dq#$~c@B)f8=`Zb*2g8n&hmMXw1|wrPR=y$ zY-oJtmIpo3S!hZEREs1QhmWvhM;_Fpnt-F0G|q4)cSgZ(Xuah@+^CU#HACUzK+W8g@DyV|~5vV!ly>=FtV_uL~(H`@rJ?@pw`ZOV07*qoM6N<$f@Y&2-T(jq literal 0 HcmV?d00001 diff --git a/angular/src/scss/icons.scss b/angular/src/scss/icons.scss new file mode 100644 index 00000000..4d2ea459 --- /dev/null +++ b/angular/src/scss/icons.scss @@ -0,0 +1,44 @@ +$card-icons-base: "~@bitwarden/jslib-angular/src/images/cards/"; +$card-icons: ( + "visa": $card-icons-base + "visa-light.png", + "amex": $card-icons-base + "amex-light.png", + "diners-club": $card-icons-base + "diners_club-light.png", + "discover": $card-icons-base + "discover-light.png", + "jcb": $card-icons-base + "jcb-light.png", + "maestro": $card-icons-base + "maestro-light.png", + "mastercard": $card-icons-base + "mastercard-light.png", + "union-pay": $card-icons-base + "union_pay-light.png", +); + +$card-icons-dark: ( + "visa": $card-icons-base + "visa-dark.png", + "amex": $card-icons-base + "amex-dark.png", + "diners-club": $card-icons-base + "diners_club-dark.png", + "discover": $card-icons-base + "discover-dark.png", + "jcb": $card-icons-base + "jcb-dark.png", + "maestro": $card-icons-base + "maestro-dark.png", + "mastercard": $card-icons-base + "mastercard-dark.png", + "union-pay": $card-icons-base + "union_pay-dark.png", +); + +.credit-card-icon { + display: block; // Resolves the parent container being slighly to big + height: 19px; + width: 24px; + background-size: contain; + background-repeat: no-repeat; +} + +@each $name, $url in $card-icons { + .card-#{$name} { + background-image: url("#{$url}"); + } +} + +@each $theme in $dark-icon-themes { + @each $name, $url in $card-icons-dark { + .#{$theme} .card-#{$name} { + background-image: url("#{$url}"); + } + } +} From a8168d6ee7ebcde490fb014b3c5f555485412762 Mon Sep 17 00:00:00 2001 From: Linus Aarnio <42450444+linusaarnio@users.noreply.github.com> Date: Thu, 16 Dec 2021 18:46:33 +0100 Subject: [PATCH 04/10] Fix for issue #1287 in bitwarden/web (#569) * Format the fieldvalue as a LocaleDateString instead of epoch when importing a date from 1P This would be better solved by storing it as a date FieldType instead of Text. But since it is unclear when new field types are added, this solution serves as a fix for now and also guides the solution when new fieldtype exists. * Remove trailing whitespace * Add tests for custom fields of 1pif imported identity * Change representation of 1pif imported dates to UTC string * Changes after running prettier Co-authored-by: Daniel James Smith --- .../onepasswordImporters/onepassword1PifImporter.ts | 6 +++++- .../importers/onepassword1PifImporter.spec.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts b/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts index 01ec4e59..fd202ba5 100644 --- a/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts +++ b/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts @@ -172,7 +172,11 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer { return; } - const fieldValue = field[valueKey].toString(); + // TODO: when date FieldType exists, store this as a date field type instead of formatted Text if k is 'date' + const fieldValue = + field.k === "date" + ? new Date(field[valueKey] * 1000).toUTCString() + : field[valueKey].toString(); const fieldDesignation = field[designationKey] != null ? field[designationKey].toString() : null; diff --git a/spec/common/importers/onepassword1PifImporter.spec.ts b/spec/common/importers/onepassword1PifImporter.spec.ts index ade73e99..ffa2417a 100644 --- a/spec/common/importers/onepassword1PifImporter.spec.ts +++ b/spec/common/importers/onepassword1PifImporter.spec.ts @@ -476,6 +476,19 @@ describe("1Password 1Pif Importer", () => { // remaining fields as custom fields expect(cipher.fields.length).toEqual(6); + const fields = cipher.fields; + expect(fields[0].name).toEqual("sex"); + expect(fields[0].value).toEqual("male"); + expect(fields[1].name).toEqual("birth date"); + expect(fields[1].value).toEqual("Mon, 11 Mar 2019 12:01:00 GMT"); + expect(fields[2].name).toEqual("occupation"); + expect(fields[2].value).toEqual("Engineer"); + expect(fields[3].name).toEqual("department"); + expect(fields[3].value).toEqual("IT"); + expect(fields[4].name).toEqual("job title"); + expect(fields[4].value).toEqual("Developer"); + expect(fields[5].name).toEqual("home"); + expect(fields[5].value).toEqual("+49 333 222 111"); }); it("should create password history", async () => { From 59a530045860f89bde2cd37e93f4e09d76e307b3 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Fri, 17 Dec 2021 11:24:38 -0500 Subject: [PATCH 05/10] move sso properties to globalstate (#583) * move sso properties to globalstate * whitespace * npm prettier changes --- common/src/models/domain/account.ts | 3 -- common/src/models/domain/globalState.ts | 3 ++ common/src/services/state.service.ts | 44 +++++++++++-------- common/src/services/stateMigration.service.ts | 18 ++++---- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index 22ab5ddf..98ea62df 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -95,9 +95,6 @@ export class AccountProfile { hasPremiumPersonally?: boolean; lastActive?: number; lastSync?: string; - ssoCodeVerifier?: string; - ssoOrganizationIdentifier?: string; - ssoState?: string; userId?: string; usesKeyConnector?: boolean; keyHash?: string; diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts index ad270bce..66ad5130 100644 --- a/common/src/models/domain/globalState.ts +++ b/common/src/models/domain/globalState.ts @@ -5,6 +5,9 @@ export class GlobalState { locale?: string; openAtLogin?: boolean; organizationInvitation?: any; + ssoCodeVerifier?: string; + ssoOrganizationIdentifier?: string; + ssoState?: string; rememberedEmail?: string; theme?: string; window?: Map = new Map(); diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 8fc30f8b..59c5a73a 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -1874,46 +1874,54 @@ export class StateService implements StateServiceAbstraction { } async getSsoCodeVerifier(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.ssoCodeVerifier; + return ( + await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.ssoCodeVerifier; } async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + const globals = await this.getGlobals( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + globals.ssoCodeVerifier = value; + await this.saveGlobals( + globals, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.profile.ssoCodeVerifier = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); } async getSsoOrgIdentifier(options?: StorageOptions): Promise { return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.profile?.ssoOrganizationIdentifier; + await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.ssoOrganizationIdentifier; } async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount( + const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); - account.profile.ssoOrganizationIdentifier = value; - await this.saveAccount( - account, + globals.ssoOrganizationIdentifier = value; + await this.saveGlobals( + globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getSsoState(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.ssoState; + return ( + await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.ssoState; } async setSsoState(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + const globals = await this.getGlobals( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + globals.ssoState = value; + await this.saveGlobals( + globals, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.profile.ssoState = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); } async getTheme(options?: StorageOptions): Promise { diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index bab3667f..903cc093 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -192,6 +192,15 @@ export class StateMigrationService { ), openAtLogin: await this.storageService.get(v1Keys.openAtLogin, options), organizationInvitation: await this.storageService.get("", options), + ssoCodeVerifier: await this.storageService.get( + v1Keys.ssoCodeVerifier, + options + ), + ssoOrganizationIdentifier: await this.storageService.get( + v1Keys.ssoIdentifier, + options + ), + ssoState: null, rememberedEmail: await this.storageService.get( v1Keys.rememberedEmail, options @@ -327,15 +336,6 @@ export class StateMigrationService { keyHash: await this.storageService.get(v1Keys.keyHash, options), lastActive: await this.storageService.get(v1Keys.lastActive, options), lastSync: null, - ssoCodeVerifier: await this.storageService.get( - v1Keys.ssoCodeVerifier, - options - ), - ssoOrganizationIdentifier: await this.storageService.get( - v1Keys.ssoIdentifier, - options - ), - ssoState: null, userId: userId, usesKeyConnector: null, }, From 9e263365497d669c9f7c85a324315a0597a1ea60 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Mon, 20 Dec 2021 08:48:47 -0500 Subject: [PATCH 06/10] [feat(Account Switching)] Allow for extending application state (#584) * [feat(Account Switching)] Allow for extending application state * [bug(Account Switching)] Remove hardcoded dev urls * [bug(Account Switching)] Init Account when signing in * [bug(Account Switching)] Check for state migration version in local storage for web * [bug(Account Switching)] Fix never lock configurations * [chore] Prettier merge * [bug] Move environmentUrls to global state * [chore] Ran prettier * [bug]change storage location for enityId and type * [style] Ran prettier Co-authored-by: Robyn MacCallum --- common/src/abstractions/state.service.ts | 6 +- common/src/models/domain/account.ts | 3 - common/src/models/domain/globalState.ts | 3 + common/src/models/domain/state.ts | 4 +- common/src/services/auth.service.ts | 53 ++-- common/src/services/crypto.service.ts | 12 +- common/src/services/environment.service.ts | 13 +- common/src/services/state.service.ts | 257 ++++++++++-------- common/src/services/stateMigration.service.ts | 22 +- common/src/services/vaultTimeout.service.ts | 2 +- node/src/cli/baseProgram.ts | 2 +- 11 files changed, 198 insertions(+), 179 deletions(-) diff --git a/common/src/abstractions/state.service.ts b/common/src/abstractions/state.service.ts index 55707b7f..5dd3da2e 100644 --- a/common/src/abstractions/state.service.ts +++ b/common/src/abstractions/state.service.ts @@ -24,11 +24,11 @@ import { CollectionView } from "../models/view/collectionView"; import { FolderView } from "../models/view/folderView"; import { SendView } from "../models/view/sendView"; -export abstract class StateService { - accounts: BehaviorSubject<{ [userId: string]: Account }>; +export abstract class StateService { + accounts: BehaviorSubject<{ [userId: string]: T }>; activeAccount: BehaviorSubject; - addAccount: (account: Account) => Promise; + addAccount: (account: T) => Promise; setActiveUser: (userId: string) => Promise; clean: (options?: StorageOptions) => Promise; init: () => Promise; diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index 98ea62df..cab08d1a 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -130,9 +130,6 @@ export class AccountSettings { enableMinimizeToTray?: boolean; enableStartToTray?: boolean; enableTray?: boolean; - environmentUrls?: any = { - server: "bitwarden.com", - }; equivalentDomains?: any; minimizeOnCopyToClipboard?: boolean; neverDomains?: { [id: string]: any }; diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts index 66ad5130..0fdce648 100644 --- a/common/src/models/domain/globalState.ts +++ b/common/src/models/domain/globalState.ts @@ -24,4 +24,7 @@ export class GlobalState { noAutoPromptBiometrics?: boolean; noAutoPromptBiometricsText?: string; stateVersion: number; + environmentUrls?: any = { + server: "bitwarden.com", + }; } diff --git a/common/src/models/domain/state.ts b/common/src/models/domain/state.ts index 17889bf4..d9087264 100644 --- a/common/src/models/domain/state.ts +++ b/common/src/models/domain/state.ts @@ -1,8 +1,8 @@ import { Account } from "./account"; import { GlobalState } from "./globalState"; -export class State { - accounts: { [userId: string]: Account } = {}; +export class State { + accounts: { [userId: string]: TAccount } = {}; globals: GlobalState = new GlobalState(); activeUserId: string; } diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index 5e44c90d..3d7506d3 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -2,7 +2,13 @@ import { HashPurpose } from "../enums/hashPurpose"; import { KdfType } from "../enums/kdfType"; import { TwoFactorProviderType } from "../enums/twoFactorProviderType"; -import { Account, AccountData, AccountProfile, AccountTokens } from "../models/domain/account"; +import { + Account, + AccountData, + AccountKeys, + AccountProfile, + AccountTokens, +} from "../models/domain/account"; import { AuthResult } from "../models/domain/authResult"; import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; @@ -538,27 +544,34 @@ export class AuthService implements AuthServiceAbstraction { result.forcePasswordReset = tokenResponse.forcePasswordReset; const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken); - await this.stateService.addAccount({ - profile: { - ...new AccountProfile(), - ...{ - userId: accountInformation.sub, - email: accountInformation.email, - apiKeyClientId: clientId, - apiKeyClientSecret: clientSecret, - hasPremiumPersonally: accountInformation.premium, - kdfIterations: tokenResponse.kdfIterations, - kdfType: tokenResponse.kdf, + await this.stateService.addAccount( + new Account({ + profile: { + ...new AccountProfile(), + ...{ + userId: accountInformation.sub, + email: accountInformation.email, + apiKeyClientId: clientId, + hasPremiumPersonally: accountInformation.premium, + kdfIterations: tokenResponse.kdfIterations, + kdfType: tokenResponse.kdf, + }, }, - }, - tokens: { - ...new AccountTokens(), - ...{ - accessToken: tokenResponse.accessToken, - refreshToken: tokenResponse.refreshToken, + keys: { + ...new AccountKeys(), + ...{ + apiKeyClientSecret: clientSecret, + }, }, - }, - }); + tokens: { + ...new AccountTokens(), + ...{ + accessToken: tokenResponse.accessToken, + refreshToken: tokenResponse.refreshToken, + }, + }, + }) + ); if (tokenResponse.twoFactorToken != null) { await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index 05c14d93..fd9d83c4 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -761,13 +761,13 @@ export class CryptoService implements CryptoServiceAbstraction { // Helpers protected async storeKey(key: SymmetricCryptoKey, userId?: string) { - if ( - (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) || - (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) - ) { - await this.stateService.setCryptoMasterKeyB64(key.keyB64, { userId: userId }); + if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { + await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); + } else if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { + await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); } else { - await this.stateService.setCryptoMasterKeyB64(null, { userId: userId }); + await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); + await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); } } diff --git a/common/src/services/environment.service.ts b/common/src/services/environment.service.ts index d95718e0..cf435d87 100644 --- a/common/src/services/environment.service.ts +++ b/common/src/services/environment.service.ts @@ -109,18 +109,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { } async setUrlsFromStorage(): Promise { - const urlsObj: any = await this.stateService.getEnvironmentUrls(); - const urls = urlsObj || { - base: null, - api: null, - identity: null, - icons: null, - notifications: null, - events: null, - webVault: null, - keyConnector: null, - }; - + const urls: any = await this.stateService.getEnvironmentUrls(); const envUrls = new EnvironmentUrls(); if (urls.base) { diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 59c5a73a..5de1ccb1 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -1,6 +1,12 @@ import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; -import { Account } from "../models/domain/account"; +import { + Account, + AccountData, + AccountKeys, + AccountProfile, + AccountTokens, +} from "../models/domain/account"; import { LogService } from "../abstractions/log.service"; import { StorageService } from "../abstractions/storage.service"; @@ -34,19 +40,21 @@ import { SendData } from "../models/data/sendData"; import { BehaviorSubject } from "rxjs"; -import { StateMigrationService } from "./stateMigration.service"; +import { StateMigrationService } from "../abstractions/stateMigration.service"; -export class StateService implements StateServiceAbstraction { - accounts = new BehaviorSubject<{ [userId: string]: Account }>({}); +export class StateService + implements StateServiceAbstraction +{ + accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); activeAccount = new BehaviorSubject(null); - private state: State = new State(); + protected state: State = new State(); constructor( - private storageService: StorageService, - private secureStorageService: StorageService, - private logService: LogService, - private stateMigrationService: StateMigrationService + protected storageService: StorageService, + protected secureStorageService: StorageService, + protected logService: LogService, + protected stateMigrationService: StateMigrationService ) {} async init(): Promise { @@ -60,7 +68,7 @@ export class StateService implements StateServiceAbstraction { async loadStateFromDisk() { if ((await this.getActiveUserIdFromStorage()) != null) { - const diskState = await this.storageService.get( + const diskState = await this.storageService.get>( "state", await this.defaultOnDiskOptions() ); @@ -71,10 +79,7 @@ export class StateService implements StateServiceAbstraction { } } - async addAccount(account: Account) { - if (account?.profile?.userId == null) { - return; - } + async addAccount(account: TAccount) { this.state.accounts[account.profile.userId] = account; await this.scaffoldNewAccountStorage(account); await this.setActiveUser(account.profile.userId); @@ -83,7 +88,7 @@ export class StateService implements StateServiceAbstraction { async setActiveUser(userId: string): Promise { this.state.activeUserId = userId; - const storedState = await this.storageService.get( + const storedState = await this.storageService.get>( "state", await this.defaultOnDiskOptions() ); @@ -1321,47 +1326,64 @@ export class StateService implements StateServiceAbstraction { } async getEntityId(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.entityId; + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.profile?.entityId; } async setEntityId(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityId = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); } async getEntityType(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.entityType; + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.profile?.entityType; } async setEntityType(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityType = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); } async getEnvironmentUrls(options?: StorageOptions): Promise { return ( - (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) - ?.settings?.environmentUrls ?? { + (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) + ?.environmentUrls ?? { + base: null, + api: null, + identity: null, + icons: null, + notifications: null, + events: null, + webVault: null, + keyConnector: null, + // TODO: this is a bug and we should use base instead for the server detail in the account switcher, otherwise self hosted urls will not show correctly server: "bitwarden.com", } ); } async setEnvironmentUrls(value: any, options?: StorageOptions): Promise { - const account = await this.getAccount( + const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.settings.environmentUrls = value; - await this.saveAccount( - account, + globals.environmentUrls = value; + await this.saveGlobals( + globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @@ -1402,17 +1424,20 @@ export class StateService implements StateServiceAbstraction { async getEverBeenUnlocked(options?: StorageOptions): Promise { return ( - (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile - ?.everBeenUnlocked ?? false + (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) + ?.profile?.everBeenUnlocked ?? false ); } async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.everBeenUnlocked = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); } async getForcePasswordReset(options?: StorageOptions): Promise { @@ -1981,10 +2006,7 @@ export class StateService implements StateServiceAbstraction { const accountVaultTimeout = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.vaultTimeout; - const globalVaultTimeout = ( - await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.vaultTimeout; - return accountVaultTimeout ?? globalVaultTimeout ?? 15; + return accountVaultTimeout; } async setVaultTimeout(value: number, options?: StorageOptions): Promise { @@ -2047,7 +2069,7 @@ export class StateService implements StateServiceAbstraction { ); } - private async getGlobals(options: StorageOptions): Promise { + protected async getGlobals(options: StorageOptions): Promise { let globals: GlobalState; if (this.useMemory(options.storageLocation)) { globals = this.getGlobalsFromMemory(); @@ -2060,39 +2082,41 @@ export class StateService implements StateServiceAbstraction { return globals ?? new GlobalState(); } - private async saveGlobals(globals: GlobalState, options: StorageOptions) { + protected async saveGlobals(globals: GlobalState, options: StorageOptions) { return this.useMemory(options.storageLocation) ? this.saveGlobalsToMemory(globals) : await this.saveGlobalsToDisk(globals, options); } - private getGlobalsFromMemory(): GlobalState { + protected getGlobalsFromMemory(): GlobalState { return this.state.globals; } - private async getGlobalsFromDisk(options: StorageOptions): Promise { - return (await this.storageService.get("state", options))?.globals; + protected async getGlobalsFromDisk(options: StorageOptions): Promise { + return (await this.storageService.get>("state", options))?.globals; } - private saveGlobalsToMemory(globals: GlobalState): void { + protected saveGlobalsToMemory(globals: GlobalState): void { this.state.globals = globals; } - private async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { + protected async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { if (options.useSecureStorage) { - const state = (await this.secureStorageService.get("state", options)) ?? new State(); + const state = + (await this.secureStorageService.get>("state", options)) ?? new State(); state.globals = globals; await this.secureStorageService.save("state", state, options); } else { - const state = (await this.storageService.get("state", options)) ?? new State(); + const state = + (await this.storageService.get>("state", options)) ?? new State(); state.globals = globals; await this.saveStateToStorage(state, options); } } - private async getAccount(options: StorageOptions): Promise { + protected async getAccount(options: StorageOptions): Promise { try { - let account: Account; + let account: TAccount; if (this.useMemory(options.storageLocation)) { account = this.getAccountFromMemory(options); } @@ -2101,51 +2125,51 @@ export class StateService implements StateServiceAbstraction { account = await this.getAccountFromDisk(options); } - return account != null ? new Account(account) : null; + return account; } catch (e) { this.logService.error(e); } } - private getAccountFromMemory(options: StorageOptions): Account { + protected getAccountFromMemory(options: StorageOptions): TAccount { if (this.state.accounts == null) { return null; } return this.state.accounts[this.getUserIdFromMemory(options)]; } - private getUserIdFromMemory(options: StorageOptions): string { + protected getUserIdFromMemory(options: StorageOptions): string { return options?.userId != null ? this.state.accounts[options.userId]?.profile?.userId : this.state.activeUserId; } - private async getAccountFromDisk(options: StorageOptions): Promise { + protected async getAccountFromDisk(options: StorageOptions): Promise { if (options?.userId == null && this.state.activeUserId == null) { return null; } const state = options?.useSecureStorage - ? (await this.secureStorageService.get("state", options)) ?? - (await this.storageService.get( + ? (await this.secureStorageService.get>("state", options)) ?? + (await this.storageService.get>( "state", this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }) )) - : await this.storageService.get("state", options); + : await this.storageService.get>("state", options); return state?.accounts[options?.userId ?? this.state.activeUserId]; } - private useMemory(storageLocation: StorageLocation) { + protected useMemory(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both; } - private useDisk(storageLocation: StorageLocation) { + protected useDisk(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both; } - private async saveAccount( - account: Account, + protected async saveAccount( + account: TAccount, options: StorageOptions = { storageLocation: StorageLocation.Both, useSecureStorage: false, @@ -2156,86 +2180,75 @@ export class StateService implements StateServiceAbstraction { : await this.saveAccountToDisk(account, options); } - private async saveAccountToDisk(account: Account, options: StorageOptions): Promise { + protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise { const storageLocation = options.useSecureStorage ? this.secureStorageService : this.storageService; - const state = (await storageLocation.get("state", options)) ?? new State(); + const state = + (await storageLocation.get>("state", options)) ?? new State(); state.accounts[account.profile.userId] = account; await storageLocation.save("state", state, options); await this.pushAccounts(); } - private async saveAccountToMemory(account: Account): Promise { + protected async saveAccountToMemory(account: TAccount): Promise { if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) { this.state.accounts[account.profile.userId] = account; } await this.pushAccounts(); } - private async scaffoldNewAccountStorage(account: Account): Promise { + protected async scaffoldNewAccountStorage(account: TAccount): Promise { await this.scaffoldNewAccountLocalStorage(account); await this.scaffoldNewAccountSessionStorage(account); await this.scaffoldNewAccountMemoryStorage(account); } - private async scaffoldNewAccountLocalStorage(account: Account): Promise { + protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskLocalOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskLocalOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); } - private async scaffoldNewAccountMemoryStorage(account: Account): Promise { + protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskMemoryOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskMemoryOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions()); } - private async scaffoldNewAccountSessionStorage(account: Account): Promise { + protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions()); } - private async pushAccounts(): Promise { + protected async pushAccounts(): Promise { await this.pruneInMemoryAccounts(); if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) { this.accounts.next(null); @@ -2245,7 +2258,7 @@ export class StateService implements StateServiceAbstraction { this.accounts.next(this.state.accounts); } - private reconcileOptions( + protected reconcileOptions( requestedOptions: StorageOptions, defaultOptions: StorageOptions ): StorageOptions { @@ -2263,11 +2276,11 @@ export class StateService implements StateServiceAbstraction { return requestedOptions; } - private get defaultInMemoryOptions(): StorageOptions { + protected get defaultInMemoryOptions(): StorageOptions { return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId }; } - private async defaultOnDiskOptions(): Promise { + protected async defaultOnDiskOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Session, @@ -2276,7 +2289,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultOnDiskLocalOptions(): Promise { + protected async defaultOnDiskLocalOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Local, @@ -2285,7 +2298,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultOnDiskMemoryOptions(): Promise { + protected async defaultOnDiskMemoryOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Memory, @@ -2294,7 +2307,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultSecureStorageOptions(): Promise { + protected async defaultSecureStorageOptions(): Promise { return { storageLocation: StorageLocation.Disk, useSecureStorage: true, @@ -2302,46 +2315,38 @@ export class StateService implements StateServiceAbstraction { }; } - private async getActiveUserIdFromStorage(): Promise { - const state = await this.storageService.get("state"); + protected async getActiveUserIdFromStorage(): Promise { + const state = await this.storageService.get>("state"); return state?.activeUserId; } - private async removeAccountFromLocalStorage( + protected async removeAccountFromLocalStorage( userId: string = this.state.activeUserId ): Promise { - const state = await this.storageService.get("state", { + const state = await this.storageService.get>("state", { htmlStorageLocation: HtmlStorageLocation.Local, }); if (state?.accounts[userId] == null) { return; } - - state.accounts[userId] = new Account({ - settings: state.accounts[userId].settings, - }); - + state.accounts[userId] = this.resetAccount(state.accounts[userId]); await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions()); } - private async removeAccountFromSessionStorage( + protected async removeAccountFromSessionStorage( userId: string = this.state.activeUserId ): Promise { - const state = await this.storageService.get("state", { + const state = await this.storageService.get>("state", { htmlStorageLocation: HtmlStorageLocation.Session, }); if (state?.accounts[userId] == null) { return; } - - state.accounts[userId] = new Account({ - settings: state.accounts[userId].settings, - }); - + state.accounts[userId] = this.resetAccount(state.accounts[userId]); await this.saveStateToStorage(state, await this.defaultOnDiskOptions()); } - private async removeAccountFromSecureStorage( + protected async removeAccountFromSecureStorage( userId: string = this.state.activeUserId ): Promise { await this.setCryptoMasterKeyAuto(null, { userId: userId }); @@ -2349,15 +2354,18 @@ export class StateService implements StateServiceAbstraction { await this.setCryptoMasterKeyB64(null, { userId: userId }); } - private removeAccountFromMemory(userId: string = this.state.activeUserId): void { + protected removeAccountFromMemory(userId: string = this.state.activeUserId): void { delete this.state.accounts[userId]; } - private async saveStateToStorage(state: State, options: StorageOptions): Promise { + protected async saveStateToStorage( + state: State, + options: StorageOptions + ): Promise { await this.storageService.save("state", state, options); } - private async pruneInMemoryAccounts() { + protected async pruneInMemoryAccounts() { // We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state for (const userId in this.state.accounts) { if (!(await this.getIsAuthenticated({ userId: userId }))) { @@ -2365,4 +2373,13 @@ export class StateService implements StateServiceAbstraction { } } } + + // settings persist even on reset + protected resetAccount(account: TAccount) { + account.data = new AccountData(); + account.keys = new AccountKeys(); + account.profile = new AccountProfile(); + account.tokens = new AccountTokens(); + return account; + } } diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index 903cc093..afe756e4 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -114,19 +114,22 @@ export class StateMigrationService { readonly latestVersion: number = 2; constructor( - private storageService: StorageService, - private secureStorageService: StorageService + protected storageService: StorageService, + protected secureStorageService: StorageService ) {} async needsMigration(): Promise { - const currentStateVersion = (await this.storageService.get("state"))?.globals - ?.stateVersion; + const currentStateVersion = ( + await this.storageService.get>("state", { + htmlStorageLocation: HtmlStorageLocation.Local, + }) + )?.globals?.stateVersion; return currentStateVersion == null || currentStateVersion < this.latestVersion; } async migrate(): Promise { let currentStateVersion = - (await this.storageService.get("state"))?.globals?.stateVersion ?? 1; + (await this.storageService.get>("state"))?.globals?.stateVersion ?? 1; while (currentStateVersion < this.latestVersion) { switch (currentStateVersion) { case 1: @@ -138,10 +141,10 @@ export class StateMigrationService { } } - private async migrateStateFrom1To2(): Promise { + protected async migrateStateFrom1To2(): Promise { const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local }; const userId = await this.storageService.get("userId"); - const initialState: State = + const initialState: State = userId == null ? { globals: { @@ -174,6 +177,7 @@ export class StateMigrationService { v1Keys.enableBiometric, options ), + environmentUrls: await this.storageService.get(v1Keys.environmentUrls, options), installedVersion: await this.storageService.get( v1Keys.installedVersion, options @@ -439,10 +443,6 @@ export class StateMigrationService { options ), enableTray: await this.storageService.get(v1Keys.enableTray, options), - environmentUrls: await this.storageService.get( - v1Keys.environmentUrls, - options - ), equivalentDomains: await this.storageService.get( v1Keys.equivalentDomains, options diff --git a/common/src/services/vaultTimeout.service.ts b/common/src/services/vaultTimeout.service.ts index 544e6bc2..cd810e8c 100644 --- a/common/src/services/vaultTimeout.service.ts +++ b/common/src/services/vaultTimeout.service.ts @@ -53,7 +53,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { async isLocked(userId?: string): Promise { const neverLock = (await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) && - !(await this.stateService.getEverBeenUnlocked({ userId: userId })); + (await this.stateService.getEverBeenUnlocked({ userId: userId })); if (neverLock) { // TODO: This also _sets_ the key so when we check memory in the next line it finds a key. // We should refactor here. diff --git a/node/src/cli/baseProgram.ts b/node/src/cli/baseProgram.ts index e8c15f9a..b2ddd22f 100644 --- a/node/src/cli/baseProgram.ts +++ b/node/src/cli/baseProgram.ts @@ -9,7 +9,7 @@ import { StringResponse } from "./models/response/stringResponse"; export abstract class BaseProgram { constructor( - private stateService: StateService, + protected stateService: StateService, private writeLn: (s: string, finalLine: boolean, error: boolean) => void ) {} From 3d7b427b0ea7d6b75cc1eebb7a7595314f564503 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 21 Dec 2021 12:02:56 -0500 Subject: [PATCH 07/10] Use MP policies when registering a new user through SSO (#587) * use MP policies when registering a new user through SSO * prettier and linting --- .../src/components/change-password.component.ts | 2 +- angular/src/components/set-password.component.ts | 2 ++ common/src/abstractions/api.service.ts | 4 ++++ common/src/abstractions/policy.service.ts | 1 + common/src/services/api.service.ts | 14 ++++++++++++++ common/src/services/policy.service.ts | 9 +++++++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/angular/src/components/change-password.component.ts b/angular/src/components/change-password.component.ts index 6ca81494..2431ece2 100644 --- a/angular/src/components/change-password.component.ts +++ b/angular/src/components/change-password.component.ts @@ -40,7 +40,7 @@ export class ChangePasswordComponent implements OnInit { async ngOnInit() { this.email = await this.stateService.getEmail(); - this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); + this.enforcedPolicyOptions ??= await this.policyService.getMasterPasswordPolicyOptions(); } async submit() { diff --git a/angular/src/components/set-password.component.ts b/angular/src/components/set-password.component.ts index e651eca5..89f6e520 100644 --- a/angular/src/components/set-password.component.ts +++ b/angular/src/components/set-password.component.ts @@ -79,6 +79,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier); this.orgId = response.id; this.resetPasswordAutoEnroll = response.resetPasswordEnabled; + this.enforcedPolicyOptions = + await this.policyService.getMasterPasswordPoliciesForInvitedUsers(this.orgId); } catch { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/common/src/abstractions/api.service.ts b/common/src/abstractions/api.service.ts index ca648f0c..4ef3c1a9 100644 --- a/common/src/abstractions/api.service.ts +++ b/common/src/abstractions/api.service.ts @@ -355,6 +355,10 @@ export abstract class ApiService { email: string, organizationUserId: string ) => Promise>; + getPoliciesByInvitedUser: ( + organizationId: string, + userId: string + ) => Promise>; putPolicy: ( organizationId: string, type: PolicyType, diff --git a/common/src/abstractions/policy.service.ts b/common/src/abstractions/policy.service.ts index 97dcb4ad..f1f6b4e5 100644 --- a/common/src/abstractions/policy.service.ts +++ b/common/src/abstractions/policy.service.ts @@ -15,6 +15,7 @@ export abstract class PolicyService { getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise; replace: (policies: { [id: string]: PolicyData }) => Promise; clear: (userId?: string) => Promise; + getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise; getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise; evaluateMasterPassword: ( passwordStrength: number, diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index 2764108e..16b94b09 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -1047,6 +1047,20 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, PolicyResponse); } + async getPoliciesByInvitedUser( + organizationId: string, + userId: string + ): Promise> { + const r = await this.send( + "GET", + "/organizations/" + organizationId + "/policies/invited-user?" + "userId=" + userId, + null, + false, + true + ); + return new ListResponse(r, PolicyResponse); + } + async putPolicy( organizationId: string, type: PolicyType, diff --git a/common/src/services/policy.service.ts b/common/src/services/policy.service.ts index 79af726a..dce9af6f 100644 --- a/common/src/services/policy.service.ts +++ b/common/src/services/policy.service.ts @@ -78,6 +78,15 @@ export class PolicyService implements PolicyServiceAbstraction { await this.stateService.setEncryptedPolicies(null, { userId: userId }); } + async getMasterPasswordPoliciesForInvitedUsers( + orgId: string + ): Promise { + const userId = await this.stateService.getUserId(); + const response = await this.apiService.getPoliciesByInvitedUser(orgId, userId); + const policies = await this.mapPoliciesFromToken(response); + return this.getMasterPasswordPolicyOptions(policies); + } + async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise { let enforcedOptions: MasterPasswordPolicyOptions = null; From d68c1dafaf0528f5323dd595773b0fa122403d0d Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Wed, 22 Dec 2021 19:46:25 +0100 Subject: [PATCH 08/10] Remove usage/detection of NativeScript (#566) * Remove usage of NativeScript * npm prettier run * Removing type from Utils.global --- common/src/misc/utils.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/common/src/misc/utils.ts b/common/src/misc/utils.ts index b39ada3b..a91ccd5a 100644 --- a/common/src/misc/utils.ts +++ b/common/src/misc/utils.ts @@ -7,7 +7,6 @@ const nodeURL = typeof window === "undefined" ? require("url") : null; export class Utils { static inited = false; - static isNativeScript = false; static isNode = false; static isBrowser = true; static isMobileBrowser = false; @@ -30,18 +29,13 @@ export class Utils { (process as any).release != null && (process as any).release.name === "node"; Utils.isBrowser = typeof window !== "undefined"; - Utils.isNativeScript = !Utils.isNode && !Utils.isBrowser; Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window); - Utils.global = Utils.isNativeScript - ? global - : Utils.isNode && !Utils.isBrowser - ? global - : window; + Utils.global = Utils.isNode && !Utils.isBrowser ? global : window; } static fromB64ToArray(str: string): Uint8Array { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return new Uint8Array(Buffer.from(str, "base64")); } else { const binaryString = window.atob(str); @@ -58,7 +52,7 @@ export class Utils { } static fromHexToArray(str: string): Uint8Array { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return new Uint8Array(Buffer.from(str, "hex")); } else { const bytes = new Uint8Array(str.length / 2); @@ -70,7 +64,7 @@ export class Utils { } static fromUtf8ToArray(str: string): Uint8Array { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return new Uint8Array(Buffer.from(str, "utf8")); } else { const strUtf8 = unescape(encodeURIComponent(str)); @@ -91,7 +85,7 @@ export class Utils { } static fromBufferToB64(buffer: ArrayBuffer): string { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return Buffer.from(buffer).toString("base64"); } else { let binary = ""; @@ -112,7 +106,7 @@ export class Utils { } static fromBufferToUtf8(buffer: ArrayBuffer): string { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return Buffer.from(buffer).toString("utf8"); } else { const bytes = new Uint8Array(buffer); @@ -127,7 +121,7 @@ export class Utils { // ref: https://stackoverflow.com/a/40031979/1090359 static fromBufferToHex(buffer: ArrayBuffer): string { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return Buffer.from(buffer).toString("hex"); } else { const bytes = new Uint8Array(buffer); @@ -160,7 +154,7 @@ export class Utils { } static fromUtf8ToB64(utfStr: string): string { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return Buffer.from(utfStr, "utf8").toString("base64"); } else { return decodeURIComponent(escape(window.btoa(utfStr))); @@ -172,7 +166,7 @@ export class Utils { } static fromB64ToUtf8(b64Str: string): string { - if (Utils.isNode || Utils.isNativeScript) { + if (Utils.isNode) { return Buffer.from(b64Str, "base64").toString("utf8"); } else { return decodeURIComponent(escape(window.atob(b64Str))); @@ -387,7 +381,7 @@ export class Utils { private static getUrlObject(uriString: string): URL { try { if (nodeURL != null) { - return nodeURL.URL ? new nodeURL.URL(uriString) : nodeURL.parse(uriString); + return new nodeURL.URL(uriString); } else if (typeof URL === "function") { return new URL(uriString); } else if (window != null) { From e4cd0af2f9ae924f9b749fefe3695f7c69135bf6 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 31 Dec 2021 09:14:43 -0500 Subject: [PATCH 09/10] [bug] Add several state value defaults (#593) * [bug] Add several state value defaults * [refactor] Implement StateVersion as an enum * [refactor] Implement StateVersion enum over magic number --- common/src/enums/stateVersion.ts | 5 +++++ common/src/models/domain/account.ts | 2 +- common/src/models/domain/globalState.ts | 8 +++++--- common/src/services/state.service.ts | 1 - common/src/services/stateMigration.service.ts | 19 +++++++++---------- 5 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 common/src/enums/stateVersion.ts diff --git a/common/src/enums/stateVersion.ts b/common/src/enums/stateVersion.ts new file mode 100644 index 00000000..aa45edbb --- /dev/null +++ b/common/src/enums/stateVersion.ts @@ -0,0 +1,5 @@ +export enum StateVersion { + One = 1, // Original flat key/value pair store + Two = 2, // Move to a typed State object + Latest = Two, +} diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index cab08d1a..6a998138 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -139,7 +139,7 @@ export class AccountSettings { protectedPin?: string; settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly vaultTimeout?: number; - vaultTimeoutAction?: string; + vaultTimeoutAction?: string = "lock"; } export class AccountTokens { diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts index 0fdce648..0dcf4939 100644 --- a/common/src/models/domain/globalState.ts +++ b/common/src/models/domain/globalState.ts @@ -1,15 +1,17 @@ +import { StateVersion } from "../../enums/stateVersion"; + export class GlobalState { enableAlwaysOnTop?: boolean; installedVersion?: string; lastActive?: number; - locale?: string; + locale?: string = "en"; openAtLogin?: boolean; organizationInvitation?: any; ssoCodeVerifier?: string; ssoOrganizationIdentifier?: string; ssoState?: string; rememberedEmail?: string; - theme?: string; + theme?: string = "light"; window?: Map = new Map(); twoFactorToken?: string; disableFavicon?: boolean; @@ -23,7 +25,7 @@ export class GlobalState { biometricText?: string; noAutoPromptBiometrics?: boolean; noAutoPromptBiometricsText?: string; - stateVersion: number; + stateVersion: StateVersion = StateVersion.Latest; environmentUrls?: any = { server: "bitwarden.com", }; diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 5de1ccb1..85280996 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -74,7 +74,6 @@ export class StateService ); this.state = diskState; await this.pruneInMemoryAccounts(); - await this.saveStateToStorage(this.state, await this.defaultOnDiskMemoryOptions()); await this.pushAccounts(); } } diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index afe756e4..7f36906e 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -2,6 +2,7 @@ import { StorageService } from "../abstractions/storage.service"; import { Account } from "../models/domain/account"; import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; +import { GlobalState } from "../models/domain/globalState"; import { State } from "../models/domain/state"; import { StorageOptions } from "../models/domain/storageOptions"; @@ -16,6 +17,7 @@ import { SendData } from "../models/data/sendData"; import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; import { KdfType } from "../enums/kdfType"; +import { StateVersion } from "../enums/stateVersion"; // Originally (before January 2022) storage was handled as a flat key/value pair store. // With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. @@ -111,8 +113,6 @@ const v1KeyPrefixes = { }; export class StateMigrationService { - readonly latestVersion: number = 2; - constructor( protected storageService: StorageService, protected secureStorageService: StorageService @@ -124,15 +124,16 @@ export class StateMigrationService { htmlStorageLocation: HtmlStorageLocation.Local, }) )?.globals?.stateVersion; - return currentStateVersion == null || currentStateVersion < this.latestVersion; + return currentStateVersion == null || currentStateVersion < StateVersion.Latest; } async migrate(): Promise { let currentStateVersion = - (await this.storageService.get>("state"))?.globals?.stateVersion ?? 1; - while (currentStateVersion < this.latestVersion) { + (await this.storageService.get>("state"))?.globals?.stateVersion ?? + StateVersion.One; + while (currentStateVersion < StateVersion.Latest) { switch (currentStateVersion) { - case 1: + case StateVersion.One: await this.migrateStateFrom1To2(); break; } @@ -147,9 +148,7 @@ export class StateMigrationService { const initialState: State = userId == null ? { - globals: { - stateVersion: 2, - }, + globals: new GlobalState(), accounts: {}, activeUserId: null, } @@ -209,7 +208,7 @@ export class StateMigrationService { v1Keys.rememberedEmail, options ), - stateVersion: 2, + stateVersion: StateVersion.Two, theme: await this.storageService.get(v1Keys.theme, options), twoFactorToken: await this.storageService.get( v1KeyPrefixes.twoFactorToken + userId, From cc989e407138405e12a461edec113e63adcbcaf1 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Fri, 31 Dec 2021 10:05:23 -0500 Subject: [PATCH 10/10] Rename all occurances of fb to formBuilder (#595) --- angular/src/components/export.component.ts | 4 ++-- .../components/settings/vault-timeout-input.component.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/angular/src/components/export.component.ts b/angular/src/components/export.component.ts index f3aba8e2..17a751fc 100644 --- a/angular/src/components/export.component.ts +++ b/angular/src/components/export.component.ts @@ -20,7 +20,7 @@ export class ExportComponent implements OnInit { formPromise: Promise; disabledByPolicy: boolean = false; - exportForm = this.fb.group({ + exportForm = this.formBuilder.group({ format: ["json"], secret: [""], }); @@ -41,7 +41,7 @@ export class ExportComponent implements OnInit { protected win: Window, private logService: LogService, private userVerificationService: UserVerificationService, - private fb: FormBuilder + private formBuilder: FormBuilder ) {} async ngOnInit() { diff --git a/angular/src/components/settings/vault-timeout-input.component.ts b/angular/src/components/settings/vault-timeout-input.component.ts index 95180f88..2f63f97f 100644 --- a/angular/src/components/settings/vault-timeout-input.component.ts +++ b/angular/src/components/settings/vault-timeout-input.component.ts @@ -21,9 +21,9 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat static CUSTOM_VALUE = -100; - form = this.fb.group({ + form = this.formBuilder.group({ vaultTimeout: [null], - custom: this.fb.group({ + custom: this.formBuilder.group({ hours: [null], minutes: [null], }), @@ -38,7 +38,7 @@ export class VaultTimeoutInputComponent implements ControlValueAccessor, Validat private validatorChange: () => void; constructor( - private fb: FormBuilder, + private formBuilder: FormBuilder, private policyService: PolicyService, private i18nService: I18nService ) {}