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 00000000..98f3e267 Binary files /dev/null and b/frontend/public/image/png/GoogleLogin.png differ diff --git a/frontend/public/image/png/KakaoLogin.png b/frontend/public/image/png/KakaoLogin.png new file mode 100644 index 00000000..f6473853 Binary files /dev/null and b/frontend/public/image/png/KakaoLogin.png differ 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 00000000..5e9129f9 Binary files /dev/null and b/frontend/public/image/png/NaverLogin.png differ diff --git a/frontend/public/image/png/ProfileImg.png b/frontend/public/image/png/ProfileImg.png new file mode 100644 index 00000000..7b68546c Binary files /dev/null and b/frontend/public/image/png/ProfileImg.png differ diff --git a/frontend/public/image/svg/SNSWrapper.svg b/frontend/public/image/svg/SNSWrapper.svg new file mode 100644 index 00000000..ddd04563 --- /dev/null +++ b/frontend/public/image/svg/SNSWrapper.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + 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 */;