From 653fd0a4af907dd57897c27292afeb9feb05d00f Mon Sep 17 00:00:00 2001 From: liamwhite Date: Sat, 12 Sep 2020 13:43:16 -0400 Subject: [PATCH] hCaptcha (#19) --- assets/js/captcha.js | 43 ++--- assets/static/images/captcha/1.png | Bin 1764 -> 0 bytes assets/static/images/captcha/2.png | Bin 2986 -> 0 bytes assets/static/images/captcha/3.png | Bin 3232 -> 0 bytes assets/static/images/captcha/4.png | Bin 2859 -> 0 bytes assets/static/images/captcha/5.png | Bin 3017 -> 0 bytes assets/static/images/captcha/6.png | Bin 3217 -> 0 bytes assets/static/images/captcha/background.png | Bin 1112 -> 0 bytes assets/static/images/captcha/i1.png | Bin 4971 -> 0 bytes assets/static/images/captcha/i2.png | Bin 7498 -> 0 bytes assets/static/images/captcha/i3.png | Bin 4536 -> 0 bytes assets/static/images/captcha/i4.png | Bin 5811 -> 0 bytes assets/static/images/captcha/i5.png | Bin 5938 -> 0 bytes assets/static/images/captcha/i6.png | Bin 5564 -> 0 bytes config/config.exs | 2 + config/releases.exs | 2 + lib/philomena/captcha.ex | 154 ------------------ lib/philomena/http.ex | 4 + .../controllers/captcha_controller.ex | 11 -- .../controllers/confirmation_controller.ex | 3 +- .../conversation/report_controller.ex | 3 +- .../controllers/gallery/report_controller.ex | 3 +- .../image/comment/report_controller.ex | 3 +- .../controllers/image/report_controller.ex | 3 +- .../controllers/image/source_controller.ex | 1 + .../controllers/image/tag_controller.ex | 1 + .../controllers/image_controller.ex | 3 +- .../controllers/password_controller.ex | 3 +- .../profile/commission/report_controller.ex | 3 +- .../controllers/profile/report_controller.ex | 3 +- .../controllers/registration_controller.ex | 3 +- .../controllers/report_controller.ex | 3 +- .../topic/post/report_controller.ex | 3 +- .../controllers/unlock_controller.ex | 3 +- lib/philomena_web/plugs/captcha_plug.ex | 60 ++----- lib/philomena_web/plugs/check_captcha_plug.ex | 71 ++++++++ .../plugs/content_security_policy_plug.ex | 2 +- lib/philomena_web/router.ex | 2 - .../templates/captcha/_captcha.html.slime | 8 + .../templates/captcha/create.html.slime | 20 --- .../templates/confirmation/new.html.slime | 4 +- .../templates/image/_source.html.slime | 4 +- .../templates/image/_tags.html.slime | 6 +- .../templates/image/new.html.slime | 11 +- .../templates/password/new.html.slime | 4 +- .../templates/registration/new.html.slime | 4 +- .../templates/report/new.html.slime | 9 +- .../templates/unlock/new.html.slime | 4 +- lib/philomena_web/views/captcha_view.ex | 9 + 49 files changed, 169 insertions(+), 306 deletions(-) delete mode 100644 assets/static/images/captcha/1.png delete mode 100644 assets/static/images/captcha/2.png delete mode 100644 assets/static/images/captcha/3.png delete mode 100644 assets/static/images/captcha/4.png delete mode 100644 assets/static/images/captcha/5.png delete mode 100644 assets/static/images/captcha/6.png delete mode 100644 assets/static/images/captcha/background.png delete mode 100644 assets/static/images/captcha/i1.png delete mode 100644 assets/static/images/captcha/i2.png delete mode 100644 assets/static/images/captcha/i3.png delete mode 100644 assets/static/images/captcha/i4.png delete mode 100644 assets/static/images/captcha/i5.png delete mode 100644 assets/static/images/captcha/i6.png delete mode 100644 lib/philomena/captcha.ex delete mode 100644 lib/philomena_web/controllers/captcha_controller.ex create mode 100644 lib/philomena_web/plugs/check_captcha_plug.ex create mode 100644 lib/philomena_web/templates/captcha/_captcha.html.slime delete mode 100644 lib/philomena_web/templates/captcha/create.html.slime diff --git a/assets/js/captcha.js b/assets/js/captcha.js index e9884cb5..ec0b4f32 100644 --- a/assets/js/captcha.js +++ b/assets/js/captcha.js @@ -1,33 +1,20 @@ -/** - * Fetch captchas. - */ -import { $$, hideEl } from './utils/dom'; -import { fetchJson, handleError } from './utils/requests'; +import { delegate, leftClick } from './utils/events'; +import { clearEl, makeEl } from './utils/dom'; -function insertCaptcha(checkbox) { - // Also hide any associated labels - checkbox.checked = false; - hideEl(checkbox); - hideEl($$(`label[for="${checkbox.id}"]`)); +function insertCaptcha(_event, target) { + const { parentNode, dataset: { sitekey } } = target; - fetchJson('POST', '/captchas') - .then(handleError) - .then(r => r.text()) - .then(r => { - checkbox.insertAdjacentHTML('afterend', r); - checkbox.parentElement.removeChild(checkbox); - }).catch(() => { - checkbox.insertAdjacentHTML('afterend', '

Failed to fetch challenge from server!

'); - checkbox.parentElement.removeChild(checkbox); - }); + const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true}); + const frame = makeEl('div', {className: 'h-captcha'}); + + frame.dataset.sitekey = sitekey; + + clearEl(parentNode); + + parentNode.insertAdjacentElement('beforeend', frame); + parentNode.insertAdjacentElement('beforeend', script); } -function bindCaptchaLinks() { - document.addEventListener('click', event => { - if (event.target && event.target.closest('.js-captcha')) { - insertCaptcha(event.target.closest('.js-captcha')); - } - }); +export function bindCaptchaLinks() { + delegate(document, 'click', {'.js-captcha': leftClick(insertCaptcha)}); } - -export { bindCaptchaLinks }; diff --git a/assets/static/images/captcha/1.png b/assets/static/images/captcha/1.png deleted file mode 100644 index 98493c224a6b599eeb0732d2667a895e250e7084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1764 zcmaJ?`8S&h8+~6PvBlC>#ZuZZj4G$ri4sbqQ?yK@2~BBZ8EFNHeXmT_S8a;Ulo=+9 z&{XXV5iuW$8jQW4EofCVleU(MrIyw*hdexBB2;%E8J3>%L#O{k5ggpHar4w z@+vbSY#0i2C+b7(A3rq1p1S8HG0lv}4u>Y294dc+Rk@5C3+)eJ(;wm6@THY%6-2w3 zF6O?$@e_tdcMUg_(SKMK5Je+Lhhwg{>nqHEC4qmr)SfhKRYZbd{uQ6fk6+eQV};qi z@~#so0U@S&eFvqr<%bKOD`mQzdE!`_`vp%%n;~cEe_mpZ?OYa&D^#5nx-ehXPLc)Ge#GW(#P(InqaOGES5AlqCOH)Sp&~RL6L>4OJ$1(x;3L+(gXQ8L zZGa@p0zsAq2_z;YAl?rbe;XhF4ImtPVxM1700NnR4t}Uy(yfAcmXAshL5?t?in1~> zEKGS(P`9?n^DT&y_yhWipu?N=iz_rK`U(`o+}Mx2}IhaWzCI^B&Aj z5H!!3SsLO8g?{FOy4)m6K){@_^@|y9w6VE=>)jN`S?hK`^W;lyk)rOXQ9#MpB7JoJ zQ=Ize5`5J$nMblfPb77BJF?IrafDZrgeBf@PZo7&Wl$^OK1&euBZ@QL-rfgtB{UXM zRTo7&Z0t#V)f^t4v+?sy_0L>U#(Bd{(CgyGU_Sv4s1NE<@oS-iQ(>D0*{Lf+PUqsHQTD`$d&N`1=B6`D z?i&yhRQ-R;-`}72m2grLmZ(jJ6fqcNoqEx!;?{}jzMf~aF3|Xpg3~x2gr&Vld9DK< zzB=`p)vMFJ!M|1TI2-{*C+NC-dF9Gg9cm4pY@ePE+GmeydgA69ZHYaRySHA2Lbo=| zNTCk{_>l(4+m$trtv_8f+7r44pG(NgAme0The9F!UIk4PCkrcIkiz+d#b%Bhf6dcF zz4xv+Zo3+eLZufUr3&p<8&mU5{!~pRrw3+0Hv+0_j2wYAsUm>5kJDnC8$%(DHG(xnqXn@y*s|4y1;(Eu%blxLtPiv9s$Fy|Z=ZNXgIcEUzNbf!6&= zitRN{lW`n)7Mhi{%o}x}=>sWqt;;%HfvZl#W$n+qZbM<>_0KVF*Y(E;oF{|GQuf4X r)@HQ`6;JPayGN}wME)B}ar2srMpGNpV>FKRLji8Co|sw};+?+%os>FQ diff --git a/assets/static/images/captcha/2.png b/assets/static/images/captcha/2.png deleted file mode 100644 index c6944f4d3f7756fa11b65729cd59abc4a36fac5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2986 zcma)8`8yPB7k&m=nk12Zsgb3!&samMkz}cc(8xM9p_j3*LnEIWp$*w7HG@R9$Ot2h zk|jnIGsrT?G9+7rzIp$G@B87Lb6w|$=enMA?)%(#;w3vv;k`0@0RX~Q=aDFGjrcbN z`MKHXx_&>m2nC$K8Vmq$(!T)-{Vd7hHl;%>oI~t!*pP6~pc^1OJY3Vq|5h-@GvJ0M zF6d^?l8FoePy;LEX@`j1MP{^(gOh^5GCNDUqL}wgBy(&ZubS_b5IwYgSzijmVI|Mc zH@lnmlSXdv^{Tu2R-ajWl`ciE(PO@zBa0{|mIP+#57HKgz00mnqJRE@Iah|YL;|!p zzqk}@cU|P2eF#>ar!`<@!%C$-oW+38EKlY}v+FnVA^$mWM)1i0O4#QUt^_H2Bw`PB z5(K58WZ*@-#RAJpaQ8}dnI*%2BNE6BJ)wPk1`R-?(LpFwzX^b5)k~Q>8t9ioDq6AC zjV`XPF-o<{3-09o74$iq{!k_E9Q_4D;*`h@w!a@eR&pclU?) z@9$+$LQ8M;p9AxaZfwgP$3!W(Eg{icop3WVvI*4*Ieduv#d}WGCX-ND2uR-VPz$RvbTSn zp1!Auo|v4R>~`Do@B!-2ucd*T%bt^ESFr4ITe0yH`y!Q1d=nJVreC%C~oTpDE)SWAUq?Q{W;g5&5VR>zB zZF%pBqaQ^3;K(*)n}CSIly`P^_U2b7_vzeRIsN|d7$dp;v0Kv__4V~e6YDo_+*k-P zPN`wHH;gt0vQ@OHn>)LE8<&1fhU^!N7@UDx_a)+yT$)|__!WGj#EdFL*X zMEdHypO5GHM%_Q(??5Fr0m$64DCpg&i=IAiXh{5;@B#m|SVt`0Sa-`8FhP z?i|nS`KVcuyho1!P}9=tD0giD;6ll5?q0%UH4AXfJNjT;lhPXRaJHO7-LBM66GpYeQ?7hTfzZ^QXFnXx|{jw~RM{ zU3&d+S66RT<7g?9X{M@rQykvR2dZ!O(iXl~xAy0nMCuM$z*onj-F*X_9m2xaHybQp zQR0caR><6(oX;7WuJ;W;R(iGX#XNd3Iv1kSUdRLEN7!=S>fCwjH&RP%F^yjT$2${6 zuhCf1te#bYT~IAFhD}_p#s*qjTRR8Dw;a~G_U?;!qd|3bb@c7$Mh|T2MV5|{21bBx z8tPlEpUbx^N5Qu`oT6QUncwT(5;B>EzWwy_??+#r!T|VPVnuk?Eh0?U3->W;1c@xY{OvAe@sK;&X|X;H*hEo&)bWY-9rMFf3k!>L^L|Q&%Ptme z)R#WPv6H!VRaLqZP2n96onLdoPv~+7?@m=D(moJY__*`~zk?qaYX^jZ_!4VQu zIZA6_RO-&61H8N@qC(7mH%6|qPA-+|;?uCh$Fs~7|yzM(qFFVWGp=< zb@ue+pGE%Vsh~yBy(%+xspO`a;_8uYQ_z)TATmiyNy=Pe)CbdE9tRckOTF~|{wHE$ zVv2Rn>dHWZmxvIsA2^oFuWK(LMNI;CPmn5Ncm5P7uCvF+LPG^a{mymZ)4?MMHL)QI zp7A+B@7Cb>)Th+Vsnm0J1p@$=Kzjy?4x zHMNyRb-3`OGyMe3rlv;+Lwi5Hy7k+v#34D0rDlBckju4!u0g? z5P1i`=wx|gFiJ8qG66e(LO1bd17E*B=c@Z}U%!-CGN$^wcG!ZmxTGZJd({moJzsG! zo~W8n%{Pq(9!NjGMR8D6h}9)zPN*4cY2lDAE+dygDrYrXLjua`EqG{~qkn`;XvAeG z{1kh2%SOklu7a}0@Tm! z@_7T^IPwQ5oO2XxZW6VSl&gSms#Hdpe!IC?q6wD;=c;so<5@Ko5ngk@iQL zDA>!{<``3V;)vJ4df{YIBmiCSM`l*bi0&nn-jE`zo$mQH$09Ph$D%{|oIF(+yK9ZN rR`!CKnlkeDx&g4VutS!g@woF}xJ#+- diff --git a/assets/static/images/captcha/3.png b/assets/static/images/captcha/3.png deleted file mode 100644 index add098016207332fb8369f7975d048ce727e827f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3232 zcma)9_d6Tv7msm?tteGgqt!}nLalPE+$O0=8>>c)x@jmyt)w>98MA^KNm^~xsI7b} zW~~ydMzlsy>rz|n_09bc?(>}Up67Xgc;EA$^Esc-^S-=oc~gl0EI$AM5Q0I?Q0$2R z*MU6jRsV0@es<~4m7_TXyto95a;HP0pM^rHBTR}0CzV( zjGC`MHfzP`EC3*=3NyQIjn7^l3-^=2kQ>)m2gEndy#QUxz#909O5$2?NP%MUb8*#g zOYhD-SZyg9k?OOuEa9h18TsrK_Ymnt@H;+3*M(2+4%*OFw?#{55>?Fa zZ4u9N-+v9#q#1NAUDO`vJxUJrHNE5`_FCG|h*Ds^Wpu&g7vp5frCb6a30 z-fAm=YE5%HbCZZ_0CIDN=M%I(kG+|Ud-duSXB6JZnLkW?SMmld)yhMia|wP?te5*q z(;DmP-q2(w_bo7mr(Ps%n!Y$YH-~a@>3vu|-u7DYmXfma1rrmBpPq%D>?Kj4-&~jU z$mpo2m)C`JiKEYh`9X~RqvJ&Ez7whg!N(MVpzYOSC!fjqynXjB`q?v{2VPzsot>Gt zvmzQOa)!aBz7xho6d;|5@}kjbNTe@oVLFpLw2t}E^u@9%&^c_~+&>^d6OppG_<*0E zzqnwTi&uEj9(01?lOTZ*{^9U8Rz!V& z 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 diff --git a/assets/static/images/captcha/5.png b/assets/static/images/captcha/5.png deleted file mode 100644 index 20fcbe851fdbb2dd9a05110960965f1bdff1281c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3017 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 S&auaEz)d3ySdF1;(*FSOW!{zm diff --git a/assets/static/images/captcha/6.png b/assets/static/images/captcha/6.png deleted file mode 100644 index 6c96307da067f547c0fa5ef42b93ce4a2183a050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3217 zcmb7{`#%%x8^_0T$YG}V5;>KJW=@Z!m_v^+CaOtN&YD@yXEED^C-P)rO5~WyR1R~< zq1i-)@YE_f4=pj9vz&d`fAIZYulu^M*X#c2y06#udEeKacG(dwDW)I>001QIY^{*| z82j5rh55VjEu&BTa5&V~H39$trvG++6wDA%2lD-r*=fOiYX}CO9C% z*EIYLoIVSIa zUXE&AJAke{Qg}JC5+S+_^fJ+R77eq9L^(j%{k^^0Z(hCXx0vWQzNX_sc3xSn;H36`tLT~wan2E_gI*_WHK+Dk}0jO)*Fwe3+)ec){P!4drwMdNY#+S57&rS)U1vLpS6FaT~b+jzqc2m z1%*Piv=GBXi@zea*D!hc`Ix}KPR{y>wb-}JqM`$$CwBSWE7(VnjwFOlB$E=5h(E|z z6^#Nf7vZ)kRBd}n7xg1ob~d<9zs6&VsGMx%{=m$#wl3dZQYbyg7dJAJedB8|d3fDq ze?f!imx>0U)odDOahl}friwS1&8=-?{zp$K*Oxt9q=Z;~^)7E#}Rn5%In#O~bV_5?C)6Wf{Eu_rrRJ+nA!;`X`&-3yWU`+=i^1eeYwNv## zoV+Rwme1~ctSK8f**5Ozai~zu==z`PAN?a0!B-GrKmRR>#o617hZ8iSZ=mpARryUzjwA6zg=fAkNXN!yITfj$u83K+ARtL2(7g%jD+d$PUt@ zxeELq-Y`-+Nol3i>4$B{UfYW<$Cwu_R)?{2B2IL#d67SBs~sYf$#z@r?(WO=U>_@| zkh|@y^<$^~uJUtyRjhQBk;D_%r$RRc+V&)J1e$V?!Cr;nY4npPPact0 ziow&K8q7Km_#yfC4M&ZQjqQYFWo2)n(agz7B642Gryb}+iywGutbM(PaR0s-U++Bw z1Lf7#L@)i+$;H`kwZ2bHqQv^B5?(pT+iV820x!?Y-LU$e3TSoH<$QhhZNa&ZD*X70 zt7}i%QB5z^rgS;IA0Oc=?2>!R3S%A2mTIqL?fUKu`^f8z@) zG~gaHZtd=_#AdSt$6I7sg|_Nc#n@Y<{K)QeCm7Am%~y70l+vt_y(;kwwPk~Qdwa*t z;z(UxHXs%Ar-maasFs$`mrCcrv8F#!k%7!|hfS=x2YxS#ppswS{1&oWtYnWkmW8Zr zX=zz>D_EL;QC-Qm&NdLCB#@C}36I>Mtd{wY(={8t z-KR&t2q`j&(u$ZJ5IPSzGZs}=K%i}$8YqMaswV$ir`3&w!@Gri#Je{)XNb(;RaO!o zWONeJ@fa^gLV2_4&T4+H&56v<>@*N77XW+&E|vCKEzUuh`Bq(*_LMV9NpuEZC?WL< zBW>D`#77z`IPfMwQ)K|(YTcg;3*W!@WJB2L|Ix8TVDP&yysw%awRM%iv3ql3TPiAS zMPgr~{QPi$H336I!!nZM-|Z)fo%bFDh>MFe^(IxUM6ZA@BavC8qJNXBjn`4y8DdNC z(&efFI|37TW((R^YYE?)))*leziZdN2F)6_j`Lg@Zks>_insJbXfco z4dcml3*~-8B;kSQB7R&P@mPp%y|dbXmMg9cj2+ScN3V>;EyozyLx`@A*gzbpCUnZu zBX%mK%)|r+oBNrn7k%!G->YN3juQIin0-wk=V4>eXjenP5VDa{Sy@V+9~amY8K6c!dP%8osQ6Dz4y16ULN zU5SNnw#{FE{Z(jojDeeU_Vt~q0V+PQ`Jx!|CR2Wl#W15jGu}9R?j!BjM82$qF#eC^ z&6}`lDm5#L^R^Owf6IchPcV!iYy+0d`A;xq zF)^`RSd8vzzYks`^%$!Y(v~G)IbAo_SaU>@CV1CIbUC7W2=?i<(8W{PS`2XfEBfGU zy!@u3TIU6IZB+Av10j#~E$l(c1r^@&({qAPE8e`p-oWDzEc>Xuqn5i5za30IgfH|P zyQF||YF6pq@^n5BFBNE%{vQMO?@`=H{Xptx5AVu)59Jj9I{~n>cC@Op^t$_B<)%d> diff --git a/assets/static/images/captcha/background.png b/assets/static/images/captcha/background.png deleted file mode 100644 index af1e9b26c1b841206b39fb0ff78e46483a59b261..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1112 zcmeAS@N?(olHy`uVBq!ia0y~yV9Wqv4kn<;*YU85o%Nc)B=-RNQ)dePb?*fdKPC`|vYC5u1$N`JEM;XL9lJ zh+V4vrV?qra_!VLDNF6` zeORWFt-f{Zq^WD39A*5m3SkXl{m8~1BH6767JF|h3%`80pUb*H4^tm|ugVjuMYmgKoQUT6*B_px*>z%yi nn|s|4802V)Z3eDv_T!!SChHhZ>r?8xK^ffB)z4*}Q$iB}sx>23 diff --git a/assets/static/images/captcha/i1.png b/assets/static/images/captcha/i1.png deleted file mode 100644 index 44c64070ffeac237e241536311a9963ac96874e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4971 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 diff --git a/assets/static/images/captcha/i2.png b/assets/static/images/captcha/i2.png deleted file mode 100644 index 03fe0d25b00fe044c16da6ab93522869beb41d80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7498 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 diff --git a/assets/static/images/captcha/i3.png b/assets/static/images/captcha/i3.png deleted file mode 100644 index 43b1acc0da1444bb73802a73d602e584ee444d21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4536 zcmV;p5l8NcP)az2_{X;e})~>X( zJF~mvqvss|;hp(@@2mao%zX1*g2e2e9A-a7C35@)3%~|kx8^H6A>+2a#cX24c1K8bgTABkbMg|U!Y6k*)v5J z4ThatwO}fpK(Xki_DNQCRiWkrC@j!S>>kw{Yr@Q}nux5yw$`}@nC`cn!UCK5#7~0H zs5*({EqW-^F~_JpLGk&k73S8&Tb>LuuWBTcSFmHv#gz8r6r=o?>dkdQpTF!N8}&5X~Ap zBMjYYAX)~CG=w2*Y_QMuY9d+&qx~TX?;nzG#fg@|D&ZKcObSJ}3W%1$qFX|d6()wE zTMb0YU{!f7B2G!?k6j2m1Ph|I|fvMO+4 zl~C>s)j*^S22j<9g4Q%5Jwa<$3z0L}Ko>9+oT8?P5B3J9S6xKTU`e)a0PYP;4;~g- zlnF|&+K8ON00#5(ptLL!*pH@d+f^X&pQ&UWbi9r-ZE`yz#NXShhkEA-b?Q6i7%C+sUrU}>Ip)F zmvK%Zzz&P;w70AC(m|=?FmeRw6p%ht){@CQ>VAG&DC5b-q9RwY+q-23I9H;~W8{zQ zWr1s^BocRAaG(tM)z<&$c9A=)zNo%hx2NwO zM5eUOy5m&RyEj_lDBv(u4?{S>WZ6p^S~eD={joT`sgqHb0iQ15^p|6vIq8zw@wZBi zTPS5Ucu9A9fr@tch?^1B^V*v0Z}$_&UA?h^c-;n6&vTpW@7VdcrtaH|Atxh~Pu=WC zcW0LLr2bW5DZai(eI-e9_79pQ__1X?~6b~40)u)cq4Fo zw(btxN)X?hau%wg*MZ7&i&hC{7QJ$c+ml{6>N(<^h|+sWb0UPrN6qEHZof_vVmUNY)0U-URc9t|wsHr%y+jlkarsVQP-1*w;d&<)NS@2>+D z6q=UwrgPsBhK-+r*zQ1M0$6h)r9WmqhN96o7?DC60DPfvey-22T)%ZHP;WdFXhVX# zX7gmw)*v+j(4yWdd=b9Exts1vWv;vK!>K!eIGAy20dRs{2|%RwZ-GRRngAF=U-dS8 zgN+1LDcO-48FJ?D{9&CP*!JFQ>VpiXqF)J8?=QmPclMN_79GNLPe4URG<;li_|y-9 zl;Q=p8qZa|A4~y07nI&k{4pfmk0`?}TKYm#K0Hsr=M@RekU=})%Y@gh8v4l2G8kM_qi>*jrdgLzfn#Osu4kj5LVY4M%2C@miBf+H&|5GhNW#LIQxwJ)Vmwl|FYix z(KhMufgT);&Iq!#*`XIg(*B6h4R%C=e<$6Bg-`DMncLW~9(dehFAgL~kc~7SXZ5x^ zL#Tvq@U|wy_rg)v+%Jq0DoN?WHt#f)A2m+ijMY|P&bAKv;t+YW=(2e=by?t=c_@p)?o1gJ z<=+FA%}!0!zdCB%L+iT{{W{8}V|FvI*{Ooj4JC2 z8CL5y*53vCb3|`i+Ox&?E04hQ#U+?WPxK;pj)ZaB&q>(~{!!!P%_6p#$=bOwQuumV zo0303wZ{|Zc6XKfYL(KM^O*14@*#A!H74&1Os@dSIyXr-Ca(d4nVYK={6)EQB#U*< zMrDRfP5r=67@(??e0IwPg#L${G~U7v=Lc&@gWT*54877*=HBJv+8?Mz6umQM^}c&Bl~Ao{1?Dl zBzu{kVj4@_4mxUuZWIPYwPkT*;+Z1LmqbOTkGjpt2L;ZfD3j%`wx;9*Ma31d2yGx| zT=rsLQ^DnC%AgTi0l!_Jn~azf+M4SFAK_Uj6`LR2)|9*nXaUkbimi^7r#kJWkLuIXzAvtCudr^s1W zc){&S-O}E!JC1Aqy`lw~vb1N*!HBnt+~xvIC`6|cRHwl&4eU?;DI$llHZ}UhEhD!A zzb2O0-)%}9BIsNZ=~=W${!(ifiJ>w$W-h&3W_aSP0!G_6aok`($dDa<9F)q{S3kEY z=L!Mtn#^>d(Fm8hGlvaqYpQ=@#I0C$x(K7rUs}3$>mjJKs(`WGp^tAamZ~&Z4b5Ym zGHoDMcVeysrpB8DU>%dUd(!Pr9|25Pu}_Q^#9H$O;4Q{p2Xp)i_Jz&6PjDaTwK2~e z(AJz-p6i4O{ey1;is$8Sj#L#yF9n_%YyFS5rsPd-Q}XjMBl{dZ_4ViWOKwb)d%uXP zy@D&cj8a~NLwEddjd)v+obqOU^W?X0nNt7$4@J(+JCfs~!>4}e_N1N%SqMD7^YJCU zsS6c3-UweTG@Xm~d$>u2*+w|_syFApI5=od0XYNsGQ(GI%vCk6NYyP*Ty+KyKSS;I z@y=@zt{a*-@wTIS)jO8-^gW}E$=h8Kd*#bprVS37 z#fp3z;rzvo$%6WMRb68Y#20LDkhu-G&0Vv3vPjK5MOvo5Mh)P5*{86ny7yqW zJKcil_XK|2)|hy3#|8uK@7mv5eL~UqoOtYbk@&SM!#!7BDPA6=y{pLb5?Vq+9dm7b;on-6RN7K6e zh0H#lW9O>qG>{jWn0V6N*WVVkUb?dz)iqAC)!#}#;}p+O-@PYz=}tS;ckc;d#?ihN zv%GYtYN}&)m~q0SI&0BmP*cC~5rKOhV|*Q( z9s7kAM07sz%h2@8Kx7S$)h$>BZ1&X52}57?fdnv9Rev3Zez}OO!6Kpp-J*Kv*k>0( zdT<8dIK~TYc4Rq-tib?^ydybg7zTj{djd+`<_%%!my1XnjA#lW8H%=H5|E1e)_2i1 z7&SmR&4Vfy!e44IY$9#2DAUvu&JqzyA4pvF#;WZ|D1=BGY?0j%LK#f%sJsmr`ga?x zqrd8jw7~)gqZB(JeRPtwZvZyy>5NeH%S2=i*3Dg$fjx`~WZuvSw)IxMW*n1exu$3k zS%az7Ljdrc^|=nKuB=sE>1*8cND-bA<+zeGDokVz_Jo5FK9u_T@8>+8&YWN~{U6v& z*RQgvuA&xWQRD%Iqx6FZOI_!qVno(pu^gnzEBTH)S??)D&p~xSL?5*2jxN=e4Sr%c ziP)nk8Cx@SX6YGMhUgkB94O>BSSBsn6ic)&H;I-OgZYADW_y|b$5h+ng~yne#^^nW z{zEWXm!HTQj4}zNpow6TIQ>~8_Gp3WHWRzrPZ;BwWx(vAOh+i48etP%gAoH-a4VL? zTi;UTJHR(JbAQ3pOr(B6w}?FCSQE_3d1WTL28;h@N+;g49Q6OJ^?VJ#5%ek*ovW#? z&=w{Qi|87Rrcm`&X%cZQm`)YVIcPp_xp>P{iau?HP)Cr2L1Yb9>`7WhAl&6Yu^l@Q$_k+wR7i;SnGi z|0~fOFZN4>gqO~QI$SqMB5Uw)g-JRU&hS&tqDKR7$dm;i_>S2y@6V#F5)of9D@kMx z1~BT~Cg@>9nXY3BIbU^U11jgEdRMVA?eH+#`KmYjy2V$DNEqzwiT^jpBEG&AY;Tqdi!CY$O!)n-!9snTey zH&3Q68nIV`m+rhy`#Q&598Hp~8-Vw$YQYPsRg$WkKI#`qw7#wB6{lM4{hFE7>!mwx zv{u%MVY0w%o1sO7eL?OqYA=-fg(HT+YNl(tRs9x*I}3^_(xWnNaM_T=>CbAaquGjF z1oCZTm=0tW)`+s)IC_)RwdCD|%0s^-@B#7OF_&ioP_;dZ1iPzhNbC{@!$-Hyf8O@b zUs+@?MBg5+nHPe2SI{sv-3*+_KZ66oY8_hm?0gm73gK+G6Ex!n10e4J1#f66R%?JU z*Vw8C#tjCH=vv^D1vh&d*$u^aLrEnVHyGGfxq8PcW341Rj51S%oTE9bjd6q7-jSd; zqxv1))Shz%z3pu4W;|Ar|1DE`B{6RBHcZ0%F_5*jv7hQFThn@(*crf7gOp`!x^V(z zsJ~;bm+s7Xna*XZ?IUW*IPFSt65qGfD*7smm9{Q@tyY^V z?OWHnAd5w9U8;s!gqB5Qktzs+2tq;xvL};FmYHNSnf2a#`bX9wnVU&w;>=C*c^;l8 z!#($Re|Nrf_TM>&V1$zL>goBAn42Zwat6qtKn#FN3LIj9Pa&~~4XY=A6Kp?+8xm{> z0!_-s;>8s7X#g_-fFTejMTfPgNAhf>isdUUE?Q!8vMo-LSF^+{P&~!)ningUXF7uH z`miu!1kQM)Jj~&=ttY^80GJ-HF|C;y7qci$2Q}OGH5=^$PBl94RP8!{t8;KU855?I1*r@)-( zFxL9Q!W8Y|^a!8c%`H*9{8bZP`l?w}vE-3)rfC@dC%aRuaJr;sH?1nw6+&d-K|~i0CkO&~^^iSn=Z>Rp1yR zT%M|}`SFyfhN-a{XG#RGA6~H7j@8>V06-{NYcNhJKmm~g z{eJ$Uc!85P6>TFFH!esCx4e5px}IYO|4L?uh#R++BRxWeSFTD$q}sP1$TwyuetDqQ zda}`>1F(~q#Gh2Ho@O4j1H*`-1y0#ebSEWvEnTlN?_Zh~t>Ks<*|}|36+{ZWbzK_z zsjg*_DDd3zM&VB<8aV=#N(^qQzbCh7NRA8xL;FTTfap!7k(A(BhCukswd02@?#GpF z_;*b^)-FgIw73Ak5a6NN@!b8F#)}k~z({x(1qZkxIWr6>R$w(8zW@+@S6-}bQnY%= zcHSyBV{v)}vLaP}`SZZ+cy@8R&P{=-NlA(K_+>1BC{tic387f2Vu|bLm&WM_zM+5I z62&2d4L4*(`0Ww_@az>Sd^ktk1oVkpt`@gV2wd-G*_3EP z2q*=96fmQHph$rUp%MVhjMw;OXG%;MGW06!t+WQjF91l=a!A*!Y=j^s{*|JnfEgYl ziWFEPj77x~*H8vDdczRlvAK!Ze5M(vn*ui!Jug|S1^}KDqXK4hh$vEEl_(q*CCbnM z*t#w=0>7J=g#X!5iMP+1;g%^b&zCS$u05PP@fL}SMf zGV%Hu6K>gF0gEdjx=0!aR~ZOH{-8*K3k#RB*VG-4`}jh0z-(4p+-SCoShpw@dX;}) zOMSZ=3?U~ktjTr;$h{$>NP#m;6FymV>L|0}SREXK|3zwr#fAI!*J9)1aY)zuH{byP zpV!&k62(@(4Fv$j3H-0;9-Tkov-6kFZn%Jqx^g^osM;@kR=0%ZyQ}fg?0C#e4)@!? z3oTC6S>1%H_)UHr3jm4}cxrX|9<+DNwLfz7J#0DCh<~0R`UK7&jv5ihG5E=p=z#dQ z`LqdB<28S;zcafrAO`(JQ3CIM?pIGloHA;=KBu-f+3E44OtQD&ziNxH)|HC$MhoU= z$AkBAPWZ*InowzRVCIb&3?L_tnyC({y*fbY=3F{#gqgipjx;pZdl7>T|9A zPQy)Vsl|J{o`&ntVO;gMlh}5?3BGh^_f)juvgB|KAx6LhL85r@paJue!(ad9?8LYH zvldtsA#h>g^P23F7naJUQHw4VXg|i&vp&McuS&3Z%NcCF&;r4W(ydMr6&4rfB>CU4 z`t4yOPBl7;{&1r00l$p}7)1!2ajtYPTW6QeXG|1_@Y@q`d3-H)E_@&NP27k3_SfQP z`)m3Y+3e_OsHN!qpPPSVP6DnNr_D^@U515)7w+)eU;qhufp@<2hxiF6O6L!hPIG$# zh_0haK`F4Sembtp(Bb7p^F}Y>aaF=MHn%6V$zqWWWBt8r4uTJyY;7SbVpyL@7HTyQn@I}21 zN9$~|X|B)IW7~}x#Ap>BxcvR|>wGdi%!IVS|5&#!HsfT)EZIZ^2)`q-JACl9vRSBd zVc7k{EQTlK?#tdjx6%jG!%9dC zoMtZm7io6Mrco`70y`lL08rVk$FpZ=l>BOHAB=}#pwgyFpPw(z=efN*^J`KIkJg30?jQwgA{L+&&2IH(LTpD zFG|y4PI5RN{+?+kCS(Pkc)m)u-wOr^yQwGlZmFJvA54tGV1w|IwLG@olmUTK{P3TZsA%n5 zB~SoQd~U?$saj}x-@h`306(7@r%8!m*AB|501}cb8$NpJ(Pgu@9(h}?^`ly#hjvX$ z_vb73--&m>KMhIRq4jIqToPV5(TLa2m~iW)C}c&d5Y_o(dAFovz~r6l(+53Q-0qez z?agxziFG7bET7pjBBT~iUvAK!3 zZAvswG}!TVlLH59+7Y2*uxxTPZl0*`w?91)TAsmz6fNCXE#(3DY)B6JLC6VQ_)38; z@#D`^SxslqMUkwdcAAB)p< znzB~cTtg594!JsboVDezXpwKxYHs$N-m9@iAXYngfh7v8+FOI~rfLUH><5!$)QI58 zykUKa7eY?p@#Qs3QxSEu{GbVZKw5h_>&DQj3C z;)ReBc<0NHU#C51mJNzhUER0n3}XdUx7mGuPP<#eioG@XYmpf`oJX%||Z(t%-nfD9o~ zL>Cq=Wn(WiB*>*vFZM00l~RJ!E%81-n*)Tirs3TKZFgl`yFf;-ioX>p;+=62NIoqK$^o*_p_2%Oq%`l;M?HZu%N z^uV==RZ!ubiX2}aDFIkjw=#FB2x)8KH?70A_0ig+4b5&5uYi!CG^0a8;MnS>RJrT? zOy5SD-mRPhi+jMeMG__S&bpPk8v%d;`RCV7js#yO@(0W3bv9{FWg8p<{Zqh=`$*6Q z-m&hnIeb;StRMW$fs1F6GI*nO>OjdPO6Z;SEAsx@^&f^6xm674iA`TOxBKL z+`g;2%`M>W#(Q%Ew}rqU3A(_s7V{HwGe;zX1vg>ftr~V2GEvbkyWvhBaeFS{_nS^N ztE=1OgZq4?*&*WE56VndxA>R3RXIUjWf*jU6PnDj(GL7foljo=xpcbRWlAaT>Q7t% zFs#VgE>WDm??A0Z^s}ZcZ*}3SEv2RgyRf6KKIeY_tocvS1&*vT%Z~K>QvaT8Nx-pY z*-)K7*00L_i^u;IP$u=&q8R`>c6FiB{m1I<+DAUC^LVW8I$$D3i3QXlY7P@kv2M_hx1F ztEPTJL07+3Y}Pz@&|r7^n3XAjPpYl-yYF2vt=d~d-6DOsZbk0OP}xs9r~=1Y+kYW< zotqTiy;{K=W>*+?8NANKRS?4c1o=b+r6gtJneQhj6(1uMg_Pi=aaHdA-ihm0V7`u!2uSV_96rr{D((x#%NWK;3u6eJG-2+%4@!h`+YWLTN^ zRN{uBV^tQ{%XfcV6RG2w_S`s)Jv)kLRSd&dw7FbGO%Bdz6ZC|@4ocsl3|SvaJIwYb zs6lrP%Wo36a;w)Ys(&WaBQ&Q!7VWzgC6;~`a8vSA%1{TPcpN~U=Oy}h{ffLNWepcl zCONTaF=faSLNOgcJOC#s41}Oa0y#j#-3N_7ogUEcRiFu44lXQwUc+zR$$G2lm{=a{ zczJQZG$r;gE>H$Se?_RLu$?ps9QVS24i*rp4)_Qi{}fBm1dgmV-a%d78#4S%?SSRW zdzDkXUCIIgj-e1FPa_m~s_CxDL7AHk20?3NMLO&^%U$R47 zapZc>n+Vx#T9a)Vn#al{Xaeh-+q`aV!$fjm;^fx={iEiRp7ey``>7Z3;N;_&5YaI) z)acaUfHAH2EfOox^?o_6K!PT)%4GMdAe${8;euE0Y%Uto&fb6dj? zswZ7MFTAJb-rNiR_^d>NB5+~h^BQK;POnWUZlZkgyxWk8d9k(lb>0!oh-&CJO`pLJ zO8z!@PnAzl1lBjSd=D~1f$T*29DH3$IhJJ<0|0~)JE%+Y7}7Mm!f?Xm5dq|IFzW68 z_^ecd(uxifY>T{JBp7gdFh*quwM($QZbVx_CDmo}LbWY2VqVqs<yQPuH;kZd zp0jY=ML!6GO@}&rFVQK!G_IQ7e@ko>5EOxVm)q+w6`SHY{w%c!u)S6f_ZR(#HL(}9 zxR0AIn)oMF(PPQ}5)^?sr`zi+0+Z0=lIvhAY;QJzTKk_nXQJ6Y;j~|wJy$d^AfNc* zs^STXz$%x}|8qG4CWeP-aKX0O2%)I&OdQp-AeIin+04Ghd5Oj2L^;7F;%FdzRw_a1 zCNU17-#K7L&%$2V1j~cveZ|#tD7iLot2HyGdp|)Yv2P4?skPSvHG)&Y3|S2Yf+DcD zP&COTz_GOn7Y~A1r^VSmUv%l*`47K%i>|J%yV7OiJ-bH^k%PhBtNkY7P~f~)CP68p ziBG#jb`;0o%%8G#{=+Zc0stSZFUTTWKJ6ajWaB+^aJe-S673lQr3^h05Qh~^aOQ(0 zX~0k+nixipUh|#XyTk>6Xt!-y@81TA@hs^p+C8^-U@1=e>#EWT&TP^E(ZcSDdg0jA zytzy6ebVFX1GU5H5hPDEo&tLm&wwi`hMcYS%TeVMoFbZfFQVJ0#@a7T&dFMGZ$bUV z*R)QT#|Rh+^DGEzZ9M{0LQ0DAbL6+G9R&nMVCv3S95#8KR`1 zrGfBO=>(;bCGfpYhUEy(UX!{2Ix4(upGaRQqoblp)JQe%&(5xg6GSFjfu%fVeJPTPP{&d=W>I>+len$Hvvt9o*A ztg3V4c1p%_a3_ir7ywL((cM#lVr9?Xp)snCzd6QBOwcGqkpegB_4~U17Akw?;IIyX zMFs|vxsISIwo)@iWK;Vs|(76RILO6Vzm?KJngKj z4wlGR4(>vc0s}zvxR{O^BY{FRbQIIE>W;P*k;Vk>La_oH<5#v z9Ndj!1#VDjAL;53ch$o&st%zk9TT_<#R{B%Z-KQfE7}17LS=`*-9_|R4(>*=0t0|C zDefzvr-+W`9U!2P0QrrkM-hq^xIv%%AUFbQ6(KbV;24TX-UR~4?_@fPP_)2He*T-o z_K8t001&G>is|?;8wefC!Cfd?U;wC1OF9hn6w?{nR?x9JxEsX_Tot3aho~3`=Q_Gb z*?Kbw=vWcmiQ)zR(aLAew@r?*3q>|imxOGc85C6Z5W~@g;spkP%G9K7)FnYEv14L{ z8K6Y=H1W~Ih!D8WzTkEmtAg;g4cQT95CYkQ3`Y|qLg4&@d{)O#&wB%wsvYlMy5EjraX=fBTeJyKvfytcX@UHXR>pNn->F z3;?C$$Gzh|Wka^kOyUq87i@n*fe|HeLq^rg_GU=g5hkb!O&s`+k@d+&Ra&JH1vq?14s77>Uj0;7y5n>Y-NjskidWrhPpS!xIz zhB-JohXW4L<1!;CG72hdRFGxDmO#=WYZJ0{I=!#ewZC`gkLv2B>QxKXU0ts_e9qw< z>fTrH{VL!7y?5We_xJn33}x+a5B}nmhm|AWcH|qGZv%3{jhmFGEfBr3q`Kw%`L-qB z%t(QYio#du5>1m_<$qD>`L-vY%t(O+>N;IWfO4ZEd{w^f$tN>TVE^_&18};oNn%PN zgu3Hc_Tm{Qu!(j(fF{8(6u@GmD*X5PwJLwiD1mR8JZO5V)&lrLI zI|8eK#^h&*5X4P$>>=|EGx+6cH!RQ07=cYg`})|=x+XEqaW0Hd;`SRiDbKVTmS<*! z!2TVo2-KBh+cXJ=nRe*x;-X{Ai)Vzuh8g@jpm=PXE+vMUt}ux29!p+4BLo&od^NRA z*Cg?jTl6rqihZH4<=!fLQ1Al#w+7b$C#SY+f(#xE2yXRnR^A+&W(x{lU?tUcR_(f! z#53p%)EUmu^*J-jMijKbw`}eyRfsFB`ZP^oDuuGVOM?)sOAO9tHU%xP)9Jn%@LBcg zQUGA63~sEbGUK5ea%G!cC}@F&;wzc@Mg-1$o2EAuTX*d{s%**)vk?U=@VYGnA4jk% zQ@^eyep>2eN0Ly?;F+CA%iE5$K9m~^?Lol`ym4>W&%1`A8T++_1E)?`j_}I9F5J50 zl=`wy`|1u3xqo!+wJs=afb+u25M&{||&4tubL18;Bbr=>5%+9kDUQdF41 z@Hca5Z$2qVfeq=p_Jo=e=j!@0p4r*PKsc6mOekvL(B$Zn;V?bH7;hcw<%|WDRCtFs zT?%!R|DE0$`7AC-frSwN2(WBc2`AJP^VE(u1|x9*2Es9(Y-;6+?MH|kCeQ3_V{gYG zkhop{c~1v(%RHP|H*RLL%+%fAn(p5*9f*7z@L};ch0ZonFO6+`{Xh=~dxKoKw3cUg zx1&tO1t--ah2YJDz3lHE;{2tx9Owzs(i`LptLt&=sejv|KT2fbzcrNgPUFwXIR)pz ziu9SQ)8UI(wf-%WGpAm=Wrwh$LUmAY_z zdm4Viv{Fz4`!^3%DfmKayAW{p;wl8Npu$UqZ*=2^6oM7AO9@8fEU7MHPHFl=QZ#O| zrz60#yV}3=+2;=|%;}GqW(sOMso~PE6$+PCUtcgrFlMl-K7Fh-w>+_~P+vYOu3;(; z_6FJ48Q@S~h$5%XaW!65>T!j0WPV;K9D$V*VtXiUSun}L-XL>IJyaK^>+f-BI5j~y zrf3@s)7%l@{q7-9iusj3E;zAv*t5mY?rQKH&E?OSZVEUnEr+nj2i4|A7oZZ#sQd;N^Y1UGi^t~2n=x7 z3g6SfOICgJ%e}Pp2GiDm-)-&3Fct5026*-Tt~4)Ks6YBwx%cMG=sZ(U0s}}>+-B8Y zU*^S7u(u;H($*h}v2)maFI-kf`(T8}wj5^Lk-m}0Lk)g7J(lN(f?I3=xV{ZJ0{YHV|c1eJN53wzc%3Nx{V{W>f6aQNW(|K{g-i#p95iv8bv`y31#8XcXz| zjWdLT8AXrBxE16D4Bvs??;7IhK!ht!uBU4#%Fd(x42Bc$$5%DZ!#6y*62Q{hVis5V z*xJ(1zOJD+U;JFfj1?CO@D>fQzM*J4@E9Z(3SK+V&FcD6JPwVyWgb3#VjYe1%K>od z={G5dCOLI}dHl=;l`}rVp&$hYFqQZ&5Pj=V51J4xpH(`p-!c47oN4pBf+z1;CS4`ouJv@;C?ZiYX+l3m%ZN|IPdHyTF=ks`_oN9 z3;grRBP{Oi~@hm^!eOoFMJtL zF}6*YGF4y{TzPv_U`19&=a3mEFo06xR!9!A0G0isz7XlLP?{9P(+`zGN!vY(XQaUE zS9)GSi02)eBobGFwwUMl*_`LL?U56KGgI4jO{94+0D{YJe>bo)n``BY87VM;62>jM zCR8{Ue@G8`@@5)FIn~JQe;Y# z2#U`Sr#7|)Gge>#U$Oh_=PsW2RyN-)Dh_`UD7WgNfG`YsXU5)XW=0FV@zZr3 z*?w181ezyFOjTx1z$M^wx4%1dYPQ#&N@ldc+2Y?GXh5+hQ~%h4ADL)w4?ciWdEd4A zk-!!NBmC1u{hCJRXE4y*8Q_(Dou_^E)$a4A+_(?PM*^pC@t@jjpM1CVA9{kB2i^$} zw(c8@#M!jJn>F*wnN{lh=Ny^&fhh=q&;99s_rXy&!I7)xW zT4YV6u2LmX3SMgNq^8KtirJ-ry3oHhbXJZ{o{)kN*jcXM8SW1MU^=cP5MMiMQ5CaF z-8{XsjX-$(oK+-l5{SkUBk2^8w;t)EHyGud4xZlGMktn8lG@V|;L**8+1k=iAQI=7TMlz{AQ92nH5BFT!+o4{ zeD%06Re?C)-xNAKmv&Et?FIHXMV1yh^(Apbxr>T3Ct;Zi6msO%ND9FxPpHM`)OcoR z8~eM5cy(VFA6rzxmm24B`RaPAi(PEo-A;cf#tVBpII*sndFAPq0!-8$xwLyCY%j1< z#tpg@B5s-p#kyS8TDW<)DppF3%O0;u!TC#Tkpfke!aJ41{vBdMP&T!mFoHDPRg_T}(DN%^^Q)Tam!L|b5{%-JNn2CBwi4r-ZuO!12Q#SaQi9p5^ zd zH2Hc`QeC7(C@887Gju(0|75Ro<`ah&ALtH#F=Cj*N!Ao$!yp(0>s8 zjU`2tZwqZ)BB)*b$Q>UuCIIVH;vE@Kuxl3AQWRyM}RqH9$wnl zMSCDZBo^mTPl(0UqjS^q%e`!G?dO$!UF=RIhI#y<25;WW1K7rchnE7c$<(h)X`N6d zL7Az!CtbbFzf1%a+&8xEwFBMM6}!3Uq&m)7Ttzr$&=H7H>UDC?;?Z?qO$t`kmvQ;( zdP-b6hdV>Rnyfujfo%jXE)HKsN(|6gG%@i$DBxdiZR)e!=K5jKiaiejhXD3<1!(CD zbNa$c0FJ9J;=Cm_)D^pM>(;4>Jr0etj;ntD4;Rm}Oqx2~*hXNKny@1Ggtus*FBB== zeI)qcWXeSZ%p))kMIz#GOP#e}w?%)Ae-lGsAvOaIvZ`JqU>aiU`q zvA)(GY?_J}_IA=4h+aAsUc76U`)98m=;<*~oH954bh<7xmaPR2@7nEsYO*#=6}Av~ zAQWBq^o}-yQ6u9q+63VuZyf9)6f^kP;wr~fR4;q{a$!Ub2nW zUoJBLkZC6G0{b@)RIQm`vAo#r;He!)NA^FZqHERyAMOjWuJ>A!zP*e1#RiCB_>jY~M*W%_+CVTCvB<}*7PW>xV2=}L!R^!$a`=26XUU;L1 zVtZ>pk(fzGAj(j5beCf&X7KWRovf}eXZA?RI#Hqw{l-Zfc3or4p=p*oA~QWM1pL;2 zvafa0*PcT1DzHD1we>nkyaruxQY`{_Zf6@Y!(<>F<2PGd*xJ&E&!uyuFUXTktqcyA z$$7E4lM2P2%jq#7yGmU@bu4mmZI zM5VeF#;sHikLc5m-0ffPw#BQ_Sn?{cq2vvzZ61fl1t-oZ+HYABBMhgmtNEUlWEl!9un zlQM5&kzFZh8w|5)e>ab8ImBO@JJE#TQ%h^qX$vc-Bq_?>E8Tm9P>&`*k8VIn?(`Io z+O=|cUKSg^*{ocILf2;MKiVHc2v}NMWZ7Tj(isfJdHZl5&7A=T!!c?~+^m^b!Qv_( z4ow2$xw{%XyQj+LxF+w=;_3>(HM#*o;n2WTrfusU=0)K0VE9S|8ArKYLs4dxct*G9 zq!2Sr4)=x##^NlWRl=ewA09{Q-0;-yq`2oK&%ryIf*XKuj%1Thc(ARjmgYs^w-5K; z85*;5F&sC?m5r4`q&<7b;*gSMbtRm+s4~+bEwbDD^iMdsi*ZiwEC(a&c#-?J)ojT`~~1cEFbY zFm7E^m!X=df~T^shx7rsud$*Z#eEJ<&^;8f)x5J6d3#Ngy7zuk((orK^EhY^M2K3< z_a5jPqTJ^qQw|Mn8n&s*63+v*CGI`XeYXD3xxUt9$=^iBG7$iEw^iTVGB2j7*xNCF zC*#4MAV&wn#0`@tn~v~Ob64sT<9-@^IWJn}Uns(c*L`xXuQL_ob*i#0>fL~#IkjDv ziEXl*5BJg$hyc*i7vwL^ovfTw%I6wpbN)E=MtX?_{ai6|+mJ@H!9z=9YO_Qd^8J zB{9SL?(5bitLBv2zDQL*$iKkA`UcNS2=V;pL%j$gSY4kUO+R4@Uz@JQ+5uY#3~;D> z@SdG*1Dvs-VtBD&Jenp)@^qx46c5{RE!Ga$M&O^FTl31w*(IA7R;HJVg~Z4@X6?Zt zO^^L|PK_&IY$GthX>+SC10q&^y0Cr*3)ExT7Mna4u$936)t&~AdTQWn{BG@0(fN+?^e#o7Ve2@DVj zxW5OqjBQK22aogMfadl-%12CyEd~DmnIfcw8dnckmk=N0!9s~&_>UKsDHlFs6k7@m zuzQ#HN1%2kKPR%?jS(30M|m-*CfHuv#sQPEMj|s z6BxJZQkZeWL>b0MgR7ayB^!^vxZ)Shlgfj2EMlufXcN&s6uY;o$Z44c?;|qv|C@=c U=cD;=AOHXW07*qoM6N<$g1;ehxBvhE diff --git a/assets/static/images/captcha/i6.png b/assets/static/images/captcha/i6.png deleted file mode 100644 index 09f8e3a37e61454b2bd35958ade47d50e30cd378..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5564 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 KMNUMnLSTZWq1uW7 diff --git a/config/config.exs b/config/config.exs index cbe58a61..09410153 100644 --- a/config/config.exs +++ b/config/config.exs @@ -27,6 +27,8 @@ config :philomena, channel_image_file_root: "priv/static/system/images", channel_banner_file_root: "priv/static/system/images", tag_file_root: "priv/static/system/images", + hcaptcha_site_key: "10000000-ffff-ffff-ffff-000000000001", + hcaptcha_secret_key: "0x0000000000000000000000000000000000000000", cdn_host: "", proxy_host: nil, app_dir: File.cwd!() diff --git a/config/releases.exs b/config/releases.exs index 86248c91..537fa99d 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -17,6 +17,8 @@ config :philomena, channel_image_file_root: System.fetch_env!("CHANNEL_IMAGE_FILE_ROOT"), channel_banner_file_root: System.fetch_env!("CHANNEL_BANNER_FILE_ROOT"), anonymous_name_salt: System.fetch_env!("ANONYMOUS_NAME_SALT"), + hcaptcha_secret_key: System.fetch_env!("HCAPTCHA_SECRET_KEY"), + hcaptcha_site_key: System.fetch_env!("HCAPTCHA_SITE_KEY"), elasticsearch_url: System.get_env("ELASTICSEARCH_URL") || "http://localhost:9200", advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"), avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"), diff --git a/lib/philomena/captcha.ex b/lib/philomena/captcha.ex deleted file mode 100644 index 48b8759b..00000000 --- a/lib/philomena/captcha.ex +++ /dev/null @@ -1,154 +0,0 @@ -defmodule Philomena.Captcha do - defstruct [:image_base64, :solution, :solution_id] - - @numbers ~W(1 2 3 4 5 6) - @images ~W(1 2 3 4 5 6) - @base_path File.cwd!() <> "/assets/static/images/captcha" - - @number_files %{ - "1" => @base_path <> "/1.png", - "2" => @base_path <> "/2.png", - "3" => @base_path <> "/3.png", - "4" => @base_path <> "/4.png", - "5" => @base_path <> "/5.png", - "6" => @base_path <> "/6.png" - } - - @image_files %{ - "1" => @base_path <> "/i1.png", - "2" => @base_path <> "/i2.png", - "3" => @base_path <> "/i3.png", - "4" => @base_path <> "/i4.png", - "5" => @base_path <> "/i5.png", - "6" => @base_path <> "/i6.png" - } - - @background_file @base_path <> "/background.png" - - @geometry %{ - 1 => "+0+0", - 2 => "+120+0", - 3 => "+240+0", - 4 => "+0+120", - 5 => "+120+120", - 6 => "+240+120", - 7 => "+0+240", - 8 => "+120+240", - 9 => "+240+240" - } - - @distortion_1 [ - ~W"-implode .1", - ~W"-implode -.1" - ] - - @distortion_2 [ - ~W"-swirl 10", - ~W"-swirl -10", - ~W"-swirl 20", - ~W"-swirl -20" - ] - - @distortion_3 [ - ~W"-wave 5x180", - ~W"-wave 5x126", - ~W"-wave 10x180", - ~W"-wave 10x126" - ] - - def create do - solution = - Enum.zip(@numbers, Enum.shuffle(@images)) - |> Map.new() - - # 3x3 render grid - grid = Enum.shuffle(@numbers ++ [nil, nil, nil]) - - # Base arguments - args = [ - "-page", - "360x360", - @background_file - ] - - # Individual grid files - files = - grid - |> Enum.with_index() - |> Enum.flat_map(fn {num, index} -> - if num do - [ - "(", - @image_files[solution[num]], - ")", - "-geometry", - @geometry[index + 1], - "-composite", - "(", - @number_files[num], - ")", - "-geometry", - @geometry[index + 1], - "-composite" - ] - else - [] - end - end) - - # Distortions for more unpredictability - distortions = - [ - Enum.random(@distortion_1), - Enum.random(@distortion_2), - Enum.random(@distortion_3) - ] - |> Enum.shuffle() - |> List.flatten() - - jpeg = ~W"-quality 8 jpeg:-" - - {image, 0} = System.cmd("convert", args ++ files ++ distortions ++ jpeg) - image = image |> Base.encode64() - - # Store solution in redis to prevent reuse - # Solutions are valid for 10 minutes - solution_id = - :crypto.strong_rand_bytes(12) - |> Base.encode16(case: :lower) - - solution_id = "cp_" <> solution_id - - {:ok, _ok} = Redix.command(:redix, ["SET", solution_id, Jason.encode!(solution)]) - {:ok, _ok} = Redix.command(:redix, ["EXPIRE", solution_id, 600]) - - %Philomena.Captcha{ - image_base64: image, - solution: solution, - solution_id: solution_id - } - end - - def valid_solution?(<<"cp_", _rest::binary>> = solution_id, solution) when is_map(solution) do - # Delete key immediately. This may race, but should - # have minimal impact if the race succeeds. - with {:ok, sol} <- Redix.command(:redix, ["GET", solution_id]), - {:ok, _del} <- Redix.command(:redix, ["DEL", solution_id]), - {:ok, sol} <- Jason.decode(to_string(sol)) do - Map.equal?(solution, sol) - else - _ -> - false - end - end - - def valid_solution?(_solution_id, _solution), - do: false - - def valid_solution?(%{"captcha" => %{"id" => id, "sln" => solution}}) do - valid_solution?(id, solution) - end - - def valid_solution?(_params), - do: false -end diff --git a/lib/philomena/http.ex b/lib/philomena/http.ex index 65c55699..2c160486 100644 --- a/lib/philomena/http.ex +++ b/lib/philomena/http.ex @@ -7,6 +7,10 @@ defmodule Philomena.Http do Tesla.head(client(headers), url, opts: [adapter: adapter_opts(options)]) end + def post(url, body, headers \\ [], options \\ []) do + Tesla.post(client(headers), url, body, opts: [adapter: adapter_opts(options)]) + end + defp adapter_opts(opts) do opts = Keyword.merge(opts, max_body: 100_000_000) diff --git a/lib/philomena_web/controllers/captcha_controller.ex b/lib/philomena_web/controllers/captcha_controller.ex deleted file mode 100644 index 95cdceb2..00000000 --- a/lib/philomena_web/controllers/captcha_controller.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule PhilomenaWeb.CaptchaController do - use PhilomenaWeb, :controller - - alias Philomena.Captcha - - def create(conn, _params) do - captcha = Captcha.create() - - render(conn, "create.html", captcha: captcha, layout: false) - end -end diff --git a/lib/philomena_web/controllers/confirmation_controller.ex b/lib/philomena_web/controllers/confirmation_controller.ex index 3c0576a7..2186bb8e 100644 --- a/lib/philomena_web/controllers/confirmation_controller.ex +++ b/lib/philomena_web/controllers/confirmation_controller.ex @@ -3,7 +3,8 @@ defmodule PhilomenaWeb.ConfirmationController do alias Philomena.Users - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] def new(conn, _params) do render(conn, "new.html") diff --git a/lib/philomena_web/controllers/conversation/report_controller.ex b/lib/philomena_web/controllers/conversation/report_controller.ex index 18c4a466..634bed1a 100644 --- a/lib/philomena_web/controllers/conversation/report_controller.ex +++ b/lib/philomena_web/controllers/conversation/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Conversation.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show plug :load_and_authorize_resource, diff --git a/lib/philomena_web/controllers/gallery/report_controller.ex b/lib/philomena_web/controllers/gallery/report_controller.ex index d530b591..879fd8fd 100644 --- a/lib/philomena_web/controllers/gallery/report_controller.ex +++ b/lib/philomena_web/controllers/gallery/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Gallery.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show plug :load_and_authorize_resource, diff --git a/lib/philomena_web/controllers/image/comment/report_controller.ex b/lib/philomena_web/controllers/image/comment/report_controller.ex index 6eb25d2b..bc79cb24 100644 --- a/lib/philomena_web/controllers/image/comment/report_controller.ex +++ b/lib/philomena_web/controllers/image/comment/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.Comment.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show diff --git a/lib/philomena_web/controllers/image/report_controller.ex b/lib/philomena_web/controllers/image/report_controller.ex index 36d45578..60621bfd 100644 --- a/lib/philomena_web/controllers/image/report_controller.ex +++ b/lib/philomena_web/controllers/image/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show plug :load_and_authorize_resource, diff --git a/lib/philomena_web/controllers/image/source_controller.ex b/lib/philomena_web/controllers/image/source_controller.ex index e8356b34..adfcc7c7 100644 --- a/lib/philomena_web/controllers/image/source_controller.ex +++ b/lib/philomena_web/controllers/image/source_controller.ex @@ -14,6 +14,7 @@ defmodule PhilomenaWeb.Image.SourceController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug plug PhilomenaWeb.UserAttributionPlug plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user] diff --git a/lib/philomena_web/controllers/image/tag_controller.ex b/lib/philomena_web/controllers/image/tag_controller.ex index 33590706..836b3b3a 100644 --- a/lib/philomena_web/controllers/image/tag_controller.ex +++ b/lib/philomena_web/controllers/image/tag_controller.ex @@ -16,6 +16,7 @@ defmodule PhilomenaWeb.Image.TagController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug plug PhilomenaWeb.UserAttributionPlug plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user] diff --git a/lib/philomena_web/controllers/image_controller.ex b/lib/philomena_web/controllers/image_controller.ex index de17a929..6ef3b712 100644 --- a/lib/philomena_web/controllers/image_controller.ex +++ b/lib/philomena_web/controllers/image_controller.ex @@ -27,7 +27,8 @@ defmodule PhilomenaWeb.ImageController do plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create] plug PhilomenaWeb.UserAttributionPlug when action in [:create] - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug when action in [:new, :show, :create] + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.ScraperPlug, [params_name: "image", params_key: "image"] when action in [:create] diff --git a/lib/philomena_web/controllers/password_controller.ex b/lib/philomena_web/controllers/password_controller.ex index feb4de52..4a0a7bb1 100644 --- a/lib/philomena_web/controllers/password_controller.ex +++ b/lib/philomena_web/controllers/password_controller.ex @@ -3,7 +3,8 @@ defmodule PhilomenaWeb.PasswordController do alias Philomena.Users - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug when action in [:new, :create] + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:update] plug :get_user_by_reset_password_token when action in [:edit, :update] diff --git a/lib/philomena_web/controllers/profile/commission/report_controller.ex b/lib/philomena_web/controllers/profile/commission/report_controller.ex index 3b41e621..64175f9d 100644 --- a/lib/philomena_web/controllers/profile/commission/report_controller.ex +++ b/lib/philomena_web/controllers/profile/commission/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.Commission.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show plug :load_resource, diff --git a/lib/philomena_web/controllers/profile/report_controller.ex b/lib/philomena_web/controllers/profile/report_controller.ex index d594930b..0c326b8d 100644 --- a/lib/philomena_web/controllers/profile/report_controller.ex +++ b/lib/philomena_web/controllers/profile/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show plug :load_and_authorize_resource, diff --git a/lib/philomena_web/controllers/registration_controller.ex b/lib/philomena_web/controllers/registration_controller.ex index dd4cf107..05445ca1 100644 --- a/lib/philomena_web/controllers/registration_controller.ex +++ b/lib/philomena_web/controllers/registration_controller.ex @@ -4,7 +4,8 @@ defmodule PhilomenaWeb.RegistrationController do alias Philomena.Users alias Philomena.Users.User - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug when action in [:new, :create] + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:create] plug :assign_email_and_password_changesets when action in [:edit] diff --git a/lib/philomena_web/controllers/report_controller.ex b/lib/philomena_web/controllers/report_controller.ex index 1ebc2412..0252582e 100644 --- a/lib/philomena_web/controllers/report_controller.ex +++ b/lib/philomena_web/controllers/report_controller.ex @@ -29,7 +29,8 @@ defmodule PhilomenaWeb.ReportController do # # plug PhilomenaWeb.FilterBannedUsersPlug # plug PhilomenaWeb.UserAttributionPlug - # plug PhilomenaWeb.CaptchaPlug when action in [:create] + # plug PhilomenaWeb.CaptchaPlug + # plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] # plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true def create(conn, action, reportable, reportable_type, %{"report" => report_params}) do diff --git a/lib/philomena_web/controllers/topic/post/report_controller.ex b/lib/philomena_web/controllers/topic/post/report_controller.ex index eaf8ae65..2d29f7b8 100644 --- a/lib/philomena_web/controllers/topic/post/report_controller.ex +++ b/lib/philomena_web/controllers/topic/post/report_controller.ex @@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Topic.Post.ReportController do plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.UserAttributionPlug - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show diff --git a/lib/philomena_web/controllers/unlock_controller.ex b/lib/philomena_web/controllers/unlock_controller.ex index baf6b768..9ee7b3c5 100644 --- a/lib/philomena_web/controllers/unlock_controller.ex +++ b/lib/philomena_web/controllers/unlock_controller.ex @@ -3,7 +3,8 @@ defmodule PhilomenaWeb.UnlockController do alias Philomena.Users - plug PhilomenaWeb.CaptchaPlug when action in [:create] + plug PhilomenaWeb.CaptchaPlug + plug PhilomenaWeb.CheckCaptchaPlug when action in [:create] def new(conn, _params) do render(conn, "new.html") diff --git a/lib/philomena_web/plugs/captcha_plug.ex b/lib/philomena_web/plugs/captcha_plug.ex index 75f1b85e..0dcb9948 100644 --- a/lib/philomena_web/plugs/captcha_plug.ex +++ b/lib/philomena_web/plugs/captcha_plug.ex @@ -1,53 +1,29 @@ defmodule PhilomenaWeb.CaptchaPlug do - alias Philomena.Captcha - import Plug.Conn - import Phoenix.Controller + alias PhilomenaWeb.ContentSecurityPolicyPlug - def init([]), do: false + @hcaptcha_url ["https://hcaptcha.com", "https://*.hcaptcha.com"] + def init(_opts) do + [] + end + + # Set CSP headers for serving captchas. + # Only holepunch CSP if the user is not signed in. + @spec call(Plug.Conn.t(), any()) :: Plug.Conn.t() def call(conn, _opts) do - case captcha_enabled?() do - true -> maybe_check_captcha(conn, conn.assigns.current_user) - false -> conn - end + user = conn.assigns.current_user + + maybe_assign_csp_headers(conn, user) end - defp maybe_check_captcha(conn, nil) do - case Captcha.valid_solution?(conn.params) do - true -> - conn - - false -> - conn - |> put_flash( - :error, - "There was an error verifying you're not a robot. Please try again." - ) - |> do_failure_response(ajax?(conn)) - |> halt() - end - end - - defp maybe_check_captcha(conn, _user), do: conn - - defp do_failure_response(conn, true) do + defp maybe_assign_csp_headers(conn, nil) do conn - |> put_status(:multiple_choices) - |> text("") + |> ContentSecurityPolicyPlug.permit_source(:script_src, @hcaptcha_url) + |> ContentSecurityPolicyPlug.permit_source(:frame_src, @hcaptcha_url) + |> ContentSecurityPolicyPlug.permit_source(:style_src, @hcaptcha_url) end - defp do_failure_response(conn, _false) do - redirect(conn, external: conn.assigns.referrer) - end - - def ajax?(conn) do - case get_req_header(conn, "x-requested-with") do - [value] -> String.downcase(value) == "xmlhttprequest" - _ -> false - end - end - - def captcha_enabled? do - Application.get_env(:philomena, :captcha) != false + defp maybe_assign_csp_headers(conn, _user) do + conn end end diff --git a/lib/philomena_web/plugs/check_captcha_plug.ex b/lib/philomena_web/plugs/check_captcha_plug.ex new file mode 100644 index 00000000..e73e7a73 --- /dev/null +++ b/lib/philomena_web/plugs/check_captcha_plug.ex @@ -0,0 +1,71 @@ +defmodule PhilomenaWeb.CheckCaptchaPlug do + import Plug.Conn + import Phoenix.Controller + + def init([]), do: false + + def call(conn, _opts) do + case captcha_enabled?() do + true -> maybe_check_captcha(conn, conn.assigns.current_user) + false -> conn + end + end + + defp maybe_check_captcha(conn, nil) do + case valid_solution?(conn.params) do + true -> + conn + + false -> + conn + |> put_flash( + :error, + "There was an error verifying you're not a robot. Please try again." + ) + |> do_failure_response(ajax?(conn)) + |> halt() + end + end + + defp maybe_check_captcha(conn, _user), do: conn + + defp valid_solution?(%{"h-captcha-response" => captcha_token}) do + {:ok, %{body: body, status: 200}} = + Philomena.Http.post( + "https://hcaptcha.com/siteverify", + URI.encode_query(%{"response" => captcha_token, "secret" => hcaptcha_secret_key()}), + [{"Content-Type", "application/x-www-form-urlencoded"}] + ) + + body + |> Jason.decode!() + |> Map.get("success", false) + end + + defp valid_solution?(_params), do: false + + defp do_failure_response(conn, true) do + conn + |> put_status(:multiple_choices) + |> text("") + end + + defp do_failure_response(conn, _false) do + redirect(conn, external: conn.assigns.referrer) + end + + def ajax?(conn) do + case get_req_header(conn, "x-requested-with") do + [value] -> String.downcase(value) == "xmlhttprequest" + _ -> false + end + end + + def captcha_enabled? do + Application.get_env(:philomena, :captcha) != false + end + + def hcaptcha_secret_key do + Application.get_env(:philomena, :hcaptcha_secret_key) + end +end diff --git a/lib/philomena_web/plugs/content_security_policy_plug.ex b/lib/philomena_web/plugs/content_security_policy_plug.ex index 27c81bed..dc053e49 100644 --- a/lib/philomena_web/plugs/content_security_policy_plug.ex +++ b/lib/philomena_web/plugs/content_security_policy_plug.ex @@ -47,7 +47,7 @@ defmodule PhilomenaWeb.ContentSecurityPolicyPlug do def permit_source(conn, key, value) when key in @allowed_sources do conn |> get_config() - |> Keyword.update(key, [], &[value | &1]) + |> Keyword.update(key, value, &(value ++ &1)) |> set_config(conn) end diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 2ae97a63..d3dadef5 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -486,8 +486,6 @@ defmodule PhilomenaWeb.Router do resources "/source_changes", Profile.SourceChangeController, only: [:index] end - resources "/captchas", CaptchaController, only: [:create] - scope "/posts", Post, as: :post do resources "/preview", PreviewController, only: [:create] end diff --git a/lib/philomena_web/templates/captcha/_captcha.html.slime b/lib/philomena_web/templates/captcha/_captcha.html.slime new file mode 100644 index 00000000..87c92fc4 --- /dev/null +++ b/lib/philomena_web/templates/captcha/_captcha.html.slime @@ -0,0 +1,8 @@ += if is_nil(@conn.assigns.current_user) do + - challenge = challenge_name(@name) + + .field.js-captcha-box + br + => checkbox :captcha, challenge, class: "js-captcha", value: 0, autocomplete: "off", data: [sitekey: hcaptcha_site_key()] + = label :captcha, challenge, "I am not a robot!" + br diff --git a/lib/philomena_web/templates/captcha/create.html.slime b/lib/philomena_web/templates/captcha/create.html.slime deleted file mode 100644 index bfce1459..00000000 --- a/lib/philomena_web/templates/captcha/create.html.slime +++ /dev/null @@ -1,20 +0,0 @@ -elixir: - options = [ - "Applejack": 1, - "Fluttershy": 2, - "Pinkie Pie": 3, - "Rainbow Dash": 4, - "Rarity": 5, - "Twilight Sparkle": 6 - ] - -div - = hidden_input :captcha, :id, value: @captcha.solution_id - img src="data:image/jpeg;base64,#{@captcha.image_base64}" - - = for i <- (1..6) do - .field - label> for="captcha_sln[#{i}]" - | Name of pony with cutie mark # - = i - = select :captcha, "sln[#{i}]", options, class: "input", name: "captcha[sln][#{i}]" diff --git a/lib/philomena_web/templates/confirmation/new.html.slime b/lib/philomena_web/templates/confirmation/new.html.slime index be1fe8cd..2f4a5177 100644 --- a/lib/philomena_web/templates/confirmation/new.html.slime +++ b/lib/philomena_web/templates/confirmation/new.html.slime @@ -4,9 +4,7 @@ h1 Resend confirmation instructions .field = email_input f, :email, placeholder: "Email", class: "input", required: true - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "confirmation", conn: @conn div = submit "Resend confirmation instructions", class: "button" diff --git a/lib/philomena_web/templates/image/_source.html.slime b/lib/philomena_web/templates/image/_source.html.slime index 4baccf80..7262d2e1 100644 --- a/lib/philomena_web/templates/image/_source.html.slime +++ b/lib/philomena_web/templates/image/_source.html.slime @@ -12,9 +12,7 @@ button.button.button--separate-left type="reset" data-click-hide="#source-form" data-click-show="#image-source" ' Cancel - = if !@conn.assigns.current_user do - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "source", conn: @conn - else p diff --git a/lib/philomena_web/templates/image/_tags.html.slime b/lib/philomena_web/templates/image/_tags.html.slime index 77d4bd6f..ae3cf38c 100644 --- a/lib/philomena_web/templates/image/_tags.html.slime +++ b/lib/philomena_web/templates/image/_tags.html.slime @@ -33,10 +33,8 @@ a> href="/pages/tags" tagging guidelines ' before editing tags. - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" - + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "tags", conn: @conn + ul.horizontal-list li .actions diff --git a/lib/philomena_web/templates/image/new.html.slime b/lib/philomena_web/templates/image/new.html.slime index 2e9b92ae..2349c093 100644 --- a/lib/philomena_web/templates/image/new.html.slime +++ b/lib/philomena_web/templates/image/new.html.slime @@ -9,15 +9,6 @@ ' Don't post content the artist doesn't want here (or shared in general), strong including commercial content. - = if !@conn.assigns.current_user do - p - strong<> Sorry, but due to spam, anonymous uploaders need to fill this out. - | If you're logged in, you can still post anonymously and won't have to deal with captchas! - - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" - p strong ' Please check it isn't already here with @@ -90,5 +81,7 @@ = label f, :anonymous, "Post anonymously" = checkbox f, :anonymous, class: "checkbox", value: anonymous_by_default?(@conn) + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "image", conn: @conn + .actions = submit "Upload", class: "button", autocomplete: "off", data: [disable_with: "Please wait..."] diff --git a/lib/philomena_web/templates/password/new.html.slime b/lib/philomena_web/templates/password/new.html.slime index 3bdd5553..20b84e85 100644 --- a/lib/philomena_web/templates/password/new.html.slime +++ b/lib/philomena_web/templates/password/new.html.slime @@ -7,8 +7,6 @@ p .field = email_input f, :email, class: "input", placeholder: "Email", required: true - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "password", conn: @conn = submit "Send instructions to reset password", class: "button" diff --git a/lib/philomena_web/templates/registration/new.html.slime b/lib/philomena_web/templates/registration/new.html.slime index b181e086..eb789ae2 100644 --- a/lib/philomena_web/templates/registration/new.html.slime +++ b/lib/philomena_web/templates/registration/new.html.slime @@ -28,9 +28,7 @@ h1 Register = password_input f, :password_confirmation, class: "input", placeholder: "Confirm password", required: true = error_tag f, :password_confirmation - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "registration", conn: @conn br diff --git a/lib/philomena_web/templates/report/new.html.slime b/lib/philomena_web/templates/report/new.html.slime index 75dd7027..57ae7b78 100644 --- a/lib/philomena_web/templates/report/new.html.slime +++ b/lib/philomena_web/templates/report/new.html.slime @@ -60,11 +60,6 @@ p .block__tab.communication-edit__tab.hidden data-tab="preview" ' [Loading preview...] - = if !@conn.assigns.current_user do - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" - p - ' This helps stop bot spam; log in if you don't want to deal with captchas. + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "report", conn: @conn - = submit "Send Report", class: "button" \ No newline at end of file + = submit "Send Report", class: "button" diff --git a/lib/philomena_web/templates/unlock/new.html.slime b/lib/philomena_web/templates/unlock/new.html.slime index e9ffb608..5d64af56 100644 --- a/lib/philomena_web/templates/unlock/new.html.slime +++ b/lib/philomena_web/templates/unlock/new.html.slime @@ -4,9 +4,7 @@ h1 Resend unlock instructions .field = email_input f, :email, placeholder: "Email", class: "input", required: true - .field - = checkbox f, :captcha, class: "js-captcha", value: 0 - = label f, :captcha, "I am not a robot!" + = render PhilomenaWeb.CaptchaView, "_captcha.html", name: "unlock", conn: @conn div = submit "Resend unlock instructions", class: "button" diff --git a/lib/philomena_web/views/captcha_view.ex b/lib/philomena_web/views/captcha_view.ex index 12dc3e7e..6351bb42 100644 --- a/lib/philomena_web/views/captcha_view.ex +++ b/lib/philomena_web/views/captcha_view.ex @@ -1,3 +1,12 @@ defmodule PhilomenaWeb.CaptchaView do use PhilomenaWeb, :view + + # Prevent ID collisions if multiple forms are on the page. + def challenge_name(name) do + "#{name}_challenge" + end + + def hcaptcha_site_key do + Application.get_env(:philomena, :hcaptcha_site_key) + end end