remove google and oauth login
This commit is contained in:
@@ -137,10 +137,8 @@ frontend/
|
|||||||
├── auth/
|
├── auth/
|
||||||
│ ├── login/
|
│ ├── login/
|
||||||
│ │ └── login.component.ts # Email/password login form
|
│ │ └── login.component.ts # Email/password login form
|
||||||
│ ├── register/
|
│ └── register/
|
||||||
│ │ └── register.component.ts # Name/email/password register form
|
│ └── register.component.ts # Name/email/password register form
|
||||||
│ └── oauth-callback/
|
|
||||||
│ └── oauth-callback.component.ts # Reads tokens from OAuth redirect
|
|
||||||
└── dashboard/
|
└── dashboard/
|
||||||
├── dashboard.component.ts # Site cards grid + toolbar
|
├── dashboard.component.ts # Site cards grid + toolbar
|
||||||
├── create-site-dialog/
|
├── create-site-dialog/
|
||||||
@@ -161,8 +159,6 @@ frontend/
|
|||||||
| POST | `/api/auth/login` | Login with email + password |
|
| POST | `/api/auth/login` | Login with email + password |
|
||||||
| POST | `/api/auth/refresh` | Refresh access token |
|
| POST | `/api/auth/refresh` | Refresh access token |
|
||||||
| POST | `/api/auth/logout` | Logout |
|
| POST | `/api/auth/logout` | Logout |
|
||||||
| GET | `/oauth2/authorization/google` | Login with Google |
|
|
||||||
| GET | `/oauth2/authorization/github` | Login with GitHub |
|
|
||||||
|
|
||||||
### Sites (`/api/sites`)
|
### Sites (`/api/sites`)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
|
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.krrishg.config;
|
package com.krrishg.config;
|
||||||
|
|
||||||
import com.krrishg.service.OAuth2SuccessHandler;
|
|
||||||
import com.krrishg.service.OAuth2UserService;
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
@@ -34,23 +32,12 @@ import java.util.UUID;
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final JwtConfig jwtConfig;
|
private final JwtConfig jwtConfig;
|
||||||
private final OAuth2UserService oAuth2UserService;
|
|
||||||
private final OAuth2SuccessHandler oAuth2SuccessHandler;
|
|
||||||
|
|
||||||
@Value("${indie.cors.allowed-origins:http://localhost:4200}")
|
@Value("${indie.cors.allowed-origins:http://localhost:4200}")
|
||||||
private String[] allowedOrigins;
|
private String[] allowedOrigins;
|
||||||
|
|
||||||
@Value("${spring.security.oauth2.client.registration.google.client-id:}")
|
public SecurityConfig(JwtConfig jwtConfig) {
|
||||||
private String googleClientId;
|
|
||||||
|
|
||||||
@Value("${spring.security.oauth2.client.registration.github.client-id:}")
|
|
||||||
private String githubClientId;
|
|
||||||
|
|
||||||
public SecurityConfig(JwtConfig jwtConfig, OAuth2UserService oAuth2UserService,
|
|
||||||
OAuth2SuccessHandler oAuth2SuccessHandler) {
|
|
||||||
this.jwtConfig = jwtConfig;
|
this.jwtConfig = jwtConfig;
|
||||||
this.oAuth2UserService = oAuth2UserService;
|
|
||||||
this.oAuth2SuccessHandler = oAuth2SuccessHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -72,25 +59,11 @@ public class SecurityConfig {
|
|||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden"))
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden"))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isOAuth2Configured()) {
|
|
||||||
http.oauth2Login(oauth2 -> oauth2
|
|
||||||
.userInfoEndpoint(userInfo -> userInfo
|
|
||||||
.userService(oAuth2UserService)
|
|
||||||
)
|
|
||||||
.successHandler(oAuth2SuccessHandler)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOAuth2Configured() {
|
|
||||||
return (googleClientId != null && !googleClientId.isBlank())
|
|
||||||
|| (githubClientId != null && !githubClientId.isBlank());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.krrishg.dto;
|
package com.krrishg.dto;
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -32,6 +31,5 @@ public class AuthResponse {
|
|||||||
private String email;
|
private String email;
|
||||||
private String name;
|
private String name;
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
private AuthProvider provider;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package com.krrishg.model;
|
package com.krrishg.model;
|
||||||
|
|
||||||
public enum AuthProvider {
|
public enum AuthProvider {
|
||||||
LOCAL, GOOGLE, GITHUB, OPENID
|
LOCAL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,6 @@ public class User {
|
|||||||
@Column(name = "avatar_url")
|
@Column(name = "avatar_url")
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Column(name = "provider", nullable = false)
|
|
||||||
private AuthProvider provider;
|
|
||||||
|
|
||||||
@Column(name = "provider_id")
|
|
||||||
private String providerId;
|
|
||||||
|
|
||||||
@Column(name = "email_verified")
|
@Column(name = "email_verified")
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private boolean emailVerified = false;
|
private boolean emailVerified = false;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.krrishg.repository;
|
package com.krrishg.repository;
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
@@ -11,7 +10,5 @@ public interface UserRepository extends JpaRepository<User, UUID> {
|
|||||||
|
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
Optional<User> findByProviderAndProviderId(AuthProvider provider, String providerId);
|
|
||||||
|
|
||||||
boolean existsByEmail(String email);
|
boolean existsByEmail(String email);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.krrishg.dto.AuthResponse;
|
|||||||
import com.krrishg.dto.LoginRequest;
|
import com.krrishg.dto.LoginRequest;
|
||||||
import com.krrishg.dto.RefreshTokenRequest;
|
import com.krrishg.dto.RefreshTokenRequest;
|
||||||
import com.krrishg.dto.RegisterRequest;
|
import com.krrishg.dto.RegisterRequest;
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import com.krrishg.repository.UserRepository;
|
import com.krrishg.repository.UserRepository;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
@@ -36,7 +35,6 @@ public class AuthService {
|
|||||||
.email(email)
|
.email(email)
|
||||||
.password(passwordEncoder.encode(request.getPassword()))
|
.password(passwordEncoder.encode(request.getPassword()))
|
||||||
.name(request.getName())
|
.name(request.getName())
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.emailVerified(false)
|
.emailVerified(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -49,10 +47,6 @@ public class AuthService {
|
|||||||
User user = userRepository.findByEmail(email)
|
User user = userRepository.findByEmail(email)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Invalid email or password"));
|
.orElseThrow(() -> new IllegalArgumentException("Invalid email or password"));
|
||||||
|
|
||||||
if (user.getProvider() != AuthProvider.LOCAL) {
|
|
||||||
throw new IllegalArgumentException("Please sign in with " + user.getProvider().name().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||||
throw new IllegalArgumentException("Invalid email or password");
|
throw new IllegalArgumentException("Invalid email or password");
|
||||||
}
|
}
|
||||||
@@ -83,7 +77,6 @@ public class AuthService {
|
|||||||
.email(user.getEmail())
|
.email(user.getEmail())
|
||||||
.name(user.getName())
|
.name(user.getName())
|
||||||
.avatarUrl(user.getAvatarUrl())
|
.avatarUrl(user.getAvatarUrl())
|
||||||
.provider(user.getProvider())
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return AuthResponse.builder()
|
return AuthResponse.builder()
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class CustomOAuth2User implements OAuth2User {
|
|
||||||
|
|
||||||
private final User user;
|
|
||||||
private final Map<String, Object> attributes;
|
|
||||||
|
|
||||||
public CustomOAuth2User(User user, Map<String, Object> attributes) {
|
|
||||||
this.user = user;
|
|
||||||
this.attributes = attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getAttributes() {
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
|
||||||
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return user.getId().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.config.JwtConfig;
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
private final JwtConfig jwtConfig;
|
|
||||||
|
|
||||||
public OAuth2SuccessHandler(JwtConfig jwtConfig) {
|
|
||||||
this.jwtConfig = jwtConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
Authentication authentication) throws IOException, ServletException {
|
|
||||||
if (authentication.getPrincipal() instanceof CustomOAuth2User oAuth2User) {
|
|
||||||
User user = oAuth2User.getUser();
|
|
||||||
|
|
||||||
String accessToken = jwtConfig.generateAccessToken(user);
|
|
||||||
String refreshToken = jwtConfig.generateRefreshToken(user);
|
|
||||||
|
|
||||||
String redirectUrl = "http://localhost:4200/oauth2/callback"
|
|
||||||
+ "?accessToken=" + accessToken
|
|
||||||
+ "&refreshToken=" + refreshToken;
|
|
||||||
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
|
||||||
} else {
|
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import com.krrishg.repository.UserRepository;
|
|
||||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class OAuth2UserService extends DefaultOAuth2UserService {
|
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
|
||||||
|
|
||||||
public OAuth2UserService(UserRepository userRepository) {
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
|
||||||
OAuth2User oAuth2User = super.loadUser(userRequest);
|
|
||||||
|
|
||||||
String registrationId = userRequest.getClientRegistration().getRegistrationId();
|
|
||||||
AuthProvider provider = switch (registrationId) {
|
|
||||||
case "google" -> AuthProvider.GOOGLE;
|
|
||||||
case "github" -> AuthProvider.GITHUB;
|
|
||||||
default -> AuthProvider.OPENID;
|
|
||||||
};
|
|
||||||
|
|
||||||
String providerId = getProviderId(oAuth2User, provider);
|
|
||||||
String email = getAttribute(oAuth2User, "email");
|
|
||||||
if (email != null) {
|
|
||||||
email = email.toLowerCase().strip();
|
|
||||||
}
|
|
||||||
String name = getAttribute(oAuth2User, "name");
|
|
||||||
String avatarUrl = getAvatarUrl(oAuth2User, provider);
|
|
||||||
|
|
||||||
User user = findOrCreateUser(provider, providerId, email, name, avatarUrl);
|
|
||||||
return new CustomOAuth2User(user, oAuth2User.getAttributes());
|
|
||||||
}
|
|
||||||
|
|
||||||
String getProviderId(OAuth2User oAuth2User, AuthProvider provider) {
|
|
||||||
return switch (provider) {
|
|
||||||
case GOOGLE -> oAuth2User.getAttribute("sub");
|
|
||||||
case GITHUB -> oAuth2User.getAttribute("id").toString();
|
|
||||||
default -> oAuth2User.getName();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String getAttribute(OAuth2User oAuth2User, String key) {
|
|
||||||
Object value = oAuth2User.getAttribute(key);
|
|
||||||
return value != null ? value.toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getAvatarUrl(OAuth2User oAuth2User, AuthProvider provider) {
|
|
||||||
return switch (provider) {
|
|
||||||
case GOOGLE -> oAuth2User.getAttribute("picture");
|
|
||||||
case GITHUB -> oAuth2User.getAttribute("avatar_url");
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
User findOrCreateUser(AuthProvider provider, String providerId,
|
|
||||||
String email, String name, String avatarUrl) {
|
|
||||||
Optional<User> existing = userRepository.findByProviderAndProviderId(provider, providerId);
|
|
||||||
|
|
||||||
if (existing.isPresent()) {
|
|
||||||
User user = existing.get();
|
|
||||||
user.setName(name);
|
|
||||||
if (avatarUrl != null) {
|
|
||||||
user.setAvatarUrl(avatarUrl);
|
|
||||||
}
|
|
||||||
if (email != null && !email.equals(user.getEmail())) {
|
|
||||||
user.setEmail(email);
|
|
||||||
}
|
|
||||||
return userRepository.save(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email != null) {
|
|
||||||
Optional<User> emailUser = userRepository.findByEmail(email);
|
|
||||||
if (emailUser.isPresent()) {
|
|
||||||
User user = emailUser.get();
|
|
||||||
user.setProvider(provider);
|
|
||||||
user.setProviderId(providerId);
|
|
||||||
user.setEmailVerified(true);
|
|
||||||
if (avatarUrl != null) {
|
|
||||||
user.setAvatarUrl(avatarUrl);
|
|
||||||
}
|
|
||||||
return userRepository.save(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
User newUser = User.builder()
|
|
||||||
.email(email != null ? email : providerId + "@" + provider.name().toLowerCase() + ".indie")
|
|
||||||
.name(name != null ? name : "User")
|
|
||||||
.avatarUrl(avatarUrl)
|
|
||||||
.provider(provider)
|
|
||||||
.providerId(providerId)
|
|
||||||
.emailVerified(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return userRepository.save(newUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import com.krrishg.dto.AuthResponse.UserInfo;
|
|||||||
import com.krrishg.dto.LoginRequest;
|
import com.krrishg.dto.LoginRequest;
|
||||||
import com.krrishg.dto.RefreshTokenRequest;
|
import com.krrishg.dto.RefreshTokenRequest;
|
||||||
import com.krrishg.dto.RegisterRequest;
|
import com.krrishg.dto.RegisterRequest;
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.service.AuthService;
|
import com.krrishg.service.AuthService;
|
||||||
import com.krrishg.support.TestSecurityConfig;
|
import com.krrishg.support.TestSecurityConfig;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -46,7 +45,7 @@ class AuthControllerTest {
|
|||||||
.accessToken("at-123")
|
.accessToken("at-123")
|
||||||
.refreshToken("rt-456")
|
.refreshToken("rt-456")
|
||||||
.tokenType("Bearer")
|
.tokenType("Bearer")
|
||||||
.user(new UserInfo(UUID.randomUUID(), "test@example.com", "Test", null, AuthProvider.LOCAL))
|
.user(new UserInfo(UUID.randomUUID(), "test@example.com", "Test", null))
|
||||||
.build();
|
.build();
|
||||||
when(authService.register(any(RegisterRequest.class))).thenReturn(response);
|
when(authService.register(any(RegisterRequest.class))).thenReturn(response);
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ class AuthControllerTest {
|
|||||||
.accessToken("at-123")
|
.accessToken("at-123")
|
||||||
.refreshToken("rt-456")
|
.refreshToken("rt-456")
|
||||||
.tokenType("Bearer")
|
.tokenType("Bearer")
|
||||||
.user(new UserInfo(UUID.randomUUID(), "login@example.com", "Login User", null, AuthProvider.LOCAL))
|
.user(new UserInfo(UUID.randomUUID(), "login@example.com", "Login User", null))
|
||||||
.build();
|
.build();
|
||||||
when(authService.login(any(LoginRequest.class))).thenReturn(response);
|
when(authService.login(any(LoginRequest.class))).thenReturn(response);
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ class AuthControllerTest {
|
|||||||
.accessToken("new-at")
|
.accessToken("new-at")
|
||||||
.refreshToken("new-rt")
|
.refreshToken("new-rt")
|
||||||
.tokenType("Bearer")
|
.tokenType("Bearer")
|
||||||
.user(new UserInfo(UUID.randomUUID(), "user@example.com", "User", null, AuthProvider.LOCAL))
|
.user(new UserInfo(UUID.randomUUID(), "user@example.com", "User", null))
|
||||||
.build();
|
.build();
|
||||||
when(authService.refreshToken(any(RefreshTokenRequest.class))).thenReturn(response);
|
when(authService.refreshToken(any(RefreshTokenRequest.class))).thenReturn(response);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.krrishg.config.EncryptionService;
|
|||||||
import com.krrishg.config.GlobalExceptionHandler;
|
import com.krrishg.config.GlobalExceptionHandler;
|
||||||
import com.krrishg.config.JwtAuthenticationToken;
|
import com.krrishg.config.JwtAuthenticationToken;
|
||||||
import com.krrishg.config.UserPrincipal;
|
import com.krrishg.config.UserPrincipal;
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import com.krrishg.repository.UserRepository;
|
import com.krrishg.repository.UserRepository;
|
||||||
import com.krrishg.support.TestSecurityConfig;
|
import com.krrishg.support.TestSecurityConfig;
|
||||||
@@ -61,7 +60,6 @@ class UserSettingsControllerTest {
|
|||||||
.id(userId)
|
.id(userId)
|
||||||
.email("user@test.com")
|
.email("user@test.com")
|
||||||
.name("Test User")
|
.name("Test User")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.krrishg.dto;
|
package com.krrishg.dto;
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -12,7 +11,7 @@ class AuthResponseTest {
|
|||||||
@Test
|
@Test
|
||||||
void constructorSetsBearerTokenType() {
|
void constructorSetsBearerTokenType() {
|
||||||
AuthResponse.UserInfo userInfo = new AuthResponse.UserInfo(
|
AuthResponse.UserInfo userInfo = new AuthResponse.UserInfo(
|
||||||
UUID.randomUUID(), "test@example.com", "Test", null, AuthProvider.LOCAL);
|
UUID.randomUUID(), "test@example.com", "Test", null);
|
||||||
|
|
||||||
AuthResponse response = new AuthResponse("access-token", "refresh-token", userInfo);
|
AuthResponse response = new AuthResponse("access-token", "refresh-token", userInfo);
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ class AuthResponseTest {
|
|||||||
.id(UUID.randomUUID())
|
.id(UUID.randomUUID())
|
||||||
.email("user@example.com")
|
.email("user@example.com")
|
||||||
.name("User")
|
.name("User")
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
AuthResponse response = AuthResponse.builder()
|
AuthResponse response = AuthResponse.builder()
|
||||||
@@ -50,13 +48,11 @@ class AuthResponseTest {
|
|||||||
.email("avatar@example.com")
|
.email("avatar@example.com")
|
||||||
.name("Avatar User")
|
.name("Avatar User")
|
||||||
.avatarUrl("https://example.com/avatar.png")
|
.avatarUrl("https://example.com/avatar.png")
|
||||||
.provider(AuthProvider.GITHUB)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertEquals(id, userInfo.getId());
|
assertEquals(id, userInfo.getId());
|
||||||
assertEquals("avatar@example.com", userInfo.getEmail());
|
assertEquals("avatar@example.com", userInfo.getEmail());
|
||||||
assertEquals("Avatar User", userInfo.getName());
|
assertEquals("Avatar User", userInfo.getName());
|
||||||
assertEquals("https://example.com/avatar.png", userInfo.getAvatarUrl());
|
assertEquals("https://example.com/avatar.png", userInfo.getAvatarUrl());
|
||||||
assertEquals(AuthProvider.GITHUB, userInfo.getProvider());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class AuthProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
void enumValues() {
|
void enumValues() {
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
new AuthProvider[]{AuthProvider.LOCAL, AuthProvider.GOOGLE, AuthProvider.GITHUB, AuthProvider.OPENID},
|
new AuthProvider[]{AuthProvider.LOCAL},
|
||||||
AuthProvider.values()
|
AuthProvider.values()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class UserTest {
|
|||||||
User user = new User();
|
User user = new User();
|
||||||
user.setEmail("test@example.com");
|
user.setEmail("test@example.com");
|
||||||
user.setName("Test User");
|
user.setName("Test User");
|
||||||
user.setProvider(AuthProvider.LOCAL);
|
|
||||||
|
|
||||||
assertNull(user.getId());
|
assertNull(user.getId());
|
||||||
assertNull(user.getCreatedAt());
|
assertNull(user.getCreatedAt());
|
||||||
@@ -33,7 +32,6 @@ class UserTest {
|
|||||||
.id(UUID.randomUUID())
|
.id(UUID.randomUUID())
|
||||||
.email("test@example.com")
|
.email("test@example.com")
|
||||||
.name("Test")
|
.name("Test")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
UUID existingId = user.getId();
|
UUID existingId = user.getId();
|
||||||
@@ -47,7 +45,6 @@ class UserTest {
|
|||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("test@example.com")
|
.email("test@example.com")
|
||||||
.name("Test")
|
.name("Test")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
user.onCreate();
|
user.onCreate();
|
||||||
|
|
||||||
@@ -63,7 +60,6 @@ class UserTest {
|
|||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("test@example.com")
|
.email("test@example.com")
|
||||||
.name("Test")
|
.name("Test")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertFalse(user.isEmailVerified());
|
assertFalse(user.isEmailVerified());
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.krrishg.repository;
|
package com.krrishg.repository;
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -25,7 +24,6 @@ class UserRepositoryTest {
|
|||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("test@example.com")
|
.email("test@example.com")
|
||||||
.name("Test User")
|
.name("Test User")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
User saved = userRepository.save(user);
|
User saved = userRepository.save(user);
|
||||||
@@ -42,7 +40,6 @@ class UserRepositoryTest {
|
|||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("find@example.com")
|
.email("find@example.com")
|
||||||
.name("Find Me")
|
.name("Find Me")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
@@ -57,27 +54,11 @@ class UserRepositoryTest {
|
|||||||
assertTrue(found.isEmpty());
|
assertTrue(found.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void findByProviderAndProviderId() {
|
|
||||||
User user = User.builder()
|
|
||||||
.email("oauth@example.com")
|
|
||||||
.name("OAuth User")
|
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.providerId("google-123")
|
|
||||||
.build();
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
Optional<User> found = userRepository.findByProviderAndProviderId(AuthProvider.GOOGLE, "google-123");
|
|
||||||
assertTrue(found.isPresent());
|
|
||||||
assertEquals("oauth@example.com", found.get().getEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void existsByEmail() {
|
void existsByEmail() {
|
||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("exists@example.com")
|
.email("exists@example.com")
|
||||||
.name("Exists")
|
.name("Exists")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.krrishg.dto.AuthResponse;
|
|||||||
import com.krrishg.dto.LoginRequest;
|
import com.krrishg.dto.LoginRequest;
|
||||||
import com.krrishg.dto.RefreshTokenRequest;
|
import com.krrishg.dto.RefreshTokenRequest;
|
||||||
import com.krrishg.dto.RegisterRequest;
|
import com.krrishg.dto.RegisterRequest;
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import com.krrishg.support.FakeJwtConfig;
|
import com.krrishg.support.FakeJwtConfig;
|
||||||
import com.krrishg.support.FakeUserRepository;
|
import com.krrishg.support.FakeUserRepository;
|
||||||
@@ -39,7 +38,6 @@ class AuthServiceTest {
|
|||||||
assertEquals("Bearer", response.getTokenType());
|
assertEquals("Bearer", response.getTokenType());
|
||||||
assertEquals("new@example.com", response.getUser().getEmail());
|
assertEquals("new@example.com", response.getUser().getEmail());
|
||||||
assertEquals("New User", response.getUser().getName());
|
assertEquals("New User", response.getUser().getName());
|
||||||
assertEquals(AuthProvider.LOCAL, response.getUser().getProvider());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -119,25 +117,6 @@ class AuthServiceTest {
|
|||||||
assertEquals("Invalid email or password", ex.getMessage());
|
assertEquals("Invalid email or password", ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void loginThrowsOnOAuthUserTryingPasswordLogin() {
|
|
||||||
User oauthUser = User.builder()
|
|
||||||
.email("oauth@example.com")
|
|
||||||
.name("OAuth User")
|
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.providerId("google-123")
|
|
||||||
.build();
|
|
||||||
userRepository.save(oauthUser);
|
|
||||||
|
|
||||||
LoginRequest login = new LoginRequest();
|
|
||||||
login.setEmail("oauth@example.com");
|
|
||||||
login.setPassword("anypassword");
|
|
||||||
|
|
||||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
|
||||||
() -> authService.login(login));
|
|
||||||
assertEquals("Please sign in with google", ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void refreshTokenReturnsNewTokens() {
|
void refreshTokenReturnsNewTokens() {
|
||||||
RegisterRequest reg = new RegisterRequest();
|
RegisterRequest reg = new RegisterRequest();
|
||||||
@@ -171,7 +150,6 @@ class AuthServiceTest {
|
|||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email("deleted@example.com")
|
.email("deleted@example.com")
|
||||||
.name("Deleted")
|
.name("Deleted")
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.build();
|
.build();
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class CustomOAuth2UserTest {
|
|
||||||
|
|
||||||
private final User user = User.builder()
|
|
||||||
.id(UUID.randomUUID())
|
|
||||||
.email("user@example.com")
|
|
||||||
.name("Test User")
|
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.providerId("google-123")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final Map<String, Object> attributes = Map.of("sub", "google-123", "email", "user@example.com");
|
|
||||||
|
|
||||||
private final CustomOAuth2User oAuth2User = new CustomOAuth2User(user, attributes);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getNameReturnsUserId() {
|
|
||||||
assertEquals(user.getId().toString(), oAuth2User.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAttributesReturnsProvidedMap() {
|
|
||||||
assertEquals(attributes, oAuth2User.getAttributes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAuthoritiesContainsRoleUser() {
|
|
||||||
var authorities = oAuth2User.getAuthorities();
|
|
||||||
assertEquals(1, authorities.size());
|
|
||||||
assertEquals("ROLE_USER", authorities.iterator().next().getAuthority());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getUserReturnsTheUser() {
|
|
||||||
assertSame(user, oAuth2User.getUser());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.config.JwtConfig;
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.RedirectStrategy;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
class OAuth2SuccessHandlerTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private JwtConfig jwtConfig;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletResponse response;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Authentication authentication;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RedirectStrategy redirectStrategy;
|
|
||||||
|
|
||||||
private OAuth2SuccessHandler handler;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
handler = new OAuth2SuccessHandler(jwtConfig);
|
|
||||||
handler.setRedirectStrategy(redirectStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void onAuthenticationSuccessWithOAuth2UserRedirectsWithTokens() throws Exception {
|
|
||||||
CustomOAuth2User oAuth2User = mock(CustomOAuth2User.class);
|
|
||||||
User user = User.builder()
|
|
||||||
.id(UUID.randomUUID())
|
|
||||||
.email("user@example.com")
|
|
||||||
.name("User")
|
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.build();
|
|
||||||
when(authentication.getPrincipal()).thenReturn(oAuth2User);
|
|
||||||
when(oAuth2User.getUser()).thenReturn(user);
|
|
||||||
when(jwtConfig.generateAccessToken(user)).thenReturn("access-token-value");
|
|
||||||
when(jwtConfig.generateRefreshToken(user)).thenReturn("refresh-token-value");
|
|
||||||
|
|
||||||
handler.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
|
|
||||||
String expectedRedirect = "http://localhost:4200/oauth2/callback"
|
|
||||||
+ "?accessToken=access-token-value&refreshToken=refresh-token-value";
|
|
||||||
verify(redirectStrategy).sendRedirect(request, response, expectedRedirect);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void onAuthenticationSuccessWithNonOAuth2UserCallsSuper() throws Exception {
|
|
||||||
when(authentication.getPrincipal()).thenReturn("plain principal");
|
|
||||||
|
|
||||||
handler.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
|
|
||||||
verify(redirectStrategy).sendRedirect(eq(request), eq(response), anyString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
package com.krrishg.service;
|
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
|
||||||
import com.krrishg.support.FakeUserRepository;
|
|
||||||
import com.krrishg.support.TestOAuth2User;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class OAuth2UserServiceTest {
|
|
||||||
|
|
||||||
private FakeUserRepository userRepository;
|
|
||||||
private OAuth2UserService oAuth2UserService;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
userRepository = new FakeUserRepository();
|
|
||||||
oAuth2UserService = new OAuth2UserService(userRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getProviderIdForGoogleReturnsSub() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of("sub", "google-123"));
|
|
||||||
assertEquals("google-123", oAuth2UserService.getProviderId(oAuth2User, AuthProvider.GOOGLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getProviderIdForGitHubReturnsId() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of("id", 456));
|
|
||||||
assertEquals("456", oAuth2UserService.getProviderId(oAuth2User, AuthProvider.GITHUB));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAvatarUrlForGoogleReturnsPicture() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of("picture", "https://example.com/avatar.jpg"));
|
|
||||||
assertEquals("https://example.com/avatar.jpg",
|
|
||||||
oAuth2UserService.getAvatarUrl(oAuth2User, AuthProvider.GOOGLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAvatarUrlForGitHubReturnsAvatarUrl() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of("avatar_url", "https://github.com/avatar.png"));
|
|
||||||
assertEquals("https://github.com/avatar.png",
|
|
||||||
oAuth2UserService.getAvatarUrl(oAuth2User, AuthProvider.GITHUB));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAvatarUrlForOpenIdReturnsNull() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of());
|
|
||||||
assertNull(oAuth2UserService.getAvatarUrl(oAuth2User, AuthProvider.OPENID));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAttributeReturnsStringValue() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of("email", "test@example.com"));
|
|
||||||
assertEquals("test@example.com", oAuth2UserService.getAttribute(oAuth2User, "email"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAttributeReturnsNullWhenMissing() {
|
|
||||||
TestOAuth2User oAuth2User = new TestOAuth2User(Map.of());
|
|
||||||
assertNull(oAuth2UserService.getAttribute(oAuth2User, "missing"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findOrCreateUserCreatesNewUser() {
|
|
||||||
User user = oAuth2UserService.findOrCreateUser(
|
|
||||||
AuthProvider.GOOGLE, "google-789",
|
|
||||||
"new@example.com", "New User", "https://example.com/avatar.jpg");
|
|
||||||
|
|
||||||
assertNotNull(user.getId());
|
|
||||||
assertEquals("new@example.com", user.getEmail());
|
|
||||||
assertEquals("New User", user.getName());
|
|
||||||
assertEquals(AuthProvider.GOOGLE, user.getProvider());
|
|
||||||
assertEquals("google-789", user.getProviderId());
|
|
||||||
assertTrue(user.isEmailVerified());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findOrCreateUserUpdatesExistingOAuthUser() {
|
|
||||||
User existing = User.builder()
|
|
||||||
.email("existing@example.com")
|
|
||||||
.name("Old Name")
|
|
||||||
.provider(AuthProvider.GOOGLE)
|
|
||||||
.providerId("google-456")
|
|
||||||
.build();
|
|
||||||
userRepository.save(existing);
|
|
||||||
|
|
||||||
User updated = oAuth2UserService.findOrCreateUser(
|
|
||||||
AuthProvider.GOOGLE, "google-456",
|
|
||||||
"existing@example.com", "Updated Name", "https://example.com/new-avatar.jpg");
|
|
||||||
|
|
||||||
assertEquals("Updated Name", updated.getName());
|
|
||||||
assertEquals("https://example.com/new-avatar.jpg", updated.getAvatarUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findOrCreateUserLinksLocalUserToOAuth() {
|
|
||||||
User localUser = User.builder()
|
|
||||||
.email("link@example.com")
|
|
||||||
.name("Link User")
|
|
||||||
.provider(AuthProvider.LOCAL)
|
|
||||||
.password("hashedpw")
|
|
||||||
.build();
|
|
||||||
userRepository.save(localUser);
|
|
||||||
|
|
||||||
User linked = oAuth2UserService.findOrCreateUser(
|
|
||||||
AuthProvider.GITHUB, "github-789",
|
|
||||||
"link@example.com", "Link User", "https://github.com/avatar.png");
|
|
||||||
|
|
||||||
assertEquals(AuthProvider.GITHUB, linked.getProvider());
|
|
||||||
assertEquals("github-789", linked.getProviderId());
|
|
||||||
assertTrue(linked.isEmailVerified());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findOrCreateUserGeneratesEmailWhenMissing() {
|
|
||||||
User user = oAuth2UserService.findOrCreateUser(
|
|
||||||
AuthProvider.GITHUB, "github-nomail",
|
|
||||||
null, "No Email", null);
|
|
||||||
|
|
||||||
assertTrue(user.getEmail().contains("github-nomail"));
|
|
||||||
assertTrue(user.getEmail().endsWith("@github.indie"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.krrishg.support;
|
package com.krrishg.support;
|
||||||
|
|
||||||
import com.krrishg.model.AuthProvider;
|
|
||||||
import com.krrishg.model.User;
|
import com.krrishg.model.User;
|
||||||
import com.krrishg.repository.UserRepository;
|
import com.krrishg.repository.UserRepository;
|
||||||
|
|
||||||
@@ -32,13 +31,6 @@ public class FakeUserRepository extends InMemoryRepository<User, UUID> implement
|
|||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<User> findByProviderAndProviderId(AuthProvider provider, String providerId) {
|
|
||||||
return store.values().stream()
|
|
||||||
.filter(u -> u.getProvider() == provider && providerId.equals(u.getProviderId()))
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean existsByEmail(String email) {
|
public boolean existsByEmail(String email) {
|
||||||
return store.values().stream()
|
return store.values().stream()
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.krrishg.support;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class TestOAuth2User implements OAuth2User {
|
|
||||||
|
|
||||||
private final Map<String, Object> attributes;
|
|
||||||
|
|
||||||
public TestOAuth2User(Map<String, Object> attributes) {
|
|
||||||
this.attributes = attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getAttributes() {
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
|
||||||
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
Object sub = attributes.get("sub");
|
|
||||||
if (sub != null) return sub.toString();
|
|
||||||
Object id = attributes.get("id");
|
|
||||||
if (id != null) return id.toString();
|
|
||||||
return "test-user";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,12 +11,4 @@ server {
|
|||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend:8080;
|
proxy_pass http://backend:8080;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /oauth2/ {
|
|
||||||
proxy_pass http://backend:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /login/ {
|
|
||||||
proxy_pass http://backend:8080;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,5 @@
|
|||||||
"/api": {
|
"/api": {
|
||||||
"target": "http://127.0.0.1:8080",
|
"target": "http://127.0.0.1:8080",
|
||||||
"secure": false
|
"secure": false
|
||||||
},
|
|
||||||
"/oauth2": {
|
|
||||||
"target": "http://127.0.0.1:8080",
|
|
||||||
"secure": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ export const routes: Routes = [
|
|||||||
loadComponent: () => import('./auth/register/register.component').then(m => m.RegisterComponent),
|
loadComponent: () => import('./auth/register/register.component').then(m => m.RegisterComponent),
|
||||||
canActivate: [LoginGuard],
|
canActivate: [LoginGuard],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'oauth2/callback',
|
|
||||||
loadComponent: () => import('./auth/oauth-callback/oauth-callback.component').then(m => m.OAuthCallbackComponent),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
|
||||||
import { AuthService } from '../../core/services/auth.service';
|
import { AuthService } from '../../core/services/auth.service';
|
||||||
import { NgIf } from '@angular/common';
|
import { NgIf } from '@angular/common';
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ import { NgIf } from '@angular/common';
|
|||||||
imports: [
|
imports: [
|
||||||
RouterLink, ReactiveFormsModule, NgIf,
|
RouterLink, ReactiveFormsModule, NgIf,
|
||||||
MatCardModule, MatFormFieldModule, MatInputModule, MatButtonModule,
|
MatCardModule, MatFormFieldModule, MatInputModule, MatButtonModule,
|
||||||
MatProgressSpinnerModule, MatDividerModule,
|
MatProgressSpinnerModule,
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
<div class="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
<div class="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||||
@@ -48,19 +47,6 @@ import { NgIf } from '@angular/common';
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="my-4">
|
|
||||||
<mat-divider></mat-divider>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<a mat-stroked-button href="/oauth2/authorization/google" class="w-full">
|
|
||||||
Sign in with Google
|
|
||||||
</a>
|
|
||||||
<a mat-stroked-button href="/oauth2/authorization/github" class="w-full">
|
|
||||||
Sign in with GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-center mt-6 text-sm text-gray-500">
|
<p class="text-center mt-6 text-sm text-gray-500">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<a routerLink="/register" class="text-indigo-600 font-medium hover:underline">Register</a>
|
<a routerLink="/register" class="text-indigo-600 font-medium hover:underline">Register</a>
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { AuthService } from '../../core/services/auth.service';
|
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-oauth-callback',
|
|
||||||
standalone: true,
|
|
||||||
imports: [MatProgressSpinnerModule],
|
|
||||||
template: `
|
|
||||||
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
||||||
<div class="text-center">
|
|
||||||
<mat-spinner diameter="40" class="mx-auto mb-4"></mat-spinner>
|
|
||||||
<p class="text-gray-500">Completing sign in...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
export class OAuthCallbackComponent implements OnInit {
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private router: Router,
|
|
||||||
private authService: AuthService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const accessToken = this.route.snapshot.queryParamMap.get('accessToken');
|
|
||||||
const refreshToken = this.route.snapshot.queryParamMap.get('refreshToken');
|
|
||||||
|
|
||||||
if (accessToken && refreshToken) {
|
|
||||||
this.authService.setSessionFromOAuth(accessToken, refreshToken);
|
|
||||||
this.router.navigate(['/dashboard']);
|
|
||||||
} else {
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,5 +10,4 @@ export interface UserInfo {
|
|||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
provider: 'LOCAL' | 'GOOGLE' | 'GITHUB' | 'OPENID';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,23 +50,6 @@ export class AuthService {
|
|||||||
return !!this.getAccessToken();
|
return !!this.getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionFromOAuth(accessToken: string, refreshToken: string): void {
|
|
||||||
localStorage.setItem(this.ACCESS_TOKEN_KEY, accessToken);
|
|
||||||
localStorage.setItem(this.REFRESH_TOKEN_KEY, refreshToken);
|
|
||||||
const payload = this.decodeToken(accessToken);
|
|
||||||
if (payload) {
|
|
||||||
const user: UserInfo = {
|
|
||||||
id: payload.sub,
|
|
||||||
email: payload.email,
|
|
||||||
name: payload.name,
|
|
||||||
avatarUrl: payload.avatarUrl || '',
|
|
||||||
provider: payload.provider || 'LOCAL',
|
|
||||||
};
|
|
||||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
|
||||||
this.userSubject.next(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSessionForLogout(): void {
|
clearSessionForLogout(): void {
|
||||||
this.clearSession();
|
this.clearSession();
|
||||||
}
|
}
|
||||||
@@ -90,12 +73,4 @@ export class AuthService {
|
|||||||
return data ? JSON.parse(data) : null;
|
return data ? JSON.parse(data) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeToken(token: string): any {
|
|
||||||
try {
|
|
||||||
const parts = token.split('.');
|
|
||||||
return JSON.parse(atob(parts[1]));
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user