From eebf62d52773665260cdb1d5fda418a97863af37 Mon Sep 17 00:00:00 2001 From: absc Date: Tue, 10 Sep 2024 19:40:30 +0000 Subject: [PATCH] Split the web front-end and the backend into separate applications. This design will make things easier to both scale and test. Having a dedicated back-end application allow us to keep the API stable, while improving the internals. --- cowlib/src/cow_base64url.beam | Bin 1692 -> 0 bytes cowlib/src/cow_cookie.beam | Bin 7572 -> 0 bytes cowlib/src/cow_date.beam | Bin 19576 -> 0 bytes cowlib/src/cow_hpack.beam | Bin 101060 -> 0 bytes dudeswave/src/Makefile | 20 - dudeswave/src/dudeswave_auth.erl | 408 ------------------ dudeswave_backend/src/dudeswave_backend.erl | 81 +++- .../src/dudeswave_backend_auth.erl | 16 +- .../src/dudeswave_backend_user.erl | 2 +- {dudeswave => dudeswave_web}/Makefile | 0 .../ebin/dudeswave_web.app | 10 +- dudeswave_web/src/Makefile | 19 + .../src/dudeswave_web.erl | 2 +- .../src/dudeswave_web_app.erl | 4 +- .../src/dudeswave_web_auth_handler.erl | 105 ++++- .../src/dudeswave_web_common.erl | 32 +- .../src/dudeswave_web_handler.erl | 2 +- .../src/dudeswave_web_supervisor.erl | 2 +- .../src/dudeswave_web_user_handler.erl | 84 +++- 19 files changed, 308 insertions(+), 479 deletions(-) delete mode 100644 cowlib/src/cow_base64url.beam delete mode 100644 cowlib/src/cow_cookie.beam delete mode 100644 cowlib/src/cow_date.beam delete mode 100644 cowlib/src/cow_hpack.beam delete mode 100644 dudeswave/src/Makefile delete mode 100644 dudeswave/src/dudeswave_auth.erl rename {dudeswave => dudeswave_web}/Makefile (100%) rename dudeswave/ebin/dudeswave.app => dudeswave_web/ebin/dudeswave_web.app (54%) create mode 100644 dudeswave_web/src/Makefile rename dudeswave/src/dudeswave.erl => dudeswave_web/src/dudeswave_web.erl (98%) rename dudeswave/src/dudeswave_app.erl => dudeswave_web/src/dudeswave_web_app.erl (97%) rename dudeswave/src/dudeswave_auth_handler.erl => dudeswave_web/src/dudeswave_web_auth_handler.erl (58%) rename dudeswave/include/defines.hrl => dudeswave_web/src/dudeswave_web_common.erl (58%) rename dudeswave/src/dudeswave_handler.erl => dudeswave_web/src/dudeswave_web_handler.erl (98%) rename dudeswave/src/dudeswave_supervisor.erl => dudeswave_web/src/dudeswave_web_supervisor.erl (94%) rename dudeswave/src/dudeswave_user_handler.erl => dudeswave_web/src/dudeswave_web_user_handler.erl (72%) diff --git a/cowlib/src/cow_base64url.beam b/cowlib/src/cow_base64url.beam deleted file mode 100644 index 7430dc894f7d7cc508fb9d2ce53aaaabdbf6f3b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1692 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 a^ByHcCQ?_+&cg0`q69-pn%db$mi`Z%G1x}{ diff --git a/cowlib/src/cow_date.beam b/cowlib/src/cow_date.beam deleted file mode 100644 index fadd9db1aed7be3165a1d55c017609b961e74058..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19576 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 (%g!%QY%c1ptRn1b0M4YPd)X1grRwmFz>@-SN$ zU_M%e`G{Zs2uJ&9^ii^}*qZDowjukAZOH**J941do=k}y$U$O9a>`$h}0pvt+AUR1) zk&lUk$j8OO#P4(6CV%+Upyqlz#``sE`zIzywQ$eH43a+WxToGp$e=ZNFTx#D;- zBTgXaiD`1aIFVc+P9hhIkCBVS$H^ze$>fvb6mqdRm0TiD3(6M<Ckxmuh@t`X;xYsCfRI&mSnUR*?O5T777icgZ8#Kq)haY<0VIVj&0 zfw?gXb3+W~`Z&yW37Bh>FxRADu1>?uW?-($!d#hyxgrm9c>(6KBFv?J`BIK<(daUA ztGJweN?bv16IYVk#Z}}EF-zve)#Ofb4Y^BPOYRoek$c4Ta>`K-8!+$(M- z_laAA^8G>iz6i{{QJBxhU_KLv`E&y2o+QlODVV#`Fn4BP=CUw%k!`xPY`BV|+ zR=<2JNB3*=De{20jXWrBCl84`$irfeJR9F)Hlf%#$-<_j^H&&Odtmw=J7PlV;Pu7voP~H zm`Cz34;Nq_D#ASImmlQlOBy{yzAPRlUlEUxuZnr{HSs8UN<2mu#N*^?@dSBBJV~Av zpCiwS&y(lH7s%Je7s)rom&iB8m&v!pSAz0)gYtJGFyD^Cd@Ba?%{a_A5-?v+!aSdX zc`gm}YzF3;EX>n6n1wvdQw5l>6=A;Wm%qxA12`Hpysd{-=x?}?|$_r){h2jW?> zD4ruf6wi|%iLa9%i*Jyhh;Ndgif@shiEopii|>$Mi0_hLith#G-v;I1L|}d$h51zs z=9h7pUnF3Do`m^X3g)M2n4e@|ew>B*Q4Z#Zd6>ll%nyn%-}lSk=jc}&{eb*hERx@d zACljSACcdQACupUpO8O@pOQa{pOHU_pOZg}Uy#3uUy{FyUy;9wUz5L!-;jTZ-;#fd z-v#CW1?B%nVEz+@`F9NFUvZd!CSd-Ng!y|4=5J}3zh+?ml7;zm4(3mJm_HU^{!oPZ zyOTI4mmx%S^&DQm{g4SeIpBU7Ce; zNe*b%DQh0Y@#3UP#*FMWiEMOuFJFq$gfVhQ-Ush!`R-7Y*_X(Il@F zE%GYSCa)G9@*2@4ONbtMtr#Y+6C*)+G$_A00_&zItQ%vn5QlX`0@n3OSl6XsU7Lng zA_ME1EUc?@u&&C(y0QT4iXyDb{qoB>dc8)kAa4+_Bq3f!-Y8y8-XvZ_-Yk|NqvEyX zE#h_Lt>X3MZQ>2&?IOsM;*De}@g}mgcr#f>jFM%=TY~a?gYsAe);&>JcgJAe6^B(W z0qf2rtUFS$%BEqJ$-pX|g;gpCt7IP5?FCr36=B`#m*2|KJ2ZM5d8c?gSxziT-X)eI z?-omw_lRZ4m{^v)SG!u`*dxtU}fjtCF?FYGfU;I$2k&LDm!FWPPzF*+8sC zHWX_I<*kD9mJwJjqOcM%Sk2?Gnk8U0O~PuDg4H+;t5F74!z`=@Iau}cu<8|H)h)uR zgBY$i4&n~ROegxHvDAvPgficQH@Vl(m~u{rs$m>?e! zTab^6Ey>nmE3%FFP*C12DDN79)g=n6a|~884y#iFR>vf)4k=je)3DlQV71M{YLkQ2 zIuGm70<1@hupaiyALeLVjXpxQ6CWkpi>=8HVjHrf*p}=hwj+~bd$P0Gf$So7B)f{8 z$Zlei>@IdDdx%}go?=(Bm)MQ$Ep`vehXv(BBd~@O28VJgf$=qtA83+ zzYMItSy+8?uzKfV^(w&XS%lTYFYm$8J{s*w_7!`P{lwm6f3XiaK`Q^@J!)S&#yp!|sltVK~+ z3uCYr#9_@(z?zqYl}W*xn}#(f18a5`)~pvxE)*A#i^PTG6XGKBN%0ACvG^ppL|hz{ zZwkscMqq7-!df4LwJr{8Z35PsB&^jbSlKkJRT)?-v#?g=U@gzXT2_FyvyOXeXrC{w$!^&k~?a0E~o`bb559_G{ ztgS^@Tm14Z9NnVPt>jkmDe@_C8@Wx~PHq=>kUPX2nG<)CJH=h(E^#-xTiiqL5uYZX z7M~%X5uYWW759>R#eL*Haeq+$Qc(V41l9{tSkK2`Jr{>{G6CyE64vn)tYc|dM>DYU zSy)GMuny;89V);&ScG-JFF(N1{Te+;9uN}0#G_s$)f*)*&(8Ca*YunIX?r}D5~E5LfS2tY@ho{-JV%}p&y#1x*U59@8{~QMP4ac|E%FWVZSqa=9r7*l-JtxNp#19ytgoW5 zzKp^8A`a{G1gy`Jus%(}`XmkO;|#2ivamkP!7AoqeNce)ei7Dte))SGeOsgNlkbQh zknf5`@;&iG@_q3m@&oZ>vM7E+ekguQek6WIek^`YejZx){}X}rcNErNF<5`bVf~SS^?MT5Zz)*6reXb(f%S71)=xQDKjvZmP=NJ) z5!QEp`F9-sN~7PCUyDDG--thw--D8l~V1tskNar7^a+82<2ix-mrh!>IniWigriI@1a{wH2aULamZ zUMPmhi$sIGSTxB?M2oysw8_gvhYX1>X^0+ais7LA=Aith2<#i9un~iOLmc+?3E0;q zVPBhqT_O$pnhfl#v#_tq!M-vN`-%eW%Zsoher|-LmPRipZSe}y5w9d&@hZ|2uO`Fd zHDp9AL0&FiOI{&fM_wsjPhKV7Kwd3^yhgl{EFs=RUMt>AUMEI_@>o!QPXzYeQP_9I zV3&)-zB2*)jwI}|DcEJwuuErPm&(E}nS*_M9`cN<@;32K@^-NtSyH@L}4%)oAtgmkgJQ5#ao7VBum>bz z_fNs@mxkRp1G`TacJCbQUU}F(3$S|>VR!e-yK}UyMthL$#GYh(u@~7v>`itQ`;eW) zzGPDDM|KwblU>9CWLI$@*-cE5-Niv<4{cV2<*qAuqVY} zPmIG(Cty!V!XBT3JuVGB+ z6XbO9$)J2=P`)7odwmr4x)|)WaoB4TuvaHxXH&3OrD3nkz+RDsy*vkdSswP%0_-J4 z*o*!0#T=cX(IwhE735rTC7BUdk@LhXIbU2&E)ds{3&pkMB5@u0 zgt(r3Qrti;7B`Yh#7#l@-k|*12<&H~u%C{>-V=wtI{|xF686p%>|7f5jtuPWS=ifh zu%F7q-dcdYr3iboU%r{6OEtQMTqbTMmy1u4E5vQ&N^v{6O58za#T>a>+)1txcadww z-Q+rP54m1^n%p2hLv9qGB{zwC$<5-vp!~(4{DlbY=cBNni@`n_hkYUe`*;%eu@vm1 zY1sJ;>?2v&hjXwGVRE~8gxn$K$((qU z+$kO-cZtWz-Qo#yk9d-NT6~UtMtq)pR(yfnE51nX6JH9--ww*(iokv|3j2*1?APP4 z&nIA?OTs>zf_)|p`*a3&Aq)Fd4)$w#*sm5~zfy$#vS0o(NB3*=74m@iDtSZh zBCx-V!u}!#`|~*L&l0ddO~U>p1^eSP?2j_AKg_}|=3sx2hy8v5_IpLx@A~EMa`YvQ zzDK?+zE8d)en7q|7RlGd56M&FM`S_#m^>|hLY@&nCC`eVk>|wE$@AhDt9QN-C*uN!V|C)mROB(ji8Q4E%VgHze{X-u1 z_XXJB6=8qtmw(IAw>A15`HuKK`L6f_`JVVA`M&rQ`GNQ|SrmUEKNNo@KN5c$#W$Be@<5^zFEIG3g1T$+Y+ zNe0fvSvVKv;9Qu8b3p;l|NQd*{PO=e`V~i=|B+vd7m(kG7n0wK7m?qI7n9$Mmykb* zmy$nkI!e-mx;chMpL5MA<5(F@9N3d(Pcz(Eww4KX;^$KhO; zfOBmUPKgwpYtnG8&cL}U3+KuloGbEhE-%1|6yb#Z+%QM~(rASITfCh7N4$dkSGSXKr$Po!`7E3Vb8sHW!@0iz=e{DG zd;RizIeNWD?;~#z?NdZx^eQCBUiG!Cap0#4&3oJJ`)4byNMWZ=}#!l{>o zQ#TK%P61BsBAi-&c`c6Kq0!pponjrboLHB)5A0exWkCN5J)?^K_4H*~P zk~PJ4WG%5hSzGKt))70Bb;V9(Juykv7dw*;#4cn*v1?F1Bq$#ofioxyCl!M;Fb-!x z0#5%VoPH@debaFIWZ?A9!s(TR(=!jJM*&XvBAjl1c{h$W(r9+CCz3tHNn}s)F|wEVIN4jA z9F#8#$`?l9EQrFHAA>V54kwdZ&%>EkfHSoSXNq4w zg`<5mI+g4zP9yt?)5-qg403=tlN>0{B2(gQa*#NO94yWyhlm++s5p-tCe9~^iwnpR z;zDwyxQHAjJ`t2}2+G$-;H-9#2WL>Egzq{Mn%VnFySxqj2`b;OvgW*_D8^GYKb`g0mwHXL|YP?44lJRIEQj@4(8z;D8SiYgtN~t-^bCV8r@GW6AzHf#e?Ju@esLEJWQ?< zkC0h0Pp%e^l550c|8^q_xjpFm?oI(c9sVtn=a&TVF!+E6u=j9@tm;CaVIJ!lnFOyrvSIDQt zSIKSSYvgwE6uCnzkU8-*xl=qt?h?ytjQ9rmtoSCmSA2`yC%zq& ze-)H}8G-Xf6wc={IG@Gge42psNfOS-DL5ac;e42ZQ_RBoAP49DJe>CmaNaG#dB-n* zhok#7`Yw4ue2+XRzE2(!KOhf_Me>OFA(;LwML6I1<==4hC5?VdzAS!6z9N24zAFAez9#-io)Ui|3*yh@Y4I2GjQA^g zR{V`TC;m>J7ylq%7yl&R5dR|I6#pjQ68}-T>!{qdBXF%KTr&pOh{Fvf;9i!5dua;p zC26=9XW(9xg?nKR?ge?c|MScL^UMGB%m3x*+Zz3ke1~-ZN4_gwK)xqlNWL##M1CM% zOcupU$PdL!$&bX#$dAPk`H5(dpNb~=nP`!pi#GX%=#XEEZcu(>P>u-P8=`QpkHNhz z4)@vw+!9H+*QDTHorZf=2JV$vxL4%hUY>^=DZmXE;d*|f$I-7e8YaIMBjh*Y<>a^G z736o~mE`y0Rpbxi)#Q)jHRMlX3G!$0TJjh1I`UWXdh$2%2J&|i{+jKWRF;7*9c9iM9)WLI%6*-gxl-Nkui z4{<)(Q(Qpy5*L!a#YI8+`k;JW1n$}>+%++{tK)F93An40a95__u1LdOo`Jh83wLP_ z?vgy*#Ra%e7U4eOmp{SLJ{o?c;tFz*xRM+!t|Ets zS#qejnj9vsA%}}=$r0i@a-_JP93^fD%AX0!pN_!Y6NS4w26tB+?#=|HzIIf zkHS44gL^Ix_iO_0nIzoPDY%6++*292uVvxBnuGgF9`4HpxGxpqzUY^~$kC-5eTiHq zzDzC`Um;hBuaYap*T_}kDKaY-$kpO$a*cR~Tq~X>*NNxI_2PMQgZMhRQGA2kB)&;* z7T*fWzYNO1h`{|k3iq=Z+)v|hKS{v-I0^To6x%f)d^lgoLN+ z?}?X_?~7NEABb0yMe!=~L-A_zBk>yYW3dGJiFhsfsdydvnRq?4>#SSFA&NVqG#U)*~Zgee!a# z0eOYkki1fCL|!E}Ca)Hokk^P!$r55S@>;Pud7YRD%9BBPrwF``QFt9<@Y=`WwM)Qj zn}pXU1+R4)-lG|Kk7VIJoP+mJ9$u>gyp~0HE&TEp9KBwnEy)|iRwTrS$Q#9n$(zJS z$eYDS$*9*w~Ot`l41w4l-Q9hEp{Tyh)J@n*f}U46qKhT@CHWV z4T!<(ABWd30k3ZoUY``a-f4KfGVpq4;q}PD>z;?#tpKlU5ndO+ybDL~&}dikPO%$V zPV7$JCH5fi7JHKSh`q>|*qgjp>_grs_9gEZ`;iZb{mBQ#0c3e`AX!07krl;3WF>KM zP(CRrpBRCcj>4M|gEu}7Z(IW2*d)9$DR`sP@J40ejm*Luk%Kop4{ulj-q0evA%6J~ zj#k#_P_l|RjI1gSC##7g$m-%qvW7T{jEkemn&KF;mN=HIEsi7Wh~vq+;smmum?rCs z6UheRB(kCSSWvzoD4!pJH!lh=6N5K54sT8Z-s~j2St)ok)9_|w;7!lMo0fw&H4kq} z0p8>yyvP0W$2r6ql2o#T8^1 zaV6PRTt#*hvt)O1HQ7U4L-rKclD))rWN&eOQ2umKz9#~2cNE^P7`&Zvc)0|;9Z7iG zQ}DK>;XRdsw>1lIOAg-VJiJW>cpHoGHu&WmINC>}8_B-nCbFNnnd~oaAqR+C$${ci zWJ=sd4idMMgT)=>5HUv%6?c-u#9icYaW^?a+(V8OpC(6%&jjVq1?49r@J>YG9go2~ z7Ke8<0WY6~cO(Vxa2npB47`I`cn5Ou_UGa4E5O@Zg!imp{wzmFYjiI;M%+h^759_l z!~^7b@gO-tJVd6&!{kKq2sug2laGl<$;ZWGEiQ2`5Qs` z>k)Y8qwvnf;GK=bJClHSItj0kf_Ewn@3joPSF`Y5$-#R$5AUS{ycdh`UhvCb;OGpE zzDUj#Um|CTFO##ySI9ZytK?kqH8LZfBIk((a=v()Tp*qy7m8=eMdCT~3GqDnr1(0y zSbT$ABEA`ve-V^_9)b5+6yB#Xc%Q`KeVl;zQ4-#VDR{*+ybm()-p|5&F9+}4JiK=b z@ZK)Md&@6>i=#_5`Zl>te1}{vzDuqU-y>Iw?~|*<56G-oBv*?cl550|$hG3fA(LO#c zzASp=D`J>@Rg93YiIti3_2ir4 z4dh!Qg7R`f`JEAj?}#E?HimGSIKrh92$xDCTr!35?P-K>%OHGf7U5fR2uJe>-&{cW zrXs>O`sFur^lgpaM7|^5Ouj2d$@j!t$oIut$q&Ta$f9^V`Jq^n{75WCek_(IKM~82 zpNeJ4&%`^(&&4~*FT`@>m*QPPd9|RtY6RgbQG_eU5UvzQxMBj~3Q2^^rx1QHjqn2* zgzwKHd|wXXd-Dj#3JBj*MEGvM{BDkZrO|uHuf-Vojd(Blt#}{#op?X_z4!q6gZLo% zqgbB&NvuHrELJ3c5i60uij~RV#46Gu`PL(*p9qfY)@Vzb|6cL9m#9O zPULlBGAK_4IKq7s2=_@M+&hJEuQb9vGYI#{BHTTPaJM|dT?+_z zDI(n2FYnCJ>owYiyg}?rLhMG~D0U}r5_^z0i#^Gx*o(YH>`mS(_91T*`;xbd{m7DH zf3lP~fGjNzB+G~?vaC2LD4!UVry~eYh$1{bhVZyJ!ebK%k4YjtI)(74G{Pe@2#?4j zJUoZ+usp&;3kVM>B0ShHAI#A^G&+R5QyfZ`6Niy^iNnde#S!E^;z%+kjw0_BN0ax7 zW61l(vE&2dIPyVpJXv0xKvodbWJPfzSxKA}l+O>!=S2|CL=m1FLwHUc;n@jC*8sX^~gr{W@o|;2=N*>|K1%w|jBK(+N{uoCqYxHrlia439Do!D*iBrky;xw{` zIGv1(Gsv3aOtO|Zi>xiqChLfE$hzWOvYwbB>x=Wq2I73Op|~I@UmKLKi6Fc>if}fD z@Txe%D-#H>NFuyEh48X8!b>v zbF{5SH<0bbjbwXq6WKxBOm-Bvke$S>WKw*J>@030yNKJ#uHp`|o0ucJi#y34;x4kM zxSQ-H?jd`NPY2~EgYpv*gpWrNJ{CjxXdK~u0^uV`gb$|>K9olIUS%mlJ5Z;$Z zcy9sWXNw3wJha z-An34wp8}5Y?U=+r~f%;lBw7I-M`P=$J5O7{hs~I`Oa-lg3D<@a48=Miq8PS#S$R6 zPyqzzVad-Ur+;Io7Z8oY^dh1QFfB%OA*PoQU4-dnL>FUv1<`0suOhkx(`$$>#k2&` zWtd(^bUCJ_h_1l24AB@&%Mo3P=?z3zVR{qMSWItmC9mR2{+t4WXZk?!)Eo$&*Z{#} zHz26=1%iq&Ab1oF1P>E{;6WM?+|LJsduM>)ZV3?FsQ`l8u;jOq)79AN9Yoh)dKb|+ zOz$DO7SsEPuEX>JqVbqML^J`@M~Eh3T7hU1rj>{$WBM4;6ilBWnu_UDMAu{b4ABjk zK1Y&iRczgUm>~`)7OY@!}JZJ+cABMXd0%q zh^Axu4$%xu-y^EP^aG-qnARb>1JjR)W?}jX(QHgVBbtNh7esS0{fbLYY2cDmBn2o! zA5d-00oBF^P_5kn)yfx8>S2It84aiw34l_AC9j7i{|QU}1D5kdgP68K^bn?P z5j~74f#?xTNkorgs)6V+OxtlK@6454mjaXyEV;HhptNAgJGlX>BP_Wv3{VU#Ih_C~ zDh*JAd_Z+L11QZBK>bw#sP?c3?UB>t*y&%0p1@QS(UX{VK=c%*0z^+^N+J3WrZl2w zFl7)ui>VON0!%w1dJfZ0h!$e1g=i6`+KB#(sScv&G1W!%0;Zi2y@+WSuH?PAlABO~ zGS&x_*c?zI8$cPk0m{%9P(8x{)gu~E1_^-bo(3rWd_d`)0aUjVKy|GE6o4fM$Z0Wl z+7;1Dn07<-GNyWnUcpo!(W{ttNAwz|28fnm+5^$+nD#`p6jMV)%P=)Uv>a0rqBk%V zBYG24V?=LZYJ%u(OnV`E2UAn7GIiLpG0Lsb@Py>7c)jteS{h|S7 znE)t@G(h#u2bB34K$(>Qs!s)=dc%_UMo#Zyr+pB;hp8E&_c1j`^Z}-Q5q*fM1)`5I zwM4W6(|(9nV%i_k$CwU4^a-X`h(5)1AfnGOl_2^Y(?N(?uIm=>uxGIiPH905!}FP&U4Rl7<0lC@kGjWcUIbN)dgDsSTp9Fdc^IYfNns zeS_(6MBid+hiEOP_K3d2bc9sH022q67}S#1=m}rzrX1S~1XA@@EHSv1T2HBlA?y-r zjI^bNw3T{mDWE)%#)&1YkskhXZx2%_uH67BDxqFgLEY7VY)Sqzq2o z0HzLo0Lo3OZXj*dkT@58%)HOs>4KZ#W8LTNx z+*x7-Dk*$CL3vmK%1hlw-PU~|pxkXG2=v;A`oKK35=w1*NZl=^7Q>|m9#Y1Cn5{tA z4^U%-)_{WBBU59fzbf3JEquS;G2NZx1UA_9N7V zUE2!O1ZD!<4_k5l(3>Sj!`A7AI?{L}Kuu65yRn2@erq+sVs)*5&Z-GoQs!s%1TA4Z z3*Au9oxl>Z1+4{U@I*(l8@{!EVA{aq!r$wrCbEP(bZ3g(O%efWGC2h5IkX)N^E8+r7F9;c8CJ=OJC!V@~w!&wx-%rT28&gDO0 z$m0~27`LFc9?GK*xkw#L8CaTMQdlZ-QP{6c`>})( zz9`jcuy+PB8e*217}Z+fqt3J!=)w6+%BV|QFu%J}D|N2X0=elM_q z*>4h(PG^b9$O+6-SEj)U^#>swyd;&iuq!ysZliGmxCUpSqMd|{29N8Jr zo@5t5k0k-1$B<5zu1N3D6>PB%qB*XFwa0 zqX6BLbOCe^(iPAKf`0Nsh42k85>P+L*?{^^&H>anaxS30lJfxdg`5wl&*a~L`b0(n>La-TP<7-& zKz$$=0qQ-u7*OxXXh7AHO91tjTneZ+MJsN3X5K;0rY0qQ0RmkKw?Er2Q~;d-Hrgo~Y0ayy`|lWBk|A=3eM zjm!YlRZ;<{D`Y01E|YLJxJ)hpP$x+^rJW!T1L`<=1W?DwqkuX}9s|@7 z@;IOllP3Umh&&0XgXAed9UxBwYCri8p!Shx0F_Uk1=L=$08o3#bAZ}S76NJ)Sp=w^ z_?J$(w-MMBW0_M)EeGSn>{_HjsA#wVu2Os8sSkpi;;OfJ!DG z0xF4o1gJ!^0#FHLC7|NT$ADT#J^|EP@+qL=$Y+3BLp}%8YO)GYv1B!%R*^M;T1mbD zR1EnNP%Fq+fLczz2Glb04WO2iZvnN0tOZmw`3_Kv$@hR-M1BC&Lb47}3&@XviXuM& z>TmKhpyrcb05y;N3aGi{H$crHzXNJE`2$dqbKarV70qUq6>6W~tEI@b&Xp zB3P;32j{=ShF9MCED@qq&z0Al@BeR>2vwoi=j)?bB20yz;_DZ%M7RolllEH35)n%E z@&9eFMJyp#s{aS;DScl3z?D~|3VjQ{KAI(FE7f!PY0@8-u*4jt`nNbg=6wH4Sz@kI zy&7K9kn%8I0hY1EJf#L)0{Z*}mb1kC20eeRgvSd(^P6e~OZ=@=*^#e|VTmZEc_6g8 zTBmrKUda**R2j738?0i9g-Q(?s>Q^YmqIK{EK+4)%r{ug5{p$CM1=8DSi=(0Dh#6C zc=|Y&SfWB7#@DZ9iKQy^q?liVbu6(=g@G?Wfq0f!u2e6;U9H51SAYbTSka*WZ_nv$ z&Qm6`M2rg0XZW6zSYoA8JvXSDOq$6ou}X!$$)uUW60s`uO(wxqmRPM+&rPyTYGyr4 ztWm1x#$c04X9G*bsn9o>bXb;Ht5nbRem8#0Ze)pdD)csd{U(-(Z_xj@WxMiQb~8&P zDD}*>RFm7o7M4gS zN)5QWH|N)7I!mlqs^>;klN!oki498iM34W@oeGv<8w~#2SVsKD%4CU+%3X7n-=tse zV2Mpi4Y-?U7(an5me{O9@5|R`v&0sq`i9DH((!XxVykik$WUcq#7|)lODL2Ya9ys+nAyt` znJV=8{OLKLC3Yy)6I?4a=|}rmB1@G)lXl+E64^=(8Y(r6Uq=U6B1e^hA-|3evP7;b zgPweYLoAV}${>`VrNb<-Q{n$li=V=2mN=loK;Mm5=6_h? zpb9;y$IsCjmN=xsfZ```mL(3W(6`|03s~Za3OzZKU*dBtaa5JT48B1jOB_>W(2ZZ@ zB9=I=)PUHO*`W{H248Z?x)E5EdtSmKOQgN8B$@l&|W5@%Hz1o928utb3>gD(6+UuB7Nsth{w z4X&|7p;7~`&mN1n^V1#S)iQ8EErUxXlt*lo~Yj(k^bi zTDrp$SCtwxG=M)pOLtk~nkoZ7zQH|~C{blFg>P`5C9bP7@Z}pkV2RQOga7sk3v*s+ zAF@Q5D%U=I*N<4DT$Oq&gq&sgHFDuao9gXb)9PnE$0zCjgB+*f5Vo^Mdi5)V`vc=HWvSmL27 zgHHTLeZdlsR2g{jQ+UY|6{-w6@>6)l5|ye9gnWb7Eb&;C0mCt&6NN9eB-m%1Ur3PGveEA8yXNfAMdhYSd8NU7lOH?b>t5N7X zfTs7NI+mzWWnj%O!AF*Op~Aq%oL8lvSmLD$J-LwIjXtx)s|Ex9#D=r}H(&ULC0;8N z&gToivcwx@!g+k*H{LT`!%7ksjK1 z5?wS;N2{?!oe~|{iK0bpOgvt&zos=miVkh7{I>Eb&u`(3f9m4VI`^ zq9gfFGU#@!NKKiLAI{V5Sy2mRLVg%e|HX=0DiONzv#806)RpK+e$-BPU`4Hz2yOTd z1+1vG5*^8p>S>Aqn-*j@b+=8&*qlRW!5uwB}$$v~h3t17V z%JX3U@af2kG*lVz*Pe7IR@6?Z0e1&zVb06C7AtD6RNpY8cji})HY@t8!GJ%1;%~J^ zQ#{W)tVr_@LOjt%@M}+(6?OQ75PRIq_t=>g3H~6&9{JDp=q{{?QYPd-+@b+1qLm3l z`FZThiWp@={&O0-8!Hkj6Y`(W(0Z(>qcS1?F$=BFiaIG1@}Ex7-C2>AG9mw|1Z}{I zw3P|@@j2at73nAw^22PpCo9rbCgg|Lv>_|%tW3xc9BCs~)J2()A28A)Rs@s@`EegD zW<_0<3Hi|&ZOn?gDHHOeFxrF_=_wQPqc6G_E7Dgc<|G^{OpB0&?a7;$=r-uQosMjAn<0!__|@NXn+!33%<^l6L8)xunVa**odYYfR_fV+ z@7a+R4N>Y@53h{O^?9D1SkX`wdVRirBrB3C)pM&6eSXfJS&@y>ocH4Ad=x7h_6H>% zc>JdZv;?SdpU&eUpNYVMR_V^!x|i^jKCjQiVR6Uy+`y$XSJ+ z|1^aj$BIU&(EIZJd$A%H6?!+m-kTMUbPhb)&a#x|J`1;AL$U}v`1z+#OipD6_tKnZUsO9T@SjPMkw^BVfz)1eXFM28~8n4uVtDrP~0@GO01Qq%OzCMr@ zO;oCH$WW8;Gl&&UYB1o>CEPt{D1RoN&Wa{0bM$=X$%}DR8xm$}k%V5}SchKTSc_iSSdHG+SPtLT zprhg28uViLwg$ZjzO6w=!M8Q&zhMskUWSq+<`ToWwBc)2ut*m?EeL9aKuTJpH;d?~ zU-=>gg0s?=5K+$HOY|^nQXOBI#zLJSn70K`yWo$024g=9xobX&il0{G<_faS|LxlbaY4OQA!vjf8UqW)O4 z{C{TE7ruiNs}lfQIR=)g7Za~7Iun+()YIc9(hnyR^FI=i z;`&&n6At^+O6Y!NW3C0zt3W#3#$y`dIMDicA zV}esk;jT6I`rxJwZ;VB$oT#zY2J}rmgsWiOMD07M+4+4Azp+iP=MwiJ8f=>tQ-u9) zXqLF#uSxGU!XmbrC?rD8PY%BqPH$``3J;mZ7xl!RHgO^)tuFV4FWGG!I5!k_*e&os zI74rSwYa&t=YR;l=N{PemPQfRTz*Zmh_@J^w{n_h7115rZ)>#Y=ALFfTOW(Ib0Q_v zq%XYHe&D7a;E-(&ebu6 zHI$=c3h!Q-I;L<&R_K_*0h0j}%xIS2ywD~iv>Q%P!HM92=EjlPVT*k*IRU>~yJBS~ zr&LyASk`Qv0Ek&SA+YptTPu1;qi+%Jpk~J`;TVk<1@oN)>^!SkUZ=^MbiOWFl-Wrm1jZ*G*Gr;^XEEb_-6%-8L$o_qs*2U^vZhVX;8mhlpx6r|&ykDZ7 zobP5LZ7gcI_Qy(Etm6kOX0OgP*kXHhX2KTRtrH2`Y8R}kUB8?k`lE9#?0h$O4NebS z#}1jpFG?q@+4D#Hj`-SM?izo{Wf{1`jT&tUJlRSI286==>>C&m0T({`FkAUtwz7$T z{^(YS-R^_e4x_~vS~2+W7deNu7VDV8G9SXd^dRn~2XHUl51sGleC`b=L|j+r`d+*U zy>1O1HmhJ7Cw8FG$g0uk7;0H~2bvb}M?(r59&B#N?-G2w!*~g&-QlB@6kyASCV}~E z7?PG|M+d?rDXSI%a=zuS#UI|;gg%^Y;LfGltRVQMbNUE(jjOe0OT+CtzHq&HUS}5U zDTO+r@J3Rg69Mb-j1IaP!oNX9ALXon58GpsqiERXYIKoWG_Hox4b==Tp9B_zV4-ju zKN{HA48GF+5B$#((5K;l{Q-SypauHrDH?5(I;j8?Y85_oefv@vlvVif^B@4Y_76Q1J4rHF{ z0vK_k;N}u53SQ9KOyFri7*LI#F2k;Kk&$X}(Zm&26axKPqnPdL4!{T-x1wMe-i8XO ze@X!Z-zrA&8uJ&7*O(S4UaLd#+6NS`y+!fb8x*g-M)6t|ir1c?c6(ndAc5T%tILYEpQFgRWW+<4JP8RDX}QXHjS zhi%EgPoA;5=~+*Dal1zw|`~-ccdjHx%leN|I7@EuO8y~>K@J# z{4;6vUC!c>()dbBa#7X2|KXa8svh8|>OOROzcGj0Z7(8FN^-H(gJxF(!r=xLxNkme zc4b51_cI630yyFhdobi{GVe9yduzZje@T!o~(@5-mwdE=GFH zH6V_W8XJ&{jGl1z|D7mc$83mJ6!DZ3dcS#aqRN~$38VU_Nj(Bs^D=%&#<7t_3I<{rj0Qj&{gUN^f0BN-{l#V>D~ zU4rq8l;on8x7;NqErO$#_gvKSj*D7qVfkyD`wNJWl3c9vuJIDrxBXxyxY_kRcLg^S z7ngi!)M~?tUE2!AB~p@`{Oh<&Fk5g5)~tPebYL{XsNrbj6OKkc!aRL!<_tz7Qj&{7 zK5>`+6oWK#yzAoX~4_Y<$~zT(y07aW6phRJ+xmJEzRq;P8~bo==4DapkV^;}QHaYSQJ#L)wz_UjUi5E?Jxn1N}*o%v%yam>K9Mll1^3dIbJ zI*J*XmMyfImcLxW1!|*994RpB+!;8mxNa3PM@r)OfN90k;;4XW-I#piY9!0R7XEoP zv|>beKpYh?ZE;kZ#XWu*g0P7GwmAHe_ZST z@?DF1Fw;KUz?Rcv4|rYA{KcL5V{77dJwu~)Jwu^&JtIKtdZq(f*E8@hFEW~(_3uH^ zu&(D;#7u`~mw&B@rFb>V2)N6Q%?`UaUXC(UGrM2QQEu(X(2bY>yLOav)yOnF`R6Sh znNBdn!saL7Z!3nv72goJl4Lqr0YbtD6S{V=S_Iubk9 zwggPaU+YK){vu&G^g0x-BN<(|`xe~WZ=4n}p8||0T&9gR2h127z<9U;#vRtGEDSJi z(SRA902tRaz_{cCX4DzLIF|rsWCdWHY60U2civ(g;Lcmj2)OeWV-I)UV(j3~Tg-5{ z^A=-Eb^^>Wxbqfc19#qnOJyCv423&yF+1S#scoV#q@K+G2`!XB7~bnj%VKindo3Xkd!AR~59w z6m736Xpbq{URCfHrf7RrfhMMCdsRUPOwsnL0&c{k?NtR_A4l7(3b;uIZLcce1~l4U zRlv<|XnR!wH*2EpRR!FXindo3aPuzOURA)&>S%ja0XO##K@{I17_@h)0N+p;w0Eii z-+UOfcd7v2q8PMyssP^r8Cyj0y^=wDrwZ`hnL&G}3h-T@L3^hP@Y;YGfhb-$Fb;^~ z)dk~-C|DAMDf~@ zks*o~mW(^1c;U);Ac~i_%os$?ajlO<6tBV=Pek!jof)?cFgkFLR8|zhZS1HA!?N~j zdT3&4s}~PDwwF5W*k0;-@cTY>S`E#-UJmLI;V)ar;iO=LCWZ0bq!7g?VUFn2RQbIcQRt zjV6UiG%3i@q!587g>W<}grP|x6io^tXi^A9lfo=CDa=HZ!VEMiOh=PK5SkPM(WEd9 zO$t-dq!5561%EUt_@PN*3Yrvr(WKymCWXmpQkaA$g^6fVm;mc*0%`ypMKKc*#bFsU z2~iyUF_RI+AtK|0C=My%n@^D9P?ed2C=O^DKSXiZ%lIRT17jutQES}tQxP44=`=)# zVj7636w@F?xqH=gM2F$CGZ3}KbS9$1F`b2|9j3vE+G84m=m<c3{gi+!x44D zGy>6)n931##xxSqQJBs~)CJQyh`M4r7tzs}&O_7<)A@+XF#Q`*cTA%Y^}uuiqGK>! zi0D{M7a{72>0(63VH%C77p6-P^~Q85qT?}LhUf%Lmm@k6(-nwL!ZZfa$(XK0)Cbd5 zi27n0i|78KY&d|2p#hu(ohG8KlHnKotY}VDYcn4M{0)CpYpD-IQ%4X3(%;=s}`wo&|7oWj$LsVYn{w=$LLq3dYBS3@+zBtz>ITO?;i^PsgEd`NDDt=pUV zNDbfyu6R%876wuay!)~{+|v~>>){jyUj&&Ct3?2JSJm}wMf79@d*RHbrmldi*j7F4 z4AgOwLM}4qIolIp#f5S?$l|sHA4r^B32Jf=Wq{3a6A?OXW zRa1xM-hj)ktL_KqlMM_iH`*T<<`nJ^EQ313RxLd@Q#$O$QO$hhJ#oKiYVLdL!bt>?I(*fS<8P{AZSaUvlu$^?xgs|xd5&m@% zW-F@(b7H3kbFxiJtGfucL$;keDS$K$PBiGe-d=TgdANs~n%cwq`k(dnKkCuH@AdWH z>g&JO*Von8f2gnjRA2wTzW!Z({g?XskM;Gn_4S|Y>)+Pbzp1Z(U0?sIzW!x>{fqkg znmKb!eIw<80m1ShKNyZj1caNyZy|!^;l9BUQ{fK*!G0nB@GGYvKe;JB85$BUH;oAJ ziwuX4Wqs$GP7Mj4;~VZDVLA)yrUnK0|N7rFA}H7|ASfa$fS-2o(*k}v zz>ffaS~hT99`DBE#*qKgoAb_F@Xq(+@%{}Ol$NxCqqOYd=a(PETiVUVd5eM^Y3;b{ zOj-kn|1sMj^n@REH8skmfy4Y#BO5sMM>#^?27XXEP}vYi>5%Z#3VzV}U)Rz3U*{lI zYv~*$hxOAtU#*2MJ9qQ)Es=A}Zr)fvZHY@o<)ufxZI%owyno+G3@W9|dI+{3a0s0P z|HV{#-;3=S)9G;f`=_h*XQL{Y7kz3+yp#XD%C2kcH7|Z`+Y9sFf)4aM zy-A;K5?89PU+i+_&E^1)`nAG`36+CnQ6pmCIMf31DfifVJ6V+JD;LiNNAFiZijHgB zdwSKy!>=&AqwU#!&|II#(W_6j8>F-AVxUp<81m%@tsj%^KlRD@w!Y*?an;WNd41nw zi*)XvPADm~`kXZMqwCLGL%;adKUw#q;fKg6^WR_oWM~&6`|uma#tt2LxYJ*^(&u?w z4)N@N(}*nNfc?ex)>rIM#YrL5!5`|Zfn0l5b$WJXlYG#PL`)NsFUXo${yt5I?{Sq zO;C20n{~gsvfOEF%KV~&_g4MeO8oZ>c1&?E@2NNXCZ*~eytF-FzG-Pt^|ijcQiBdS z1FxdGGZh*A4Lk1fHJ()9abiWz+`rswb$3+ubI!bJ)^m@qlT6XWjt7|zcCkW*gAE^? z5;hAjI5vfp?ybeLuuMgzlVhey&z_!6P7;N})+y4#CPpYnP!qpd^FlR(a{Z1V(LB!I z*+8e%p~#Y>?2d%wpdLN#WHKI%iS<`F42$i-hjig8$Ak?{VVpQIzPpZ*2|QSru`+l_ zdSu$lA{~as^pGQ{iQk~Psv1GLz7CL8FYVW}+X?X)R~zf)H5YP>Jgs%={Bw;mw|dm| zC`-!Tx?%p5a(6e^b3x9{4n^sPcz6g&$AsT7T1+Q&Zy~1Rc@Ql-XJi-E3^E<0vBOVz zt(v=^uD2l59@=&)JSO3rFT%x8J4asM= z-CE`P@|@A^N@h^>rv!(?(}x9@FUvLBzExWHx$JRX*LCv;mru+-p8a#x_iY;rjGnz+ z^=a6`0#>%@aO$w)C*FpoeREPB4><1(=x)kPXQjoTy^Bh(+ZDUMylpf@Y%r}bA%cHAqN?ZGPg?v=L6m*6hF%7y|lvw$8HX_(Lx?1R~m>PBvo4T!K84I?1Vv6aF6|@ z_TR@Ux3UUr@SqeyUcn&Ai3|kpG3(@Wi!C4YjXUi8*tE2P zZY=7&qjrTb#DNF);s=S9{lt@!cyLg5qI;o9Gsv7fD)#W^o#>Ewg;;roRJ~oH8B$i! z4@@dWkXK+LIgyS)7IRqsIeXpr4&NQ#+SGk2dzklg?bku&4`qw;zILt&@mMtK$HAIC z*{5Yc4C>ZzE^2;A;;vf7U3I8kBjiDHWl!<$q)G`NObYwSCKxmY_t+PTxfeFoB@TFz z`C#X?6JA%V-mcNST2>(eyGs$|74(x#$Uxv8vtEAWqUG+sae2;b9Mc+s8fU8e@NInj&0|r{nU$a@Qq&H{cZ1UeOG5)sub^^d$HG6Omn8zZtxmx zSm-C?!PwX=#gSpLCVWU2u6GP$n}U~kcYLMT=mk7-TyFSXdFCe>yraM`^2o3l6FGvK zP*!t&HG=YrAcgIkOrH|4`0jhJIp^Bd^zm5a^5bZYtKUPHypOl8XnG%9I{V|lxj*vX z-(S73*Sl9m*RFQ&|+2-FcHa0?GFPRp>GV!@-r33bRtHOO~1zaRttgIXXi( zW}CW@j$b*V)Ky6F7LlYc-K8HTbH+R*;lH(gZt2(U-fHZJRaIESXXmA=grt3 zn->~=@EU4;;@Z2Tsjdg3c0TQB+GoZ*m*P_It~cMUOpQ4>Sn;&0>6}H6M{Z~ApRRH{ zI?W?kH)Uwt#hw}ASF$(vH~u&7qIpL6x$G|ejoYrdxG>}0aktwuR(*Ssc2ja>-4VlX zVWWmrZj3tD@5{;=@2Hzk3??RZIBM7}u~J{$^*0z78pt;HD0Jk5N9t)as++!KVkqZ}&!6PRrGJ5~KXicrEP#fTKt)dlzWs+VQ1${9@9?6-U zWL@UE%IM(W?59IdjIXcyQNQWy!`z2!77W;R$9K@TY2`m}-v)WJ|Go05_t%V&s5Ym3 zJ$mb=xZ6L?b{pvL@lhwm+CCu0?e^|7UY16yi>8zad)<+*{%DoH=0!w}->V12*q0*6E9fOzF zXDaq4oVC~JEXHJq(7@3=@qU*J!E4Kc@7x;I8O)UL%_(5|4)!(Lx|eaiwaVz_Ic9bx zIjDBomVdr&$aLLV8@$pMBZ|Vc_l|s6Y4s2&$^qgetrkK?0V{8I?H=V#pS=#=X{&= za#K+G&pWrR^JZVY@?hM-0kcmKq?5ACK48DwZGVp&Iw=S3 z0}k3xEm_t}cctC8kdoOWlEW5+p5ARfvoiAGxfiEa-CV%z`DQrj)2I`#H+?u3@+9)1 zLu{Pl)^ErbZgR-qDtzI{gGu7C2~#>Yg%sINqe8>oyGKo!xJe#4>&d&j4l!}^TiKc~ zs`qY1FbRw;@$HBpMKUwJz-dQ*V$e>1T@$v5qh*$zEaD0k*1`rj>Up{_C)EtJSJ7#P?5h}4}Hsyi)yN*nRi z-ykY~TJpg?_JU%Ab5qzUEOjtX8Zg5!xOtSJqhkT!5_&&qyzTJUgZb2)F^L7NIW5VRxBM@Y1CRLfaZ|h_6If_mDYFSh2 zvCVnGzM7KSxYR$S=ayDU69)#Bjh zw5OJ)Ml%LF7O(PlxfZ@Tb#gbi3*8Qi7tzE$k;u;kZ8)V(+Z2lFLD z9wbi@idQ5}vEqYCp_#0`K~r##tx#weS`Ur=dtvhHFVwyF232NDG!4ppt-y*>1bGE! zlJ*%0++*D2+7~TX^o>h%_7jyh(2Yfc9p=k~whlZPE?$v1MIdgIz=L?%I7i=}%Tt77 z8%d`2@YuA?JJBKWvbFNEtukAtXBCEF95$*DL0X$^)j0$;>IaeBc zS8-$%ADqG#3)?z0h2+Zq;;wI}T0goxfBS$JLqcvWklf2ESP*$+RLosDf|}69nr*8P zlvPCke14Dm`uEZbZ_U#1>{QzWT@;yPirePD$sBWh#?jrAyCA;j7`sJr>o?>Gzd7VD6%OKqt@u#Fl+I0IlkBBl;gAQ}3r@Iw`(Xd- z$(DPLF(?Rs z40TdS`+yS*t>Uk0d^&hy*V7iJpMs9M7X$C$((t_0@dxg0e|je*DnxQSd#0JON!-Q0 z8T+nfU$QXXzviMmqi;#}8Vh5;H5UUj`ku{hKfu^F?xFw&6xpxKE=)ROeQe3!^46cK zG+viN-TQ0ChUe6@)C>6|gNwCbuQM^Eo4Ss(PFr`=>355~m)R8(CJ z{y1&Gw;2Pz?c21V`r3NU1?3gJbzbjd=GX_Z8nI&7j4=nFw;Ik-WUs?90e#WJ1E{@CSdojDch4Iojr=c0w?`D6W;xS)4CD=YQ z&yDPFY?FGV-r(GP@Z;%(1I0%J*GH7}ex5$@irw+et7jkWeaCLXfz=NAD}_8ro-$DU zEPje6A4G*hnX`UVaEl$N7I+*xs+;cWs=X^USC{!}f@jwdWETh}&S?nT zVxG!BuMYZNX!&!B?jrNJ(;JE=J3sC->T#RWBQ~*}6+B22E-Ktg<{1W0+dB4g>6HUJ zH>Pg!8dvxH--~DQ6NCfnc1OjauL>5=Q+qtgFn_({%K-Xs_8dpcd-S(hby;!CIhF@b z`Rj!dj^<4urTZG8uYxeJ(-xm~pzLGKWXr^b6J#%+`oDq)oTe|`zULfsS$=D$W<<5w zdIX-}Y?Z)1#!Rv-r(n_#zZ0uJxL0;D%?kXY_2$Op`Sl^APp5r-aBEI!E;zJ*(b!kp zw%pI3KIdiBoAf@NG2S~YybX_u;-{>${! z^rK$IN4-}B-F7V=;{EfC_RnkD3$uP6uNf+P6tclH2a>{vOnu~?2}O$>>)W9 z6>&N=>-*WuAKMMM+vncQ)1mu+H2jdT$#nNtmlCa?&YQQbdT$Z7(D2IDb6;4ip)b;Z zY#sGwtMFk`<=?U>*Vs3XwbtTOp0V2u#Fbaf{>h%J+cE3e>x;v?z<~Q7yV?o2hvNrQ zCH;@@Yk>1p`Ug!z`T>c_=%3(LTs8g``E-0Fs>jl0pI@|F5($%>N z6zA>u;N+{ne7UfbY@EX{lH6TOd?)-II%?v_K8qi_!!|(TF*as`{Jb54OwCS`aaBHP zOYOiNd&#*UqM)qtHmT=ay?+*MdAsTLve{*Ka~`D5A75_bzGvtB-Ze)(4v#)Oye90` z>TMsEcO1B-`4K6J2$w`8b~h4-{{~|_JDJB5`51VVRbRaD<&tB}X8HLX&6DS^t=>{q z=<;ET(foYoUnPgm-WlipF>tgzM z*vG%CUutciB&|D9_C9ygDr(G>;VIf~Js;oyVl#QeI$19)0qtx`#Mg7Rkr6yoP zxqe#^ehopk?QDsE76Q+hsE4<@o33%PxZh@97mYyHv)Jjx*OU)6&oj2VMtaul&V9ap zt8LxOvi+ThZa8#9>*qh_KcfAmG#?B=M&0Y8`a{&u5meBPw3^XHYfll94-zp&=Z zn5(wd>uP;VhflthWO29I;Xdt9TX=})$EW{>7}}c7t z#O}$$G^dg+LhE_;^WXifEBkhH>3}0`!Tf7!F%o%}?Hmk|M5e6>467F8YFeMpx2vhP zk6)*;>tL@v0XC-ZXRsrRx1K1ruG{@-`vcp$*Ja1^x;j~x9Wl|ZX?HxVZ}X$te|Clc zY<)W|nfy1n#Re;?hR3?_K_LW=B?(QzO)N=B?;PSa;K9D&`NhZHZk?TGt0j@U#kk0; zh9gjDg6eAt2;9K2lC-}{0}Cwgn8o#X&fsXE?ye2?Cb9$F?RbzGXd9cV2z2CwQ^^vc zsY6ppPVXtc6S@x_RsIJn{ST(wnn(`FVp8RSjtFuzO{=agK`;#5xt6wR$jW1HZOZKG zT*?OJJV=;7qWtdYkc{~zHM=}Q98YG}m}Vayee&*rp9f}u3|Vn@S(z*OE-fu4_5Q*3 z<@+|JPCW2Z5ir8^Zs2XF;#BWLW_pb(d9It@Z|(owde)V{?`-~b;OK+W zsqm~r$r>RKlG6u@HzuZw`Cx3v$Sn1nf?MnkMc|b!rJDz39eurd>b-%NtFEolyj+$h z1{<#<$hBo8mT3swVxGwZFInnZ#1%Pbn3p!tjrImRN>&Qv9C+X$)=f&+7wabSAWdee zXQ$o_G6NlAi#G2>hr}z+$}6tw+Dgs1vNV05dmTZpt-8c=JA%M;dMo2o%0^7oNrLxSr(E#U%lopj}VuWL$dOl9j9FUtl=RmGqB@9X0~hW zF-7)BJ~+9>2qPVuLUMX4?%rQBFn$aX8NR&bsNiOT|F7~FFY2kO7C$D=a5vo)czay2y?6hzebK3P2X|%#Of-EF z=rz9B+xte@z7?sL4T_JP9+U0sk*kxEYoD3umJ{f4YJl<6IH#Zt*~RQ+GvmTICn`gB zEBlB}%97M0r~k^?RqDOvc)nzgU*zkentO5OM?2?53>bU!{d}a{LnEu4W3I>%)CjWcT|LS_?DrXCS{!scWlmn+gL=Iho7}cV zr#6j!wRg*p{nP7TzFa%zxZm@?wNB1|a(7i4T%#|rS~XY9gIq1lBzP2sxCWz zFkC)Be8RC}ltzebZqnKD;wXm-JB>cFxd~@i3nwYwc3-|;HaGt4QejucTfOCN#e*Fx zY&1G#!t)(MQsA-ffpN%#ks*?~rDw*26IGAw+V+vm%~{$O46b@))3!t8+cev@J4&L1 zCaT`=|(Xjpcz;Nwjc`gWGaGB&W9&8^<>VkzGfb{E>eoJg!}se)Yn1 z*O-s;z&)B{szxtIUn%<>4=N$FzW^SIlEDIR@0r>wE>xwPl& zH^sRx+OL0F)T=noE4B0fUIV6j1z*~CZ$m(&Uus2BFWcC@3ej)K5C%B9Z4_qm!CUN< zkS=Tr$+BcaJFnpSefNF_M1EUe@v)C>OkcSuLo>5_^hN~Uz^Nonh#*;#oN4R0!!0o= z*Z8INI(#^ygNln4TE=-pE#6JZdWfzRkTrjqY zq2;19&BE%@n-NR^8l`Dw2=+>n6}DbK+Iy_nmT)n9(#H8g>CGSAz` z&Qvr7r-V(y<#YFI)K2YqebdvTO;7FH4v-wlu??0y*@0ke%uM-tdjvZ)msbhSS`OP_ zPm~hAy86xr%Yv2#>Yi~n*dRQkAPnuy#7i70=UAUi;{W@_sNN!P#hRbJ_Z>*hJkTpA zfHYk_!)sjeFz;hQ?D*oD-YfR*(Z6%kr{+wQ{Wy;fk3O^x{o2xML5t0&)LbU3W%ZF} zU0q-Le5A%?+2_Qw>xHKjZ+kA^BWo3ZHc7Zu@wUfuBk^Fz3P%ms%uu7{gW!YY3Kxwz zvLi`nw+LT5RXE0~J=lSclcP?ZSa(YPHgoA7$=vH_k~Fu<-+Hv2BRNufW{c+Q8iBN} zQEBM^HFE9oP;Fm)Mh~{UhDv%cMoKOw)KyUk(_JZ1lU_0nMm92I#w!Y?t_JC;+?!q= zF?A(9Z>e-ksh{F1nd;UpdI_bGqLR7$Xwv=t{PR1XS?l{fYp=cb+OyA@Gk@%L?6kkD z!b3h8c&M3&?fieK1)Ae0q;1c&__wn3V9XzC`rpbYEjM2zKAw|fRp{kwOFq+v=DFEM zTg*<)jiOYu_XZu0CQ8-)0jadL7rWX&K0R`7eDu7#k7Q*>-sHdGUKsH{;ILtVKleSW zF1^V3?dG=HJ0r5V9Pdp>rt8D9>@)P!B z8%yf1kACq~-!xxCTRU13qpeVM=qjRhs*ANOd@US9Ql&fcqSFSME!&;MjZ zcP*E%s9`!3#{OPwIKgx0PP?(!UzxNHvq|_=rXJVzDIsaK$G)OP`MNr&Jz6(1eGPww zi}$8w#|jhV%*R{rIgh(%UvXJ8=7CXzIicBZ)metyG9S|&}MyuSRIk!(-MBvhxctA&nLRoH7KV! zrno)qzMSy-dCMzCx?EmWd_-w@*ZQ9P@gw{CnjuM>&)+YpZ&;s{Twb_mzkS7*u%{O} zV~+MHueE!YUu5i0ezJ9Os=q5evGlg0)wpSt@v`6O8qS?jTV6L9HFji1H!;lCl?wFoWO&8K);xK_ zcD`oaZg_6vsD#g-Drd7APpva8^=L+s>u(jY)bWs%bH|$ZGpvQ$BZreH?1C${_)|qluhbh6>N?4#M|z{!SNOD zALMVtrRNWn8e1{~$Bdh|qsiC9iDSicW?z42_A4t>eb&jByT@}*raM{YCK$IEnHux_ zM_(PL;BGp~GFGphn;x&@-q};G?$S|Z_G41ia|JIj*Z=e6Gew-Me(kkF*V73d-I2x1 zSo^#pTi;Dd^r#c36<1_mDvGHrJr}t3>cjiBm7}*6MW*?ku08Y2Ik}aueC(eYAx~HOVSWMz77Pey3=oBK_oy!u5qQ?6ea`eh$0cCOZ!^6C0(9rMmqD(M>8 zb1TW_^|^v5J^|~LaH8oUYO#8P2!24gAcz+%;73J^1(M!|Sj|=Z)d2#YAWkF>4JEE~ zgn?wwJ3Wq2YM;M55wb_#U=KkzB4mHO!ES;xRrVbOT~&M;L1z`G>IG&HA$#);h7+9r z)vjMd(CMpPzlxxv${tT}ii*b&ob;75sFdtfd?i7)iU$$2QSsh+s<=NvD;1~mq3Q!j z&I~dq+IS*#!hHyiB|`1}Ur1yO5#rNZV=#&!@#)QX1VNHNZ7LB%B9xt~WuWFm`BOCx z!-!BmR4zkRoEqPN2<2l*M4t%dL*>FyaWjJ2Do%|_)gVwldahAXVuEve*G-MA+khBJ zMDJ2X=+PVnF()ksdWCY_T*F*qgz=%IT_Xtd!&k=(#9!Bw5h3K^RU)xaBoOn$u_Q_o z8tMiS!6fF9-k1YOA9eD&ObICv38M{~Vl&ysmNE%NRLRKKtAd=bkMRHTBgW zy!hNcaEoKvsFh`<8v>(c7w_(!SLXRcb?QZjd1Yn->5k<`d?(N*YG>b=a%DhX;=oxD z@<6PN*MF8$u*Z226z3;js9kI`;Yv#zefOXR1$K2G2ISgIIXDML2*{F9*QDy8OUWgW zOit)%GMb8JBTwXwe9%JViZ>Sg@LnqKlRE{dpRaA+p&`tCwx`S?^C#V56qG#widVyY|X4Hb(kOIBaN9Jmv z2}3{!7@!XZFdPiQ2u8vv7z3tY0Y88pOaeAIz*Jzt3~&Zla07SXz)t|+33I^$9{MjUW%9C#ds0+#S1YPFTnG$5BA3M zuos?-=U`7f8w36c&%zw+f!(nio{3$t3wFjc@O12i9q}|g6;Htq_(wb$Pr~-t4%_01 zn2l|)HMYV(U`sp!Tj23{95%;eu^DDzQ_RFB*cgw&qwy#_5*y(W*booL!|+gSfb}s0 z>tS82gSD|19)dM7!Wvi|55|M=Ks*4eVLGOP5_;eZbi-%(1RtRbI^hGnhYolL?Vy0S z&<1bdHMBwtG{Y-sf|u|Dp2IVE3XSAXfjFChFbU& zYM>f!!A+=w|G*8n4wY~Xu0jP|fy?j*T!M1A2p8Zyl)*VT3%|n|I1Q)ZB%FZbPzuMO z1dc*6{02whFvy??3gHkGKtAL_E*yjdupjopUdVwx@GJZR*{~aSK^E+U9k3m?!B*G; znXnl$ARW>`3Y#Dmeuj;(0a9Q+tb=4of<#ygYajvQVKu}-EW|)GM1cgv5D6j>LIkXW za0r7?zz_l}As7TWNGg3Qiz=_P5W^rQ5%Y^uyj7B5>XOO&mbZIt~i+alX8 z+bP>B+a=4PG5c;;UXUP=7ZM#Dyow*e3l|E*qD3|#;&3i`lMA_3&XfCv`dyo#tJI+B zF$wEECNfpz8#wo~DEnD-`&rccS(5r$=>04vdf!IZrK$C6O{6h3=o3x9C8k1UUmrCW zHFf5<0CbuQ(?aE9Vxs)U`kcQHPCY!RV@6+_b015hS@cCo*83JOxvxFhMU6I*+1s6{ s0lfu@N~PYn9KR2ua#8;8cSm~k1)K3blKR(3J$-FV+Q7a^Flih90Sd}5od5s; diff --git a/dudeswave/src/Makefile b/dudeswave/src/Makefile deleted file mode 100644 index 8a14b81..0000000 --- a/dudeswave/src/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -.PHONY: all clean -.SUFFIXES: .erl .beam - -ERLC?= erlc -server - -ERLFLAGS= -I ../../ - -OBJS= dudeswave.beam dudeswave_app.beam -OBJS+= dudeswave_supervisor.beam dudeswave_handler.beam -OBJS+= dudeswave_user_handler.beam dudeswave_auth.beam -OBJS+= dudeswave_auth_handler.beam - -all: ${OBJS} - -.erl.beam: - ${ERLC} ${ERLOPTS} ${ERLFLAGS} $< - -clean: - rm -f *.beam - diff --git a/dudeswave/src/dudeswave_auth.erl b/dudeswave/src/dudeswave_auth.erl deleted file mode 100644 index e442efd..0000000 --- a/dudeswave/src/dudeswave_auth.erl +++ /dev/null @@ -1,408 +0,0 @@ -% -% Copyright (c) 2024 Andrea Biscuola -% -% Permission to use, copy, modify, and distribute this software for any -% purpose with or without fee is hereby granted, provided that the above -% copyright notice and this permission notice appear in all copies. -% -% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -% --module(dudeswave_auth). --moduledoc """ -Dudes users management module. - -Here lives all the functions for the APIs needed to create, update and delete -users from the dudeswave database. -""". - --include_lib("dudeswave/include/defines.hrl"). --include_lib("storage/include/storage.hrl"). - --export([authenticate/2, details/1, new/3, - update/4, delete/1, logout/2, auth_cookies/1, invalidate_cookies/1, - set_auth_cookies/4, read_login_data/1, read_new_user_data/1, - read_update_user_data/1]). - --doc """ -Verify a session with an existing cookie. - -Spec: - -``` --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when - User :: binary(), - Auth :: {cookie, binary()} | {password, binary()}, - Reason :: term(). -``` - -Authenticate a user with either an existing `Cookie` or by issuing a new one -after authenticating with `Password`. - -If `Cookie` is valid, the function returns `true`. If the authentication is denied -returns `false` -""". --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when - User :: binary(), - Auth :: {cookie, binary()} | {password, binary()}, - Cookie :: binary(), - Validity :: pos_integer(), - Reason :: term(). - -authenticate(User, {cookie, Cookie}) -> - case storage:read(?COOKIESBUCK, Cookie) of - {ok, [R]} -> - CurTime = calendar:now_to_universal_time(erlang:timestamp()), - CookieTime = R#object.value, - {user, CookieUser} = proplists:lookup(user, R#object.metadata), - - if - CookieTime >= CurTime -> - if - User =:= CookieUser -> true; - true -> false - end; - true -> false - end; - {ok, []} -> false; - {error, _} -> {error, service_unavailable} - end; - -authenticate(User, {password, Password}) -> - case storage:read(?USERSBUCK, User) of - {ok, [R]} -> - Validity = case application:get_env(cookie_validity) of - {ok, Value} -> - erlang:system_time(seconds) + Value * 86400; - undefined -> - erlang:system_time(seconds) + ?DEFVALIDITY * 86400 - end, - - {hash, Hash} = proplists:lookup(hash, R#object.metadata), - {salt, Salt} = proplists:lookup(salt, R#object.metadata), - {approved, Appr} = proplists:lookup(approved, R#object.metadata), - - Auth = crypto:hash(sha256, <>), - - if - Appr =/= true -> false; - Auth =:= Hash -> - Cookie = base64:encode(rand:bytes(64)), - case storage:write(?COOKIESBUCK, <>, - Validity, [{user, User}]) of - ok -> {true, Cookie, Validity}; - {error, Reason} -> {error, Reason} - end; - true -> false - end; - {ok, []} -> false; - {error, Reason} -> {error, Reason} - end. - --doc """ -Close an existing session - -Spec: - -``` --spec logout(User, Cookie) -> ok | {error, Reason} when - User :: binary(), - Cookie :: binary(), - Reason :: term(). -``` - -Invalidate and delete `Cookie` associated with `User` from the system. -""". --spec logout(User, Cookie) -> ok | {error, Reason} when - User :: binary(), - Cookie :: binary(), - Reason :: term(). - -logout(User, Cookie) -> - case storage:read(?COOKIESBUCK, Cookie) of - {ok, [R]} -> - {user, User} = proplists:lookup(user, R#object.metadata), - storage:delete(?COOKIESBUCK, Cookie); - {ok, []} -> - {error, not_found}; - {error, Reason} -> - {error, Reason} - end. - --doc """ -Return user details. - -Spec: - -``` --spec details(User) -> Value | {error, Reason} when - User :: binary(), - Value :: term(), - Reason :: term(). -``` -""". --spec details(User) -> Value | {error, Reason} when - User :: binary(), - Value :: term(), - Reason :: term(). - -details(User) -> - case storage:read(?USERSBUCK, User) of - {ok, [R]} -> R#object.value; - {error, Reason} -> {error, Reason}; - {ok, []} -> {error, not_found} - end. - --doc """ -Create a new user. - -Spec: - -``` --spec new(User, Password, Email) -> ok | {error, Reason} when - User :: binary(), - Password :: binary(), - Email :: binary(), - Reason :: term(). -``` - -The `User` is created, and stored in the application's users bucket -`Password` is salted and hashed with SHA256 before being stored. - -The new user is saved with a metadata `approved` of `false`, -""". --spec new(User, Password, Email) -> ok | {error, Reason} when - User :: binary(), - Password :: binary(), - Email :: binary(), - Reason :: term(). - -new(User, Password, Email) -> - Salt = rand:bytes(?RANDBYTES), - Hash = crypto:hash(sha256, <>), - - Data = #{<<"email">> => Email}, - Metadata = [{salt, Salt}, {hash, Hash}, {approved, false}], - - storage:write(?USERSBUCK, User, Data, Metadata). - --doc """ -Update user's details - -Spec: - -``` --spec update(User, Name, Email, Desc) -> ok | {error, Reason} when - User :: binary(), - Name :: binary(), - Email :: binary(), - Desc :: binary(), - Reason :: term(). -``` - -The details apart from `User` are updated. The username itself is immutable -and cannot be modified. All the other fields, excluding the e-mail, are the -ones that can be seen in the public page. -""". --spec update(User, Name, Email, Desc) -> ok | {error, Reason} when - User :: binary(), - Name :: binary(), - Email :: binary(), - Desc :: binary(), - Reason :: term(). - -update(User, Name, Email, Desc) -> - {ok, CurData, Metadata} = case storage:read(?USERSBUCK, User) of - {ok, [R]} -> - {ok, R#object.value, R#object.metadata}; - {error, Reason} -> {error, Reason} - end, - - Data = CurData#{<<"email">> => Email, <<"name">> => Name, - <<"description">> => Desc}, - - storage:write(?USERSBUCK, User, Data, Metadata). - --doc """ -Delete an existing user from the database. - -``` --spec delete(User) -> ok | {error, Reason} when - User :: binary(), - Reason :: term(). -``` -""". --spec delete(User) -> ok | {error, Reason} when - User :: binary(), - Reason :: term(). - -delete(User) -> - % We are missing the cleanup of the cookies - % here. For that, we need to add at least another - % API to the storage layer. - storage:delete(?USERSBUCK, User). - --doc """ -Get the authentication cookies from a cowboy request. - -Spec: - -``` --spec auth_cookies(Req) -> {User, Cookie} when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(). -``` -""". --spec auth_cookies(Req) -> {User, Cookie} when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(). - -auth_cookies(Req) -> - #{?DUDEAUTH := Cookie, ?DUDENAME := User} = cowboy_req:match_cookies([?DUDEAUTH, - ?DUDENAME], Req), - - {User, Cookie}. - --doc """ -Invalidate the cookies in the passed request. - -Spec: - -``` --spec invalidate_cookies(Req) -> Req0 when - Req :: cowboy_req:req(), - Req0 :: cowboy_req:req(). -``` - -A new request `Req0` is returned to the caller with the cookies zeroed and -completely invalidated. -""". --spec invalidate_cookies(Req) -> Req0 when - Req :: cowboy_req:req(), - Req0 :: cowboy_req:req(). - -invalidate_cookies(Req) -> - Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, <<"">>, Req, - #{max_age => 0}), - Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, <<"">>, Req0, - #{max_age => 0}), - - Req1. - --doc """ -Set the authentication cookies for the provided client request - -Spec: - -``` --spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(), - Validity :: pos_integer(), - Req0 :: cowboy_req:req(). -``` - -A new request object `Req0`is returned, with the user and auth cookies set. -""". --spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when - Req :: cowboy_req:req(), - User :: binary(), - Cookie :: binary(), - Validity :: pos_integer(), - Req0 :: cowboy_req:req(). - -set_auth_cookies(Req, User, Cookie, Validity) -> - Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, Cookie, Req, - #{max_age => Validity}), - Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, User, Req0, - #{max_age => Validity}), - - Req1. - --doc """ -Spec: - -``` --spec read_login_data(Req) -> {User, Pass, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Req0 :: cowboy_req:req(). -``` -Read the login details from the `Req` body and return `User` and `Password`. -""". --spec read_login_data(Req) -> {User, Pass, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Req0 :: cowboy_req:req(). - -read_login_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"user">> := User, <<"password">> := Pass} = json:decode(Data), - - {User, Pass, Req0}. - --doc """ -Read new registration informations from the request - -Spec: - -``` --spec read_new_user_data(Req) -> {User, Pass, Email Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Email :: binary(), - Req0 :: cowboy_req:req(). -``` -""". --spec read_new_user_data(Req) -> {User, Pass, Email, Req0} when - Req :: cowboy_req:req(), - User :: binary(), - Pass :: binary(), - Email :: binary(), - Req0 :: cowboy_req:req(). - -read_new_user_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"user">> := User, <<"password">> := Pass, - <<"email">> := Email} = json:decode(Data), - - {User, Pass, Email, Req0}. - --doc """ -Update user informations. - -Spec: - -``` --spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when - Req :: cowboy_req:req(), - Email :: binary(), - Desc :: binary(), - Name :: binary(), - Req0 :: cowboy_req:req(). -``` -""". --spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when - Req :: cowboy_req:req(), - Email :: binary(), - Desc :: binary(), - Name :: binary(), - Req0 :: cowboy_req:req(). - -read_update_user_data(Req) -> - {ok, Data, Req0} = cowboy_req:read_body(Req), - #{<<"email">> := Email, <<"description">> := Desc, - <<"name">> := Name} = json:decode(Data), - - {Email, Desc, Name, Req0}. \ No newline at end of file diff --git a/dudeswave_backend/src/dudeswave_backend.erl b/dudeswave_backend/src/dudeswave_backend.erl index 399df0b..e2a4879 100644 --- a/dudeswave_backend/src/dudeswave_backend.erl +++ b/dudeswave_backend/src/dudeswave_backend.erl @@ -23,6 +23,10 @@ % Module callbacks -export([init/1, handle_call/3, handle_cast/2, terminate/2]). +% Public API exports +-export([auth/3, logout/2, user_details/1, new_user/3, + update_user/4, delete_user/1]). + % % Startup functions % @@ -35,11 +39,86 @@ init([]) -> {ok, 0}. +% +% Public APIs +% + +auth(cookie, User, Cookie) -> + gen_server:call({local, ?MODULE}, {cookie, User, Cookie}); + +auth(password, User, Password) -> + gen_server:call({local, ?MODULE}, {password, User, Password}). + +logout(User, Cookie) -> + gen_server:call({local, ?MODULE}, {logout, User, Cookie}). + +user_details(User) -> + gen_server:call({local, ?MODULE}, {user_details, User}). + +new_user(User, Password, Email) -> + gen_server:call({local, ?MODULE}, {new_user, User, Password, Email}). + +update_user(User, Name, Email, Desc) -> + gen_server:call({local, ?MODULE}, {update_user, User, Name, Email, Desc}). + +delete_user(User) -> + gen_server:call({local, ?MODULE}, {delete_user, User}). + % % Callbacks % -handle_call(_Msg, _From, State) -> {noreply, State}. +handle_call({cookie, User, Cookie}, _From, State) -> + case dudeswave_backend_auth:authenticate(cookie, User, Cookie) of + true -> + {reply, true, State}; + false -> + {reply, false, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; + +handle_call({password, User, Password}, _From, State) -> + case dudeswave_backend_auth:authenticate(password, User, Password) of + {true, Cookie, Validity} -> + {reply, {true, Cookie, Validity}, State}; + false -> + {reply, false, State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; + +handle_call({logout, User, Cookie}, _From, State) -> + case dudeswave_backend_auth:logout(User, Cookie) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({user_details, User}, _From, State) -> + case dudeswave_backend_user:details(User) of + {error, not_found} -> {reply, not_found, State}; + {error, Reason} -> {reply, {error, Reason}, State}; + Val -> {reply, Val, State} + end; + +handle_call({new_user, User, Password, Email}, _From, State) -> + case dudeswave_backend_user:new(User, Password, Email) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({update_user, User, Name, Email, Desc}, _From, State) -> + case dudeswave_backend_user:update(User, Name, Email, Desc) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end; + +handle_call({delete_user, User}, _From, State) -> + case dudeswave_backend_user:delete(User) of + ok -> {reply, ok, State}; + {error, Reason} -> {reply, {error, Reason}, State} + end. + handle_cast(_Msg, State) -> {noreply, State}. diff --git a/dudeswave_backend/src/dudeswave_backend_auth.erl b/dudeswave_backend/src/dudeswave_backend_auth.erl index 4164fec..c6b563a 100644 --- a/dudeswave_backend/src/dudeswave_backend_auth.erl +++ b/dudeswave_backend/src/dudeswave_backend_auth.erl @@ -20,10 +20,10 @@ Dudes authentication module Here lives all the functions for the APIs needed to handle users authentication. """. --include_lib("dudeswave/include/defines.hrl"). +-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("storage/include/storage.hrl"). --export([authenticate/2, logout/2]). +-export([authenticate/3, logout/2]). -doc """ Verify a session with an existing cookie. @@ -31,9 +31,12 @@ Verify a session with an existing cookie. Spec: ``` --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when +-spec authenticate(Type, User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when + Type :: cookie | password, User :: binary(), Auth :: {cookie, binary()} | {password, binary()}, + Cookie :: binary(), + Validity :: pos_integer(), Reason :: term(). ``` @@ -43,14 +46,15 @@ after authenticating with `Password`. If `Cookie` is valid, the function returns `true`. If the authentication is denied returns `false` """. --spec authenticate(User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when +-spec authenticate(Type, User, Auth) -> true | false | {true, Cookie, Validity} | {error, Reason} when + Type :: cookie | password, User :: binary(), Auth :: {cookie, binary()} | {password, binary()}, Cookie :: binary(), Validity :: pos_integer(), Reason :: term(). -authenticate(User, {cookie, Cookie}) -> +authenticate(cookie, User, Cookie) -> case storage:read(?COOKIESBUCK, Cookie) of {ok, [R]} -> CurTime = calendar:now_to_universal_time(erlang:timestamp()), @@ -69,7 +73,7 @@ authenticate(User, {cookie, Cookie}) -> {error, _} -> {error, service_unavailable} end; -authenticate(User, {password, Password}) -> +authenticate(password, User, Password) -> case storage:read(?USERSBUCK, User) of {ok, [R]} -> Validity = case application:get_env(cookie_validity) of diff --git a/dudeswave_backend/src/dudeswave_backend_user.erl b/dudeswave_backend/src/dudeswave_backend_user.erl index 699f642..907a561 100644 --- a/dudeswave_backend/src/dudeswave_backend_user.erl +++ b/dudeswave_backend/src/dudeswave_backend_user.erl @@ -15,7 +15,7 @@ % -module(dudeswave_backend_user). --include_lib("dudeswave/include/defines.hrl"). +-include_lib("dudeswave_backend/include/defines.hrl"). -include_lib("storage/include/storage.hrl"). -export([details/1, new/3, update/4, delete/1]). diff --git a/dudeswave/Makefile b/dudeswave_web/Makefile similarity index 100% rename from dudeswave/Makefile rename to dudeswave_web/Makefile diff --git a/dudeswave/ebin/dudeswave.app b/dudeswave_web/ebin/dudeswave_web.app similarity index 54% rename from dudeswave/ebin/dudeswave.app rename to dudeswave_web/ebin/dudeswave_web.app index 3eb1cc7..2101f05 100644 --- a/dudeswave/ebin/dudeswave.app +++ b/dudeswave_web/ebin/dudeswave_web.app @@ -1,12 +1,12 @@ -{application,dudeswave, +{application,dudeswave_web, [{description,"The dudeswave web experience"}, {vsn,"1.0.0"}, - {modules,[dudeswave,dudeswave_app,dudeswave_handler, - dudeswave_user_handler,dudeswave_supervisor, - dudeswave_auth,dudeswave_auth_handler]}, + {modules,[dudeswave_web,dudeswave_web_app,dudeswave_web_handler, + dudeswave_web_user_handler,dudeswave_web_supervisor, + dudeswave_web_auth_handler]}, {registered,[]}, {applications,[kernel,stdlib,erts,cowboy,ranch]}, - {mod,{dudeswave_app,[]}}, + {mod,{dudeswave_web_app,[]}}, {env, [ {ip,"127.0.0.1"}, {port,8080} diff --git a/dudeswave_web/src/Makefile b/dudeswave_web/src/Makefile new file mode 100644 index 0000000..75e14ed --- /dev/null +++ b/dudeswave_web/src/Makefile @@ -0,0 +1,19 @@ +.PHONY: all clean +.SUFFIXES: .erl .beam + +ERLC?= erlc -server + +ERLFLAGS= -I ../../ + +OBJS= dudeswave_web.beam dudeswave_web_app.beam +OBJS+= dudeswave_web_supervisor.beam dudeswave_web_handler.beam +OBJS+= dudeswave_web_user_handler.beam dudeswave_web_common.beam + +all: ${OBJS} + +.erl.beam: + ${ERLC} ${ERLOPTS} ${ERLFLAGS} $< + +clean: + rm -f *.beam + diff --git a/dudeswave/src/dudeswave.erl b/dudeswave_web/src/dudeswave_web.erl similarity index 98% rename from dudeswave/src/dudeswave.erl rename to dudeswave_web/src/dudeswave_web.erl index 9893223..ad16985 100644 --- a/dudeswave/src/dudeswave.erl +++ b/dudeswave_web/src/dudeswave_web.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave). +-module(dudeswave_web). -behaviour(gen_server). diff --git a/dudeswave/src/dudeswave_app.erl b/dudeswave_web/src/dudeswave_web_app.erl similarity index 97% rename from dudeswave/src/dudeswave_app.erl rename to dudeswave_web/src/dudeswave_web_app.erl index 5a264cd..06f502d 100644 --- a/dudeswave/src/dudeswave_app.erl +++ b/dudeswave_web/src/dudeswave_web_app.erl @@ -13,14 +13,12 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_app). +-module(dudeswave_web_app). -behaviour(application). -export([bootstrap/3, start/2, stop/1]). start(_Type, StartArgs) -> - crypto:rand_seed(), - {ok, Addr} = case application:get_env(ip) of {ok, AddrConf} -> inet:parse_address(AddrConf); undefined -> undefined diff --git a/dudeswave/src/dudeswave_auth_handler.erl b/dudeswave_web/src/dudeswave_web_auth_handler.erl similarity index 58% rename from dudeswave/src/dudeswave_auth_handler.erl rename to dudeswave_web/src/dudeswave_web_auth_handler.erl index bd77f7e..ed59839 100644 --- a/dudeswave/src/dudeswave_auth_handler.erl +++ b/dudeswave_web/src/dudeswave_web_auth_handler.erl @@ -94,9 +94,9 @@ forbidden(Req, State) -> <<"POST">> -> {false, Req, State}; _ -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:authenticate(User, {cookie, Auth}) of + case dudeswave_backend:auth(cookie, User, Cookie) of {error, service_unavailable} -> exit(service_unavailable); true -> {false, Req, State}; false -> {true, Req, State} @@ -112,9 +112,9 @@ content_types_accepted(Req, State) -> end. resource_exists(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:details(User) of + case dudeswave_backend:user_details(User) of [] -> {false, Req, State}; {error, Reason} -> @@ -135,11 +135,11 @@ is_conflict(Req, State) -> {true, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:logout(User, Auth) of + case dudeswave_backend:logout(User, Auth) of ok -> - {true, dudeswave_auth:invalidate_cookies(Req), State}; + {true, invalidate_cookies(Req), State}; {error, _} -> {false, Req, State} end. @@ -151,11 +151,11 @@ delete_completed(Req, State) -> {false, Req, State}. % login(Req, State) -> - {User, Pass, Req0} = dudeswave_auth:read_login_data(Req), + {User, Pass, Req0} = read_login_data(Req), - case dudeswave_auth:authenticate(User, {password, Pass}) of + case dudeswave_backend:auth(password, User, Pass) of {true, Cookie, Validity} -> - {true, dudeswave_auth:set_auth_cookies(Req, User, Cookie, Validity), State}; + {true, set_auth_cookies(Req, User, Cookie, Validity), State}; false -> {false, Req0, State}; {error, _} -> @@ -170,3 +170,88 @@ logout(Req, State) -> {ok, Req, State}. % terminate(_Reason, _Req, _State) -> ok. + +% +% Private functions +% + +-doc """ +Invalidate the cookies in the passed request. + +Spec: + +``` +-spec invalidate_cookies(Req) -> Req0 when + Req :: cowboy_req:req(), + Req0 :: cowboy_req:req(). +``` + +A new request `Req0` is returned to the caller with the cookies zeroed and +completely invalidated. +""". +-spec invalidate_cookies(Req) -> Req0 when + Req :: cowboy_req:req(), + Req0 :: cowboy_req:req(). + +invalidate_cookies(Req) -> + Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, <<"">>, Req, + #{max_age => 0}), + Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, <<"">>, Req0, + #{max_age => 0}), + + Req1. + +-doc """ +Spec: + +``` +-spec read_login_data(Req) -> {User, Pass, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Req0 :: cowboy_req:req(). +``` +Read the login details from the `Req` body and return `User` and `Password`. +""". +-spec read_login_data(Req) -> {User, Pass, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Req0 :: cowboy_req:req(). + +read_login_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"user">> := User, <<"password">> := Pass} = json:decode(Data), + + {User, Pass, Req0}. + +-doc """ +Set the authentication cookies for the provided client request + +Spec: + +``` +-spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(), + Validity :: pos_integer(), + Req0 :: cowboy_req:req(). +``` + +A new request object `Req0`is returned, with the user and auth cookies set. +""". +-spec set_auth_cookies(Req, User, Cookie, Validity) -> Req0 when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(), + Validity :: pos_integer(), + Req0 :: cowboy_req:req(). + +set_auth_cookies(Req, User, Cookie, Validity) -> + Req0 = cowboy_req:set_resp_cookie(<<"?DUDEAUTH">>, Cookie, Req, + #{max_age => Validity}), + Req1 = cowboy_req:set_resp_cookie(<<"?DUDENAME">>, User, Req0, + #{max_age => Validity}), + + Req1. diff --git a/dudeswave/include/defines.hrl b/dudeswave_web/src/dudeswave_web_common.erl similarity index 58% rename from dudeswave/include/defines.hrl rename to dudeswave_web/src/dudeswave_web_common.erl index 48c12ba..7762d6e 100644 --- a/dudeswave/include/defines.hrl +++ b/dudeswave_web/src/dudeswave_web_common.erl @@ -13,11 +13,29 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % +-module(dudeswave_web_common). --define(APPBUCK, dudeswave). --define(USERSBUCK, dudes). --define(COOKIESBUCK, cookies). --define(RANDBYTES, 32). --define(DEFVALIDITY, 365). --define(DUDENAME, "dudename"). --define(DUDEAUTH, "dudeauth"). +-export([auth_cookies/1]). + +-doc """ +Get the authentication cookies from a cowboy request. + +Spec: + +``` +-spec auth_cookies(Req) -> {User, Cookie} when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(). +``` +""". +-spec auth_cookies(Req) -> {User, Cookie} when + Req :: cowboy_req:req(), + User :: binary(), + Cookie :: binary(). + +auth_cookies(Req) -> + #{?DUDEAUTH := Cookie} = cowboy_req:match_cookies([?DUDEAUTH], Req), + #{?DUDENAME := User} = cowboy_req:match_cookies([?DUDENAME], Req), + + {User, Cookie}. diff --git a/dudeswave/src/dudeswave_handler.erl b/dudeswave_web/src/dudeswave_web_handler.erl similarity index 98% rename from dudeswave/src/dudeswave_handler.erl rename to dudeswave_web/src/dudeswave_web_handler.erl index 9d8ece1..a4f16d6 100644 --- a/dudeswave/src/dudeswave_handler.erl +++ b/dudeswave_web/src/dudeswave_web_handler.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_handler). +-module(dudeswave_web_handler). -moduledoc """ The dudeswave GET handler. diff --git a/dudeswave/src/dudeswave_supervisor.erl b/dudeswave_web/src/dudeswave_web_supervisor.erl similarity index 94% rename from dudeswave/src/dudeswave_supervisor.erl rename to dudeswave_web/src/dudeswave_web_supervisor.erl index dad62ce..869a491 100644 --- a/dudeswave/src/dudeswave_supervisor.erl +++ b/dudeswave_web/src/dudeswave_web_supervisor.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_supervisor). +-module(dudeswave_web_supervisor). -behaviour(supervisor). -export([start/0, diff --git a/dudeswave/src/dudeswave_user_handler.erl b/dudeswave_web/src/dudeswave_web_user_handler.erl similarity index 72% rename from dudeswave/src/dudeswave_user_handler.erl rename to dudeswave_web/src/dudeswave_web_user_handler.erl index 3c6b623..da011dc 100644 --- a/dudeswave/src/dudeswave_user_handler.erl +++ b/dudeswave_web/src/dudeswave_web_user_handler.erl @@ -13,7 +13,7 @@ % ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF % OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. % --module(dudeswave_user_handler). +-module(dudeswave_web_user_handler). -moduledoc """ JSON API to manage users. @@ -138,9 +138,9 @@ forbidden(Req, State) -> <<"PUT">> -> {false, Req, State}; _ -> - {User, Auth} = dudeswave_auth:auth_cookies(Req), + {User, Auth} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:authenticate(User, {cookie, Auth}) of + case dudeswave_backend:auth(cookie, User, Auth) of {error, service_unavailable} -> {true, Req, State}; true -> {false, Req, State}; false -> {true, Req, State} @@ -168,9 +168,9 @@ content_types_accepted(Req, State) -> end. resource_exists(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:details(User) of + case dudeswave_backend:user_details(User) of [] -> {false, Req, State}; {error, _} -> @@ -194,9 +194,9 @@ is_conflict(Req, State) -> {false, Req, State}. allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), - case dudeswave_auth:delete(User) of + case dudeswave_backend:delete_user(User) of ok -> {true, Req, State}; {error, _} -> {false, Req, State} end. @@ -208,31 +208,85 @@ delete_completed(Req, State) -> {true, Req, State}. % create_user(Req, State) -> - {User, Pass, Email, Req0} = dudeswave_auth:read_new_user_data(Req), + {User, Pass, Email, Req0} = read_new_user_data(Req), - case dudeswave_auth:new(User, Pass, Email) of + case dudeswave_backend:new_user(User, Pass, Email) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. modify_user(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), - {Email, Desc, Name, Req0} = dudeswave_auth:read_update_user_data(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), + {Email, Desc, Name, Req0} = read_update_user_data(Req), - case dudeswave_auth:update(User, Name, Email, Desc) of + case dudeswave_backend:update_user(User, Name, Email, Desc) of ok -> {true, Req0, []}; {error, _} -> {false, Req0, State} end. user_details(Req, State) -> - {User, _} = dudeswave_auth:auth_cookies(Req), + {User, _} = dudeswave_web_common:auth_cookies(Req), #{details := Details} = State, Data = Details#{user => User}, {iolist_to_binary(json:encode(Data)), Req, State}. % -% gen_server callbacks +% Private functions % -terminate(_Reason, _Req, _State) -> ok. +-doc """ +Update user informations. + +Spec: + +``` +-spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when + Req :: cowboy_req:req(), + Email :: binary(), + Desc :: binary(), + Name :: binary(), + Req0 :: cowboy_req:req(). +``` +""". +-spec read_update_user_data(Req) -> {Email, Desc, Name, Req0} when + Req :: cowboy_req:req(), + Email :: binary(), + Desc :: binary(), + Name :: binary(), + Req0 :: cowboy_req:req(). + +read_update_user_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"email">> := Email, <<"description">> := Desc, + <<"name">> := Name} = json:decode(Data), + + {Email, Desc, Name, Req0}. + +-doc """ +Read new registration informations from the request + +Spec: + +``` +-spec read_new_user_data(Req) -> {User, Pass, Email Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Email :: binary(), + Req0 :: cowboy_req:req(). +``` +""". +-spec read_new_user_data(Req) -> {User, Pass, Email, Req0} when + Req :: cowboy_req:req(), + User :: binary(), + Pass :: binary(), + Email :: binary(), + Req0 :: cowboy_req:req(). + +read_new_user_data(Req) -> + {ok, Data, Req0} = cowboy_req:read_body(Req), + #{<<"user">> := User, <<"password">> := Pass, + <<"email">> := Email} = json:decode(Data), + + {User, Pass, Email, Req0}.