- Добавил шифрование паролей в БД.
- Добавил валидацию полей ввода. https://www.youtube.com/watch?v=AdLXmE4rjy4&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=14
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -77,6 +77,10 @@
|
|||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
<artifactId>flyway-mysql</artifactId>
|
<artifactId>flyway-mysql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -13,6 +15,10 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class WebSecurityConfig {
|
public class WebSecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder(8);
|
||||||
|
}
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package net.sytes.kashey.sweater.controller;
|
||||||
|
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.validation.FieldError;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class ControllerUtils {
|
||||||
|
static Map<String, String> getErrors(BindingResult bindingResult) {
|
||||||
|
|
||||||
|
Collector<FieldError, ?, Map<String, String>> collector = Collectors.toMap(
|
||||||
|
fieldError -> fieldError.getField() + "Error",
|
||||||
|
FieldError::getDefaultMessage
|
||||||
|
);
|
||||||
|
return bindingResult.getFieldErrors().stream().collect(collector);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.sytes.kashey.sweater.controller;
|
package net.sytes.kashey.sweater.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import net.sytes.kashey.sweater.domain.Message;
|
import net.sytes.kashey.sweater.domain.Message;
|
||||||
import net.sytes.kashey.sweater.repository.MessageRepository;
|
import net.sytes.kashey.sweater.repository.MessageRepository;
|
||||||
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
||||||
@@ -7,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -14,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -48,25 +51,34 @@ public class MainController {
|
|||||||
|
|
||||||
@PostMapping("/main")
|
@PostMapping("/main")
|
||||||
public String addMessage(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
public String addMessage(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||||
@RequestParam String text,
|
@Valid Message message,
|
||||||
@RequestParam String tag,
|
BindingResult bindingResult,
|
||||||
@RequestParam("file") MultipartFile file,
|
Model model,
|
||||||
Model model) throws IOException {
|
@RequestParam("file") MultipartFile file
|
||||||
|
) throws IOException {
|
||||||
|
|
||||||
Message newMessage = new Message(text, tag, customUserDetails.getUser());
|
message.setAuthor(customUserDetails.getUser());
|
||||||
|
|
||||||
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
if (bindingResult.hasErrors()) {
|
||||||
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
Map<String, String> errorsMap = ControllerUtils.getErrors(bindingResult);
|
||||||
if (!uploadDir.exists()) {
|
model.mergeAttributes(errorsMap);
|
||||||
uploadDir.mkdirs();
|
model.addAttribute("message", message);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
||||||
|
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
||||||
|
if (!uploadDir.exists()) {
|
||||||
|
uploadDir.mkdirs();
|
||||||
|
}
|
||||||
|
String uuidFile = UUID.randomUUID().toString();
|
||||||
|
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
||||||
|
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
||||||
|
message.setFilename(resultFilename);
|
||||||
}
|
}
|
||||||
String uuidFile = UUID.randomUUID().toString();
|
|
||||||
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
|
||||||
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
|
||||||
newMessage.setFilename(resultFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageRepository.save(newMessage);
|
messageRepository.save(message);
|
||||||
|
model.addAttribute("message", null);
|
||||||
|
}
|
||||||
model.addAttribute("messages", messageRepository.findAll());
|
model.addAttribute("messages", messageRepository.findAll());
|
||||||
model.addAttribute("tag", "");
|
model.addAttribute("tag", "");
|
||||||
return "main";
|
return "main";
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package net.sytes.kashey.sweater.controller;
|
package net.sytes.kashey.sweater.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import net.sytes.kashey.sweater.domain.User;
|
import net.sytes.kashey.sweater.domain.User;
|
||||||
import net.sytes.kashey.sweater.repository.UserRepository;
|
|
||||||
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class RegistrationController {
|
public class RegistrationController {
|
||||||
private final CustomUserDetailsService customUserDetailsService;
|
private final CustomUserDetailsService customUserDetailsService;
|
||||||
@@ -27,11 +26,20 @@ public class RegistrationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/registration")
|
@PostMapping("/registration")
|
||||||
public String addUser(@ModelAttribute User user, Map<String, Object> model) {
|
public String addUser(@ModelAttribute @Valid User user, BindingResult bindingResult, Model model) {
|
||||||
|
|
||||||
|
if (user.getPassword() != null && !user.getPassword().equals(user.getPassword2())) {
|
||||||
|
model.addAttribute("passwordError", "Passwords do not match!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
model.mergeAttributes(ControllerUtils.getErrors(bindingResult));
|
||||||
|
return "registration";
|
||||||
|
}
|
||||||
|
|
||||||
String result = customUserDetailsService.addUser(user);
|
String result = customUserDetailsService.addUser(user);
|
||||||
if (!result.isEmpty()) {
|
if (!result.isEmpty()) {
|
||||||
model.put("message", result);
|
model.addAttribute("usernameError", result);
|
||||||
return "registration";
|
return "registration";
|
||||||
} else {
|
} else {
|
||||||
return "redirect:/login";
|
return "redirect:/login";
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
package net.sytes.kashey.sweater.domain;
|
package net.sytes.kashey.sweater.domain;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public class Message {
|
public class Message {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank(message = "Please fill in the field.")
|
||||||
|
@Length(max = 2048, message = "Message too long.")
|
||||||
private String text;
|
private String text;
|
||||||
|
@Length(max = 255, message = "Tag too long.")
|
||||||
private String tag;
|
private String tag;
|
||||||
|
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package net.sytes.kashey.sweater.domain;
|
package net.sytes.kashey.sweater.domain;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -11,10 +13,18 @@ public class User {
|
|||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
@NotBlank(message = "Username must not be empty.")
|
||||||
private String username;
|
private String username;
|
||||||
|
@NotBlank(message = "Password must not be empty.")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@NotBlank(message = "Password confirmation must not be empty.")
|
||||||
|
private String password2;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Email(message = "E-mail is not valid.")
|
||||||
|
@NotBlank(message = "E-mail must not be empty.")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
private String activationCode;
|
private String activationCode;
|
||||||
@@ -90,4 +100,12 @@ public class User {
|
|||||||
public void setActivationCode(String activationCode) {
|
public void setActivationCode(String activationCode) {
|
||||||
this.activationCode = activationCode;
|
this.activationCode = activationCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPassword2() {
|
||||||
|
return password2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword2(String password2) {
|
||||||
|
this.password2 = password2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
@@ -19,19 +20,26 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Value("${hosting.path}")
|
@Value("${hosting.path}")
|
||||||
private String hostingPath;
|
private String hostingPath;
|
||||||
|
|
||||||
public CustomUserDetailsService(UserRepository userRepository, EmailService emailService) {
|
public CustomUserDetailsService(UserRepository userRepository, EmailService emailService,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
|
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
throw new UsernameNotFoundException("Username is empty!");
|
||||||
|
}
|
||||||
|
|
||||||
User user = userRepository.findByUsername(name)
|
User user = userRepository.findByUsername(name)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
|
||||||
return new CustomUserDetails(user);
|
return new CustomUserDetails(user);
|
||||||
@@ -50,7 +58,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setActivationCode(UUID.randomUUID().toString());
|
user.setActivationCode(UUID.randomUUID().toString());
|
||||||
user.setRoles(Collections.singleton(Role.USER));
|
user.setRoles(Collections.singleton(Role.USER));
|
||||||
user.setPassword("{noop}" + user.getPassword()); //пока не введено шифрование паролей
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
sendMessage(user);
|
sendMessage(user);
|
||||||
@@ -114,7 +122,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtils.isEmpty(password)) {
|
if (!ObjectUtils.isEmpty(password)) {
|
||||||
user.setPassword("{noop}" + password);
|
user.setPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
insert into usr (id, username, password, enabled)
|
insert into usr (id, username, password, enabled)
|
||||||
values (1, 'admin', '{noop}admin', true);
|
values (1, 'admin', '$2a$12$.XB1cxFOs6F8vgouixcTP.TaY2GXwFspYHID12JpLwotvoYI0JwnC', true);
|
||||||
|
|
||||||
insert into user_role (user_id, roles)
|
insert into user_role (user_id, roles)
|
||||||
values (1, 'USER'), (1, 'ADMIN');
|
values (1, 'USER'), (1, 'ADMIN');
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
<#import "parts/login.ftl" as l>
|
<#import "parts/login.ftl" as l>
|
||||||
|
|
||||||
<@com.page>
|
<@com.page>
|
||||||
<#if message??>
|
<#if Session?? && Session.SPRING_SECURITY_LAST_EXCEPTION??>
|
||||||
<div class="mb-3"> ${message} </div>
|
<div class="alert alert-danger" role="alert">
|
||||||
|
${Session.SPRING_SECURITY_LAST_EXCEPTION.message}
|
||||||
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<@l.login "/login" false/>
|
<@l.login "/login" false/>
|
||||||
</@com.page>
|
</@com.page>
|
||||||
@@ -12,14 +12,26 @@
|
|||||||
aria-controls="collapseExample">
|
aria-controls="collapseExample">
|
||||||
Click to add message
|
Click to add message
|
||||||
</a>
|
</a>
|
||||||
<div class="collapse" id="collapseExample">
|
<div class="collapse <#if message??>show</#if>" id="collapseExample">
|
||||||
<div class="form-group mt-3">
|
<div class="form-group mt-3">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="text" placeholder="Text"/>
|
<input type="text" class="form-control ${(textError??)?string('is-invalid', '')}"
|
||||||
|
value="<#if message??>${message.text}</#if>" name="text" placeholder="Text" />
|
||||||
|
<#if textError??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${textError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="tag" placeholder="Tag">
|
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
||||||
|
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag">
|
||||||
|
<#if tagError??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${tagError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
|
|||||||
@@ -3,24 +3,57 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label"> User Name:</label>
|
<label class="col-sm-2 col-form-label"> User Name:</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input type="text" class="form-control" name="username" placeholder="User name"/>
|
<input type="text" name="username" value="<#if user??>${user.username}</#if>"
|
||||||
|
class="form-control ${(usernameError??)?string('is-invalid', '')}"
|
||||||
|
placeholder="User name"/>
|
||||||
|
<#if usernameError??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${usernameError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputPassword3" class="col-sm-2 col-form-label"> Password: </label>
|
<label for="inputPassword3" class="col-sm-2 col-form-label"> Password: </label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input type="password" class="form-control" name="password" placeholder="Password">
|
<input type="password" name="password"
|
||||||
|
class="form-control ${(passwordError??)?string('is-invalid', '')}"
|
||||||
|
placeholder="Password"/>
|
||||||
|
<#if passwordError??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${passwordError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<#if isRegistrationForm>
|
<#if isRegistrationForm>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2 col-form-label">Password:</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input type="password" name="password2"
|
||||||
|
class="form-control ${(password2Error??)?string('is-invalid', '')}"
|
||||||
|
placeholder="Confirm password"/>
|
||||||
|
<#if password2Error??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${password2Error}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label"> E-mail:</label>
|
<label class="col-sm-2 col-form-label"> E-mail:</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input type="email" class="form-control" name="email" placeholder="E-mail"/>
|
<input type="email" name="email" value="<#if user??>${user.email}</#if>"
|
||||||
|
class="form-control ${(emailError??)?string('is-invalid', '')}"
|
||||||
|
placeholder="E-m@il"/>
|
||||||
|
<#if emailError??>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
${emailError}
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#if !isRegistrationForm>
|
<#if !isRegistrationForm>
|
||||||
<a href="/registration" class="btn btn-primary">Add new user</a>
|
<a href="/registration" class="btn btn-primary">Add new user</a>
|
||||||
</#if>
|
</#if>
|
||||||
|
|||||||
Reference in New Issue
Block a user