From 8d7b3d6dc27f8f07e96860f06d0e259b56c1f5bd Mon Sep 17 00:00:00 2001 From: uku Date: Wed, 20 Nov 2024 09:30:57 +0100 Subject: [PATCH] feat: add discord login --- bun.lockb | Bin 136864 -> 139464 bytes migrations/0001_init.sql | 12 +++++ package.json | 3 ++ src/app.d.ts | 8 ++- src/hooks.server.ts | 25 +++++++++ src/lib/auth/cookie.ts | 19 +++++++ src/lib/auth/discord.ts | 14 +++++ src/lib/auth/index.ts | 15 ++++++ src/lib/auth/session.ts | 76 +++++++++++++++++++++++++++ src/routes/+page.server.ts | 5 ++ src/routes/+page.svelte | 20 +++++++ src/routes/login/+server.ts | 19 +++++++ src/routes/login/callback/+server.ts | 53 +++++++++++++++++++ src/routes/logout/+server.ts | 16 ++++++ wrangler.toml | 5 ++ 15 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 migrations/0001_init.sql create mode 100644 src/hooks.server.ts create mode 100644 src/lib/auth/cookie.ts create mode 100644 src/lib/auth/discord.ts create mode 100644 src/lib/auth/index.ts create mode 100644 src/lib/auth/session.ts create mode 100644 src/routes/+page.server.ts create mode 100644 src/routes/login/+server.ts create mode 100644 src/routes/login/callback/+server.ts create mode 100644 src/routes/logout/+server.ts diff --git a/bun.lockb b/bun.lockb index 5ccfe2be3f324579e28a81dffeb02b4e97008e79..3cfcf9cc256fd4ab2e525f5a3466d019de7c8a52 100755 GIT binary patch delta 25479 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 Xrq_ls%1pm5%B0A2B6s>PQKn@8U!=lo delta 24073 zcmX?ckYmAGjtP32!N&1(r<$$0lN+({_m0UDzxNp}iM_x2kFjId1U*H@{j~yOObj3p zH8EUXsVIyQ#D-!n28M?33=9m43=9o9sflShsU-{(FG$ocVPat5WngHS#l*k>Qj?dM zm!Fx!z>r#;RGOKS!jQ(wz#z`R&=A1Nz#zuJ&|n3nRiHEW)Wz#z)N(D0OnfkA|U zq2VHw-V3EyLg}ecx)DOxH{?PX4UtgVjfH_hn1P|e7)mQZX3DF9U-lLp?*oY+eQiIR=J? za$W`o6$XZea9#!m1qOx&OQ^UQF9U-z14F|X9tH*_28M>KJPZu-3=9q1p!5tLi2h0_ zKb8k#krNLCgERv}gDDRrgr6}mFbFa*G^7^iWagDHFzgd!VBo4}U}#VjXJ8NjM@v~A zD7M7KAyL5srTN4e7&sXi8d6eI3v@G!8PKvdBPc1Gj$;>HcNG&d`FHJ3~Wbl@UXiP0GC`wIbV9*tVq=94d5FaKc=NEy3 z21YAFeWsuYk>?eIB)XK;q|$T-28P^XkhKg9Oi&N`D?vhNi4r6#3KB~)bhA?{%QGfF zViv1^rVI(NjKpHyw4B8B+sY94u2X?1|EmIVUSby5I}B~A3=C2X3=Ku95Nk_|GIg_x z85jym^D2|`Q&K;OLXzkmQHcCLQAi5eC<-y}oH~St%GWo{hiaHA3bCkF6cV7)nvihF zD%Pz`%*|m)EJ`m(EGkar*M!8Zs}_W|(1I9vSql;#plD9aDQ2h?g~+Eu9db?^l1Y6; zL1~|%K@aS(dPJIpNx(F~A_C?>n8RQZh3+F*#K0_uMSz4Z#A7fI!pz+*!oZ*a$^(l; zATD}r2+4#xhLB8l+K_=kf`Ord*%0EBI}8wm(9?dZ5ya@&4hRtS>s3k= zh(wDOq}aG@1#!r6sCa5|QDOlDs9?_3P0cG~*l7jv$$Be@&*nqb=UYMInA-{xqK0;m z0{pZsguY`3kuOe6Nd@JS+@i^!*xc%2F$0UaXO57xtL6-G7A(eLF?`+;Vw{ZLxO z#i_-~3=Df5A>p(VN@uu2{0&aO3=9lYq2@M0lN;XCI*N>;_j0Vv8&Y^c|i2> zcrY-iF)%cgWr9MBfdLk7uwaQ~fRwgA43I1!?hVlg$^u2H6%2*mkl-oKECOfVlKhgy zoXp~q+@jpf+*AezZ6Am{n>VC%{_F*Ie|o*h(3sO7#NEEL4`{_ zLqk=53MhUUQv4wr!u%ltAY94;Trz`zHp z;uDK?OA?cEQn$rU{=}&!Bo_~Hpa7KK5s+$2@vfE6Clbm5{vW8^B5R} zlOPd5qZUna16q@Y>MfcF8I!fR-I#u7O)lcrTYQ6?n^9u&O)-1Mrpc1x_DqKvCre4% zF=;bRj*_%vN@kioOVW;MBh%ztl6I`0nHU&sCd*1#Gubjvj*_xts$rfyOUjP5mX(3Q zZ}MCzYu3}O3=BSCmN*-Xl?-RChqHdOF)(<6)w!}WFnEAjy>Ql5I7^lTCYuUpt%b9` z!dbSQFm67Ow+pq{SFrl$ssuQ*j0co5^=2t(g=hCf`!CV~UoTETwM8IZ=Xv!Ht2TfpK!8 znmOln2?ho$P<1-_p_n<7g5=~|>UNyymTm!4a{3A!@ytxS9wDn=c%1^$fYsWcJ9un$YlOO7ubDo!HVDJOGcA>aAtE>V8gVp4_n$}EJ z3X^Z?*)gqEm@K7l$N4~kfx&@+p@C!aLUD6WO+`q!vVcuUR%BoZ0Gj~v{bof51|u*p zQOcb2sUib|4mcVWb596^d%?bR6=LcwWiiu&YPrgluX)hFLFwc`}kU|?_r zr*n|DFpbGkW_FyjG$3)pIayKFob$W}B(#~qp3&2U#1JG%x-=m%%K}a_2Q=YskTB=` zp$XFmirGLd2oD?xtP`~u7@R=P;k=^-i9JZF6Vqm3FlS(B;G4YA$bwOOvXrGAQ>*sm zC`&ue$Jz`GUXWl=H|NyVVPNotcwWz(vrLD9AqdP%R5NFKtTQ>v%8p4%ck(PNJEm;i z$+xWRIJf9R+zoaqE0Z3$@DjJ?bkl>l3u1qp9>nKdAipy0)|)J4W5@bc50ngJEv-5A z^&yHmz~0T*hj^D^@zuvnfE149Uyb;t-*3d^cmb8;9%>|_KbZ>A9A$+PV3m?juc zzGZL6dCC};XhEq*zyubQ4>ir1N=zotaO$%$s>oT3&C4EEr-d}wdZnP>sAfqn8qdvn&Q z7O)U{Z2?N^pb)aQgoQ?XnecAVB$5Cd4jhX1mHIFS+TL<{T5QEqlj zRo0Vdx!JLvu?7dap*5$74a9lOpy=WZv4Lc5#>t7|=A3OdkW|eG4uCy2kidbY90prZ z#DdBs8{5fI9(J6qwh*5&f#c_q?c`e?cAOq|ki^OicEcpQ$x)tmtRL+_z5rDd=Ju0s zdD?NN+cPlef$IlQ5KXmbU@(A`;%ercN9`er3tY%@{zdRK%sEXR7#KppHOWFJbIyYf z5S7f66D7?#e?oculM}7XnG78#-}1I&GIp9Q=`rH;6YlCqFc{V04=t6=27t z>OOf^fE{a^I|GCD*aH&NjNn+?mp$+CvlOmjUaM+Mn&p7n%iWd&(vW%OcTaF{$d+=jyoWXehq-sgUq1HiYX;<@~v7m% z51xE0(vI_8Ff84J8XYDfkW9h`$_12T=eThVq*>|v9oV(gf_!zM??*fGrrn>;JVj#D9=fgv2?Nl$a8^6<%0 zv39IS!$GC_U2$tp&IpLH9Fr9z%~@R|Kn1s~gf(Y9hy^KdB+Z$2Mu0QG$B4JZ0AhiS z1vT4_#>0{ssM6(6fEWwW;|}6YmesXp>PnbAE5(lUM*;&w8o2yXG&E<5OPnl~YR5Dy zadK3u9q0W-NGgOhb`+8z2?tW9CM8XlO0#2~mBhf{KUp@-n)PK81A{M^WtI$MHN#mK z;4Ik`1_p1ix{MSC22U_+BbXJNZo>j*fQ)lag-Q0nSy$mKxipw;I-Ip3je)@(Y}|J^ z%Pt)z)(U5xNoQbi1*;RxfU&~htl2P@21_ObLlh`b!J_~O;%x?GGY&a*td5Y@hoQE=DWjcuWDHC4a>zFesWKEXJwPT9NnjDpD$Jv<$32#Wn zx;Ja`tz0`+)@)E^11iKEvnNO8*)dJao;)khs$Q4@JWv957l;jFfM^~DaCaQU1r3uk zFf)LM&B1&nFp0_+XJBBkgo=|u*Mqf?pn=NjA@+k^F2umV;0-pOjx^MMtQNB{FfjNt zfM+cjiWnFe*cccXDxeMo(V$79Dh37y(ELy}R2&}-Qdh&kzyQ)$2UQ;nV_>5}8ek5@ zMuX%Vpaz1*{Xk}dXpnd_$YGPuR*O&mQ!O!Br$z&8KoVcZqR}W=OfNDghK|&K585lr?%Ty2tG?D+2?=V@61=_YE3!KcNo#1?B&R^8Z0C1koVoe-OdIzyPAbOeRQFGlGHx zRHQJ0@=ZMh12a^B6dI(36+|#FFo0-~1#D0YK%@VlJPe{iRkJWuTm)<(0|Ru$0GI}) zKL&BALC7>nND@RaFff2d0;ScMKxGpH0|STw(x492 zfQ<%8X)-~I977O?fq?-;gY+3e`5+p^H-=hZ$^=R67EpO)8l=vWiJ=}`C0aohfM_uW z28IAApY}Ak{G*PApxhGzbvQnnpMilPnF*51@V?a1VPyY zy#a`=-3JOekT^&^$Se>a8x4}j*8bxJNsKlC85kIl8-k!5gxv50g(`>-%ABJOKn8}< z2Hha+;fw;4FZP5p3QpGS4QCXZ+}In=C_MRQZ#bjK zWW&C2M$yTgec_B^lOOhlGm1~P>dismYEL!WpF}&zum>C^Pxv zgm6aL$(|F#8RaG~oEXk1Kl$gxa7Km6fs?`+6(_Hp6watLnQ?MBqw?g)$>EGDlQ&Kd zXH=ccIVGG?ZF1t2a7OjXJEw#*YD^ZK8qTOWIdf__qt@huQ^OgxCreHXXVjToI4zt} zck;<;;f#8d6{m+Y>QAnm9?obm`Qr3&M#IUPGr}2-CO6IqXEdICb4EC$$z;Qs;f$t} zJ7wa>q>G$$w{i zGulq}oD)9TVixb@wR5}~?I-`76F&LHEZ)hnbG;cIC$F3vK6%D$-pRc4ycwM*N6rhM z>@kOT^4@vgjINV8=Z8=JF^6|@?tE`X_sKiwhfiKHmv^%40&hmo$(aknCr8ZVoqTqI zH>3At$%WyQIp*_Du3hNO=sWr3!tlvE=JQV0UF6N^Ke=*I_~eWQyp!)P@@5R2thqRR zvcy8($-Rrc8G|R^TpT|6#6sT5woAMjLnn7G37=fChJ9+L> zZ^p>UFPDZ-zOk5hvhOl)#^}j2mxWL6Si(E`?=o-3*vX#D!zWuT<(<5Cxi@3{DyV9F6bu#Cw@X0@x^G?oP z<;|ErdFQI|$tzayPL^Hm&6qhkb9MOSh?TsP&#v}n%$_W{CVVo-D&EPpYrGkAC!bss zK6%F~-pRUay&3Z-SFR18oUxjB^4+!GjD?dm*M(1(Si?KHcbzw5@#LH9!Y7|t!#mk_ zy*Fd&h9)WQz^FlhCHHK^39#$lTU2toou_yn{n#o&RyY?D|YZse!I(?ar$J- z-QklpcJfZ1yW5*_=H!>V!zbU^$vfG1k2mA&$uswaPwv>oJNfS(Z^pTkJ@#zEf6cMp3rZk()nBz&^OA>PToN4yy~ zPri91eDaAyypwH@dNXdF+<7#7a>ZfZ$#0K(Gj5-3c`SUg#u47hbB}p5?wtJcSoq`{ zM|dau9`|P4J$dHw@W~xVc_;rp?#;M&vge8L$ri_WC$Bx>&A5N^&lBO3UmW9|9DCB6 z@!;f@C&MStILpX&xB8oIK?~p>=|#y(~~97hEL`=%{#gFtT*G?$tTZ-Pu_8wce3s| zZ^rYJE6;^b&N#z6`R+My#*33R&xcQzILkY^_q;db<;geChfh9nmUpu41#iZylRGbj zPp&w}JNfMeZ^rABEiZ;o);P~QdG1AT#+#F0UJRdn<2>(V-%H+%wS1=JoK&5m$L9 zpS|wQ_CO0la^=nN$r;yqC*Qs4&G>P$ z=B@C_5;u4!_ulem{5<*Qt?hID?RIfb_ zpSahYdqzhJomXbsE&IcKKaH|-pRf%yg{|x3vey>A{$%tApnC2#xSo3*4yxzgfa|$8;h=i%P59&$uXrcR zzV!yxbZ^5aN4(~peD_~L-^zq?|3KMe)I;_dLP3lSG?z){Pv?asNVYouJ=BL zGg?l5`6-;yYO>?!a8RxHIh@gE^2g8NpgQkMIHTRM}7-ubeX*ITR5n$`yS5dHaYQoIH;!k9?s}7S@1_VsGj=~&geDy;E!-n zE%!5=(Pwhu&u~y3_cNT)Z?fXAa8M2RE1WT4^2J}_p!)51IAhS{#^2$f+U<8ZW5{H~ zKjEOd?N9jRieJ2w-~IvDZGXcjYy9S&JohiSZu{Fk=|2zC4#vrR|C2=ZnIVggL49)A zLgT{eiIR-7w+rwxS};z&AjUF1hLMqj^&>X}1OIkKamH-0Aa5#kJJ1^u1_oY`xIEKz z0ba%vU>z4&89BrX6&S$#e?Vr#cEA)WOn=A9xQ|y`8M5fvRGEQ60Hk|2#8lXhks3_~ z2EOTv;*9q}=3!bknQ=P@H)97QYlsB{1Mg(P|KZc$@Gx?)?zTiVub?Qkq$D%7XrluI zgTQ3L|JoqqxKiP6pZ<@JaT9Bw3j+h!^pC8J)gWn3&?Fpa&sg>Jjr@%DlUg<40p7F4e-)I89{9v8@^3=9l*P%+T3t`Jnro{_;DJW>uC zD`tgihRv4ofo235!4V6dF#}11`WT>@5|C#=vt}SMjR+9Qz+e(P{a+lT+H{3@MxU_z zj0_A97#SEqK?a)JInBtxaE6hA;VdHq!#PFO#~7%nn0FkE6}V7Sc4z;K0; zf#E760|RJMX$~U;!(2uNh6Ria44}!TS&R$}GZ?3PB{51lhBAOxS%H@BFhnpgFhnze z*R(RkGB7a2Gl17EF(fiDFo1$LnSp@;wEF_ILne)ZfdRCsEMq!j0;95O5F-OaFe3v) zC?f*{C{2VjGBAL=63NKG5XH#A0E!6X=@Szeg(E>>Y{SUF018)oMg|7ZOrtI%1A`tT z1A{&zWUSYRfq}snx|9Mm*#H_MSBB2&s)A-iKr@yM3=A3!3=Eo}xME;n(4KxUfziCa zmI*Q+^@Ndu;TaO!0?Wdf#E$P1H%VK28NG} z3=E$b85lk@GBAMV9YGUicNrPL6KJ;>85ltG6Y&&a^g#>l_`nluGXn1bd$ zK{K|RjF5EtkAZ>V69WUoXV5hE_vwEV7?tatL6ffx3=FOe;N@Tp?hN3i;-JO3pyj&Y z37kMC28JLe28LiJ1_o0m1_m=G1_pB`$aIzz69a=Z69a<`69a=R69WTiW=WfgfkBUn zfkB^%fdMpk#sS(N3))Eo>LD{RFz_)kFz_=mFbIHVjRct(7(kf=G-<~Inmb`)VEE3+ zz_5;ifnhxZ1H)Pd28IO;3=E*f0-yy1pj8H-l?QDM3=HiI3=E*P51{e&TIj4WC{KZ= zr^FZ;7(mlkps6iT*Ibc_fdMq%Ccy-m=mX`c4Gi@R46_*+7&;gj7(ip^pc!=mMg|5U z&{QQO1A`?K1A{mt0|RKnO__;-0W>fKny&oA$iUFTz`y{SUk4Rm5{!_!H_(I}Xxb7~ zG=YjN(C{i~S`<{8fJzV0(DQNz1_sb72hg&NCPPLBhGvjqj0_B*IWud}t`jB(23sZu z20JDO274w31_vev21h0a1}7#424|+}=aLzXv~-vl7<54nVq{ z83qQ1(+ms@pt13-3=9l&L6iH?^&_AX3}mVzbU_5Dj0Kgw(*v^^<+=WYN=;B{$~N6G zmC;#IfB`bS3@Xz=WgVyx1Q(7B;N?~fEYp{!GOE{u@}@d8uY&Td3UuuaC@*hjU|;~{ z6VUn*6KHgUq8$|Spx6Sf!>NR>&jIBLP~HIL5hKvvL(o=MXz2qgnLwpf9JJH|m0;lv z3=E;5j2ObezyPW}{GbURlmXnJ83L3!oItA=7#Kk7hCw?@KwC^0Ky?>rmdly}ykr%$ z2?Mn39kgu&JPT$4U6*CY0A7s?S}P6OPrv{&&kK}Jp{t&JKzpn9sn#0NMx)+L>9-z`y|7(q|5;3v;1`I4DR!xe4TZP_YBbRkjQa44~o%l)FH) zP9XK5VhL14fr1ny56Y3C3;WLB$!!98kpp3Skch2C(lz>Or~J6k6=ngUUZp zkb}w}P(Cz7(itqC>}vBw_$)RS^`B;4>WE;KvfqVjr*G_2q0Hpy?l!2lM z*^%bZ^Z;`l)T8y_0v6e)pr8f?JIDf%;h=^XsG0)>JE&R%1vjW_1Njb=ok1l#$bq25 z3QC-tpk)=PCIBT`Pyz;73MytmWf({tRE~k<;z98bTE_%R7@$H9R7HRcgeeBaJ%|m; zhM=m$0a_@6s+biF3=FWc1Xj%~VPIfb%D}*|3>w^^DhN~+t%m9Y6{etq6=WvJG4Y`I z1X&1jJg$m;9${c$09kYxs^BP;4^nd+v;gG) z|Np0;g%qfO0yz>S2U?*8N@SoFTcAP-w36#40|Ub~=qj;GP`&4&X#|v3KxyY9DF1_2 z!+|yygAx`f(SZ^WDBeMd4^-*GQZ*={g5nXB*g%OCls-VI9G1dC2^Ew;K`9-Sph4*a zl&C>b3rdinL=8&)HVoh`D&R{0|Nr{`|F1%q&|LwoKw)5DxD0hI$S0uS1SLjL7K0_i z8w?B#*P&`bTe@#U)q_$zC@q1qA_zZ(MhVCh&!JfybdJJPXf!+lMWHsdYzJuu`R+9X z1H(%O28I{V<^^aK9>^i^a=!jQ$W5Rq1w}0=nnBSCS`r6}T2P>Yq8*e3K+%nyG;p~A zX59}41_sb68{eSG<|_jO1I(?URtqS}eF0^d4-5LZAJzLP~8bycgGECeKRsJfM(M< z7#SGYK?~kM-2zaPA5^!3G7Km%Kmi6y3ZO-NAWQy&Vgb}fXJlXiwX^<%7AAoVVqjok zVPs%n11_D9rkfT5?3Q)@f)Qk~jWMB}1wn{{W z85tO083d#W6l;>xFXk|c=Ysk)ptha{BLjmns6_%=%f`sS0P4elJO*kDg4_+lpb`U= zG!3BbX;5hbYF~re*&zRc+S#CnD+q(cOhC;kMg|5WMg|5`Mg|76=?ili?dw5Z98eP- z)KmvG*{wj0CPoGZ3q}S8P-`DFyBfgAzyMl)M_dFmFu>v$lomlfg%D^kg3=+V?E!Kg z$P54f|DPU_%_!miA32$U!UUB3KuHsn6hX-plw3hc6qF=E$rO}iK}ibK|L8~uwP_d_ zLRM^Fl*jmhar%V!0&xDXsV3~doq!)HbOb5%FjCet-3WN&S zF)|#2cG^1_r<_5^M6yiRDPfe5fgL;}@#~ji&?&Q2go;L%=?Nu_5@xWC-M5?5+zhAP zI*3pKJIzK*NW+-1`|(1A%wCr1>p*&8=if}Wexsb#JF5es0(L^qiEj<^HA_M#BV>NE zO#cIN8SG4*AiE3NGO~ip5h_$zryG z8KZ<5?0_4#l1nK$=RPwb6u^$a*{sSEYw)5~7$I|&XSyFqFYM5q?*6}Xwkr$1La2Zp ztMemnW}V5LQ~wb%mb}yFlrc)kzz*UuS6^#k^!tJvLPa_6^a~)ABF~d_Q15Fj*}?}nM=IW<;oc)%wXpM@jtp~sPpIaAA}0ni9sDjv(@i*c=94- zxTD+~?AdqP*Q zW8(kF#29CyX9QB|BRajHl~KeBl#gJ?t=vIJQr1tl`6LsgCDbkRMW+{3FiJ3O6rC7=ZkvC^o&Xl2L+5S8V!Ot zSM}%306P?%CijU=*Q;hUVqy`W{;!%*VtQ3Iqm&Hngroy}&sVx{P6357sDLtMU=SCd zzM-1YNCtMsQpL`%=B!qptC<+%jPwlj%orG;2FbuqU9y;_J4NxZ&`Bo7Is-khl~5H- zf|ApHY8WMCVCO4^2VU`w{dDpn*m2-6D3zSvP{Sy}^jvECtQtlmCSjTB=RkZpndvWT z7=2`52PWw+w>$8xHK`UHn}(p!TOvC>pq9~xDL`)eyjn&HCU5!aJ8Bt?n2yO$k86j7 zDq9_+mP)t+=yVW<2H43*iH8)Ay3dgO3XTV3kV)S1(;t9zKS(oM#G+rV|5Xry z4?7I$&5xwJ@xmM?U>|^^<+9fFb&ZS?Oy{(xpJ+rfQ&()dSQDcb#|a(Ckvrg|KE0-i zaU0y)siM>Uni(aSAjwXGNkVt}xoM0N(EK3EY6Lnp3zV=WU~IHl+^&9he4TA zZz`iCqv-U$$&3^u- znIHioq5w)zu(O>eZa;iDQ^~LcQT*!|PXE^d%D#rvB{~_kWMI_*dW9jtm~A*cu9H!L z>9pbWhE7HaCa6vs*!fX;a-a8Ky`f@>F#ECL^a;I;TC89*rzdtY%1)Q+Vl-h&HJTpK z#V8>II}%Ht=}}zZyLSaljB%EF26`q83{YQU*abU<>-oCMiQ$h5zk_oOxN?HJYC20d zqZFvzQ|M-tFvDH$!4ARdPMNZC&#ja9p+$oc14ER_^t^6H2~g?Z(G4p7O{VVw(Hl&r ze*l>YJD1D$^MQ@01WYrS7~{$@V zn@un3Vf110GMRp$hf%@|b~INzyXHLK#haFcquEf;l7RtsSeLcDM_kn;FK@7nk)9C) z1Jt9i;Ggc$%P7SJ3olsQLqnbm9P$hd4KPi5qSNp7GD;ARGq_G}tLbiij6R^2^^`tF z7bX>}>9_hA6`1m^rvK?tLb(9j1o*ct)_2S z&*&oqJN)Y8t8e!|^(?%?#29C+XP{@yz;M}W8c6?RtLg9NF^aJm8osxhUN;BBWKmEB z-(ofW!%{{u7E=ajtv`L^1V&k=)3(zuOkk8SgPjXi`El!w+xC0vpk=g?2?Oj%u>{X# zZl%u9TVU(K$SB3M!DRY^iHvbv zoetp1j|OO9gMtX^>L$nOHj@~Qn5H^TFPOw=Bm+Bg%>U=%t$SFKW3HTioo6zmmfbH$NWl-A$J`mxP`zGY#SZ~SQ76b5R=CSQNP!^1lnNFd8x0xJ(b3!RW)3;W~ZM48~bZ;_lP!W->~6z)s?{i(}ezLS8kIC9&y1_g~h3QeV7=@VHJ*MZ)Vl-my@MK^Roj!j)qbOYd&Md}PMEAzi;Q1ex znU7md|F@n|0%kRfp&=v(!%7GVSiu1;4$z7hJ=_67mUo$+c})*n!$_f{H8Ae@$dLef46`!jfvZTde}ln1vA)k zfd@w!8hvM4PGDjb_lKl+*r9lg+8iDe=kuM1)=Y*B3}*h*CoE@_ka2(>qo+BY@5O;{ z5>j9l;2{ie|LG4FGD?`i4&LJ~ORv9r|DQG!W1N{DxUmX5nr}t^wcQsBc@@BV4M6ou zvHx_vMT`<=z0lMBew^ddn(4Rg8WW>|o-wFSW`Ldl_efxo_v}@NK}~W4JtI&Ve%^n2 z%_2q@m9PHbO^*$@b9HLK^e2lLjbvbF4hCE@RkPH7;{`7HKrIyp*TCrwiy3K^TCCt7;4)OL{MKF)`K| zK_Wmde)^OZj7DZ#;voYUDLvc5GVjcL2aY^&Qi2_+8k+Ft|E~#&-@r29Xx(5k{SU}= z*a3|-Y_YR#dzR)QlHtLG=>{tqC1hY{G&XNYJ+EcyzZ2>Qa|VW+3DZ+nG8&n|PHUV! z^ORI!?hbCS3PVtqf*tUjxzlEa30v+n(8!jdxt^gV!*%BAXF#UIj)Xq=bm9tOarf^C z6~CFMtzwig!#1D@F#>kV^rDq@S?@ShFCnxVvrP8`>BTmn2+<2Wx%vcqz;w44xrY#X zb6KX(S;Z(J13m1z;Sj?+ceR&H4hR)fS*BkAnT~Bh5n?**0PQ5PKb-vXBHs{tFS1OR zTg@n8hHXF*q8D~3_dIEXU6)@bMD>K3B zN@!IGt*sF)w1TemxgmK42weSUy*Dd(lQlS;pyWH!&XE zuDg}7G>dD6C}=4H1H%T<>1*Vf6oucj{l0VYG3Vz%QGo5z0RJl Iq` { + const db = event.platform?.env?.TCL_GUESSR_D1 ?? null; + const token = event.cookies.get("session") ?? null; + + if (db === null || token === null) { + event.locals.session = null; + event.locals.user = null; + return resolve(event); + } + + const { session, user } = await validateSessionToken(token, db); + if (session !== null) { + setSessionTokenCookie(event.cookies, token, session.expiresAt); + } else { + deleteSessionTokenCookie(event.cookies); + } + + event.locals.session = session; + event.locals.user = user; + return resolve(event); +}; diff --git a/src/lib/auth/cookie.ts b/src/lib/auth/cookie.ts new file mode 100644 index 0000000..68acc60 --- /dev/null +++ b/src/lib/auth/cookie.ts @@ -0,0 +1,19 @@ +import type { Cookies } from "@sveltejs/kit"; + +export function setSessionTokenCookie(cookies: Cookies, token: string, expiresAt: Date): void { + cookies.set("session", token, { + httpOnly: true, + sameSite: "lax", + expires: expiresAt, + path: "/", + }); +} + +export function deleteSessionTokenCookie(cookies: Cookies): void { + cookies.set("session", "", { + httpOnly: true, + sameSite: "lax", + maxAge: 0, + path: "/", + }); +} diff --git a/src/lib/auth/discord.ts b/src/lib/auth/discord.ts new file mode 100644 index 0000000..6994872 --- /dev/null +++ b/src/lib/auth/discord.ts @@ -0,0 +1,14 @@ +import { Discord } from "arctic"; +import { DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET } from "$env/static/private"; + +export interface DiscordUser { + id: string; + username: string; + avatar: string; +} + +export const discord = new Discord( + DISCORD_CLIENT_ID, + DISCORD_CLIENT_SECRET, + "http://localhost:8788/login/callback", +); diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts new file mode 100644 index 0000000..2f5b7a6 --- /dev/null +++ b/src/lib/auth/index.ts @@ -0,0 +1,15 @@ +export type SessionValidationResult = + | { session: Session; user: User } + | { session: null; user: null }; + +export interface Session { + id: string; + userId: string; + expiresAt: Date; +} + +export interface User { + id: string; + name: string; + avatarHash: string; +} diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts new file mode 100644 index 0000000..11256e0 --- /dev/null +++ b/src/lib/auth/session.ts @@ -0,0 +1,76 @@ +import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; +import type { Session, SessionValidationResult, User } from "."; + +interface ValidateResponse { + id: string; + user_id: string; + expires_at: number; + name: string; + avatar_hash: string; +} + +export function generateSessionToken(): string { + const bytes = new Uint8Array(40); + crypto.getRandomValues(bytes); + return encodeBase32LowerCaseNoPadding(bytes); +} + +export async function createSession( + token: string, + userId: string, + db: D1Database, +): Promise { + const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); + const session: Session = { + id: sessionId, + userId, + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 7 days + }; + + await db + .prepare("INSERT INTO session (id, user_id, expires_at) VALUES (?, ?, ?)") + .bind(session.id, session.userId, Math.floor(session.expiresAt.getTime() / 1000)) + .run(); + + return session; +} + +export async function validateSessionToken( + token: string, + db: D1Database, +): Promise { + const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); + + const row = await db + .prepare( + "SELECT session.id, session.user_id, session.expires_at, user.name, user.avatar_hash FROM session INNER JOIN user ON user.id = session.user_id WHERE session.id = ?", + ) + .bind(sessionId) + .first(); + + if (row === null) return { session: null, user: null }; + + const session: Session = { + id: row.id, + userId: row.user_id, + expiresAt: new Date(row.expires_at * 1000), + }; + + const user: User = { + id: row.user_id, + name: row.name, + avatarHash: row.avatar_hash, + }; + + if (Date.now() >= session.expiresAt.getTime()) { + await invalidateSession(sessionId, db); + return { session: null, user: null }; + } else { + return { session, user }; + } +} + +export async function invalidateSession(sessionId: string, db: D1Database): Promise { + await db.prepare("DELETE FROM session WHERE id = ?").bind(sessionId).run(); +} diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..353be24 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,5 @@ +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ locals }) => { + return { user: locals.user }; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 271c903..267ec96 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,29 @@

TCL-Guessr

+ + + +