Skip to content

Commit

Permalink
Merge branch 'Hexlet:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kitdim authored Oct 22, 2024
2 parents 4c25a7c + 78a4071 commit b6f0ceb
Show file tree
Hide file tree
Showing 45 changed files with 874 additions and 382 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies {
implementation("io.github.jpenren:thymeleaf-spring-data-dialect:3.6.0")
implementation("org.webjars:webjars-locator-core:0.58")
implementation("org.webjars:bootstrap:5.2.3")
implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0")
// Database
runtimeOnly("org.postgresql:postgresql:42.5.4")
implementation("io.hypersistence:hypersistence-utils-hibernate-60:3.2.0")
Expand All @@ -48,7 +49,6 @@ dependencies {
implementation("org.mapstruct:mapstruct:1.5.3.Final")
// Annotation processors
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")

// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public SecurityFilterChain filterChain(HttpSecurity http,
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());

http.authorizeHttpRequests(authz -> authz
.requestMatchers(GET, "/webjars/**", "/widget/**", "/fragments/**", "/img/**").permitAll()
.requestMatchers(GET, "/webjars/**", "/widget/**", "/fragments/**", "/img/**",
"/favicon.ico").permitAll()
.requestMatchers("/", "/login", "/signup", "/error", "/about").permitAll()
.anyRequest().authenticated()
)
Expand Down
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
Expand Up @@ -167,14 +167,19 @@ public String deleteAllowedUrl(Model model,
@GetMapping("/integration")
@PreAuthorize(IS_USER_RELATED_TO_WKS)
public String getWorkspaceIntegrationPage(Model model, @PathVariable Long wksId, HttpServletRequest req) {
if (!workspaceService.existsWorkspaceById(wksId)) {
var wksOptional = workspaceService.getWorkspaceInfoById(wksId).orElse(null);

if (wksOptional == null) {
//TODO send to error page
log.error("Workspace with id {} not found", wksId);
return "redirect:/workspaces";
}

addTokenAndUrlToModel(model, wksId, req);

model.addAttribute("wksInfo", wksOptional);
model.addAttribute("wksName", wksOptional.name());

getStatisticDataToModel(model, wksId);
getLastTypoDataToModel(model, wksId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,10 @@ public class Account extends AbstractAuditingEntity implements Identifiable<Long
@ToString.Exclude
private String password;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String firstName;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String lastName;

@OneToMany(mappedBy = "account", cascade = ALL, orphanRemoval = true)
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
Expand Up @@ -3,14 +3,11 @@
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Validation annotation to validate that 2 fields have the same value.
* An array of fields and their matching confirmation fields can be supplied.
Expand All @@ -27,6 +24,8 @@
* message = "The password and it confirmation must match")}
*/
@Constraint(validatedBy = FieldMatchConsiderCaseValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldMatchConsiderCase {

String message() default "The {first} and {second} fields must be equal";
Expand All @@ -45,16 +44,4 @@
*/
String second();

/**
* Defines several <code>@FieldMatch</code> annotations on the same element
*
* @see FieldMatchConsiderCase
*/
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {

FieldMatchConsiderCase[] value();
}
}
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;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.hexlet.typoreporter.domain.account.constraint.AccountUsername;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -16,14 +15,13 @@ public class UpdateProfile {
@AccountUsername
private String username;

@Email(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", message = "The email \"{0}\" incorrect")
@Email(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
message = "The email \"${validatedValue}\" is not valid")
private String email;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String firstName;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String lastName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import io.hexlet.typoreporter.domain.account.constraint.AccountUsername;
import io.hexlet.typoreporter.service.dto.FieldMatchConsiderCase;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
Expand All @@ -19,14 +18,15 @@
@FieldMatchConsiderCase(
first = "password",
second = "confirmPassword",
message = "The password and it confirmation must match")
message = "{alert.passwords-dont-match}")
@ToString
public class SignupAccountModel {

@AccountUsername
private String username;

@Email(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", message = "The email \"{0}\" incorrect")
@Email(regexp = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
message = "The email \"${validatedValue}\" is not valid")
private String email;

@AccountPassword
Expand All @@ -37,11 +37,9 @@ public class SignupAccountModel {
@ToString.Exclude
private String confirmPassword;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String firstName;

@NotBlank
@Size(min = 1, max = 50)
@Size(max = 50)
private String lastName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@NoArgsConstructor
public class WorkspaceUserModel {

@Email(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "The email \"{0}\" is not valid")
@Email(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
message = "The email \"${validatedValue}\" is not valid")
private String email;
}
Loading

0 comments on commit b6f0ceb

Please sign in to comment.