From 57a8722305de34ca25ea508fac30e2fc4d89e6a6 Mon Sep 17 00:00:00 2001 From: Han Kyuhyun Date: Thu, 14 Sep 2023 08:46:53 +0900 Subject: [PATCH] Add login page (#209) * Add Login Page. * Add backend login logic Signed-off-by: Han Kyuhyun Signed-off-by: jongmin Lee Signed-off-by: June Saehwan Signed-off-by: Basaeng Co-authored-by: ByunJeongHeum --- frontend/.env | 6 +- frontend/public/image/png/GoogleLogin.png | Bin 0 -> 11306 bytes frontend/public/image/png/KakaoLogin.png | Bin 0 -> 5705 bytes frontend/public/image/{ => png}/LPVSLogo.png | Bin .../public/image/{ => png}/LPVS_logo_bar.png | Bin frontend/public/image/png/NaverLogin.png | Bin 0 -> 9057 bytes frontend/public/image/png/ProfileImg.png | Bin 0 -> 1257 bytes frontend/public/image/svg/SNSWrapper.svg | 18 ++ frontend/src/App.jsx | 2 + frontend/src/css/Home_style.css | 122 +++++++++---- frontend/src/css/Login_style.css | 164 ++++++++++++++++++ frontend/src/pages/Home.jsx | 125 +++++++++---- frontend/src/pages/Login.jsx | 60 +++++++ pom.xml | 8 + .../java/com/lpvs/auth/MemberProfile.java | 30 ++++ .../auth/MyAuthenticationSuccessHandler.java | 38 ++++ .../java/com/lpvs/auth/OAuthAttributes.java | 57 ++++++ src/main/java/com/lpvs/auth/OAuthService.java | 77 ++++++++ .../java/com/lpvs/auth/SecurityConfig.java | 64 +++++++ .../lpvs/controller/LPVSWebController.java | 63 +++++++ .../java/com/lpvs/entity/LPVSLoginMember.java | 18 ++ src/main/java/com/lpvs/entity/LPVSMember.java | 65 +++++++ .../com/lpvs/exception/ErrorResponse.java | 41 +++++ .../lpvs/exception/LoginFailedException.java | 16 ++ .../lpvs/exception/PageControllerAdvice.java | 29 ++++ .../lpvs/repository/LPVSMemberRepository.java | 22 +++ .../lpvs/service/LPVSLoginCheckService.java | 57 ++++++ src/main/resources/application.properties | 33 ++++ src/main/resources/database_dump.sql | 4 +- 29 files changed, 1046 insertions(+), 73 deletions(-) create mode 100644 frontend/public/image/png/GoogleLogin.png create mode 100644 frontend/public/image/png/KakaoLogin.png rename frontend/public/image/{ => png}/LPVSLogo.png (100%) rename frontend/public/image/{ => png}/LPVS_logo_bar.png (100%) create mode 100644 frontend/public/image/png/NaverLogin.png create mode 100644 frontend/public/image/png/ProfileImg.png create mode 100644 frontend/public/image/svg/SNSWrapper.svg create mode 100644 frontend/src/css/Login_style.css create mode 100644 frontend/src/pages/Login.jsx create mode 100644 src/main/java/com/lpvs/auth/MemberProfile.java create mode 100644 src/main/java/com/lpvs/auth/MyAuthenticationSuccessHandler.java create mode 100644 src/main/java/com/lpvs/auth/OAuthAttributes.java create mode 100644 src/main/java/com/lpvs/auth/OAuthService.java create mode 100644 src/main/java/com/lpvs/auth/SecurityConfig.java create mode 100644 src/main/java/com/lpvs/controller/LPVSWebController.java create mode 100644 src/main/java/com/lpvs/entity/LPVSLoginMember.java create mode 100644 src/main/java/com/lpvs/entity/LPVSMember.java create mode 100644 src/main/java/com/lpvs/exception/ErrorResponse.java create mode 100644 src/main/java/com/lpvs/exception/LoginFailedException.java create mode 100644 src/main/java/com/lpvs/exception/PageControllerAdvice.java create mode 100644 src/main/java/com/lpvs/repository/LPVSMemberRepository.java create mode 100644 src/main/java/com/lpvs/service/LPVSLoginCheckService.java diff --git a/frontend/.env b/frontend/.env index 10e2f4dc..7029ec44 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,3 +1,3 @@ -REACT_APP_GOOGLE_REST_API_KEY={type_your_key} -REACT_APP_NAVER_REST_API_KEY={type_your_key} -REACT_APP_KAKAO_REST_API_KEY={type_your_key} +REACT_APP_GOOGLE_REST_API_KEY= +REACT_APP_NAVER_REST_API_KEY= +REACT_APP_KAKAO_REST_API_KEY= diff --git a/frontend/public/image/png/GoogleLogin.png b/frontend/public/image/png/GoogleLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..98f3e2676406b3075eb753efc62098a104129175 GIT binary patch literal 11306 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 Y;QwDv7?xQ7BYk1yrB$S=B+Nqp2SVo*dH?_b literal 0 HcmV?d00001 diff --git a/frontend/public/image/png/KakaoLogin.png b/frontend/public/image/png/KakaoLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..f647385399e9298cef49cc3df22b024435ac6264 GIT binary patch literal 5705 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 literal 0 HcmV?d00001 diff --git a/frontend/public/image/LPVSLogo.png b/frontend/public/image/png/LPVSLogo.png similarity index 100% rename from frontend/public/image/LPVSLogo.png rename to frontend/public/image/png/LPVSLogo.png diff --git a/frontend/public/image/LPVS_logo_bar.png b/frontend/public/image/png/LPVS_logo_bar.png similarity index 100% rename from frontend/public/image/LPVS_logo_bar.png rename to frontend/public/image/png/LPVS_logo_bar.png diff --git a/frontend/public/image/png/NaverLogin.png b/frontend/public/image/png/NaverLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..5e9129f9a20e96d2d9aca7447828fbeb44d3c62e GIT binary patch literal 9057 zcmc(lbyQnHyypYODW!zs)}U>1D=tNYTY=&bAh^4h7K#=3;FJI@?#11uXmQu#P`ub) z-k$gN?Ax<{?%4y$o!ptLyr+mZ z1pt?qrciYy5XhSm1PTZOf&Kxv0{(zNZX6)ct}zHCm;wTkIHotK2?H4zFa=pD(8J?j zc5^{Ia0k;>PDvVb8yf=$n@Y9$&AR`@wl|iH0QX5bo$Hmjr0T`^_QPEoCP3X;+|v4VL92Rw)fPn+7C*1 zS9;Gkm-1dlH`g%e#4(r#HsyJp|M9Xt;I;AN_L*{kw_L+z%CFxJb)ccL$3;V5J78`- zVS9n|MS?RnDBw;abQyBR8`pxIRV~1fMH2fbU}HumUk4b_4ytyP0Ht+;!S=wT`nVDtnP1>YC;h)R zOaw!7dW&qI zXr7;;vL{z-VgN_?w`WKv$2+*wsr=KIbfll_AzUmYNbaw)_9<+a6Hz0CN!?TtJqb$y zL%LETx9TH;6br-&3zFlx<%FpJ4f4A^oczvecT;wgsf?DRTL1^vN8#Mp{1wSHtm@M2 zG?1lfE0Yb@w8!@35z1n2j3U=t4c#$1KHA<{vgzky0m-n z?igeHAQC&WF`RAPclzmDO#D;mw#-I32a`gn3<$rvY+JROcj7r7R+S=d z6=|G%X>+=dycS*F#;2%r%Bb3m8kwOtL%!8l^2CBgp=8+9hrY1}IXxOvG?L`{h{gat zT&-1w;QF#V8lGOC$_A4RYen)^j2Ly;OWBfuX|H5wg?n`2$?LUHZRkrc9PKcF9KIpM zjPQtB`@^ed@&J-M%ozf~w*=iDQ)ADI&)GJ&ZWBF+p7~GIbK;r{ol_<1{k@XvCl^0X zUZSFms1A4zZ7Z3rYvU$ND=!yXkaB4fqFYjzFSJGXZ8F2dz!*8RD_|SPSsRj8M}_A& zncm|p#WrFEpngsBiM}(Um4Uq{OM)ix+tmFu;}z+D8SUOWaPr|&r$X-JBIa(fVWKTx z=SqK87e1@N-Pz@W5+;=_W8IgQIR3U&5ia5!6al3-*J5u?^~e91dWv4!hY+*Pl%|!F z&j_02W{a%qU-3dvoN76`9jh&)Q2oUd~hEZwi4%)erHQ^L(RXnSC-mXd$_FzTfU4ptx~< zrtOz&TwyvXbvzc-N6a6({SNjRn;>dUyy-qQM|Lll_qV>9A0cPr(rRE_V2sx^tX#9*OVf=l_q-9@@R+1b1R`CV4@ zmNIFoHln5M-6ZrmFPM}RVIlX!f|2p&p-0Y2)?1?0k1;0)+ipm8>ZF`*rY)(BU`S$p zJ0^b*Uy$iTD#X8*nZdA%mub%j6KXD&C)1NjAA0;13 z*mruOpUE8^{lZ`%S~n}hQsWmXHkNT_?&l&Gl8OaQg_c<*@#spi@u7y6#r>yzF$w>^ z6U~Zqb!rXi9;s{96=)c{Pb6X;H+`h}c(C|M&T=8&jT!g2F1(y#YwCu4PX^X&U@6OTj zVsH$-TZIplcDIz9BVd_i*%=h9W4rM`|w2(^yO0_Cj<3jN5n`xxaOH7rMJ zZPK>*mqDLU(^mwXnfA5Y#g{a%d=DWZ$#Vx?Yay^G^}$k?gayMXRibb4)ti>7vzauo zLzr1ZNy_hfT%DEg$d1%#P40>Krm#ZH-sp*%i2Q1|#QnIp*iMln;~McP z%!odZn0?waIa?vH_52{4i3}Fi9O1E9W)@bJ+S|Z)qIeV~N5aW%{$-w?M^w$5m%{gE zhM-W^Y$u|Do(SwNJ7(^ibL$Pvzz~De{&A0E;~NbB@qZq^-kvEdI4k*0M81u?#Kb~2 z!M+#m-xNHHZMdU79JXY6X*$}Bmp{Sh2t^f56A+9%hp!EXSn=JIber~Zcq>KTtN46#5En+8)fXjU=6`(gdUdOTrb29? zOVI~gEW;fW#7F%PW%z`xbWpFyqi{*6D_H0&`}VK3K{;qf4d#8|mgRPv2Ll5-9Dlj2 zKBF22y zkX`ahq!;s*)+Q^9`|qb!n{?92PW*EN(`nS({h1{{zEjvxixpl!XN!+i`7JhdXSWwD z{eyJexVAas#`*}^x3+qi+iK>r!odjPK(^sdX&AkgFE7-zW1@u=krGIn}^1ZR|$(_B1pV9;e zg_Di1SRa88i<-aU*igR{8Q5#5o9q?r=WEp2M08_*Wy=}b?PgKzTH0{*uNF1R?C)>Y zY>uv3%G<~0t6-i)YvY>c*%D%|@8eGU_@geYF-jCV6Pl$yN`I`o!XYZ6*fQG*)8^It zEE}xPIjyGLGK(i(QxZe2hf;E~1c@94tkY8?7grfCLR8wash3uQ^Z3ZVL8iiVEE9D9 za^Ob(3d+hRV1Uzyk)9~(Lb3N2yS1GnJvd9k3~SM%09PY+<=Js>qo{)ns^EBX7E#c|mF>G;+|+7#fHxTy=F)jjv(K+)*YwLuQ>V zI){Qv%>PDbx6mZ1Il@76spCTRN$emI4Y$`rnEEo45TM))~!j5tbWUgxlIQ+V2ynwZ_(w^YKC#Z}9M z2p%{7a(cr!CpUZ=^~sw3G)ww+>a(^BV-YHBnuu4PpO;TR!-hAP zRfLBDPBQ_2K9u5^n23iuD?TedXWR06@NZcek2&g+>F z{DBFz92@tc>NZ1v4I!!bE!u(VflkG>Ri-c0e0Rm8$YCAzQnBT|zvO@)kpO3xeG!L? zO20J#8=gx)qXCEO#4#>mfILr6hl*-28bv7+Cd1cH&8Ic&n1yr0(!<(l6@9<|0Rz2s z6=wph6r?Kdt;*ujfcGvZSZ8<;Z5chLActkt@lbdW8NG{|MdS+wc72)PYwtuV@ z?gU2Sno2=*zK^Jpm2X0e*{k!>Dt)Q9!yVqs)GhwEPUNN;2+!>XOI$IlJ0TPiiBGdi zi8aPUA-HG;VW2Y1Xw)Bxl}oOMDEl29U~N23y7r<^l9m#tVMXw^#LvjSxK3XS22dH= zv#1%H{g1lh(T=oBl6oB>%DW9qW}#D_d`xT2J0%3c;pTqD@gsb~9eeG$U1neda-d}& zXWd;a9PP7C)HvTkED@6YEJV=I*rN_Rx&PNAQkhW_o?ygDd`?U-|c_jgxrB;%#smN-vhE}zOm_vP1 z^~xdMNDq?qXb7JF%wKK)*%M(=k@V6)Y+w#<;^MWgN;QR$U4ZTTTaBUDPP(v zUbC|B_d&s7B{`TbX8~%&l}@1guF%7mW$K+zM+hTNI14ptFLO$QVA&OZjzS<=E!f)j3*uF% z#Mcmk3$8h^Xy*Qs#7t<_j80_lM&H$v#hk+TTH?{9Kc+t4?-NGASW@{-(mn2^env?J z-nErt&M@fEPdUpo`asI4Y7|xD3uA#mHw@i|egClw6|nj?Y=m;j$a5@Z2kkY@Lr>c~ zV9tE8@cx-Q*dG7sN0E2@I|(b2AnBBekr%H?RFLSvaMt-HJk*g;|1%+up_Ke_Qg!)w z@jAY88`j$5=*cnfz?|0cNJa$?~=5qQfY*A-=v2c&EAR+ z)4d`vaO6_B)Hvc}_tgryrD(la9X+$RJ;5GC2JPLMvk28P7pm6`6SmUtb#P;#Z?7)E zQ?eJPL97*pq{J%7|0veE*EF@;av!?YG|`AxtN_yO^)&Pcw+`~$s8F=5XeS{n}S zR^R4XU=+IP80P5`9Q!qg)Urmk*RCTl4Ki%{G<0Pu!YBkc5zK9m1l^BTeIfC?;lPgc zQJCVGH+0`!xlsuAbpmNBMzNVkOkQyRc z1?B})%7?%slP-HckTiQ^{p?q+VLcz;L@d706$SyA`SL@%F=cQhywt@;5v@JAh zXN0QE*IFW%bEz1UT0n-50U!1H0{dz*P>1)p#FzYmxpv?Y%KOesqSe7_kU zX6A(&knJ4AP%wLh<9qUZj?9J`Oh z7njfgKJ1t23O${u-L8A%(H?5zvfXH5mY6(m>YR-onUYEGS@@k4MUCzqR7HtnGleps z&8iZngzx6p&rtyW9zgAgdeM{9smt5rCr1 zbiL+kBa)Jl7#|xt#E^XFpxVDU9V?4^+fB2Q7I@0e11q)~(XhIT? zVFBRD!`+zzO)K*u5C)*t03S3OdtAY}$efI1WI5O=?|Y+%qFIaD(~+YS64?e;FW_Ce ztW7+S;?4`wlPnkWO~0ADZ^yS04KKX89#TPR{2JZ$_&P)zi zRc3Hq8pac9BzaB&7y~RU9NiAytTBu zrd0c1c`WQ~KZ<>RZ?SoQ;RcKrshW}im1M)t+La2mZ6Bblg?!oON6i+BMtF|sRR5z~ z#O8CNpHucL4s0j^1F4L6;Tjc2xL}_n_;u`dW07819=lo%#n6`Igg+0f64(t~XtI3eF zY$CAUSp7wEJw0&g=(ofLXpy&Thbav_b(7r5BZ(#5r8=JZ7DCJX_h=#CSOeYPCM0}l z$`xhbWSi|=WYDhY)08f0PI(ebh~Q}3_VdC1qoyb!NsG0s_U~-6X@VjLqh<0+NQUBC zP8|LW$>H?(MTY;ggR5(;cq)fA9VyUa7)Z?enj!=HK`PBf2H?~`au@(KZrA5$Rfg1c z=}2bIN-sd2`48}q6oM_o-B8LGcq4_YY*HLAvz|`U&?e!v(Br++?iiU`QA7I-VB(h0 zn^%>dGbC2|d}I=Y7hZ?3=k% z2}I2XSiOoJl?s^1h7=7i#Moi<-xXvh-p}Hkr0nmE1mA;zit7X(d;w(a&YSHEAGjjs z+zg^nZ;na6!@P3a9wf9INRuJ_3_u*<*+EU*4rITlI!p*d!E&Xu{XRIqdRY{Cx`bva z>}9YbvkA;KvY!Q9N67=Qjkgy@Hg1e43dR-YW)d7Mke1kOii-+*q+W>;7M6~oLsdF8 zx*D@NKr|2vL=Bp)ho^h7IBQL0JT142qcj_f4$kJB>IWoW=pcQOaFv(JJ$h#;H+CdU z;aN=Tsio2xtunc}oD~phP}srhSbiw3^)zC{*@;W9*Z-3#2go8m2|UX1+NWWYKzM7o zUGS-uV`foOVksG&Obc`ca2%en3yW*@YrGG#jxNd!`AEkn^m29rb4T04+R$!{0(WVl z^Ri%evbFxq>9M?a@H9`0{=IS&oTOoL0-`UdWwN!j_FF%rku;$7{1Ev%X|B3ICa(43 zFq?NNWZrC>f{3pm8FvriGdZVlW!l5XvjSQ zZv>NTU68qY16V?%h9>hSk6;#fMz0#FExaW-HI*e@Qey=7{CuF$49!f$f^Hp7)hGbe z_(slu3bIHh&0B!=aIeyjb9a{V&!U08N zpjU(x@10+D;Dfba5@x}&h*@V=m;~~YN+-VYI00gk`kXSn+WF&+;Qh=U`O%PG4-lsk z)v8QYx1nx5@=o0HF;ggWtrCr?m~ihN0!7rC#F z$Y{1N7t<`mE>*2qSDMjDD#t|vl|Bg)7npvlLV@rA6u*y6vrT>k1CS5+0QeGor`HGA zd<3I(EFROe&uwugBkKsJe_&BmM64_tI1>R1&euCd%-taeGQ~M29s33!t-1fXnB5H~ zi8tK*!iFpY!r%}@9yUmi4(D4gYKhFM5$Ub?faz{x0z^cBC^Gk=p<-X?>bNb7+Y~Ud zGXi6YVLOu5U!DVEZXH@3DG>E2lFk?#eH-X2u<~D!WIy`2V`H zM^yM~bwCihp8PgQ=}Eo}#rfDX-SsCzC^!&)Vx^K_XDO6j=5b2E1bQtyGi_=tw%cB7 z8Tvj{e+A@1Y0l!V^$6B%c<#aEmZ7!gs!d>EsbVI|NLCa#b&x|~Ve~-5D7xlPo z$S|@5%dp!US`2RMEhvsy>tcXtU9scznu<)hC8M*F@;8~!G(Uw00gU0>%QWkU{V z^_94Z<+C;qehY?{MHQQ7=AssHoO*A(y6I$+sX3X>UhiDny!VAnRP?Lt z=KhyPcDEvWs3DJgDpgc@AEd00Hr@Ohy~_PWTqrjI9sEv2a0@lcpFR#?NFb_B8I3=e ztcxWG7JVk83$Q4~NdO#WM?^uF6Fc&k*-CZiDKepHL#(ZKg%iN(k(^Z(;j*G_puK2nJ!iT+7Eb_MkI-$jzj_(Xaxm2bVe%4^w^ zm)n5A*-=7s5)k`cP`@-nJL}U~d;&Pe*IBEcBs*d6XEdSFc!D|7FbP#ye_oH4R8;@F znNSzurQtA7d~7-aSc^tM9VvpMQOW)9u>F4O7eI>x|42ngj}vQu-gW9{vh#KD2M;UK z?Ntor;b2W}z(*sfwz5C2enB@NN5kASMAUH*npVa}L0>t*42`Wl<80un!m0shLNX2%LBl4ygf(ZpjHmMMfgUEW&rHUJW} z8|pL%*`iYd#S8xi6|u1PPR{c6awl2~+iaZGZJ58ctwG@QR$-Upseb_A4=&Lr-Sa^d zEACKxh+)Kq2pO#bF(0-;#AU6R7+ZYT*#uEuj*{$dC=;-}I?*l(IVM6?;88EI{V20{Ve|i+JVTFt;79!tUfmZvi9KnoFSq#X5$aTK55g0bfaJ=4sX)J`cGSRtD z`hsUvjGWXec6LY9w36e;H+h*Nal}wmWdKqvXeWgAW4G9MiL4>Ldd$!K2U%lB6RmNT z4?`_uK-gHb9@oqF!ot&Ys~2(f9h!B+zhc3Q+>p3rnL=K!FTMCqXC~Eq7mh{xd+8({ zYbNX7HsK(x;>7ik`^<3d>3F97y|UQi46oHndjBWFeHHMP(`3cTh7Xd5Y1)Ld=UhXs z+5~)Fhfa=3$8_a>8lQVLwqpL!u6BoflrhV_+vLBio!$vA^G!R=e( zRIDe4Z%VFxTemJEvML^4K7!QsyYmBpwt)`)t2lC@#&`tmNY8g8PnH&!{5g&qooJX0C62WF zw;(+fBqQqAVF4C*O${4n+L3)mWG_FtorTHwXQl69?!9Rs8 zlgRP(LGZ-KhjTOWrj?=T=I~D=^ttVT&2&!m|3BO2|E*S@m!e}bz*hek*g_X?w&}OW zoqU|`x}DL05<Ud0VFsWPirUsHv>BdGb^~)|J^{l^qB)-@an&6aJ8~CcX2hbbNuf%0)kuu|Fe=; zvuuw?SHO=p=AKq?L22Mk1rB#HcLj01)u8xh4&;*lSFWm+r@8G1Dd4&GE>;fqAOS88 hZxR>n|FewF|0=`9!7=MYBLS2F$w@0ql}Z?Y{vUsIBM|@q literal 0 HcmV?d00001 diff --git a/frontend/public/image/png/ProfileImg.png b/frontend/public/image/png/ProfileImg.png new file mode 100644 index 0000000000000000000000000000000000000000..7b68546cdf4064023ffa72307a0b54254d6c66a6 GIT binary patch literal 1257 zcmV=P)_4W|`;{k`@*wO}9=y7$Bxcm*BaM4=8M2vhevHfl%@bK_}t*tHW@9*R4>I$x|uJHHwM{H~?LPJBr1YY09 z!q&ys&DSh=55yyE-{=)?Z*OU4W`-&&D=9HCk$io9DI_F>a&vR3t*wpL*VpOp?vCET zI@=dG3-QY1=Ko-Ib(Qk+^2pQElL7++DIy|55%Ti#qOhdkle(k-53K zp`xN9^6~MZxVSiKYHFgv!9nWi=%C`_Vnr|^A%WW4+v)!PUW?R>CMic`1WQXxB+JBl zdV1*a@bKx{+}s@1)YPbJ<>lqHySqy|@bfhX*H^#)U^Bi&m>;T+0RaIhC@26+^>i&g zJsoLjX<+GaaBu*N#R45D6#48DB5SZ^I@%o^9ITEP7Z=J{7ChSzmQjT96V zq)f7{xw)Ad8XAZtQzalLCx`m_`iN^4If=sOgl6>aT3K091SF$a7K)CJCN75L;o(6X zL)6*XNk>OV^bUL%E-qx1YaKel>#?@BhRMlEOifK;adA-vd3JU-*gMF`$WXCnr&?>l z!h7O{ko2b?a1s*Kr>CdNB#0&BB#_xt_yFSFp^;h7A>I*>Y;JC|g@pyo&dy?E zV*^~-QBqQZ)YMcp5$J(KLh|tb{(kiK_F{T^8j+Ea;QocItSm%DMX7otgrpd98&QOW zgxPFXbxA5aPX;J0Ek$*8wZ8b0bhDR(9IP}jFhI%4$>i73PV$y0V;LM|ukrCqAi8N67W>H*TUZ$$5Dpgw`WS?WiF9~H_nb-AQMVA*FCcRM8{YotEB5Hp*6`IvnyEt>cj0J{g8 TQ&h + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b696010b..93856027 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,7 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Home from './pages/Home'; +import Login from './pages/Login'; function App() { return ( @@ -16,6 +17,7 @@ function App() { } /> } /> + } /> diff --git a/frontend/src/css/Home_style.css b/frontend/src/css/Home_style.css index 5c373e76..bff757a7 100644 --- a/frontend/src/css/Home_style.css +++ b/frontend/src/css/Home_style.css @@ -15,71 +15,76 @@ .home .div { background-color: #ffffff; + border: 1px none; height: 1080px; - width: 1920px; position: relative; + width: 1920px; } .home .LPVS-info { height: 896px; - width: 1273px; - position: absolute; - top: 120px; left: 593px; + position: absolute; + top: 140px; + width: 1273px; } .home .overlap-group { background-color: #000000ba; - height: 896px; - width: 1271px; + height: 800px; position: relative; + width: 1265px; + top: -10px; } -.home .open-source-code { +.home .user-guide { color: #ffffff; font-family: "Inter-Medium", Helvetica; font-size: 24px; font-weight: 400; + left: 42px; letter-spacing: 0; line-height: normal; position: absolute; - top: 35px; - left: 42px; + top: 15px; width: 1195px; } -.home .text-wrapper, -.home .span { +.home .text-wrapper { font-weight: 500; } +.home .span { + font-family: "Inter-Regular", Helvetica; +} + .home .LPVS-logo { height: 885px; - width: 574px; + left: 15px; position: absolute; - top: 139px; - left: 0; + top: 125px; + width: 574px; } -.home .LPVS-remove { +.home .LPVS-github { height: 407px; - width: 472px; - position: absolute; - top: 446px; - left: 0; + left: 30px; object-fit: cover; + position: absolute; + top: 400px; + width: 472px; } -.home .license-pre { +.home .license-explain { color: #00057b; font-family: "Inter-Bold", Helvetica; font-size: 60px; font-weight: 700; + left: 56px; letter-spacing: 0; line-height: normal; position: absolute; - top: 88px; - left: 56px; + top: 80px; } .home .text-wrapper-2 { @@ -87,37 +92,92 @@ font-family: "Inter-Bold", Helvetica; font-size: 70px; font-weight: 700; + left: 56px; letter-spacing: 0; line-height: normal; position: absolute; - top: 0; - left: 56px; + top: -10px; white-space: nowrap; width: 217px; } .home .menubar-top { height: 101px; - width: 1842px; + left: 39px; position: absolute; top: 8px; - left: 39px; + width: 1842px; } .home .menu-line { background-color: #0057b8; height: 3px; - width: 1842px; + left: 0; position: absolute; top: 98px; - left: 0; + width: 1842px; } -.home .LPVS { - height: 96px; - width: 309px; +.home .menu { + height: 43px; + left: 1270px; + position: absolute; + top: 37px; + width: 578px; +} + +.home .overlap { + height: 43px; + left: 302px; + position: absolute; + top: 0; + width: 272px; +} + +.home .profile { + height: 43px; + left: 136px; position: absolute; top: 0; + width: 135px; +} + +.home .overlap-group-2 { + background-color: #d9d9d9; + border-radius: 50px; + height: 43px; + position: relative; + width: 133px; +} + +.home .image { + height: 31px; + left: 11px; + object-fit: cover; + position: absolute; + top: 7px; + width: 35px; +} + +.home .text-wrapper-3 { + color: #000000; + font-family: "Inter-SemiBold", Helvetica; + font-size: 20px; + font-weight: 600; + left: 56px; + letter-spacing: 0; + line-height: normal; + position: absolute; + top: 8px; + white-space: nowrap; + width: 71px; +} + +.home .LPVS { + height: 96px; left: 0; object-fit: cover; + position: absolute; + top: 0; + width: 229px; } diff --git a/frontend/src/css/Login_style.css b/frontend/src/css/Login_style.css new file mode 100644 index 00000000..50ce1857 --- /dev/null +++ b/frontend/src/css/Login_style.css @@ -0,0 +1,164 @@ +/** + * Copyright 2023 kyudori, Basaeng, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +.login { + background-color: #ffffff; + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; +} + +.login .div { + background-color: #ffffff; + height: 1080px; + position: relative; + width: 1920px; +} + +.login .SNS-login { + height: 500px; + left: 560px; + position: absolute; + top: 290px; + width: 800px; +} + +.login .overlap { + background-image: url(../../public/image/svg/SNSWrapper.svg); + background-size: 100% 100%; + height: 510px; + left: -5px; + position: relative; + top: -1px; + width: 810px; +} + +.login .login-header { + height: 58px; + left: 36px; + position: absolute; + top: 27px; + width: 752px; +} + +.login .overlap-group { + height: 58px; + position: relative; + width: 750px; +} + +.login .line { + background-color: #000000; + height: 1px; + left: 0; + object-fit: cover; + position: absolute; + top: 57px; + width: 750px; +} + +.login .text-wrapper { + color: #000000; + font-family: "Inter-Bold", Helvetica; + font-size: 40px; + font-weight: 700; + left: 10px; + letter-spacing: 0; + line-height: normal; + position: absolute; + top: 0; + width: 249px; +} + +/* Common SNS item styling */ +.sns-item { + display: flex; + flex-direction: row; + align-items: center; + position: absolute; + width: 500px; /* adjust based on content size */ + left: 150px; /* generic left alignment */ +} + +.sns-item .text-wrapper-2 { + font-family: "Inter-Bold", Helvetica; + font-size: 30px; + font-weight: 500; + margin-right: 20px; +} + +.sns-item .text-wrapper-3 { + font-family: "Inter-Bold", Helvetica; + font-size: 30px; + font-weight: 500; + margin-right: 20px; +} + +.sns-item .text-wrapper-4 { + font-family: "Inter-Bold", Helvetica; + font-size: 30px; + font-weight: 500; + margin-right: 20px; +} +.login .google .sns-logo { + margin-left: -10px; +} + +.sns-logo { + width: 366px; + height: 90px; + position: relative; +} + +/* Adjust positioning for each sns item */ +.login .naver { + top: 125px; +} + +.login .kakao { + top: 250px; +} + +.login .google { + top: 375px; +} + +/* Rest of your styling remains the same */ +.login .menubar-top { + height: 101px; + left: 39px; + position: absolute; + top: 8px; + width: 1842px; +} + +.login .menu-line { + background-color: #0057b8; + height: 3px; + left: 0; + position: absolute; + top: 98px; + width: 1842px; +} + +.login .menu { + height: 43px; + left: 1708px; + position: absolute; + top: 37px; + width: 133px; +} + +.login .LPVS { + height: 96px; + left: 0; + object-fit: cover; + position: absolute; + top: 0; + width: 229px; +} diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 8d2d5dc7..f07580ef 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -5,57 +5,75 @@ * found in the LICENSE file. */ -import React from "react"; +import React, { useState, useEffect } from "react"; +import axios from "axios"; import { Link } from "react-router-dom"; import "../css/Home_style.css"; export const Home = () => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [username, setUsername] = useState(""); + + useEffect(() => { + axios.get("/login/check").then((loginresponse) => { + if (loginresponse.data.isLoggedIn) { + setIsLoggedIn(loginresponse.data.isLoggedIn); + axios.get("/user/info").then((userInfoResponse) => { + setUsername(userInfoResponse.data); + }); + } + }); + }, []); + + function truncateName(name) { + if (/[\u3131-\u314e\u314f-\u3163\uac00-\ud7a3]/g.test(name)) { + return name.length > 3 ? `${name.substring(0, 3)}.` : name; + } else { + return name.length > 5 ? `${name.substring(0, 5)}.` : name; + } + } + return (
-

- - Welcome to the License Pre-Validation Service (LPVS). -

Usage Procedure

-
    -
  1. Sign up and Login to the service.
  2. -
  3. Go to user information page, enter your GitHub ID (required) and Organization Name (optional), then click the "Admit" button.
  4. -
  5. Login to GitHub using the GitHub ID you entered in step 2.
  6. -
  7. To configure the repository for license validation, follow these steps: -
      -
    1. Go to the repository you want to validation.
    2. -
    3. Navigate to Settings -{">"} Webhooks -{">"} Add Webhooks.
    4. -
    5. Enter 'http://{"<"}IP where LPVS is running:7896/webhooks{">"}' in the Payload URL field.
    6. -
    7. Select 'application/json' for Content Type.
    8. -
    9. Enter 'LPVS' in the Secret field.
    10. -
    11. Under "Which events would you like to trigger this webhook?", select 'Let me select individual events.' and check only the 'Pull Request' option.
    12. -
    13. Click the green 'Add webhook' button.
    14. -
    -
  8. -
  9. After completing the webhook setup for the repository, create a Pull Request on that repository to see the results of license validation.
  10. -
- -

Important Notes

-
    -
  • If you enter a GitHub ID that is already used by someone else in your user information, it will not be reflected.
  • -
  • This service is only available for Public Repository.
  • -
  • Webhook settings are mandatory for using this service.
  • -
- -

+
+

Welcome to the License Pre-Validation Service (LPVS).

+

Usage Procedure

+
    +
  1. Sign up and Login to the service.
  2. +
  3. Go to user information page, enter your GitHub ID (required) and Organization Name (optional), then click the "Admit" button.
  4. +
  5. Login to GitHub using the GitHub ID you entered in step 2.
  6. +
  7. To configure the repository for license validation, follow these steps:
  8. +
      +
    1. Go to the repository you want to validation.
    2. +
    3. Navigate to Settings -{">"} Webhooks -{">"} Add Webhooks.
    4. +
    5. Enter 'http://{"<"}IP where LPVS is running:7896/webhooks{">"}' in the Payload URL field.
    6. +
    7. Select 'application/json' for Content Type.
    8. +
    9. Enter 'LPVS' in the Secret field.
    10. +
    11. Under "Which events would you like to trigger this webhook?", select 'Let me select individual events.' and check only the 'Pull Request' option.
    12. +
    13. Click the green 'Add webhook' button.
    14. +
    +
  9. After completing the webhook setup for the repository, create a Pull Request on that repository to see the results of license validation.
  10. +
+

Important Notes

+
    +
  • If you enter a GitHub ID that is already used by someone else in your user information, it will not be reflected.
  • +
  • This service is only available for Public Repository.
  • +
  • Webhook settings are mandatory for using this service.
  • +
+
- Lpvs remove + img -
+
License
- Pre -
+ Pre
Validation
Service
@@ -63,9 +81,42 @@ export const Home = () => {
+
+
+
+
+
+ img +
+ {isLoggedIn ? ( + + + {username?.name ? ( +
{truncateName(username.name)}
+ ) : ( +
Loading...
+ )} + +
+ ) : ( + + Login + + )} +
+
+
+
+
- Lpvs - + img +
diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx new file mode 100644 index 00000000..e739d21c --- /dev/null +++ b/frontend/src/pages/Login.jsx @@ -0,0 +1,60 @@ +/** + * Copyright 2023 kyudori, Basaeng, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import React from "react"; +import { Link } from "react-router-dom"; +import "../css/Login_style.css"; + +export const KAKAO_AUTH_URL = "http://localhost:7896/oauth2/authorization/kakao"; +export const NAVER_AUTH_URL = "http://localhost:7896/oauth2/authorization/naver"; +export const GOOGLE_AUTH_URL = "http://localhost:7896/oauth2/authorization/google"; + +export const Login = () => { + return ( +
+
+
+
+
+
+
+
SNS Login
+
+
+
+
Naver
+ + Naver login + +
+
+
Kakao
+ + Kakao login + +
+
+
Google
+ + Google login + +
+
+
+
+
+
+ + Lpvs + +
+
+
+ ); +}; + +export default Login; diff --git a/pom.xml b/pom.xml index d0f19e55..191b030b 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,14 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + org.springframework spring-core diff --git a/src/main/java/com/lpvs/auth/MemberProfile.java b/src/main/java/com/lpvs/auth/MemberProfile.java new file mode 100644 index 00000000..fc2c6bc2 --- /dev/null +++ b/src/main/java/com/lpvs/auth/MemberProfile.java @@ -0,0 +1,30 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.auth; + +import com.lpvs.entity.LPVSMember; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class MemberProfile { + private String name; + private String email; + private String provider; + private String nickname; + + + public LPVSMember toMember() { + return LPVSMember.builder() + .name(name) + .email(email) + .provider(provider) + .build(); + } + +} diff --git a/src/main/java/com/lpvs/auth/MyAuthenticationSuccessHandler.java b/src/main/java/com/lpvs/auth/MyAuthenticationSuccessHandler.java new file mode 100644 index 00000000..c030d698 --- /dev/null +++ b/src/main/java/com/lpvs/auth/MyAuthenticationSuccessHandler.java @@ -0,0 +1,38 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.auth; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Component +public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + String REDIRECT_URI = "http://localhost:3000/login/callback"; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + System.out.println("oAuth2User = " + oAuth2User); + + response.sendRedirect(UriComponentsBuilder.fromUriString(REDIRECT_URI) + .queryParam("accessToken", "accessToken") + .queryParam("refreshToken", "refreshToken") + .build().encode(StandardCharsets.UTF_8).toUriString()); + } + +} diff --git a/src/main/java/com/lpvs/auth/OAuthAttributes.java b/src/main/java/com/lpvs/auth/OAuthAttributes.java new file mode 100644 index 00000000..7ff09590 --- /dev/null +++ b/src/main/java/com/lpvs/auth/OAuthAttributes.java @@ -0,0 +1,57 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.auth; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; + +public enum OAuthAttributes { + GOOGLE("google", (attributes) -> { + MemberProfile memberProfile = new MemberProfile(); + memberProfile.setName((String) attributes.get("name")); + memberProfile.setEmail((String) attributes.get("email")); + return memberProfile; + }), + + NAVER("naver", (attributes) -> { + Map response = (Map) attributes.get("response"); + System.out.println(response); + MemberProfile memberProfile = new MemberProfile(); + memberProfile.setName((String) response.get("name")); + memberProfile.setEmail(((String) response.get("email"))); + return memberProfile; + }), + + KAKAO("kakao", (attributes) -> { + Map kakaoAccount = (Map) attributes.get("kakao_account"); + Map kakaoProfile = (Map)kakaoAccount.get("profile"); + + MemberProfile memberProfile = new MemberProfile(); + memberProfile.setName((String) kakaoProfile.get("nickname")); + memberProfile.setEmail((String) kakaoAccount.get("email")); + return memberProfile; + }); + + private final String registrationId; + private final Function, MemberProfile> of; + + OAuthAttributes(String registrationId, Function, MemberProfile> of) { + this.registrationId = registrationId; + this.of = of; + } + + public static MemberProfile extract(String registrationId, Map attributes) { + return Arrays.stream(values()) + .filter(provider -> registrationId.equals(provider.registrationId)) + .findFirst() + .orElseThrow(IllegalArgumentException::new) + .of.apply(attributes); + } + +} diff --git a/src/main/java/com/lpvs/auth/OAuthService.java b/src/main/java/com/lpvs/auth/OAuthService.java new file mode 100644 index 00000000..679b1fad --- /dev/null +++ b/src/main/java/com/lpvs/auth/OAuthService.java @@ -0,0 +1,77 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.auth; + +import com.lpvs.entity.LPVSMember; +import com.lpvs.repository.LPVSMemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class OAuthService implements OAuth2UserService { + + private final LPVSMemberRepository lpvsMemberRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails() + .getUserInfoEndpoint() + .getUserNameAttributeName(); + + Map attributes = oAuth2User.getAttributes(); + + MemberProfile memberProfile = OAuthAttributes.extract(registrationId, attributes); + memberProfile.setProvider(registrationId); + + Map customAttribute = customAttribute(attributes, userNameAttributeName, + memberProfile, registrationId); + + return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("USER")), + customAttribute, + userNameAttributeName); + + } + + private Map customAttribute(Map attributes, String userNameAttributeName, MemberProfile memberProfile, String registrationId) { + Map customAttribute = new LinkedHashMap<>(); + customAttribute.put(userNameAttributeName, attributes.get(userNameAttributeName)); + customAttribute.put("provider", registrationId); + customAttribute.put("name", memberProfile.getName()); + customAttribute.put("email", memberProfile.getEmail()); + return customAttribute; + + } + + private LPVSMember saveOrUpdate(MemberProfile memberProfile) { + + LPVSMember lpvsMember = lpvsMemberRepository.findByEmailAndProvider(memberProfile.getEmail(), memberProfile.getProvider()) + .map(m -> m.update(memberProfile.getName(), memberProfile.getEmail())) + .orElse(memberProfile.toMember()); + + return lpvsMemberRepository.save(lpvsMember); + } + +} diff --git a/src/main/java/com/lpvs/auth/SecurityConfig.java b/src/main/java/com/lpvs/auth/SecurityConfig.java new file mode 100644 index 00000000..056c8755 --- /dev/null +++ b/src/main/java/com/lpvs/auth/SecurityConfig.java @@ -0,0 +1,64 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final OAuthService oAuthService; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .cors() + .and() + .csrf().disable() + .headers().frameOptions().disable() + .and() + .logout() + .logoutRequestMatcher(new AntPathRequestMatcher("/oauth/logout")) + .logoutSuccessUrl("/") + .invalidateHttpSession(true) + .clearAuthentication(true) + .and() + .authorizeRequests() + .anyRequest().permitAll() + .and() + .oauth2Login() + .successHandler(new MyAuthenticationSuccessHandler()) + .defaultSuccessUrl("http://localhost:3000", true) + .userInfoEndpoint() + .userService(oAuthService); + + return http.build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.addAllowedOrigin("http://localhost:3000"); + configuration.addAllowedMethod("*"); + configuration.addAllowedHeader("*"); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + +} diff --git a/src/main/java/com/lpvs/controller/LPVSWebController.java b/src/main/java/com/lpvs/controller/LPVSWebController.java new file mode 100644 index 00000000..93c865b3 --- /dev/null +++ b/src/main/java/com/lpvs/controller/LPVSWebController.java @@ -0,0 +1,63 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.controller; + +import com.lpvs.entity.LPVSLoginMember; +import com.lpvs.entity.LPVSMember; +import com.lpvs.repository.LPVSDetectedLicenseRepository; +import com.lpvs.repository.LPVSLicenseRepository; +import com.lpvs.repository.LPVSMemberRepository; +import com.lpvs.repository.LPVSPullRequestRepository; +import com.lpvs.service.LPVSLoginCheckService; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Map; + +@Controller +public class LPVSWebController { + private LPVSMemberRepository memberRepository; + private LPVSDetectedLicenseRepository detectedLicenseRepository; + private LPVSPullRequestRepository lpvsPullRequestRepository; + private LPVSLicenseRepository licenseRepository; + private LPVSLoginCheckService lpvsLoginCheckService; + + public LPVSWebController(LPVSMemberRepository memberRepository, LPVSDetectedLicenseRepository detectedLicenseRepository, + LPVSPullRequestRepository lpvsPullRequestRepository, LPVSLicenseRepository licenseRepository, + LPVSLoginCheckService LPVSLoginCheckService) { + this.memberRepository = memberRepository; + this.detectedLicenseRepository = detectedLicenseRepository; + this.lpvsPullRequestRepository = lpvsPullRequestRepository; + this.licenseRepository = licenseRepository; + this.lpvsLoginCheckService = LPVSLoginCheckService; + } + + @GetMapping("user/info") + @ResponseBody + public LPVSMember personalInfoSettings(Authentication authentication) { + lpvsLoginCheckService.loginVerification(authentication); + return lpvsLoginCheckService.getMemberFromMemberMap(authentication); + } + + @GetMapping("login/check") + @ResponseBody + public LPVSLoginMember loginMember(Authentication authentication) { + Map oauthLoginMemberMap = lpvsLoginCheckService.getOauthLoginMemberMap(authentication); + boolean isLoggedIn = oauthLoginMemberMap == null || oauthLoginMemberMap.isEmpty(); + + if (!isLoggedIn) { + LPVSMember findMember = lpvsLoginCheckService.getMemberFromMemberMap(authentication); + return new LPVSLoginMember(!isLoggedIn, findMember); + } else { + return new LPVSLoginMember(!isLoggedIn, null); + } + } + +} diff --git a/src/main/java/com/lpvs/entity/LPVSLoginMember.java b/src/main/java/com/lpvs/entity/LPVSLoginMember.java new file mode 100644 index 00000000..f107a0b9 --- /dev/null +++ b/src/main/java/com/lpvs/entity/LPVSLoginMember.java @@ -0,0 +1,18 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter @AllArgsConstructor +public class LPVSLoginMember { + private Boolean isLoggedIn; + private LPVSMember member; +} diff --git a/src/main/java/com/lpvs/entity/LPVSMember.java b/src/main/java/com/lpvs/entity/LPVSMember.java new file mode 100644 index 00000000..81672df0 --- /dev/null +++ b/src/main/java/com/lpvs/entity/LPVSMember.java @@ -0,0 +1,65 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicUpdate; + +import javax.persistence.*; + +@Getter @NoArgsConstructor +@DynamicUpdate +@Entity +@Table(name = "member") +public class LPVSMember { + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "provider", nullable = false) + private String provider; + + @Column(name = "nickname", nullable = true, unique = true) + private String nickname; + + @Column(name = "organization", nullable = true) + private String organization; + + @Builder + public LPVSMember(Long id, String name, String email, String provider, String nickname) { + this.id = id; + this.name = name; + this.email = email; + this.provider = provider; + this.nickname = nickname; + } + + public LPVSMember update(String name, String email) { + this.name = name; + this.email = email; + return this; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public void setOrganization(String organization) { + this.organization = organization; + } +} diff --git a/src/main/java/com/lpvs/exception/ErrorResponse.java b/src/main/java/com/lpvs/exception/ErrorResponse.java new file mode 100644 index 00000000..9ebd5b33 --- /dev/null +++ b/src/main/java/com/lpvs/exception/ErrorResponse.java @@ -0,0 +1,41 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.exception; + +import java.time.LocalDateTime; + +public class ErrorResponse { + + private LocalDateTime timestamp = LocalDateTime.now(); + private String message; + private String code; + private int status; + + + public ErrorResponse(String message, String code, int status) { + this.message = message; + this.code = code; + this.status = status; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public String getMessage() { + return message; + } + + public String getCode() { + return code; + } + + public int getStatus() { + return status; + } +} diff --git a/src/main/java/com/lpvs/exception/LoginFailedException.java b/src/main/java/com/lpvs/exception/LoginFailedException.java new file mode 100644 index 00000000..67ce6071 --- /dev/null +++ b/src/main/java/com/lpvs/exception/LoginFailedException.java @@ -0,0 +1,16 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + + +package com.lpvs.exception; + +public class LoginFailedException extends RuntimeException { + + public LoginFailedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/lpvs/exception/PageControllerAdvice.java b/src/main/java/com/lpvs/exception/PageControllerAdvice.java new file mode 100644 index 00000000..ac225429 --- /dev/null +++ b/src/main/java/com/lpvs/exception/PageControllerAdvice.java @@ -0,0 +1,29 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public class PageControllerAdvice { + + private ErrorResponse errorResponse; + + @ExceptionHandler(LoginFailedException.class) + public ResponseEntity loginFailedHandle(LoginFailedException e) { + log.error("loginFailed" + e.getMessage()); + errorResponse = new ErrorResponse(e.getMessage(), HttpStatus.UNAUTHORIZED.name(), HttpStatus.UNAUTHORIZED.value()); + return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED); + } + +} diff --git a/src/main/java/com/lpvs/repository/LPVSMemberRepository.java b/src/main/java/com/lpvs/repository/LPVSMemberRepository.java new file mode 100644 index 00000000..dad6d2c3 --- /dev/null +++ b/src/main/java/com/lpvs/repository/LPVSMemberRepository.java @@ -0,0 +1,22 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.repository; + +import com.lpvs.entity.LPVSMember; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface LPVSMemberRepository extends JpaRepository { + Optional findByEmailAndProvider(String email, String provider); + + @Query(value = "select m.nickname from LPVSMember m where m.email= :email") + String findNicknameByEmail(@Param("email") String Email); +} diff --git a/src/main/java/com/lpvs/service/LPVSLoginCheckService.java b/src/main/java/com/lpvs/service/LPVSLoginCheckService.java new file mode 100644 index 00000000..3ef51929 --- /dev/null +++ b/src/main/java/com/lpvs/service/LPVSLoginCheckService.java @@ -0,0 +1,57 @@ +/** + * Copyright 2023 Basaeng, kyudori, hwan5180, quswjdgma83 + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +package com.lpvs.service; + +import com.lpvs.entity.LPVSMember; +import com.lpvs.exception.LoginFailedException; +import com.lpvs.repository.LPVSMemberRepository; +import com.lpvs.repository.LPVSPullRequestRepository; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class LPVSLoginCheckService { + private LPVSPullRequestRepository lpvsPullRequestRepository; + private LPVSMemberRepository memberRepository; + + public LPVSLoginCheckService(LPVSPullRequestRepository lpvsPullRequestRepository, LPVSMemberRepository memberRepository) { + this.lpvsPullRequestRepository = lpvsPullRequestRepository; + this.memberRepository = memberRepository; + } + public Map getOauthLoginMemberMap(Authentication authentication) { + try { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + Map attributes = oAuth2User.getAttributes(); + return attributes; + } catch (NullPointerException e) { + return null; + } + } + + public void loginVerification(Authentication authentication) { + Map oauthLoginMemberMap = getOauthLoginMemberMap(authentication); + + if (oauthLoginMemberMap == null || oauthLoginMemberMap.isEmpty()) { + throw new LoginFailedException("LoginFailedException"); + } + } + + public LPVSMember getMemberFromMemberMap(Authentication authentication) { + Map memberMap = getOauthLoginMemberMap(authentication); + String email = (String) memberMap.get("email"); + String provider = (String) memberMap.get("provider"); + + LPVSMember findMember = memberRepository.findByEmailAndProvider(email, provider).get(); + + return findMember; + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8fcfdaac..6b9ecbdf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -48,3 +48,36 @@ app.table.detectedLicenseSchema=lpvs app.table.diffFileName=code_licenses app.table.pullRequestsName=lpvs_pull_requests app.table.queueName=queue + +# Login Configuration +#Google +spring.security.oauth2.client.registration.google.client-id={YOUR_GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-secret={YOUR_GOOGLE_CLIENT_SECRET} +spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:7896/login/oauth2/code/google +spring.security.oauth2.client.registration.google.scope=profile, email + +#Naver +spring.security.oauth2.client.registration.naver.client-id={YOUR_NAVER_CLIENT_ID} +spring.security.oauth2.client.registration.naver.client-secret={YOUR_NAVER_CLIENT_SECRET} +spring.security.oauth2.client.registration.naver.redirect-uri=http://localhost:7896/login/oauth2/code/naver +spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.naver.scope=name, email, profile_image +spring.security.oauth2.client.registration.naver.client-name=Naver +#Provider-Naver +spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize +spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token +spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me +spring.security.oauth2.client.provider.naver.user-name-attribute=response + +#Kakao +spring.security.oauth2.client.registration.kakao.client-id={YOUR_KAKAO_CLIENT_ID} +spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:7896/login/oauth2/code/kakao +spring.security.oauth2.client.registration.kakao.client-authentication-method=POST +spring.security.oauth2.client.registration.kakao.authorization-grant-type = authorization_code +spring.security.oauth2.client.registration.kakao.scope=profile_nickname, profile_image, account_email +spring.security.oauth2.client.registration.kakao.client-name=Kakao +#Provider-Kakao +spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize +spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token +spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me +spring.security.oauth2.client.provider.kakao.user-name-attribute=id diff --git a/src/main/resources/database_dump.sql b/src/main/resources/database_dump.sql index 19622e83..eb864f23 100644 --- a/src/main/resources/database_dump.sql +++ b/src/main/resources/database_dump.sql @@ -164,12 +164,12 @@ DROP TABLE IF EXISTS `member`; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `member` ( `id` bigint(20) PRIMARY KEY NOT NULL AUTO_INCREMENT, - `e-mail` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, `name` varchar(255) NOT NULL, `nickname` varchar(255) DEFAULT NULL, `provider` varchar(10) NOT NULL, `organization` varchar(255) DEFAULT NULL -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */;