From 0901e242eb3b5eff4f4daf007930374001ccb6f8 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Thu, 31 Oct 2024 02:00:15 -0700 Subject: [PATCH] add authentik login --- .env.example | 2 +- bun.lockb | Bin 163310 -> 164427 bytes package.json | 1 + .../components/app/auth-form/auth-form.svelte | 22 ++--- src/lib/server/auth.ts | 14 +++- src/lib/server/oauth.ts | 9 ++ src/routes/auth/authentik/+server.ts | 30 +++++++ src/routes/auth/authentik/callback/+server.ts | 78 ++++++++++++++++++ 8 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 src/lib/server/oauth.ts create mode 100644 src/routes/auth/authentik/+server.ts create mode 100644 src/routes/auth/authentik/callback/+server.ts diff --git a/.env.example b/.env.example index 55924c1..59c7535 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ DATABASE_URL=local.db AUTH_DOMAIN=auth.lab.cazzzer.com AUTH_CLIENT_ID= AUTH_CLIENT_SECRET= -AUTH_REDIRECT_URI= +AUTH_REDIRECT_URI=http://localhost:5173/auth/authentik/callback diff --git a/bun.lockb b/bun.lockb index 15cd4e33edfb15cd92c04fd1f88f1b8b982074b1..9876da1da224563cea767de562e8312e050df91a 100755 GIT binary patch delta 25998 zcmeHwcUTq2yZ_G0QI?3Jf^-!th#*)fq6h5dU@sIEMT(*b3Rtim)Rkdcvyg^o?Hwc9tiPXE>0pEHHft(e70)kA-%pn%I@T|Ysx9Y! z_~pcl%o?AOkd=}kNsDVp(i@VLGlNNz!)r7Pauvuc9W}ZLv@+!JDOpKWR&r+2_@uO~ z%rWViSqYh$QstVGWRG+i)DAQ)J}o^ZQIe7}hmA`~O_YwfNRl)3TR>rA%~_){IdgS7 zUZ?S(PB82aifiVC>a>AQtLgLwN~81=ou1O^4xMfSg>N~_begNvaVoV+ISF8@!=SfL z!*%MXQ+J(aq^G8i8zV`}y(GyK_Bo&)pvjY%kiZ`F~c`k+TZ@h|5K zoJch>|k}h--l+w58@(NIQ$O%l6s)7FGtzF<_P^v&u zW@<`WmLxT8qIE?BogM+DE>2AvpJtVC?A8;$)am!23JemHlE(O@WJ=w9B&jN74_*JG zzb4mhsuk=AN-^Sq^va;ueI=;@XiBEv@YM8S(t81#-9_jr#!`l-rDq^|PWd7PNRgb9 zmgtAJJs7ANZ07|w&erYCG-(gAQGI(i*NRC_jnB$TN|Z(=Wsj%ozkr@f`4)Cm&7{n6 zSt+TR2)VK2k}|TTCoQymNtt6Zl5p7%VMh^>*-~>;K&EQP1!?k3$cQK_{2w-MI4&?c zlP)7k6Or%+3@408Nf-e+B{OLv4T5>W+J%RPXt6&gK5K*@YL_q~K6Mn*>xXLXGa^3I z50xM83Yp5e0ZL;ftd&;5_>rnV(r(CZQeT7$7>ez287Y1vQHe3*(y|lM6O)or5enHp zpp@PMlp>`OC>2<_jYgexyEi~-Fh1j<6>D27n>Bx%hHJDgDCOD)O5IL<7LWG3-B?RM z3rbhn5}}RA7=#1G*EZC!X=uNT5kxg_37Ia`12umGG_spEd10bU z%EY7@3YjA5Ojpe=JAQO3t}FRKPZe8Fzb0 zQbo$xzBiAxtz~_yk5=B8l#KW+H1+67bQR3jeYJA4MkJ-trMv5LG$@72IOL-#M;+d< zuiamr&2y@PlH&!SR4*HyW)9GMEF~>vOnheMvwo^<%?XOqGWdW}!F8iG!-tT`p`DOb z3b``mzA;)4bOEIc{44uAv06hJ_Qudlg#(Jj$15|eHHK+z@Fgf+!k8wEX+eUXI@#!K zqmysMYxxR6DSaQ>p(f}yP^#KHm>QfxAL(>JvNmY9gOW?%fWpp-3zoYy{vWN37!Ktx zjsJROyavyx#biV*_4*{pME@g)tjO?xv(}gIP+WCX;IC_aLTctjbfYvZB`ec!T#9sg zoYt4on4rnyEG(@E3+XiUG?6r(ppA!#pj4h4D7A#c1m34|L+cm}UUg*xrO=20rD7s< zx)sBYLI;ydd}@4R(x;GVG%Nw7;rSXikqtC>j8>t z*ySM9)OzjZ%)XJeRgN~u96=i*8zyVD_y#jHJ&o#&q=}LhGDT`;N(TIvq^$I;_|%ll ztkD^xQ%0k&LuYE~j-Kxr3#g zvzw8c0Ir*A_Zhe@D)+FIYlC|?O*gBQJ6y^+;f_yB9bC$-E#+>4>!j5M>y^f3mvTRq zawe?L9aOunrQEzyuAr22LI|jK@`&2pt(n=h)k~5Fa?fUACdayx6r*yZ!Sz?Uo#3=o z&w7&7SJjOHH(2EkgG*7lp!$*&uW|yMmihqPa8)<30TvpS+YWAs%GE|ZYTPt%T8^{e zv>LQ%WTbL%n(hX;D9S6h_vS@GX43^M-UE1XP?+hl549mUIkz!i8f;cVn`phO#*#d) z33m%IE9ar?g7A>}C68dap&wrg$fUPOu`k)I2ir)wO~ZGbH@Wu~VffV0%Ea zQOEoNNUc-{Yau(emo4d);gG1c(L%L?<<&vFsI6JK3ni)zrCzWkp?hg62@h6! zLZbGmq&DfdkjN5gXsxTI619pOnxvDOM$%|V?I2Yk&y~+1QHxdJm?D%bV72R^%avxK zTK85`D?T1lh}!0xz|ob}swz(*QF$`8lc^(oXw3(EhRM@g@ueLRkWdU#lhFyLR$-FV zOP%rF0f#1}LPCO-A0bgS5Vq!E_J|jD3RhYqMA||pkxrQksXrt}mtn_wUgvPxwGA)o zY*zZV(dsF2FVA3Ev{6ULAtJI% zhot&D860`Y_@(I~wh;7DfuJILv~^AuHH?BIcu_aAoF2j5EN0~}(z+oH4TEYqx8+Nr z914&6K&iS-kro+la!28D>KLB^uAj=C2G>dDJlaz|Xh4ntN5clu-6&Z8s6AiW9qkLH z*10s!>SH)+wY!9C=62w*Jx~!{sWz@ssiW5LXc!E|j*zI#N^1YlhXi9>feJeZi3+Qv zMoLXwg*prYj}Gew3C#sdM2aaFQaB&nFpL%Pyk6moJswFAy3|>9gXQ*}xm$0u@*b4b zyij@tv)_1L?{LKja}^qt!nS*`X#^x~vilKSFV+1jxcS!_q93?c)DZHLuH3D!SxG?a z4nSti5Qx@Ykf?rITt9}S4LTYR9W6$)kfjw8CLOvchVJK(D9qq9M!+3N)B+V%sYRqD zp%HSb(wrf$iR7{U&5A>J{7?iX!U0pq`0l)@zghmOJ9iskRvsgbDxnTTQwVB+mDoK@ znFNlSSZx5^b72xBYGPHAxAdebN@1`@Xi!jLw6%xS zntLLUt>CDyaAAbyR!G{YCJ$dhBA+nbnnP@Q(=txiUJ0($$aI<{z9KwKZrO*&#+v1& zefZK?vvM8LN?nO52o34g*O-DXbqiL;K%yF|bD;cPUz$CYJ7r2th5r4t`r1%@$z%HQ z*g9gR5xQ>s^Fu#yjn zs*br2(RUjX0wXd^?i9tB#+jAeD6KJU)yXh2T9dReehU)y1UeX3J)}!oz}v=X5sJat zJ6IVB2_@?RwH*@qsxBDHOGwn*xB{9xB-R*2BxUIm!V=OKkSOZafug*GL|vh-*-E>C zTA2z>TuP2EAs_A(e}vQumRiGA7{r$jGb=3yNs>R5XmG3m!*mJrLEjKt2sGPZc|-0Z zNP3sU(0_2LU)_V{S%bM-qFMO{O06t?I3|XhYQ#|x3~msnScp?EA3q?3#qroAv$A}s zrb9qB4rV8LVN$r#D_+xKgv4ROiRZD&W_jo^zBJj)w(-K`aQ1?`4i8rX6SOK|o`I`l zAtCb73A6_Nq)S+7G0N-{x!VY{VosDKge0|8Y_KvP5@mo?AIlUZiX264;jkoa4nbOf zteiJ>N(FCNv}CIy#L?RhD8u9Ryq(nw7!58X_S%LwUk=~ zuCrQRVJTN9)zA$u;y*5_iOJwBd~k-U>Z;oR36;jRA8T-P zOSw~}T&)Zvb$BWFMJe|bTxYesh)jcfyOcXp$~k3`+f>ikQtpFN?)Os8XPjY|1}=bm zdWFd=$MM(+X4z>xUkZ{qo)>|v8qeJ(nsJ*EGcjBVn;=QJo1lp*33sB9XvspjH4J9^ zxodW~VwzYw0bssu3yH>#wuZa~sRblVF+GA!-;qQ&knAOonG~*Av$f?L!*)Q3Ed-il z)Z0C|!X)lC*(`UP#A7F$m7|l4*%A|}(qytR6ft<^#L3)kidkMana55so1P#ojC&3b zlbcWBMbKNPaJQ*uwu{G14VP`E@}*PFa^6&4G!>KeRAc?2**#*K78{6^dKk>p_|j=+ zldY8&jN-asN*{2TS>ZXVVA=o)i#6`S2j*xVVphT(#dnaJsZMxd61@RQo5tuSuHke^ zLhNHyV)A7_@VuOGx#J98lw($A&(PKxGz6CDpCIWYH9o`^LV%i!W{dVSOI^nVJ{c0V zF79ox7=J#K$IdV-H=yjMDe=4$^d{yG^=|J?a6!J&0BS!ZcB( z?%=Tisdmwl)R=x{krDwa5R;yI5VZj?;YcO`V^^xc{T4Z^)y4Qx52BO{BSxiJsun+j zfYN0V)9Pgqfa>u&rRpN2)Z=wZ`4BMbL6pi@FRD>lvf;&x!ugF^wg#+ZG7F%CC>4RwFKQ5r03Q*JU2i^cq0u_NGpg!;tpo1vstv1wwIEYf;RsDs!K#HYUR>;)>-BOA~LdTK*T#X*$usk5D}G}fEw+Se(m z{B-?4qf~lR-Hs@+0lG|7fgGyKM2T&s%b-@ZdaZQ@QDWQZa(R@TjnLCyr&N)4x}K;l z-7Vri~~riQ(~iVA{wpJ7!q*2PDvGu6XhSI z+YQ$3h?0JYt{vDON>}TtC z^FXO3`Sv-EY8@6rL3tO^NvFheUH)g33SO++l}D*7Rv?{BR_b;{iT!}#S9B_1HKa=1 zZ?3bI>a|f%{#2)*ku8q$C`|>M^>m_C+BdpPl;nI}ew~tjtF9+X<@`h;Nfp=$1*PrM z4TzHdXI*}sl72t*lypE(CrbLmy8Q1`eO9FmRKufs0Yoc7J`GB$GrFEAvA^iFP}dW! z2K_Z%CQ9Yq&}E|bke`B*=dVE3BrB|_<`qCGg9#@}s0d0SW=G1uP*PRa?d(A*zXK@g ztLxNJr!`55<8?|Q-9*=04d(w7wU*Z8->Cq#jGta}qQo}EiEa@(>H7bSqW}N14FAja z6o0Q*fcm@}JRwglI*rslBT7NpSJ(H`^+bsspzEV_eR-7f$LZ-r)%bs1K@}K=1PZDo z-LO1LNyBlXijL4}if&Jo=Fv1zQjO8|uTxTG$of4e7`32GP@2Cc>+%$`!tpw#3g$px z6?8r*nJm=pUZ>>1d%FI=r(v-A7lwKn>NABGY{}!gz~9dmXaqX`ey&g-P}H{g&mRx; z73e?DsJ>YL`?=!p=Ze3dE3`@Z@8^oYpDX@;u2An!q`#jl{+;IuTC&NGZt4DhuK4@8 z;(zbCqPBWB_}@Q(Y>ru(=opc-x}ZAq|2(Ja%IM@)))^%cBFTR!#Wflcnpj zo=slXtLAfOem48?2RDYSnYV_`&Aw5!)?1sl+toR?RIX^@FTQfIrhXY#vEX`d=M{F- zDre05W7P2{3B4B<)?-`d{1*G;^}H(K179i!x7Da)>-wzgzW2Ka)^3yD;#J_CfOVVS z?USBVkcQxA(#xtZLGhxj^ssrW%hjs~bA6vWbY8I0RW``A1Zih_z zWcB!G^(rMVc;PywqR)^6-=_ZdU1#s4;z{j#Zg3p(nOet(qTv)x$8GktP>xLb7#IE=Pumq+bGtRXXD(B z7vOB+KKW5Bl26CEJ1@k!2M^p5#d`ADIQQb0aPG}Rw??r(d?C(#c?r(_c-!xySbv^} z^8kJq=P2Iw`zRL8SK}PRpW+aKSr^kJRawG zz7^+TyxLDuEPaqj*L>Vqx?7>?2Y9Dx{l`7V^-eQGCfU zd%ohRg>ha2sY?O;J7!^vdEPPj2k8YQ!Mhf~zvJ+)z{2wQQ%KP#;NNizTgEpWhkqyG z-w6v_!DCLqKS~Aaywh|IS+2H$3kw{DbrYQaFAOasmEbgnt(-YzN;8$>kFKyJ*3S;*^W<57J>sd${W* z`1dRPyJTT|`F=>=zrnv>EqME#{VV)~R1E1L_xTO}{SNT(7C{b6CJc-|lI57G-rXL#2u@b4=8yJBI5{3)d9 z68Lx3!p`yySK;3^_*Y_K#XP12{z2LX=>k`-!N2S9@0x{O;#(oP+<<@AE%@{- zbQscQ?s^0M-GqNPEbI#356Sx${JUvkB|Q5k{DV{s={onh1^;ftzgre|lNUk?xdZ=h zTi9(r`!@W8bQ98D9(o7<-GzU5EbKlnfz;(5{JU#m4|(2Q_y_3)q{qDLJ@|JY{@t^% zr~E0T=m+rczJ)#G8}7ruhw$%#1>e!cJb-_Ywn2Kum51=}5&U~-ks0IPJsiMY9>c*$ z7FL0$Jc5Ie4ns0=*T-=12^@TEVU_rPNZwE3;1df|c=i)G2&ov-8{Fq99Q+dwKD97= zUI;1V865o6!m9Gwf5JgXHz8Hyq0ivpb2#|S!fNmmNL^mQ!RHp{#PgoRK}atkIrFYB z;NVL*_`Kg z!n_u5h3F!)Dxwx+-B>-5!l2kEGkbBE!JvU~m7$ne0g9LQiN25qKyfPred}UimRl!Ns2%bS`msRm7rKr5sK!b zgcM!igy>!gik2d;5)@BJ@q!e=qN^lG+IvxOp5JS9c69TbBVD8j@B^40bYDD3T^ zXd`0mpx8o+ZKMbn${P&JpNJ>YR%|8GPE@N5(q5zx=^%Cx=_p+7K{|=CL^_N8M7jvC zDj;1&Hj!?kfQUu-R0W9?(}{E!g+zLYKnIYXVm6Up;u4YGBD5MvAF+@~Ur|D&pJ-bh zq`$}`GCiNuJfL}Ep6N05PH1Cc?3Ie`ooF+}3TmqdmLr6$Nw5llA1V!GH$ z3YU6Nc-Dntrbwv^#XeFTCdDk_S`Uhe^`V$r4~nSU3{SE z?hVCak>?G?6H>e&g%DkRpjh7+iqCwY$P-UV5$y}b;Kooa6B`;sVcP@>dtWG4h!|fe zwvb{QDLxQN6DUUbK{2`s6syHnQn>g-;pqp(ha$xfihZOwOp3L_)gS%!u^3BaUH*Q5 z7G`=skhSIx?l$(upLEx8!e;=YZ2<1#pcbhun5~n*>#Hi-D*(y(^RB}DYF*d~rWy?m zVz#Cj+<#ZiAKR8$8NcY}trlwh5%T25`0ZXx%Ma+r9;n&umj`QqLG&@GgO-ZwB&tO& zXoDh?&A2Wlv}fxsnwiYDic1k}jw~Yc)7r9jGW#z7%l7O7V^74wjx51c3!i8@P~9i1 zSH~axW@V)$Wy~E)Y7}!{EqY34X3L7jtj=r>Q@yApYxEhz<-b;K$Zb>hMAVv!b0(eg zCGOMlAx+LL@gkCSb~XNdDAgpZYV-c~yho$5VABy}WuvsW?W1(naWX zQ3iM_bBs>uGl)#xj?##=1E%UaN+Xs&d!hI7l$UlFAWB~+%>$_H@zANCV$rMHw*fll zL5Y8A3sloRc~{rbJ1M>Or1`qe0Xp)Yjs?1oUKO8KHF$TW>#9Q@s_W>x0LoGWu+l4C zYHkh#1IhGE-mDn|UhuOWn{N{C4n^;VWI|0e*+BqfZ3zPm1`M&ZyvLqT(m4t_b;% zwUJ-71X*L%~eJI}V_Lod`?@?+{?9jJ}TtvlN-=)pQS_C(sM%CJwJ< z7HeCu?SS?`2cRS10MNJzN0}{wARq*23it!wKx4ocs0Y*sY5}!@I)EGC4mbnVfXaY9 zUD*$0pEaDAb_Pn zPoNjj2j~ybczg$->23r-gZwngJ^&m9j!L5DIu_)#1N=^47qA=H1H37EuVYQE31I1E zJH5H@56~Nb`aQA+&pdmnCmemEkfa*XEpb9{r)+s;;%3A>X8L$cX0-%vf zBXd1K(*;cr1ArCOKCxg10*e6pY;+QU30KMi=(R4sFjhZS9RZpMIHF=s0DU{w2yg_d z0tdq@HIelP81La(5G4S`RX(v1sDaS(v|ReyX1hxt-yD{_W(^$+W?w?qJbem z7VOf2gWwMVdw~7G1n96bN)Nzi05qNF05pX!0V2RR2k66_KY?e!5+D!gO=0skFcWx_ z3AYWbTjS4=X%p}{@C85%Z7x8w@aM4W1at#tNaCFhtW&o}(0c&Hehaz8he9O~ z2mt(nrhp%S>ZmJmVM=^{WblKm=x2A84V51!I!W}`g20UBuE0b7ABz_-9Rz}Em(XER-f`glFC z23Q4r0IUF(0W_mg@Ce|2U?DIEpsA87n+v=J%mUs7asaA01tJ9`-7ZZ5rD&%SL?ekt z7L72{PbO<(rUNs9834_}R47F*&Cl-wG)FH076I=899RtG0abwI09Bayl>q4ptATaE zTHr(ABj96TBd`JZglhXKK+#SSPF_;9Q`FO!2-H;h0M(jWY861OvJ;>d_!0O4$N|0w zwgKCLpMV{}Dj)~g1MCB+i&gPxBztk%1D_!bV@5dt@t!${f^8IfG_yQ05!Kas0>PR;sc&ql2%AjZ6k}Y(&R@V z3+h}G(27z)LqVzSfBe{?eF$tQ zC}3tL+gDU56-J=7Zy?YHhylz%J0Kir3q%04%+XpoK%{)dde)2uy9!ym0GZ%vWgE-H zk*`=ED|KBqFdiTuCxA`_asW!33QPee0fxRHWJ|i*<@NcH3{9_(mO)bsXuVbj=o4Zmp<>c|E;sn3u(lbpn$T`+yiNsQmc zn#poa;eU_0coPjqU7$A`gALdePUD$}Gk?i=nUxGIqdQvtioE zTec03msyl<1Z4B+h#}QlO)N{<Sv z0}qY}uiLDKX!0{_VxO!&9GrD{F}A#Z;o3WG5u#4pA^eLJd7>+PB;7h z)x3LAtjw-z9dYbu>e4#ej$fM_*bEN1I;%#RMWmh*a%XbSDh_WKl%o3HJT33zNb_uKjEFu`1ax!gzmh$1|UeF>c$zWjdD{In%y7Wzfx7#~r2KP-)n z-4^EW+Lcl_Ft_Yt^L<1FEaXi-;)`FHi|{_cs=FWYQU9C#(`UM3a$gseS z76OyoV&MTq*fX&a#KYLaucj%+vxnzQyr>EF4{S~)Ir)l z+IKgj&Y0-b>pC=Ezq^&~`|pN^smy_<`oqb$_;%+Oit;_^haJ%+|HcAs4GUc~N`TcTZ1U`zkz6#r=21;hjMga?z=LL&S1uW8IXowa7b|q1s;j8BUi4y6WsG@7A z*i?W^XIHr^^uJt z0aa@nE@~kH*ftS-k~KGSd2ET$W_ZiF>Gj%FdfF8kutNId4iBxf>LhC{f7MnTI?4KZ zWaCaoLB|A!f7D>g%xZ41Y_7EHG<1{sGM{y3R?#7mtHqp)c z9j^EZ4a$~U9GnnU&meO5iuPw%caML(4!W5Zd?~lH`1A}y)Y!8!tag{UNsGGS4YqpM zfSUYeG7{B)VQ!^P8#AFcGnG2)QSQv7&aV1QXv}38TMvI>XXNa5;s~~Fb2qk>oam8q z>{4#rJ+(#DTP=+GBDjv0@F;?zTvNS+%*mYH<>l9F>rYWDtoHde(G!N+WZ~*=>|yy% z#>j1hyOsP5J3VY)i6Q5iYml*fW?Z1*#RPUnynU8+mm!`$ia_>jA*9ja%vy(-gykXaec(T^Oz2dy*;n>PMF+lj`uVa1jqeD z@he$h5pV&GW9;VHXZGn$wW7P(l{HRMUy*nLQ_!Zq+6yl|hxpdQ!R zKvO^YbMlCt(eJI5*%4@*p?N6Me`T&Ij?r3QB*yI8xw7gilgwVh0C(@D+lux-aijjE zbO$^D;Ie8jg(DBGvVnGYuQ%@GXd^RUXdIz2wifNtbmtn^1G^lNhe8d$?GzteVy-QX zJxx99UaGfUsk;>h`VwO7avIB)pY8PcgE6WPYLq?{H<2~S*uL~eVr>87*4sO%^-;?< zHbnh2J@D()TL~+XQa0(g{uSe}+y>GMr)F?3=j!?>(%MPx5+i;@N$id|^DA3M&oNoQ zu?}+DVDa5=tQBJM;cu*Y(4{!-E_K1hp#0!H0d|h8F{Up%Y=&qp^t`6rfBmrCX=N!c zLqy!~2%cbAG(Z~`kB+(V*38!NWfp^wLifH0>RKn=_nR=YEX6uRe2Tmt@4>>AdgONC zp(Z=Nno(x)*${CN7A^O|f_gRKO7>y9Rm)zxezBf%V!{WO0Xwtqm*u@D{4S&6s}I#K zzb~(@*Y5k529_DLL`oeLdb?-Th+Z>q<3|_6-Tp(xl*?Fm#ts$lUuF{(?|3{l!*ZK& z{sS|9p|B9SA*>+fO;9v!Fs!9v;>aH;1~b#uKQOLq3=icp_oEXU1i_Mlaj>T5(J#F@oklLht~EM#lvvB^)SmKDe_ob1-bFk|UXSvgfVI4M0uHzVN6UKx)-3c*`Eyc-u6cEO;nucgrK=Cc zj+NBLmea(N8>~b9HEG(Nmpi7^>Il2@=YLwUvDvmzH5Z;vnn`}n@85(w<@TFuVC=cS z5yU)jI zlW6pK@zHITEIUjPmG7Y5#zw~Rz0WTHwP+gM?_+YJvD|oqh=2uC3#cj5?=Vk!;so(7 zbQ>rrmy8XPn=eY3 zv}468e4QAXt*yIBp!6Kpd3&6g?BtuF=4npPa>gdhQ=9qM-dUqUU8KHe9|Q79YRASr2;t!Ow2~Edr%aCy9$F1K%G#y^Fa^|1Hn) zuP!*SdXq)$JywSvsi)xAKKFF2JXB%h_d&UIoyZrT-^1*tKMQ;4n>FX;?TWv>AnIMZ z4{m{`Mx(_|6)#Z~tpqOjF=1y+6&>zl;oU2&_n9yIMQps!ylOb+sNXc?bY1w(+4BW6 zZ{lu7TtI@{Do4l<*hIN~j(GWi)e*}du+hRbY*$bHCiWXBo8?h#gx1~Y{D5o=uAe};BX^^XOl4MvJ!51E?@ zKbTo(h>wxa(bzP4%EBG7mA2Pefg7lq;>IJ^imu@Om^HKO^^9Tl!@b*ck@y(jY_5OI z0^~vMMbTqcOMbVj_*18DPgt#1Rr+Ynd$#}NkD@l#adm}U8O=T2xqp>cjxEkNfb0Ny zPw0@o;p1DKiFmFRzIl3n#uK)tMt$uU=%#*6HO3F?B%&s3_@s#R%+&OenN3Gd$Xd)~ fkLqP2jG^=k5WNHCdcv`?>>&ag$o2EV6^;FWf_VlJ delta 25557 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 { @@ -17,12 +17,14 @@
- + + +
diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 839907e..8a36ee4 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -3,6 +3,8 @@ import { sha256 } from '@oslojs/crypto/sha2'; import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding'; import { db } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; +import type { RequestEvent } from '@sveltejs/kit'; +import { dev } from '$app/environment'; const DAY_IN_MS = 1000 * 60 * 60 * 24; @@ -25,6 +27,16 @@ export async function createSession(userId: string): Promise { return session; } +export function setSessionTokenCookie(event: RequestEvent, sessionId: string, expiresAt: Date) { + event.cookies.set(sessionCookieName, sessionId, { + path: '/', + sameSite: 'lax', + httpOnly: true, + expires: expiresAt, + secure: !dev, + }); +} + export async function invalidateSession(sessionId: string): Promise { await db.delete(table.session).where(eq(table.session.id, sessionId)); } @@ -33,7 +45,7 @@ export async function validateSession(sessionId: string) { const [result] = await db .select({ // Adjust user table here to tweak returned data - user: { id: table.user.id, username: table.user.username }, + user: { id: table.user.id, username: table.user.username, name: table.user.name }, session: table.session }) .from(table.session) diff --git a/src/lib/server/oauth.ts b/src/lib/server/oauth.ts new file mode 100644 index 0000000..9d11186 --- /dev/null +++ b/src/lib/server/oauth.ts @@ -0,0 +1,9 @@ +import { Authentik } from 'arctic'; +import * as env from '$env/static/private'; + +export const authentik = new Authentik( + env.AUTH_DOMAIN, + env.AUTH_CLIENT_ID, + env.AUTH_CLIENT_SECRET, + env.AUTH_REDIRECT_URI +); diff --git a/src/routes/auth/authentik/+server.ts b/src/routes/auth/authentik/+server.ts new file mode 100644 index 0000000..5810654 --- /dev/null +++ b/src/routes/auth/authentik/+server.ts @@ -0,0 +1,30 @@ +import { generateState, generateCodeVerifier } from "arctic"; +import { authentik } from "$lib/server/oauth"; + +import type { RequestEvent } from "@sveltejs/kit"; + +export async function GET(event: RequestEvent): Promise { + const state = generateState(); + const codeVerifier = generateCodeVerifier(); + const url = authentik.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]); + + event.cookies.set("authentik_oauth_state", state, { + path: "/", + httpOnly: true, + maxAge: 60 * 10, // 10 minutes + sameSite: "lax" + }); + event.cookies.set("authentik_code_verifier", codeVerifier, { + path: "/", + httpOnly: true, + maxAge: 60 * 10, // 10 minutes + sameSite: "lax" + }); + + return new Response(null, { + status: 302, + headers: { + Location: url.toString() + } + }); +} diff --git a/src/routes/auth/authentik/callback/+server.ts b/src/routes/auth/authentik/callback/+server.ts new file mode 100644 index 0000000..791e8fa --- /dev/null +++ b/src/routes/auth/authentik/callback/+server.ts @@ -0,0 +1,78 @@ +import { createSession, setSessionTokenCookie } from "$lib/server/auth"; +import { authentik } from "$lib/server/oauth"; +import { decodeIdToken } from "arctic"; + +import type { RequestEvent } from "@sveltejs/kit"; +import type { OAuth2Tokens } from "arctic"; +import { db } from '$lib/server/db'; +import { eq } from 'drizzle-orm'; + +import * as table from '$lib/server/db/schema'; + +export async function GET(event: RequestEvent): Promise { + const code = event.url.searchParams.get("code"); + const state = event.url.searchParams.get("state"); + const storedState = event.cookies.get("authentik_oauth_state") ?? null; + const codeVerifier = event.cookies.get("authentik_code_verifier") ?? null; + if (code === null || state === null || storedState === null || codeVerifier === null) { + return new Response(null, { + status: 400 + }); + } + if (state !== storedState) { + return new Response(null, { + status: 400 + }); + } + + let tokens: OAuth2Tokens; + try { + tokens = await authentik.validateAuthorizationCode(code, codeVerifier); + } catch (e) { + // Invalid code or client credentials + return new Response(null, { + status: 400 + }); + } + const claims = decodeIdToken(tokens.idToken()); + console.log("claims", claims); + const userId: string = claims.sub; + const username: string = claims.preferred_username; + + const [existingUser] = await db.select().from(table.user).where(eq(table.user.id, userId)); + + if (existingUser) { + const session = await createSession(existingUser.id); + setSessionTokenCookie(event, session.id, session.expiresAt); + return new Response(null, { + status: 302, + headers: { + Location: "/" + } + }); + } + + const user: table.User = { + id: userId, + username, + name: claims.name as string, + }; + + try { + await db.insert(table.user).values(user); + const session = await createSession(user.id); + setSessionTokenCookie(event, session.id, session.expiresAt); + } catch (e) { + console.error('failed to create user', e); + return new Response(null, { + status: 500 + }); + } + + return new Response(null, { + status: 302, + headers: { + Location: "/" + } + }); +}