From e1e4d26154f827d048aa386b8b993bbed2114f33 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Wed, 22 Jan 2025 19:01:47 +0530 Subject: [PATCH 1/2] More memberlist changes (#29069) * Remove parenthesis from Invited user label * Ensure adequate margin * Truncate user id with ellipsis * Fix tests --- playwright/e2e/right-panel/memberlist.spec.ts | 2 +- .../with-four-members-linux.png | Bin 18344 -> 18082 bytes .../views/messages/_DisambiguatedProfile.pcss | 2 ++ res/css/views/rooms/_MemberTileView.pcss | 1 + .../memberlist/tiles/MemberTileViewModel.tsx | 2 +- .../tiles/ThreePidTileViewModel.tsx | 2 +- .../MemberTileView-test.tsx.snap | 2 +- 7 files changed, 7 insertions(+), 4 deletions(-) diff --git a/playwright/e2e/right-panel/memberlist.spec.ts b/playwright/e2e/right-panel/memberlist.spec.ts index 25d5f346631..cd22626575f 100644 --- a/playwright/e2e/right-panel/memberlist.spec.ts +++ b/playwright/e2e/right-panel/memberlist.spec.ts @@ -42,7 +42,7 @@ test.describe("Memberlist", () => { await app.viewRoomByName(ROOM_NAME); const memberlist = await app.toggleMemberlistPanel(); await expect(memberlist.locator(".mx_MemberTileView")).toHaveCount(4); - await expect(memberlist.getByText("(Invited)")).toHaveCount(1); + await expect(memberlist.getByText("Invited")).toHaveCount(1); await expect(page.locator(".mx_MemberListView")).toMatchScreenshot("with-four-members.png"); }); }); diff --git a/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png b/playwright/snapshots/right-panel/memberlist.spec.ts/with-four-members-linux.png index 8e99fefd8750b3769cb677517800baa4e315c129..460eec3a8c18305eee0da1bbadcd43a44efd1a5d 100644 GIT binary patch literal 18082 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 S5c8ok09h%ePn8m1zyCK<>of=e literal 18344 zcmeIabx>RHzwaApDQziG3X~QKv{0l_pv9dEE$*&qad+1mUZ6mM;_edML!dapEojgX zED07|&ielLK6|d5Id`9X=lpSJ_Dr5IkmMn2t!I7Y^?t7vrm8GMdYASt2m~UPla*En zfe55PAi|N`guq{{C6@$&9|W%IGVeg;Lr*qApr;@?>9?9*sh9;%T`DxPdyjGM6OFD< z9FrDR1&B%TCUqpKZy zFv!;+u8)k3Qi#U#8_j&f;`d=YuLcITv~+YRL9ap&irGLQ+ILV;>Hn9p0^Pa;YOywR z2~uQ@>!Y3wxB1{MK;e9Ncqr^6&K=UTy4N7;+c(jX<_vPnljkV;EvqGzS5RP?IZU(b zzWX=KhrsO(N1Tp)F;#f*ROj->;|-D%@p3`B4^Yr-Z2uaH`T?mnKh@Sj&Fx1rWuTgD z=O$@Q?PoFlYqXBEctR`XELE%2a&_+@>2g)8I_@o^5yK>pO%CgM*e&ALs%Sr$iQ}0R zb125>h7URIeE(0#43%b#vEKR2EOD9SNx<)DEHUwje7xq=ZiglZJJqC?deSYKECGKE zb(069q@CYM#cstL8SDK%cPYmPt*m8LkX`X_&B-$_zrWwdEqOSuYNbk5*_AV(f1IQm zm){rVJ3~DP(k{hroe>}3PC0GQv}hTdUKNVqe?sy_LQ7rGD6qV5sIQsY6W2BxH%$C8 zu8%o7wSn4`hl}}Ou|!ixBVIEf`^3$;Jbrp~GWEd$-+riKXs({n@s5ZyD&Vci;`G!^ z$?=jgfndM?R_u?$8@|~OG!D%DT*XviBg>Z(Tw$THLN8A)!w=euhW5mipIge4+DTl5 zgd87HT-&}!Mdutpk?=luE}v-YM83$B(VbS9F(NL^P&7to<#W^DxO&V% zgW(C840j?<%uoDME2qYox%f^DFr}+1+_UUjLWDyXS(G57dy80-Q|Q2c=EX zj|4wEWtt=NYB{e&!HUvjKNes-i*fAkWRE2rRR4&vq)u1(xxPECer@I%jrICOBZy5S z|L8@cksWxk_onJFXwZ!D6m`s1&uRFMSm`#oJ#)Ws6ZUv;Z`vqCXLV7mTR`~9&=*OG z)F(R(1?D?ael7AG3%(oHP5pm>Z}y#eIJxymo)aFy+3)?Ft1nEO>GXfOM2rQ{`^=h8tMX(-++8g{kzmJDey|r(Au6!H%n2b~{ z#sMltW|LlcCvIR_f)=vc{>+jayK=X>fO+nA#JEeia}aY2laG%NN4gd})j2t%iky};dh8AZL;muMx*h`(Z%CXUEH=2m{5<(;cgcq3~-N4H1Knnru1hR!$@ zOSA)8_B39lJZ<{OQ@zr>9+X(K6BRHMOd z`aK84R*$$tm?5jtAJx#@63TEVk1F!*Nu$P+0h9~z(I$oW;o52q+bEjble8EW{m~E) znGK{bzpsd_SUM&l>(Ex(qY?OI{RQz=k(-ATa(S_K;!8fp$J9<^O*k~BQd)AWk z-6+BWdu>AYvBrb8n{jZ|(auB7Z7U$$d`B1?n%>NO z=I@`DwrQC;stV~y=L40cbi5YiA2IvDC3J7(&z&Gb4Dss;xBC+$nIgc3 zJlL}3`zBim*?aM%kme1i9|J;l{0nK&+3dXb1K2U)#uvjZcF3ein~Cb(GBt;_*7~zW_JIgNi=4|r1aFngfMOS!$5a!&UYrk3!V5_D0yTxGW2w@H+w6(j`wL5SMq80I-iZUT`7;3WKLo z4%ruwQ|jj!f;W_O@HH8g8peXR_=i;Sz?NM&-+sgPa!|>fSMRfPQNk*JA(Bn6ky|On zs*%MD*%9(0gIky1;i9=D@S;ea$y*?U-*VkT9>8BMK20|N8gA&&BXYFnn@1j;5!>q26X$Xih?dj2OL&HgEC`Mp- z9gRLGb`y458iA*O42jl;oLq#kP(njPR{}OR*VY9sgp9+k`G84)sb+tw2L5v-ryj2V zGv$58K%qicw!lCsPEPoT41am)V=T*rcuHboMtppl*LAx>Mu_|QGG)lg=E;tP6+4~6 z!O9q~{YNWVD2h;$`_UMS0bR=4TFB$akEPwP7eq`|6%|jjJ3G(eI;F^6G%Hh+)AGtb zbqP6x=vXENgv$=MHgy$YN;9Q+`^a&5o$}Gq-c$nxU}O!_rqV6YDwbOxgX!%~FB&VBd^~##cr@QF;x` zB-HCQor)svhQkzDsE{NzJ(Ue08OHu{Ka-F}7Rw>5>j~$7k96k*JxfDq5LbLmepWlLh^KZlxq8 z>GhCd7{z6ovh$Ur=*0>X!&_4lQ}&rM9IaScFGkUF#Rw2yufRJzhk6-A`kcl zhtF9`ZdjPt(w8gdU~>D(VipP!F(Fb?%oNl>r>rjbdRT@&81Fhwz2~{sb?WwtBV-== zc(Oh78FS5_29LcjXkG-3*i$3=u>eS27C!t3=_xr`@e^?`ud8n#fPN_|ip+lQY;I;Y zIXT(P!a~A3w$gXFH8Mr;yw3V^n@4J9PUzJ2QlH)dk=gO<%7MZ*JK8Z{L1gU=`)cuQ z%E`&KI(J({-!u*`?rjz#5qS!6ij!)M|rXtP!LBTJfD=RB(Druh` z7uSv#1F$2KyFr%byfew*0C1fjs&I5QTZIvU@AeNNx83WB4aL>{YQ_D%Le_GbpgxtU z>>-5w_Kb!MI#`dBMH@RRA56ul0}`>mfcG(jFs=|4hGKh z>(`c*m7ld8z6#EPP(?A)HJ`5L0}B+S#TjtlA8Rab%`m@sacXY2AYm7pAxUvZ^d}ar zyZvD5{dyo0p&(_AJ_8GZho2qa*{-{OHJZq6r+3!X|DC+_8`^QU>A5uC5uTa8lS;_7 zU3QJ6bqQM8>7si^e_1ng&#=kQymzF2+i0M>`)n(o*LL_SfXDF)d4z?CdY=YU@T=7lM(9 z;)=rOn|qYk3F)(TcR)6s$R_mi&L<|pF^gZfjGkH9m<+7CxEwg&68l27wnlT;OPp(5 zYZXabu7s(DJ)r=FvNLlcYKx#ptNb;_rtf2tiYmz*Lx@L&7kqbn{|#a zw7H;a7N@$gW5pIDOBu_sX>$t{*$yPD;#_t5{F2k=UwP)hcX}vLS=p)N3etr4@eK6# z*S_K#og{N59b6=+si_eMwk=6_NB+wezvoBx7M3@gf^%v zPo>JG+GQWeQ1&kePyAXXk^XeFEU0Ejh2mT-^`iZSdVBkq$+`wM*PaVaRtAvRcWP-V zU|mCfZ9{d|qJJ+BxbunI?g2H^{arkfUu9+1^z_Ksye6FT*>+xv55W?w;OMm_$V(~n ze^b@+KdOfIYIN&Y&NASEjoR`@(AHEsljC{HF#`k3vSzKfYrJ0Ai~H4<(X1D(PVeoA z)W!oA1IA|5R|nTW_~TKd;Y&q}q|U&q0S$~_)YflIA-NFpS})sH_ZYej75$H_Ps;?&C|e_*MvT4)WhE6KI2Gnc1iBo#A|w`fTqSVI)uv;-@}yqV?Q$79WdPrNrKOCgMawj zqZ6G>aHCY#T-C&080-C612=cv(Ht*s_cJ}08Jp%yw!@u`b_3t@u{0NzIoCvnSS`q# z?R{I748^E$IcCJ}-e>wQ>9T=+?6nvFx`o=OrxX+-?zqJn9mP7mL=*Df7T=H)(O)WE zgBy{3-lytSY)lX8Ht@(_Q%u<)=k`+fJ+vdu&2yI27A+eJVGs3CN(%3>RXsY$$ATY` zPnlM{gCiefc%C!Km;9oT%cJfTI>1WVt8i`EO5$!3l+yT{^h{4FvpT7RD>LQ#6S8lON-yu!;s1~3s zB0xbAZ;7I!n9+X}H?63sIpH$h(q1kgC39l(%*gk%R#pypdO$hngnH&qBH5MqQy(}5 z4)<|sSD-=$<8wNcrNKrldHxbTknmlo*VfCc@E8Vwjg{tG=SfDPQ9SDcbT~^zO${Dz zkNtWm6+Qi9Q@SxlUBB(NufjIRLl@6HY9rNCnq?5!^4k@CjV{kyzo8$EjQ#9HwsS?7 zL+RX?1bNyFk9Oz7HeNPUFh<)STqEMTajDnaTCHH%a$2bwHSdd<>G?Mcj)ZO7Z!RS) zohcb1$tnZtU2fk~TcF7k?$c8O74i0NZfX&2f;?-Ok@GlDYxvia6hvCwok2tWA5j~a z&VpcuH&yrUIV8|SMVfv%VrM1%%nFGp9uxn}&}_Y28f6wU<&?F<%sM;iemUu)I5ob^ zCrfaM7Ttr_%BNSxTkihZWtg%FAORVZ_)JbtLVU1@?KTkejt6l5x16=6xExY?9b31M5;=Z()i@_ zSndJ_u&c374gvpzW*uk1uW)T#fhUrR2Mw> zaj@&d%jD_}ILY`HeRWk0hl`x%`#OL|7@XI+I=L9so&^p|wJr}UktsU}IWDkflUoGL zx0)tqXRid`yWPfKyIsbfkQ&`!j5D!6<44npOT@st7ycI28bM18r#*7Ab=7eumP6fZ zD3qSQ{tmJlQf~g_$rD2(qnm^Q&>Vsa1&j}+34`F;ZH>04j!g2+aV&AYVTB8yGJJGm zwEBRTrkbZ){bNij_d@tA#r^)ctge^oCnoWoua>0<0pY<%=7v|gVb&iJbVN`@VzI8p zdMb}PgV09iaC$GZ?UcjV!j5sS2h^D@Zp-MSnav1OXJqJ#vV?4u5=(a7cmKmfw^v@~ z=BQCvwCCo7L+xQzrdf?1I4<|Nz{gAS(pybmzvmVlDV(3^9%W z(Tivuw9d0cxJxi)|IkI(h_OOP+Yv|X?<|$hP_nhbZi2I4>NbQR*mwb<9?%w=1 zf;9?bF9m0`w6yS9YSCXtg-cS>`QlfjB~P5zf3|SSnZ$}4|N^ZmSQD}-~~q-t21 z8$LVQstY*q0Q!^5AMRjCW&G#rs3GaYrIpoU7i)b(?av>>_K6yiwhuJPjdeBMcBaF$ z%k<^*#x5={GKbPatrMF1{Vw~#S5x%g-S#S*^aG)g$?_I z$VU&3F8aD!%lr=UMDMb`mQ=@uq*Tj3Upw$lXDFW$Y2>+wUEW&3ZYfHq5zYb7>)`I) znv9Al2o6~5H(EptC79$J(;aLq6{X1`dFMy=+*I53JIl_OBNCA^l5QKfDZ09i?I&3? z*V}IplX0Cdi@ksUo|^N@*HZVCv2A$xI0OgtzH%0uJByC&NU=d&bx0rfeS?G*=H?zk z?0vt9)|C{Nmd47Lm8G}R{Yfh7JAf7K5ncNYEMFYxpWxuc_eg^TBAgqaG#Zv){+KM+ zv9tf3p`B|&fO6oYz$ec%A_j(rrKn%!=56_OO?~7@U#RDa4V}7b?e5*(G@%o+mpnVJ zWR{60rROc2($cRze3q?Ipj>Z-m6PU8+Bx8P=!rwt8X)d7GUCR@)X6IAh3C8apU5c31SL-$GUn)A}{ z9B%!H+7_^7@*s68-JVI15Wn{m&#AC=ky9S-!^g|VW85@fZE1xwcAOO7KuunpgvI$D zVM|Jod@z$}SydV2a}>_Xs{n`XVkGwH*<5XP1R>^ZLG{Hh}B{aq3gow74V zinK6EHN?%OK@aRZyDwl{y6u9*=A_U_gY%luMkn)=rGT&Y{+<{y{?*Gng#K4Y62uLu z2-Ny3o8gYp9__Abdt!-8_(=JO9##W?tpk__%!rApm0-9YOfq;Y`lq%Cm@me<456G@c*s@Ag+>Te{iplwYZUdpzS- zH(k$%Nk%b$K#qb+Thw!p+#e&f>kl?0i=dV9#jqK|HWGgEqlG}@P-tRpIqUL@q3zY!4oQTr75Eo@S4QKT%v_*EIptbKw2c1bJzOcIODzi_9Zygvo?wlWK zb~tic+P$EC1v4lZZS{F#s7R9eEDcJcXKpiyPJkGXXImC!s7%B=Q@ zH+$gir>VZv+VHc}5(m=1+@XIXQFcM>b?SE9CiaDJ16amHU#{;N-}=wd`z}t77lD(m zpf9sYk4l;j>CqxLD(?CY{pS_&u|2WlzUTW5cix{-G4Ynec0bFG(^S88pWv_9WzB3d<1mE$`l5w8+APufo~OIv)u<2g zq=p~NKE&E){dX^b$p|`*`x)g8FT(N!17fS1nzH&T>3#S#=FaOEw54-RiRd2!H%Nct zYm-1gO{N9aBj5k0fQtWJuynMvOr-d6(VC5haJD_vE%IP zx|9G-t@=9IZ{?_)YsP(rg(Puf#ASrHHq~u{{5A zv{CKU-qMGI75K=3>IyGs4b)zWFghDWlI{w#3VtN`?!V69UoYp%Nv&Y~uL>;NhhIZu zoEqK=uG~2%$`~puFHaY|?D+F&$K^e99+0r#4Ri!Q`OMMJVMNLBR{CA#Tg35fEf!Pc z*h`-DsKWhdxk>xqYO%V_al@{P;Na}#jt~wh?D%=vm$spEW^y;|yxIbUaEUY%m~Fl< zH~1xMNvu>;(%yzeH?*2uD4+s-n%OH|7}p-(Y4VyPOj^ZJ$LK#QD=dRC^qfrCZw-g0 zNsu=`Sm$wbTwf?wYDUhkm%;u5 zLc-S#-0X2Ve@ZfO$pJDJvcZYaSSAXZz2*~J<>66eW-{e`9~1U=t8C!ALXIP2GX%Ay zk}mFiysIAY5=iObnl^X|5ygiO8DWu7QOlzq#(_T{r5*A2Fte9s=A7C=K@t-8rsk_> zRIUx=q~C|!>+c&}fcoOTow}BlIz`dEQp6yTx2E9B&UW)$z(bn#mb4kpeBpIVWK2-8 zkhyj5lD2k2%(=%0>|Cw+K?VSMjVg8z99u8bLmiMUO43^02 z>Qx0Gd@nS+1IJfcR@SbUhH!UwY!43=*jJBucv8m7l>TMT!3w@>eyM-FPj%4`mawZ9 z?6`ZAP)Z7n>S;In1{98^=cXMydW)(AnXj)PN4lrbT zR<;afuC))utWIq6dugtBhJ(qW?!v;+@HqKA-lS431x6-O_pO@!z3}kx0~+q824Oln zI!;CrSHXPN+3|uLJOIoE4peOoKoGiGurHA7Ppv;}m3N^epiw$zHmbY1x?M}fJuf&p z7ilZgLZ3hA!*2^+NqV#Z$vsAPqSm$|BLi23Dp_+0%E{>%GHM3a_tZsAlSLPR;)}*D zk8WcEt}cQAU5T8Yw|7>5k}-q8IM4>MycNf!yKVP+oZ266XAk4K89Mt&)30d_h)LjbVW67vD zZC@-1faRJj{NIxu5Y9^Fk-J*`B%B&Jv-Gpeu|eiL91mCbMbh7LZw(P=j;c2sXX(N^ z=InKudeF88K%_z@N&hb31tO&idxIFkiZ4{dLRzg{V8b7UQ|bm62+R z&i3wN(b%|II)les{|ZaN$5R_pJe^3mzD}IeUOi4oO!r^CBkh#!r&1{ilix*Yl}CIP z|DNeis)o)8y)>)?ROG5zS2ihf?)|(uYlj>$6gD;Rf}*8vw85->g^sE&!ZT!Ko`Xl% z$lM2;8(Qi;3bVW=Xf6rgl>JXr9rV^qGEnWvO!F_T1Z{dfXF;1wl+R+&OE14UBqOnP zBb*c@TB0Mz8q*`g(uIwH%laLWWljzw2`0w-1HUAyc#^l=VE>}L(O?WZ8D28K|3#~i z=Kp!LWXJC7NDzYx)50o?6&9WU5!U~gAOk(jHA9gpTI_OZ>+0m^46*Jn%* ziVBLC(N34Betoov`=S2J*zlKest@PEe)-(3?z6Y17e0Yh&v^w#K5^#f=SMsoV#|&l z8?&GDP)xbX3Tpg-9-?WwGa5VBwG>t%gE$qQ_aF>~>rp-#DosBW z?=9VFu(5}asPg-rsG@rOePGo0r@aH$2qs7(izsolAbvg70P65@!xg82O~@e0(oA05XfjhOnC`>);SJsLGf ze+8A5zGObegwNO8!q=sMeDu!_TJ$@Ypjc?}P6>VYzL}GTGEACY%tKaQo|}tHOY*^^ z>2ix-*o7u-9UZpk&uyfBq5`pyi%MspnB375hec$m@^7#awb?4i0bqQ7pPAFtO)NU6#6;euf5IrD2PK!M zo}@TcoJUTPChD$#>h-yT zqkAGCiznyPdk#kLrJ2+aSP|RJ=-l3{M4`Caxh%2@`pBY0?*9_y?2!X zMkL~a6X6}px(0gqoyVe%Si8#UKcVvS@(F&ho!yw6uC6Wy1_r)-70g_L&S0Z9*uRne z_1;zaf^X2f;siIM2>HJX@P%7`w-A7VbhMuge_TxUo-`TIRaAv>3vm)f>=bG7JSom%ZS zerM+|r4vv@1Vhw~b(4J94kmDk4h(Xtm5&mK_wKo<{rhBhyUys54mm5RowP1V{ zv;QP?a1!=`K{7oqMaX$)j?_?6bUnK{r5b94@!d%@fygjOs%dKO%ujq?s~`}Fn8bVa zuh!T%Gii)x=>AC|-9o_YYmX0dw6x_V{~R=?rz0IpV7{N<1N4jQRhv-Zd$BS7K~Dgq zcfrgpQ1eO+i?t*UbVtc!Ydjx6yME)bgFTBY+}k&}7C&sfLL34NK=S#)( zt@$wwcg++TBt{J>(euwaxDBwxsOs86Jt-ON7AmhVJ^vadi>8N$>z$4_L8MabKV^au zt9jI$H(dWW-%Q=LgEaNVtHCm9_Nk(`nU9R zLBZnC2C$kD)cYJsk_e%FOnZc-c<^B2YbemaBfrb=7`L((729B!1#M2=xblq;!;E5L zlAs=@otuA1TrHFXKD!nSrQ#mWkCrqNQv~73pdPD3@!LY*MQcj7_ab!41*}cRb8PQZ z)H-ZV7Wuao@u68orTjM|V4l}U3%u6hL#b&to4B&UeV%;}tj!4E$A6o*!y-J`8TL zv0|rvEv(qcql#cv&S-SvI5U*fORA3R_)||TC98aDI_|-gEV*6XM=5h&)AlS35f$v& zz4?Exc^Z&iQ5m8sDTz|(ju$8tbw*&b3lgnxHCucpi>ak4 z)TWOH_w#Y3>tdFH^KqyZ?rV5~drYlhJbj@adVztm6;4o;yDI}AcDg{p_cj063g943&3{(-Z= zM8zr5;jOK$>&qiHaNG4&$!SIaFW2elYHvqR*v1NK`;;h%#6k&tgn!4K*vz)SK?438 zIp>^?b%3|(J$f(^a0#9($sKDF+$ZO;n>t{ldmkBWCnV3nRdSD@>HP`5P&R)z_fyekodetRjXG% zotYOGf6wLc^LMMWv(*phjmu@(OA8A%FB?=(R8pB@Dq6DNo$f_R5JL_44;)5!PWO~L zyA@!@BeMlv2Vm2(1Ha$N=?Uqtk1)@eVn+VX+jXC>o*!f6q>Y;0{_I_OQBpeVc{dgn z3G<1a2l2EoOwK?eqgz`uWe}MhMf^fSuyQ4*M!f+h zgCr}Hm_Z60;lPntQ^gO_gLlvFFXv2zhs%fM$~WGn0Q{_+3K8G_zMC|lx=Y#*=d_XJ zf)uKD&y4iIIi=ra?bG0W!{joC&nu9S%5v{5lEXnoB^w8}r~F=Ld@I0A^uA>4G*tS! zfA$&-gI2l}Y9k}luXz8WTpA6d47nk;l$aYANQPOd{ocLBDMx2ld(qrLN0|^-yz(Ps zt}-loXUYCxa5AZ!fR^F44FH}>NXE;I2I(qZ#>eZhUvq867z;>TT5=Tm`0#lqDdzKd zokfd@2|+B%26mokP*$p1W%ArNLyP+xawnqKP)SM2g-8<(ne$HKK7kC@=d42RRXTeO zaXJ1bV;#voxCIq;$p9vf{E9*^Y&Onf=yatAEV#C|KAd{+L0g&2v$EVaJwJct@89g= z;sVFl8pdkR;`>84AfXavEh&N5lNTal zP*bB=u4>rg0xc}m%~BPYRkq%;f*A6$vEfcOt6dt!_u4!7(A@5~4-wX%}!FZy+=k6 z0B9!B8KMT-#sRAy7mXx3zljHskNH-%hb_30gCF{U&hDg6*=PV7hRW5 ze{LriRL8xi6ieX}JCc8{74)GCRyx;$Zlho-&bKN0=kgJtDNmDh&3d-j-6ZIY@5VSL zUjI%61I|>DCuE6mwbtImcpXh`c_*471m$rAPwR-wZ3zj90&NeY?%FbK&4MoT;#R(D zK*~Hmo>Q59H80S%d`j0arSP2U%Actzuf72I?+nq+pAUxO)Ly@(zro4D;fWQ~)YoQ^ z^n02ytkRHSXJMhPrq=lRAPU$gN4SL?|1Ufg0*>nmX>s-L=vL+)6}oWgxbA3Ps<6=B z&Q7nzELhI4@eae-W)PRYp8lWEu4ZO(Msc5atZ^#IuloN2me*CnubGV@bBhJpo;a!4 zePKK%>oT6$7mziWvQ$rDV^vjaA<<{}yC^5&Jah9oj}J{v{I#`pqJf*m-=Rjp0@Ozr z6%-))3%VxhJefSO(Tz?s;~nZKpP|)_{ybp-( z0cHL+lBxkBAy<;r6Ip3t(+yKfx(51Y7O0(R!11)l-uO<^V@;K^vJ#Lla(b*&>M)hB zgj{Rt$2Y*lEz-%b#CK)?6W}^>LjUHN{^HzuEADYzPf5K04S#-I30g{kmx}AkE@7yi zF!Q}=`Eg!s-x{Dz#`c?Cp8o(g4do&Xc*WkThku8bWffkn8W&SjJ(Re90eHJ9wwFRh z+u$AQ(0*8vVK~%zufG;hEc#XKOUpizZG#a-Q6R@x-CIOoXlEH*Z++4EmtFfG%l7|Z zgzIRkU;X34uH}2J?SA(aq_E~)*EneRZc#bSKZy6Q_didzJfITQ|K#8gBp%WKKhoYK zWpv^D}AV3ot>vtylQ_ZDY7z_w|kW;?@2lF*~4Ju3| z+ltrf%b7BgY+14MRx`h$Ly`38@HAJUw-c29r35uaK+9#nUG_5Og4e?rXd!QH*I`j2 zp9kOEF!i@*1&vr)YdKDRZxP6;H)lZ!e43e^X)sWn;r~0;p}tB1$OD7qPvh6)ub(hv zDK)8_Ggg;9r#YS?R80xS_veNG^C>p8tq81)|K%y~aCqSlDj$e(*n0LTETWfcEvlH$XO4i5`g*%yyG z3}$?1ZvVK*E{#1Y%*`noMoDm&+Q#>f75B2JEyK@?k%#T?WdgwGGJY3E+=+AfO55ZX zikOMY@3+8&ZFFxirbEM$QAGy({@0~jr( zrJW#nxl-Cr%Cw+gJWy*4>==@`Y7k^x$6DkarMtcxct0ughcKj0GyiGA!`jbl?*}e>sF)nbqg^MtopG6 zs1ArxQ`fZ%Z@?L$f({F{-si@(+B_WGNzmPte?&Pdde_$y%+<@=S1lgnm2sA#fT(EK zgZxekevOfvDh3OcEz}%@FkUw}p@Q`(9fWJQBgK+(eF%<^l>5o2RFJL?aJ)g!gJW(< zZ@0;RfVvkU_u`w$)~2m?>boDbw6x4o<9tnuN{SAK%@?a%)Y3KoVQSEp1XxUad!Id~ ziL_ru=%VojdqZ<|NdY8q39x9J^Cs=@LC=|d@tZj0z#PQ>{FLOOmZs)JX|wbAIv|jt zX0o;5Q}(dy&yTS_mv@bty?grq&Nmi)wFc^}**8#gc zD@mz$;5iK_l6{`a>#g2dM_-K|U=+^0Cg#h}%gudhR9976qj_5WB{YHCK*V8ZBO}1D z(c8B3I^e8paL|6^*IT>;iE%K{;Yb>%^N2t4KJhXRcV(p@Zhjow6}XWfNhz->^pMFh~$hZo9U_7&HMaxPbLVjq~OJJ2h1Qw9u%jxfq{Wih9D~K&Z=f~(jTvk(7zorb=C_U?1dT`NJkOjY0A-& zXW#W}tdRNcoG24Em2^qhql_Dy0Ccw)J_`*Mb-R7IzbE0k{&LG-3(F=^TAKuPDFdnd zLt>^MQ=v}Ym+KhLmWS?EXh7_#+({L;A`xXukl3u-`yQ8bXwE>l-rHW7Q->1kJPK29Dy)=65-G7MmCk9;3ADB+j zZ<;Lm$Wg=}KYlc+Y^b>xIOrp=cK3stb68N&3@l^c$=%u0oi$tet9?mvt~U`8tI2AD zaXJmekU6`jXw&lfTer$=Wdl2CGS?62M7ZpBg*fi(XShT!{I zu+16QX$WA6(CYWr-G%?b1Y5iteo8YuGz3tc`_imRIEWfF#DK%2kduMu2ArZ1O-ROH7K1m{( zCT6~$%XnSC9+C%d_S~K=(g?|Vf6H;cM&xL#Ho5k<(xM zm9=UC`Z3`|cJnV~qdDZCcj@IJm{uVlx^adl$DNC}&W~(Ii!J$x8B=P++!+M#8_-SS zH3Qzh4N-2tJUhC=CuaPP)&vBqgx~tQ`uAh9WTmB}86_MRfXfgT8l0;wx^i>ZLdZ3O zN$Gg)rtdczGCUb$Hkpe8Og)(CN_f4}c4T0=2KQ0N7-}nkb4e=GF;hhvsLuMYJMsH_ z4p)de2|04cF5_ka)WU0bE(;qDL8`EXz~&yR1lnZY(5 zb^tG41F+@Ek!@B>l@5N%T>|Lr_?+Hc`=2w1MBgSSBjezKg#Hx&lN2xDh<(X)_nuu3 z$^~|@m6AF?9~&FH;CnP#+5i}_1Yj3O?LV!*ekIu3^Bf-=?>NIhW3oi&)9@G}8?37% zBk^BvgfQlduk0E3r{A`E--b>}NwP4rFwoQIH}mEW8Gk(zxY!OO{lKJSSwNF6aDNL; zP1R3**OHAGIE{hUfRui^Kwk}NihGN?at-mgN~yi}^f$kOztYtd#>$E_A~N6YaF9%ul7C$~2=h8as&l_j1s6-;?BV(90V zF;jqbk8%Hl6(lJwOjiGyLWG8Ui!pwQGa*n4P&Q{oM1ZT7jGG;xdGRPm*p;~3#wRA5 z*@?}bNjTBjH^){ZmvzN~{X7Mmsedlbsg^w5ZTtG35}8y3;OU$W8SYc8Et7t&i=L`# zL01Iq(ycu^Uy1(wXfqF6Pk2SgB@Upr1>2MTv{~Zk^D0=N2ZLp7jaP32^hna6MNvh? zvHP47fVgPCBh23}g*R#d=9IR(7WuiyKy#cRVRudi=1ZO96SjDI$I zm6cm^4JZ~s#pFY()qTOE)qup`1{AW}`kAk)*8pl6q-?!-#-+kZ188y(ewg^F%hslQ zA`a|TYGU@^-78Cx=;ig5kxuLi%GOAfsI^!mH8gy4rYvI1EsQ-^LrloBz^3?;dUUKM zff+qJvWBEJkQ8zA7}r`D`W^~cT@b$UgrJF$wY%*VBJJY-^ovSS1;ZFA9_e~h!j>8S zA{{%^33EfPkefCC&I3chdE+W!dlob3RBm^{@Ii7*G3XTP6CDxs7G% z<-JnFfqeVNeKaeMWuUFmi}Y9vrOt8YkB=Ws2C(Yic$`dPX-6VdWi?{W584&C$8{Sg zfcFkILf1Hx=d;SaX%dg8$OQOlZ1@NO&P@!+T4jZjFno8bql&;qolAE@Ho-x&J1wT9 zr@aff`NG)$-z32QRb%siz5hS&;r_3E^ - (Invited) + Invited
Date: Wed, 22 Jan 2025 13:48:28 +0000 Subject: [PATCH 2/2] Switch OIDC primarily to new `/auth_metadata` API (#29019) * Switch OIDC primarily to new `/auth_metadata` API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Simplify the world Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../settings/devices/LoginWithQRSection.tsx | 3 +- .../settings/tabs/user/SessionManagerTab.tsx | 7 +-- src/stores/oidc/OidcClientStore.ts | 18 ++---- src/utils/AutoDiscoveryUtils.tsx | 4 +- src/utils/oidc/authorize.ts | 2 +- src/utils/oidc/isUserRegistrationSupported.ts | 4 +- src/utils/oidc/registerClient.ts | 4 +- test/test-utils/oidc.ts | 39 +----------- test/unit-tests/Lifecycle-test.ts | 19 ++---- .../components/structures/auth/Login-test.tsx | 14 ++--- .../structures/auth/Registration-test.tsx | 14 +++-- .../tabs/user/SessionManagerTab-test.tsx | 17 +++-- .../stores/oidc/OidcClientStore-test.ts | 63 ++++++++++--------- .../utils/AutoDiscoveryUtils-test.tsx | 14 ++--- .../utils/oidc/TokenRefresher-test.ts | 2 +- test/unit-tests/utils/oidc/authorize-test.ts | 5 +- .../utils/oidc/registerClient-test.ts | 12 ++-- 17 files changed, 92 insertions(+), 149 deletions(-) diff --git a/src/components/views/settings/devices/LoginWithQRSection.tsx b/src/components/views/settings/devices/LoginWithQRSection.tsx index 4043d28c805..3cd762c126c 100644 --- a/src/components/views/settings/devices/LoginWithQRSection.tsx +++ b/src/components/views/settings/devices/LoginWithQRSection.tsx @@ -31,8 +31,7 @@ export function shouldShowQr( ): boolean { const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"]; - const deviceAuthorizationGrantSupported = - oidcClientConfig?.metadata?.grant_types_supported.includes(DEVICE_CODE_SCOPE); + const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE); return ( !!deviceAuthorizationGrantSupported && diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index bc4355652b6..d6a64426f14 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React, { lazy, Suspense, useCallback, useContext, useEffect, useRef, useState } from "react"; -import { discoverAndValidateOIDCIssuerWellKnown, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { defer } from "matrix-js-sdk/src/utils"; @@ -163,10 +163,7 @@ const SessionManagerTab: React.FC<{ const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]); const oidcClientConfig = useAsyncMemo(async () => { try { - const authIssuer = await matrixClient?.getAuthIssuer(); - if (authIssuer) { - return discoverAndValidateOIDCIssuerWellKnown(authIssuer.issuer); - } + return await matrixClient?.getAuthMetadata(); } catch (e) { logger.error("Failed to discover OIDC metadata", e); } diff --git a/src/stores/oidc/OidcClientStore.ts b/src/stores/oidc/OidcClientStore.ts index f814b1a6cc2..b58405c2dbc 100644 --- a/src/stores/oidc/OidcClientStore.ts +++ b/src/stores/oidc/OidcClientStore.ts @@ -50,11 +50,8 @@ export class OidcClientStore { } else { // We are not in OIDC Native mode, as we have no locally stored issuer. Check if the server delegates auth to OIDC. try { - const authIssuer = await this.matrixClient.getAuthIssuer(); - const { accountManagementEndpoint, metadata } = await discoverAndValidateOIDCIssuerWellKnown( - authIssuer.issuer, - ); - this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer); + const authMetadata = await this.matrixClient.getAuthMetadata(); + this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer); } catch (e) { console.log("Auth issuer not found", e); } @@ -153,14 +150,11 @@ export class OidcClientStore { try { const clientId = getStoredOidcClientId(); - const { accountManagementEndpoint, metadata, signingKeys } = await discoverAndValidateOIDCIssuerWellKnown( - this.authenticatedIssuer, - ); - this.setAccountManagementEndpoint(accountManagementEndpoint, metadata.issuer); + const authMetadata = await discoverAndValidateOIDCIssuerWellKnown(this.authenticatedIssuer); + this.setAccountManagementEndpoint(authMetadata.account_management_uri, authMetadata.issuer); this.oidcClient = new OidcClient({ - ...metadata, - authority: metadata.issuer, - signingKeys, + authority: authMetadata.issuer, + signingKeys: authMetadata.signingKeys ?? undefined, redirect_uri: PlatformPeg.get()!.getOidcCallbackUrl().href, client_id: clientId, }); diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index efc8f285cfd..a9dd44e2c33 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -11,7 +11,6 @@ import { AutoDiscovery, AutoDiscoveryError, ClientConfig, - discoverAndValidateOIDCIssuerWellKnown, IClientWellKnown, MatrixClient, MatrixError, @@ -293,8 +292,7 @@ export default class AutoDiscoveryUtils { let delegatedAuthenticationError: Error | undefined; try { const tempClient = new MatrixClient({ baseUrl: preferredHomeserverUrl }); - const { issuer } = await tempClient.getAuthIssuer(); - delegatedAuthentication = await discoverAndValidateOIDCIssuerWellKnown(issuer); + delegatedAuthentication = await tempClient.getAuthMetadata(); } catch (e) { if (e instanceof MatrixError && e.httpStatus === 404 && e.errcode === "M_UNRECOGNIZED") { // 404 M_UNRECOGNIZED means the server does not support OIDC diff --git a/src/utils/oidc/authorize.ts b/src/utils/oidc/authorize.ts index 3f39683cdfe..339d71414fb 100644 --- a/src/utils/oidc/authorize.ts +++ b/src/utils/oidc/authorize.ts @@ -39,7 +39,7 @@ export const startOidcLogin = async ( const prompt = isRegistration ? "create" : undefined; const authorizationUrl = await generateOidcAuthorizationUrl({ - metadata: delegatedAuthConfig.metadata, + metadata: delegatedAuthConfig, redirectUri, clientId, homeserverUrl, diff --git a/src/utils/oidc/isUserRegistrationSupported.ts b/src/utils/oidc/isUserRegistrationSupported.ts index 8c91ee543b9..22d32173ad0 100644 --- a/src/utils/oidc/isUserRegistrationSupported.ts +++ b/src/utils/oidc/isUserRegistrationSupported.ts @@ -15,8 +15,6 @@ import { OidcClientConfig } from "matrix-js-sdk/src/matrix"; * @returns whether user registration is supported */ export const isUserRegistrationSupported = (delegatedAuthConfig: OidcClientConfig): boolean => { - // The OidcMetadata type from oidc-client-ts does not include `prompt_values_supported` - // even though it is part of the OIDC spec, so cheat TS here to access it - const supportedPrompts = (delegatedAuthConfig.metadata as Record)["prompt_values_supported"]; + const supportedPrompts = delegatedAuthConfig.prompt_values_supported; return Array.isArray(supportedPrompts) && supportedPrompts?.includes("create"); }; diff --git a/src/utils/oidc/registerClient.ts b/src/utils/oidc/registerClient.ts index 61ec4ee3f29..72ea11ddcd2 100644 --- a/src/utils/oidc/registerClient.ts +++ b/src/utils/oidc/registerClient.ts @@ -40,9 +40,9 @@ export const getOidcClientId = async ( delegatedAuthConfig: OidcClientConfig, staticOidcClients?: IConfigOptions["oidc_static_clients"], ): Promise => { - const staticClientId = getStaticOidcClientId(delegatedAuthConfig.metadata.issuer, staticOidcClients); + const staticClientId = getStaticOidcClientId(delegatedAuthConfig.issuer, staticOidcClients); if (staticClientId) { - logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.metadata.issuer}`); + logger.debug(`Using static clientId for issuer ${delegatedAuthConfig.issuer}`); return staticClientId; } return await registerOidcClient(delegatedAuthConfig, await PlatformPeg.get()!.getOidcClientMetadata()); diff --git a/test/test-utils/oidc.ts b/test/test-utils/oidc.ts index 72cdad04ef1..c8032551b0c 100644 --- a/test/test-utils/oidc.ts +++ b/test/test-utils/oidc.ts @@ -6,41 +6,4 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { OidcClientConfig } from "matrix-js-sdk/src/matrix"; -import { ValidatedIssuerMetadata } from "matrix-js-sdk/src/oidc/validate"; - -/** - * Makes a valid OidcClientConfig with minimum valid values - * @param issuer used as the base for all other urls - * @returns OidcClientConfig - */ -export const makeDelegatedAuthConfig = (issuer = "https://auth.org/"): OidcClientConfig => { - const metadata = mockOpenIdConfiguration(issuer); - - return { - accountManagementEndpoint: issuer + "account", - registrationEndpoint: metadata.registration_endpoint, - authorizationEndpoint: metadata.authorization_endpoint, - tokenEndpoint: metadata.token_endpoint, - metadata, - }; -}; - -/** - * Useful for mocking /.well-known/openid-configuration - * @param issuer used as the base for all other urls - * @returns ValidatedIssuerMetadata - */ -export const mockOpenIdConfiguration = (issuer = "https://auth.org/"): ValidatedIssuerMetadata => ({ - issuer, - revocation_endpoint: issuer + "revoke", - token_endpoint: issuer + "token", - authorization_endpoint: issuer + "auth", - registration_endpoint: issuer + "registration", - device_authorization_endpoint: issuer + "device", - jwks_uri: issuer + "jwks", - response_types_supported: ["code"], - grant_types_supported: ["authorization_code", "refresh_token"], - code_challenge_methods_supported: ["S256"], - account_management_uri: issuer + "account", -}); +export { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "matrix-js-sdk/src/testing"; diff --git a/test/unit-tests/Lifecycle-test.ts b/test/unit-tests/Lifecycle-test.ts index ba5885c414c..284a3c6531e 100644 --- a/test/unit-tests/Lifecycle-test.ts +++ b/test/unit-tests/Lifecycle-test.ts @@ -749,11 +749,8 @@ describe("Lifecycle", () => { "eyJhbGciOiJSUzI1NiIsImtpZCI6Imh4ZEhXb0Y5bW4ifQ.eyJzdWIiOiIwMUhQUDJGU0JZREU5UDlFTU04REQ3V1pIUiIsImlzcyI6Imh0dHBzOi8vYXV0aC1vaWRjLmxhYi5lbGVtZW50LmRldi8iLCJpYXQiOjE3MTUwNzE5ODUsImF1dGhfdGltZSI6MTcwNzk5MDMxMiwiY19oYXNoIjoidGt5R1RhUjU5aTk3YXoyTU4yMGdidyIsImV4cCI6MTcxNTA3NTU4NSwibm9uY2UiOiJxaXhwM0hFMmVaIiwiYXVkIjoiMDFIWDk0Mlg3QTg3REgxRUs2UDRaNjI4WEciLCJhdF9oYXNoIjoiNFlFUjdPRlVKTmRTeEVHV2hJUDlnZyJ9.HxODneXvSTfWB5Vc4cf7b8GiN2gdwUuTiyVqZuupWske2HkZiJZUt5Lsxg9BW3gz28POkE0Ln17snlkmy02B_AD3DQxKOOxQCzIIARHdfFvZxgGWsMdFcVQZDW7rtXcqgj-SpVaUQ_8acsgxSrz_DF2o0O4tto0PT6wVUiw8KlBmgWTscWPeAWe-39T-8EiQ8Wi16h6oSPcz2NzOQ7eOM_S9fDkOorgcBkRGLl1nrahrPSdWJSGAeruk5mX4YxN714YThFDyEA2t9YmKpjaiSQ2tT-Xkd7tgsZqeirNs2ni9mIiFX3bRX6t2AhUNzA7MaX9ZyizKGa6go3BESO_oDg"; beforeAll(() => { - fetchMock.get( - `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`, - delegatedAuthConfig.metadata, - ); - fetchMock.get(`${delegatedAuthConfig.metadata.issuer}jwks`, { + fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig); + fetchMock.get(`${delegatedAuthConfig.issuer}jwks`, { status: 200, headers: { "Content-Type": "application/json", @@ -772,9 +769,7 @@ describe("Lifecycle", () => { await setLoggedIn(credentials); // didn't try to initialise token refresher - expect(fetchMock).not.toHaveFetched( - `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`, - ); + expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`); }); it("should not try to create a token refresher without a deviceId", async () => { @@ -785,9 +780,7 @@ describe("Lifecycle", () => { }); // didn't try to initialise token refresher - expect(fetchMock).not.toHaveFetched( - `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`, - ); + expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`); }); it("should not try to create a token refresher without an issuer in session storage", async () => { @@ -803,9 +796,7 @@ describe("Lifecycle", () => { }); // didn't try to initialise token refresher - expect(fetchMock).not.toHaveFetched( - `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`, - ); + expect(fetchMock).not.toHaveFetched(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`); }); it("should create a client with a tokenRefreshFunction", async () => { diff --git a/test/unit-tests/components/structures/auth/Login-test.tsx b/test/unit-tests/components/structures/auth/Login-test.tsx index 3b1a85b3e9b..ee3c608256a 100644 --- a/test/unit-tests/components/structures/auth/Login-test.tsx +++ b/test/unit-tests/components/structures/auth/Login-test.tsx @@ -384,7 +384,7 @@ describe("Login", function () { await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); // didn't try to register - expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint); + expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint); // continued with normal setup expect(mockClient.loginFlows).toHaveBeenCalled(); // normal password login rendered @@ -394,25 +394,25 @@ describe("Login", function () { it("should attempt to register oidc client", async () => { // dont mock, spy so we can check config values were correctly passed jest.spyOn(registerClientUtils, "getOidcClientId"); - fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 }); + fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 }); getComponent(hsUrl, isUrl, delegatedAuth); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); // tried to register - expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object)); + expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object)); // called with values from config expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(delegatedAuth, oidcStaticClientsConfig); }); it("should fallback to normal login when client registration fails", async () => { - fetchMock.post(delegatedAuth.registrationEndpoint!, { status: 500 }); + fetchMock.post(delegatedAuth.registration_endpoint!, { status: 500 }); getComponent(hsUrl, isUrl, delegatedAuth); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); // tried to register - expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registrationEndpoint, expect.any(Object)); + expect(fetchMock).toHaveBeenCalledWith(delegatedAuth.registration_endpoint, expect.any(Object)); expect(logger.error).toHaveBeenCalledWith(new Error(OidcError.DynamicRegistrationFailed)); // continued with normal setup @@ -423,7 +423,7 @@ describe("Login", function () { // short term during active development, UI will be added in next PRs it("should show continue button when oidc native flow is correctly configured", async () => { - fetchMock.post(delegatedAuth.registrationEndpoint!, { client_id: "abc123" }); + fetchMock.post(delegatedAuth.registration_endpoint!, { client_id: "abc123" }); getComponent(hsUrl, isUrl, delegatedAuth); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); @@ -455,7 +455,7 @@ describe("Login", function () { await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); // didn't try to register - expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registrationEndpoint); + expect(fetchMock).not.toHaveBeenCalledWith(delegatedAuth.registration_endpoint); // continued with normal setup expect(mockClient.loginFlows).toHaveBeenCalled(); // oidc-aware 'continue' button displayed diff --git a/test/unit-tests/components/structures/auth/Registration-test.tsx b/test/unit-tests/components/structures/auth/Registration-test.tsx index 82ac0b666cd..a8c170eac7f 100644 --- a/test/unit-tests/components/structures/auth/Registration-test.tsx +++ b/test/unit-tests/components/structures/auth/Registration-test.tsx @@ -158,24 +158,26 @@ describe("Registration", function () { describe("when delegated authentication is configured and enabled", () => { const authConfig = makeDelegatedAuthConfig(); const clientId = "test-client-id"; - // @ts-ignore - authConfig.metadata["prompt_values_supported"] = ["create"]; + authConfig.prompt_values_supported = ["create"]; beforeEach(() => { // mock a statically registered client to avoid dynamic registration SdkConfig.put({ oidc_static_clients: { - [authConfig.metadata.issuer]: { + [authConfig.issuer]: { client_id: clientId, }, }, }); fetchMock.get(`${defaultHsUrl}/_matrix/client/unstable/org.matrix.msc2965/auth_issuer`, { - issuer: authConfig.metadata.issuer, + issuer: authConfig.issuer, }); - fetchMock.get("https://auth.org/.well-known/openid-configuration", authConfig.metadata); - fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] }); + fetchMock.get("https://auth.org/.well-known/openid-configuration", { + ...authConfig, + signingKeys: undefined, + }); + fetchMock.get(authConfig.jwks_uri!, { keys: [] }); }); it("should display oidc-native continue button", async () => { diff --git a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 030f769de21..f334c6b28f8 100644 --- a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -57,7 +57,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore"; import { getClientInformationEventType } from "../../../../../../../src/utils/device/clientInformation"; import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext"; import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore"; -import { mockOpenIdConfiguration } from "../../../../../../test-utils/oidc"; +import { makeDelegatedAuthConfig } from "../../../../../../test-utils/oidc"; import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext"; mockPlatformPeg(); @@ -215,7 +215,7 @@ describe("", () => { getPushers: jest.fn(), setPusher: jest.fn(), setLocalNotificationSettings: jest.fn(), - getAuthIssuer: jest.fn().mockReturnValue(new Promise(() => {})), + getAuthMetadata: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_UNRECOGNIZED" }, 404)), }); jest.clearAllMocks(); jest.spyOn(logger, "error").mockRestore(); @@ -1615,7 +1615,6 @@ describe("", () => { describe("MSC4108 QR code login", () => { const settingsValueSpy = jest.spyOn(SettingsStore, "getValue"); const issuer = "https://issuer.org"; - const openIdConfiguration = mockOpenIdConfiguration(issuer); beforeEach(() => { settingsValueSpy.mockClear().mockReturnValue(true); @@ -1631,16 +1630,16 @@ describe("", () => { enabled: true, }, }); - mockClient.getAuthIssuer.mockResolvedValue({ issuer }); - mockCrypto.exportSecretsBundle = jest.fn(); - fetchMock.mock(`${issuer}/.well-known/openid-configuration`, { - ...openIdConfiguration, + const delegatedAuthConfig = makeDelegatedAuthConfig(issuer); + mockClient.getAuthMetadata.mockResolvedValue({ + ...delegatedAuthConfig, grant_types_supported: [ - ...openIdConfiguration.grant_types_supported, + ...delegatedAuthConfig.grant_types_supported, "urn:ietf:params:oauth:grant-type:device_code", ], }); - fetchMock.mock(openIdConfiguration.jwks_uri!, { + mockCrypto.exportSecretsBundle = jest.fn(); + fetchMock.mock(delegatedAuthConfig.jwks_uri!, { status: 200, headers: { "Content-Type": "application/json", diff --git a/test/unit-tests/stores/oidc/OidcClientStore-test.ts b/test/unit-tests/stores/oidc/OidcClientStore-test.ts index 8de1d9dad10..164f90f5319 100644 --- a/test/unit-tests/stores/oidc/OidcClientStore-test.ts +++ b/test/unit-tests/stores/oidc/OidcClientStore-test.ts @@ -15,7 +15,7 @@ import { OidcError } from "matrix-js-sdk/src/oidc/error"; import { OidcClientStore } from "../../../../src/stores/oidc/OidcClientStore"; import { flushPromises, getMockClientWithEventEmitter, mockPlatformPeg } from "../../../test-utils"; -import { mockOpenIdConfiguration } from "../../../test-utils/oidc"; +import { makeDelegatedAuthConfig } from "../../../test-utils/oidc"; jest.mock("matrix-js-sdk/src/matrix", () => ({ ...jest.requireActual("matrix-js-sdk/src/matrix"), @@ -24,28 +24,30 @@ jest.mock("matrix-js-sdk/src/matrix", () => ({ describe("OidcClientStore", () => { const clientId = "test-client-id"; - const metadata = mockOpenIdConfiguration(); - const account = metadata.issuer + "account"; + const authConfig = makeDelegatedAuthConfig(); + const account = authConfig.issuer + "account"; const mockClient = getMockClientWithEventEmitter({ - getAuthIssuer: jest.fn(), + getAuthMetadata: jest.fn(), }); beforeEach(() => { localStorage.clear(); localStorage.setItem("mx_oidc_client_id", clientId); - localStorage.setItem("mx_oidc_token_issuer", metadata.issuer); - - mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({ - metadata, - accountManagementEndpoint: account, - authorizationEndpoint: "authorization-endpoint", - tokenEndpoint: "token-endpoint", - }); + localStorage.setItem("mx_oidc_token_issuer", authConfig.issuer); + + mocked(discoverAndValidateOIDCIssuerWellKnown) + .mockClear() + .mockResolvedValue({ + ...authConfig, + account_management_uri: account, + authorization_endpoint: "authorization-endpoint", + token_endpoint: "token-endpoint", + }); jest.spyOn(logger, "error").mockClear(); - fetchMock.get(`${metadata.issuer}.well-known/openid-configuration`, metadata); - fetchMock.get(`${metadata.issuer}jwks`, { keys: [] }); + fetchMock.get(`${authConfig.issuer}.well-known/openid-configuration`, authConfig); + fetchMock.get(`${authConfig.issuer}jwks`, { keys: [] }); mockPlatformPeg(); }); @@ -116,7 +118,7 @@ describe("OidcClientStore", () => { const client = await store.getOidcClient(); expect(client?.settings.client_id).toEqual(clientId); - expect(client?.settings.authority).toEqual(metadata.issuer); + expect(client?.settings.authority).toEqual(authConfig.issuer); }); it("should set account management endpoint when configured", async () => { @@ -129,17 +131,19 @@ describe("OidcClientStore", () => { }); it("should set account management endpoint to issuer when not configured", async () => { - mocked(discoverAndValidateOIDCIssuerWellKnown).mockClear().mockResolvedValue({ - metadata, - accountManagementEndpoint: undefined, - authorizationEndpoint: "authorization-endpoint", - tokenEndpoint: "token-endpoint", - }); + mocked(discoverAndValidateOIDCIssuerWellKnown) + .mockClear() + .mockResolvedValue({ + ...authConfig, + account_management_uri: undefined, + authorization_endpoint: "authorization-endpoint", + token_endpoint: "token-endpoint", + }); const store = new OidcClientStore(mockClient); await store.readyPromise; - expect(store.accountManagementEndpoint).toEqual(metadata.issuer); + expect(store.accountManagementEndpoint).toEqual(authConfig.issuer); }); it("should reuse initialised oidc client", async () => { @@ -175,7 +179,7 @@ describe("OidcClientStore", () => { fetchMock.resetHistory(); fetchMock.post( - metadata.revocation_endpoint, + authConfig.revocation_endpoint, { status: 200, }, @@ -197,7 +201,7 @@ describe("OidcClientStore", () => { await store.revokeTokens(accessToken, refreshToken); - expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint); + expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint); expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token"); expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(refreshToken, "refresh_token"); }); @@ -206,14 +210,14 @@ describe("OidcClientStore", () => { // fail once, then succeed fetchMock .postOnce( - metadata.revocation_endpoint, + authConfig.revocation_endpoint, { status: 404, }, { overwriteRoutes: true, sendAsJson: true }, ) .post( - metadata.revocation_endpoint, + authConfig.revocation_endpoint, { status: 200, }, @@ -226,7 +230,7 @@ describe("OidcClientStore", () => { "Failed to revoke tokens", ); - expect(fetchMock).toHaveFetchedTimes(2, metadata.revocation_endpoint); + expect(fetchMock).toHaveFetchedTimes(2, authConfig.revocation_endpoint); expect(OidcClient.prototype.revokeToken).toHaveBeenCalledWith(accessToken, "access_token"); }); }); @@ -237,7 +241,10 @@ describe("OidcClientStore", () => { }); it("should resolve account management endpoint", async () => { - mockClient.getAuthIssuer.mockResolvedValue({ issuer: metadata.issuer }); + mockClient.getAuthMetadata.mockResolvedValue({ + ...authConfig, + account_management_uri: account, + }); const store = new OidcClientStore(mockClient); await store.readyPromise; expect(store.accountManagementEndpoint).toBe(account); diff --git a/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx b/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx index a49707e3101..575dc885a00 100644 --- a/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx +++ b/test/unit-tests/utils/AutoDiscoveryUtils-test.tsx @@ -355,21 +355,19 @@ describe("AutoDiscoveryUtils", () => { hsNameIsDifferent: true, hsName: serverName, delegatedAuthentication: expect.objectContaining({ - accountManagementActionsSupported: [ + issuer, + account_management_actions_supported: [ "org.matrix.profile", "org.matrix.sessions_list", "org.matrix.session_view", "org.matrix.session_end", "org.matrix.cross_signing_reset", ], - accountManagementEndpoint: "https://auth.matrix.org/account/", - authorizationEndpoint: "https://auth.matrix.org/auth", - metadata: expect.objectContaining({ - issuer, - }), - registrationEndpoint: "https://auth.matrix.org/registration", + account_management_uri: "https://auth.matrix.org/account/", + authorization_endpoint: "https://auth.matrix.org/auth", + registration_endpoint: "https://auth.matrix.org/registration", signingKeys: [], - tokenEndpoint: "https://auth.matrix.org/token", + token_endpoint: "https://auth.matrix.org/token", }), warning: null, }); diff --git a/test/unit-tests/utils/oidc/TokenRefresher-test.ts b/test/unit-tests/utils/oidc/TokenRefresher-test.ts index 643f61faacb..66f0e800e02 100644 --- a/test/unit-tests/utils/oidc/TokenRefresher-test.ts +++ b/test/unit-tests/utils/oidc/TokenRefresher-test.ts @@ -38,7 +38,7 @@ describe("TokenRefresher", () => { }; beforeEach(() => { - fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig.metadata); + fetchMock.get(`${issuer}.well-known/openid-configuration`, authConfig); fetchMock.get(`${issuer}jwks`, { status: 200, headers: { diff --git a/test/unit-tests/utils/oidc/authorize-test.ts b/test/unit-tests/utils/oidc/authorize-test.ts index bea9724fce3..af2fd432bc0 100644 --- a/test/unit-tests/utils/oidc/authorize-test.ts +++ b/test/unit-tests/utils/oidc/authorize-test.ts @@ -61,10 +61,7 @@ describe("OIDC authorization", () => { }); beforeAll(() => { - fetchMock.get( - `${delegatedAuthConfig.metadata.issuer}.well-known/openid-configuration`, - delegatedAuthConfig.metadata, - ); + fetchMock.get(`${delegatedAuthConfig.issuer}.well-known/openid-configuration`, delegatedAuthConfig); }); afterAll(() => { diff --git a/test/unit-tests/utils/oidc/registerClient-test.ts b/test/unit-tests/utils/oidc/registerClient-test.ts index 0c6a41bc36d..197beeb2bf8 100644 --- a/test/unit-tests/utils/oidc/registerClient-test.ts +++ b/test/unit-tests/utils/oidc/registerClient-test.ts @@ -58,7 +58,7 @@ describe("getOidcClientId()", () => { const authConfigWithoutRegistration: OidcClientConfig = makeDelegatedAuthConfig( "https://issuerWithoutStaticClientId.org/", ); - authConfigWithoutRegistration.registrationEndpoint = undefined; + authConfigWithoutRegistration.registration_endpoint = undefined; await expect(getOidcClientId(authConfigWithoutRegistration, staticOidcClients)).rejects.toThrow( OidcError.DynamicRegistrationNotSupported, ); @@ -69,7 +69,7 @@ describe("getOidcClientId()", () => { it("should handle when staticOidcClients object is falsy", async () => { const authConfigWithoutRegistration: OidcClientConfig = { ...delegatedAuthConfig, - registrationEndpoint: undefined, + registration_endpoint: undefined, }; await expect(getOidcClientId(authConfigWithoutRegistration)).rejects.toThrow( OidcError.DynamicRegistrationNotSupported, @@ -79,14 +79,14 @@ describe("getOidcClientId()", () => { }); it("should make correct request to register client", async () => { - fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, { + fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, { status: 200, body: JSON.stringify({ client_id: dynamicClientId }), }); expect(await getOidcClientId(delegatedAuthConfig)).toEqual(dynamicClientId); // didn't try to register expect(fetchMockJest).toHaveBeenCalledWith( - delegatedAuthConfig.registrationEndpoint!, + delegatedAuthConfig.registration_endpoint!, expect.objectContaining({ headers: { "Accept": "application/json", @@ -111,14 +111,14 @@ describe("getOidcClientId()", () => { }); it("should throw when registration request fails", async () => { - fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, { + fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, { status: 500, }); await expect(getOidcClientId(delegatedAuthConfig)).rejects.toThrow(OidcError.DynamicRegistrationFailed); }); it("should throw when registration response is invalid", async () => { - fetchMockJest.post(delegatedAuthConfig.registrationEndpoint!, { + fetchMockJest.post(delegatedAuthConfig.registration_endpoint!, { status: 200, // no clientId in response body: "{}",