diff --git a/backend/src/main/java/pl/polskalokalnie/auth/AuthController.java b/backend/src/main/java/pl/polskalokalnie/auth/AuthController.java index 3f0a3b6..1ae9ba5 100644 --- a/backend/src/main/java/pl/polskalokalnie/auth/AuthController.java +++ b/backend/src/main/java/pl/polskalokalnie/auth/AuthController.java @@ -23,7 +23,6 @@ import pl.polskalokalnie.auth.dto.AuthResponse; import pl.polskalokalnie.auth.dto.LoginRequest; import pl.polskalokalnie.auth.dto.RegisterRequest; import pl.polskalokalnie.auth.dto.RegisterResponse; -import pl.polskalokalnie.auth.dto.SocialLoginRequest; import pl.polskalokalnie.auth.dto.UpdateProfileRequest; import pl.polskalokalnie.auth.dto.UserResponse; @@ -47,11 +46,6 @@ public class AuthController { return authService.login(request); } - @PostMapping("/social") - public AuthResponse social(@Valid @RequestBody SocialLoginRequest request) { - return authService.socialLogin(request); - } - // --- Aktywacja konta --- @PostMapping("/activate") diff --git a/backend/src/main/java/pl/polskalokalnie/auth/AuthService.java b/backend/src/main/java/pl/polskalokalnie/auth/AuthService.java index cabcc3b..7c2dc27 100644 --- a/backend/src/main/java/pl/polskalokalnie/auth/AuthService.java +++ b/backend/src/main/java/pl/polskalokalnie/auth/AuthService.java @@ -19,7 +19,6 @@ import pl.polskalokalnie.auth.dto.AuthResponse; import pl.polskalokalnie.auth.dto.LoginRequest; import pl.polskalokalnie.auth.dto.RegisterRequest; import pl.polskalokalnie.auth.dto.RegisterResponse; -import pl.polskalokalnie.auth.dto.SocialLoginRequest; import pl.polskalokalnie.auth.dto.UpdateProfileRequest; import pl.polskalokalnie.auth.dto.UserResponse; import pl.polskalokalnie.user.AccountType; @@ -261,45 +260,6 @@ public class AuthService { return authToken; } - /** - * Symulowane logowanie spoleczne: znajduje lub tworzy konto powiazane z dostawca. - * Nie ma tu prawdziwego OAuth - dane przychodza z frontendu jako demo. - */ - public AuthResponse socialLogin(SocialLoginRequest request) { - AuthProvider provider = request.provider(); - String email = normalizeEmail( - request.email() != null && !request.email().isBlank() - ? request.email() - : defaultSocialEmail(provider)); - String name = request.fullName() != null && !request.fullName().isBlank() - ? request.fullName().trim() - : defaultSocialName(provider); - - textModerationService.validateOrThrow(name); - - AppUser user = userRepository.findByEmailIgnoreCase(email).orElseGet(() -> { - if (blockedEmailRepository.existsByEmailIgnoreCase(email)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Ten adres e-mail został zablokowany i nie można go już użyć do rejestracji"); - } - AppUser created = new AppUser(); - created.setEmail(email); - created.setFullName(name); - created.setNick(nickService.generateFromName(name, email, null)); - created.setRole(Role.USER); - created.setProvider(provider); - // Logowanie spoleczne nie wymaga rekopisania danych, wiec konto jest od razu zweryfikowane. - created.setVerified(true); - return userRepository.save(created); - }); - - if (user.isBlocked()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Konto zostało zablokowane"); - } - - leadSyncService.syncUser(user); - return buildAuthResponse(user); - } - // Odstep, po ktorym wejscie do aplikacji (odswiezenie /auth/me) na nowo odnotowuje aktywnosc. // Bez tego zapisywalibysmy do bazy przy kazdym zadaniu profilu - a wystarczy zgrubna dokladnosc. private static final Duration LAST_ACTIVE_REFRESH = Duration.ofMinutes(2); @@ -491,20 +451,4 @@ public class AuthService { String source = fullName != null && !fullName.isBlank() ? fullName : user.getFullName(); return nickService.generateFromName(source, user.getEmail(), user); } - - private String defaultSocialEmail(AuthProvider provider) { - return switch (provider) { - case GOOGLE -> "demo.google@gmail.com"; - case FACEBOOK -> "demo.facebook@facebook.com"; - case LOCAL -> "demo.local@polskalokalnie.pl"; - }; - } - - private String defaultSocialName(AuthProvider provider) { - return switch (provider) { - case GOOGLE -> "Użytkownik Google"; - case FACEBOOK -> "Użytkownik Facebook"; - case LOCAL -> "Użytkownik"; - }; - } } diff --git a/backend/src/main/java/pl/polskalokalnie/auth/SecurityConfig.java b/backend/src/main/java/pl/polskalokalnie/auth/SecurityConfig.java index 550de4d..cb23376 100644 --- a/backend/src/main/java/pl/polskalokalnie/auth/SecurityConfig.java +++ b/backend/src/main/java/pl/polskalokalnie/auth/SecurityConfig.java @@ -35,7 +35,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/error").permitAll() - .requestMatchers("/api/auth/register", "/api/auth/login", "/api/auth/social").permitAll() + .requestMatchers("/api/auth/register", "/api/auth/login").permitAll() .requestMatchers("/api/auth/activate", "/api/auth/resend-activation", "/api/auth/forgot-password", "/api/auth/reset-password", "/api/auth/verify-phone", "/api/auth/resend-phone-otp").permitAll() diff --git a/backend/src/main/java/pl/polskalokalnie/auth/dto/SocialLoginRequest.java b/backend/src/main/java/pl/polskalokalnie/auth/dto/SocialLoginRequest.java deleted file mode 100644 index a617062..0000000 --- a/backend/src/main/java/pl/polskalokalnie/auth/dto/SocialLoginRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package pl.polskalokalnie.auth.dto; - -import jakarta.validation.constraints.NotNull; -import pl.polskalokalnie.user.AuthProvider; - -/** - * Symulowane logowanie spoleczne. W wersji produkcyjnej email/name pochodzilyby - * z tokenu dostawcy OAuth (Google/Facebook); tutaj przychodza z frontendu jako dane demo. - */ -public record SocialLoginRequest( - @NotNull AuthProvider provider, - String email, - String fullName -) { -} diff --git a/backend/src/test/java/pl/polskalokalnie/auth/AuthControllerTest.java b/backend/src/test/java/pl/polskalokalnie/auth/AuthControllerTest.java new file mode 100644 index 0000000..e468d41 --- /dev/null +++ b/backend/src/test/java/pl/polskalokalnie/auth/AuthControllerTest.java @@ -0,0 +1,44 @@ +package pl.polskalokalnie.auth; + +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest +@AutoConfigureMockMvc +class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Regresja: symulowane logowanie spoleczne wydawalo token dla dowolnego adresu e-mail + // bez hasla - wystarczylo podac adres administratora, zeby dostac jego sesje. + @Test + void socialLoginEndpointDoesNotIssueToken() throws Exception { + mockMvc.perform(post("/api/auth/social") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"provider\":\"GOOGLE\",\"email\":\"admin@softspm.com\"}")) + .andExpect(status().is4xxClientError()) + .andExpect(content().string(not(containsString("token")))); + } + + @Test + void adminCanLogInWithEmailAndPassword() throws Exception { + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"email\":\"admin@softspm.com\",\"password\":\"Admin123!\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.token").isNotEmpty()) + .andExpect(jsonPath("$.user.role").value("ADMIN")); + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 74d942e..a02974b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12984,28 +12984,8 @@ type LoginStep = 'login' | 'register-choice' | 'register-private' | 'register-co // Dane firmy z rejestru REGON (GUS) - odpowiedz /api/gus/company. type GusCompany = { name: string | null; nip: string; regon: string | null; address: string | null }; -// Oficjalne logotypy dostawcow logowania spolecznosciowego (SVG w barwach marki). -function GoogleIcon() { - return ( - - ); -} - -function FacebookIcon() { - return ( - - ); -} - function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { - const { login, register, socialLogin, forgotPassword, resendActivation } = useAuth(); + const { login, register, forgotPassword, resendActivation } = useAuth(); const [step, setStep] = useState('login'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -13181,37 +13161,8 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { } }; - const socialButtons = ( - <> -
- lub kontynuuj z -
- -
- - -
- -

Twoje dane są u nas bezpieczne. Stosujemy szyfrowanie SSL.

- + const securityNote = ( +

Twoje dane są u nas bezpieczne. Stosujemy szyfrowanie SSL.

); return ( @@ -13326,7 +13277,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { {submitting ? 'Przetwarzanie…' : 'Zaloguj się'} - {socialButtons} + {securityNote} )} @@ -13493,7 +13444,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { {submitting ? 'Przetwarzanie…' : 'Zarejestruj się'} - {socialButtons} + {securityNote} )} @@ -13650,7 +13601,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) { {submitting ? 'Przetwarzanie…' : 'Zarejestruj firmę'} - {socialButtons} + {securityNote} )} diff --git a/frontend/src/auth.tsx b/frontend/src/auth.tsx index f9ead1b..412fd66 100644 --- a/frontend/src/auth.tsx +++ b/frontend/src/auth.tsx @@ -104,7 +104,6 @@ type AuthContextValue = { loading: boolean; login: (email: string, password: string) => Promise; register: (email: string, password: string, fullName: string, details?: RegisterDetails) => Promise; - socialLogin: (provider: Exclude) => Promise; activateAccount: (token: string) => Promise; resendActivation: (email: string) => Promise; forgotPassword: (email: string) => Promise; @@ -266,15 +265,6 @@ export function AuthProvider({ children }: { children: ReactNode }) { [], ); - const socialLogin = useCallback( - async (provider: Exclude) => - applyAuth(await apiFetch('/auth/social', { - method: 'POST', - body: JSON.stringify({ provider }), - })), - [applyAuth], - ); - const activateAccount = useCallback(async (token: string) => { await apiFetch('/auth/activate', { method: 'POST', body: JSON.stringify({ token }) }); }, []); @@ -372,11 +362,11 @@ export function AuthProvider({ children }: { children: ReactNode }) { const value = useMemo( () => ({ - user, token, loading, login, register, socialLogin, + user, token, loading, login, register, activateAccount, resendActivation, forgotPassword, resetPassword, verifyPhone, resendPhoneOtp, updateProfile, changePassword, verifyPassword, passwordCooldown, deleteAccount, loadSettings, loadSearchPreferences, saveSettings, refreshUser, logout, }), - [user, token, loading, login, register, socialLogin, + [user, token, loading, login, register, activateAccount, resendActivation, forgotPassword, resetPassword, verifyPhone, resendPhoneOtp, updateProfile, changePassword, verifyPassword, passwordCooldown, deleteAccount, loadSettings, loadSearchPreferences, saveSettings, refreshUser, logout], ); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 2eba440..c280b1e 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -13604,67 +13604,6 @@ a.city-card:hover { width: 100%; } -.login-divider { - align-items: center; - color: #6e7d92; - display: grid; - font-size: 13px; - font-weight: 850; - grid-template-columns: 1fr auto 1fr; - gap: 9px; -} - -.login-divider::before, -.login-divider::after { - background: #dfe5ed; - content: ""; - height: 1px; -} - -.social-login-row { - display: flex; - gap: 10px; -} - -.social-login { - align-items: center; - background: #ffffff; - border: 1px solid #dce4ec; - border-radius: 10px; - color: #24344b; - cursor: pointer; - display: flex; - flex: 1 1 0; - font-size: 14px; - font-weight: 800; - gap: 9px; - height: 46px; - justify-content: center; - min-width: 0; - transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; -} - -.social-login:hover:not(:disabled) { - background: #f7f9fc; - border-color: #c4d2e2; - box-shadow: 0 2px 10px rgba(20, 34, 58, 0.07); -} - -.social-login:disabled { - cursor: default; - opacity: 0.6; -} - -.social-login-icon { - flex: 0 0 auto; - height: 18px; - width: 18px; -} - -@media (max-width: 400px) { - .social-login-row { flex-direction: column; } -} - .login-security-note { align-items: center; color: #7a8798; @@ -14018,8 +13957,6 @@ a.city-card:hover { .remember-me, .forgot-password, .login-submit, - .social-login, - .login-divider, .login-security-note { font-size: 13px; } @@ -14035,8 +13972,7 @@ a.city-card:hover { } .login-field input, - .login-submit, - .social-login { + .login-submit { height: 44px; }