Skip to content

Commit

Permalink
Merge pull request #311 from Nika-code/display-username
Browse files Browse the repository at this point in the history
[#304] Display user name with email
  • Loading branch information
Malcom1986 authored Oct 17, 2024
2 parents 9ed1055 + 2ba550e commit 78a4071
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.hexlet.typoreporter.controller;

import io.hexlet.typoreporter.domain.account.Account;
import io.hexlet.typoreporter.security.service.AccountDetailService;
import io.hexlet.typoreporter.service.AccountService;
import io.hexlet.typoreporter.service.dto.account.CustomUserDetails;
import io.hexlet.typoreporter.service.dto.account.UpdatePassword;
import io.hexlet.typoreporter.service.dto.account.UpdateProfile;
import io.hexlet.typoreporter.handler.exception.AccountAlreadyExistException;
Expand All @@ -23,15 +25,14 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Slf4j
@Controller
@RequestMapping("/account")
@RequiredArgsConstructor
public class AccountController {

private final AccountService accountService;
private final AccountDetailService accountDetailService;

@GetMapping
public String getAccountInfoPage(final Model model, final Authentication authentication) {
Expand Down Expand Up @@ -64,8 +65,15 @@ public String putProfileUpdate(final Model model,
try {
final String email = authentication.getName();
Account updatedAccount = accountService.updateProfile(updateProfile, email);
final var authenticated = UsernamePasswordAuthenticationToken.authenticated(updatedAccount.getEmail(),
updatedAccount.getPassword(), List.of(() -> "ROLE_USER"));

CustomUserDetails userDetails = (CustomUserDetails) accountDetailService
.loadUserByUsername(updatedAccount.getEmail());

final var authenticated = new UsernamePasswordAuthenticationToken(
userDetails,
userDetails.getPassword(),
userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticated);
return "redirect:/account";
} catch (AccountAlreadyExistException e) {
Expand Down Expand Up @@ -93,8 +101,14 @@ public String putPasswordUpdate(final Model model,
try {
final String email = authentication.getName();
Account updatedAccount = accountService.updatePassword(updatePassword, email);
final var authenticated = UsernamePasswordAuthenticationToken.authenticated(updatedAccount.getEmail(),
updatedAccount.getPassword(), List.of(() -> "ROLE_USER"));
CustomUserDetails userDetails = (CustomUserDetails) accountDetailService
.loadUserByUsername(updatedAccount.getEmail());

final var authenticated = new UsernamePasswordAuthenticationToken(
userDetails,
userDetails.getPassword(),
userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticated);
return "redirect:/account";
} catch (OldPasswordWrongException | NewPasswordTheSameException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.hexlet.typoreporter.controller;

import io.hexlet.typoreporter.domain.workspace.AccountRole;
import io.hexlet.typoreporter.security.service.AccountDetailService;
import io.hexlet.typoreporter.service.account.EmailAlreadyExistException;
import io.hexlet.typoreporter.service.account.UsernameAlreadyExistException;
import io.hexlet.typoreporter.service.account.signup.SignupAccountMapper;
import io.hexlet.typoreporter.service.account.signup.SignupAccountUseCase;
import io.hexlet.typoreporter.service.dto.account.CustomUserDetails;
import io.hexlet.typoreporter.service.dto.account.InfoAccount;
import io.hexlet.typoreporter.web.model.SignupAccountModel;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -27,8 +28,6 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;


@Slf4j
@Controller
Expand All @@ -39,6 +38,7 @@ public class SignupController {
private final SignupAccountUseCase signupAccountUseCase;
private final SignupAccountMapper signupAccountMapper;
private final SecurityContextRepository securityContextRepository;
private final AccountDetailService accountDetailService;

@GetMapping("/signup")
public String getSignUpPage(final Model model) {
Expand All @@ -61,8 +61,16 @@ public String createAccount(@ModelAttribute("signupAccount") @Valid SignupAccoun
final InfoAccount newAccount;
try {
newAccount = signupAccountUseCase.signup(signupAccountMapper.toSignupAccount(signupAccountModel));
final var authentication = UsernamePasswordAuthenticationToken.
authenticated(newAccount.email(), null, List.of(AccountRole.ROLE_GUEST::name));

CustomUserDetails userDetails = (CustomUserDetails) accountDetailService
.loadUserByUsername(newAccount.email());

final var authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

autoLoginAfterSignup(request, response, authentication);
return "redirect:/workspaces";
} catch (UsernameAlreadyExistException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package io.hexlet.typoreporter.security.service;

import io.hexlet.typoreporter.repository.AccountRepository;
import io.hexlet.typoreporter.service.dto.account.CustomUserDetails;
import io.hexlet.typoreporter.utils.TextUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Collections;

@Service
@RequiredArgsConstructor
public class AccountDetailService implements UserDetailsService {
Expand All @@ -19,10 +24,11 @@ public class AccountDetailService implements UserDetailsService {
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
final String normalizedEmail = TextUtils.toLowerCaseData(email);
return accountRepository.findAccountByEmail(normalizedEmail)
.map(acc -> User.withUsername(acc.getEmail())
.password(acc.getPassword())
.authorities("USER")
.build())
.map(acc -> {
Collection<GrantedAuthority> authorities =
Collections.singletonList(new SimpleGrantedAuthority("USER"));
return new CustomUserDetails(acc.getEmail(), acc.getPassword(), acc.getUsername(), authorities);
})
.orElseThrow(() -> new UsernameNotFoundException("Account with email='" + email + "' not found"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.hexlet.typoreporter.service.dto.account;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@AllArgsConstructor
public class CustomUserDetails implements UserDetails {

private String email;
private String password;
@Getter
private final String nickname;
private Collection<? extends GrantedAuthority> authorities;

@Override
public String getUsername() {
return email;
}

@Override
public String getPassword() {
return password;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

21 changes: 14 additions & 7 deletions src/main/resources/templates/fragments/panels.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@
</li>
<!-- End Switcher -->
<div class="navbar-nav" sec:authorize="isAuthenticated()">
<li class="nav-item">
<span class="d-block px-2 py-2 text-white-50" th:text="${#authentication.getName()}"></span>
</li>
<li class="nav-item">
<form th:action="@{/logout}" th:method="post">
<button class="btn btn-link nav-link" type="submit" th:text="#{navbar.logout}">Logout</button>
</form>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="profileDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
</svg>
</a>
<ul class="dropdown-menu dropdown-menu-end pb-0" aria-labelledby="profileDropdown">
<span class="py-1 px-3 d-block fw-bolder" th:text="${#authentication.getPrincipal().nickname}"></span>
<span class="py-1 px-3 d-block" th:text="${#authentication.getName()}"></span>
<form class="border-top dropdown-item" th:action="@{/logout}" th:method="post">
<button class="btn px-0 text-start w-100" type="submit" th:text="#{navbar.logout}">Logout</button>
</form>
</ul>
</li>
</div>
<div class="navbar-nav" sec:authorize="isAnonymous()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import com.github.database.rider.core.api.configuration.DBUnit;
import com.github.database.rider.spring.api.DBRider;
import io.hexlet.typoreporter.repository.AccountRepository;
import io.hexlet.typoreporter.service.dto.account.CustomUserDetails;
import io.hexlet.typoreporter.test.DBUnitEnumPostgres;
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.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
Expand All @@ -16,6 +18,8 @@
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.util.List;

import static com.github.database.rider.core.api.configuration.Orthography.LOWERCASE;
import static io.hexlet.typoreporter.test.Constraints.POSTGRES_IMAGE;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -75,7 +79,8 @@ void updateAccountWithWrongEmailDomain() throws Exception {
.param("confirmPassword", password)
.param("firstName", userName)
.param("lastName", userName)
.with(user(correctEmailDomain))
.with(user(new CustomUserDetails(correctEmailDomain, "password", "SampleNickname",
List.of(new SimpleGrantedAuthority("USER")))))
.with(csrf()));
assertThat(accountRepository.findAccountByEmail(wrongEmailDomain)).isEmpty();
assertThat(accountRepository.findAccountByEmail(correctEmailDomain).orElseThrow().getEmail())
Expand Down Expand Up @@ -105,7 +110,8 @@ void updateAccountEmailUsingDifferentCase() throws Exception {
.param("lastName", username)
.param("username", username)
.param("email", emailUpperCase)
.with(user(emailLowerCase))
.with(user(new CustomUserDetails(emailLowerCase, "password", "SampleNickname",
List.of(new SimpleGrantedAuthority("USER")))))
.with(csrf()));
assertThat(accountRepository.findAccountByEmail(emailUpperCase)).isEmpty();
assertThat(accountRepository.findAccountByEmail(emailLowerCase)).isNotEmpty();
Expand Down
Loading

0 comments on commit 78a4071

Please sign in to comment.