Improved validation, enforced uniqueness on usernames

This commit is contained in:
neviyn 2021-04-04 23:57:28 +01:00
parent db6282eec6
commit d5f7f4f53c
6 changed files with 104 additions and 54 deletions

View File

@ -52,6 +52,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<dependency>
<artifactId>spring-boot-starter-validation</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>

View File

@ -13,12 +13,19 @@ import javax.persistence.JoinTable
import javax.persistence.ManyToMany import javax.persistence.ManyToMany
import javax.persistence.ManyToOne import javax.persistence.ManyToOne
import javax.persistence.OneToMany import javax.persistence.OneToMany
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
@Entity @Entity
open class User( open class User(
@field:NotBlank(message = "Username is required")
@Column(unique = true)
open var username: String = "INVALID", open var username: String = "INVALID",
@field:NotBlank(message = "Email address is required")
@field:Email(message = "Email address invalid")
open var email: String = "INVALID", open var email: String = "INVALID",
@JsonIgnore @JsonIgnore
@field:NotBlank(message = "Password is required")
open var password: String = "INVALID", open var password: String = "INVALID",
@ManyToMany(cascade = [CascadeType.ALL]) @ManyToMany(cascade = [CascadeType.ALL])
@JsonIgnore @JsonIgnore
@ -29,7 +36,11 @@ open class User(
) )
open var projects: MutableSet<Project> = mutableSetOf(), open var projects: MutableSet<Project> = mutableSetOf(),
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long? = null @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long? = null
) ) {
override fun toString(): String {
return "User(username='$username', email='$email', password='$password', id=$id)"
}
}
@Entity @Entity
open class Project( open class Project(

View File

@ -8,6 +8,7 @@ import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import org.springframework.ui.Model import org.springframework.ui.Model
import org.springframework.validation.BindingResult
import org.springframework.web.bind.annotation.ModelAttribute import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
@ -18,6 +19,7 @@ import org.springframework.web.server.ResponseStatusException
import java.time.Instant import java.time.Instant
import javax.persistence.EntityManager import javax.persistence.EntityManager
import javax.transaction.Transactional import javax.transaction.Transactional
import javax.validation.Valid
@Controller @Controller
@ -37,7 +39,8 @@ class HtmlController @Autowired constructor(val userRepository: UserRepository,
} }
@PostMapping("/register") @PostMapping("/register")
fun register(@ModelAttribute newUser: User) : String { fun register(@Valid @ModelAttribute("user_details") newUser: User, bindingResult: BindingResult): String {
if (bindingResult.hasErrors()) return "register"
newUser.password = passwordEncoder().encode(newUser.password) newUser.password = passwordEncoder().encode(newUser.password)
userRepository.save(newUser) userRepository.save(newUser)
return "login" return "login"
@ -59,15 +62,14 @@ class HtmlController @Autowired constructor(val userRepository: UserRepository,
@PostMapping("/profile") @PostMapping("/profile")
@Transactional @Transactional
fun updateLoggedInUser(@ModelAttribute userData: DisplayUser, @AuthenticationPrincipal userDetails: CustomUserDetails, model: Model) : String { fun updateLoggedInUser(@Valid @ModelAttribute userData: DisplayUser, @AuthenticationPrincipal userDetails: CustomUserDetails, model: Model): String {
if(userData.id == userDetails.user.id!! && passwordEncoder().matches(userData.oldPassword, userDetails.password)) { if (userData.id == userDetails.user.id!! && passwordEncoder().matches(userData.oldPassword, userDetails.password)) {
val user = userDetails.user val user = userDetails.user
user.email = userData.email user.email = userData.email
if(userData.password.isNotEmpty()) user.password = passwordEncoder().encode(userData.password) if (userData.password.isNotEmpty()) user.password = passwordEncoder().encode(userData.password)
userRepository.save(user) userRepository.save(user)
model.addAttribute("message", "Your profile has been updated") model.addAttribute("message", "Your profile has been updated")
} } else {
else{
model.addAttribute("error", "Incorrect existing password") model.addAttribute("error", "Incorrect existing password")
} }
model.addAttribute("userData", DisplayUser(userData.id, userData.username, userData.email, userData.password, "")) model.addAttribute("userData", DisplayUser(userData.id, userData.username, userData.email, userData.password, ""))

View File

@ -1,17 +1,46 @@
package uk.co.neviyn.projectplanner package uk.co.neviyn.projectplanner
import java.time.Instant import java.time.Instant
import javax.validation.constraints.NotBlank
import javax.validation.constraints.PastOrPresent
import javax.validation.constraints.Positive
data class DisplayUser(val id: Long, val username: String, val email: String, val password: String, val oldPassword: String) data class DisplayUser(
@field:Positive val id: Long,
val username: String,
val email: String,
val password: String,
@field:NotBlank val oldPassword: String
)
data class NewProject(val title: String) data class NewProject(
@field:NotBlank val title: String
)
data class NewEvent(val title: String, val description: String, val start: Instant, val end: Instant) data class NewEvent(
@field:NotBlank val title: String,
val description: String,
val start: Instant,
val end: Instant
)
data class EditedEvent(val id: Long, val title: String, val description: String, val start: Instant, val end: Instant) data class EditedEvent(
@field:Positive val id: Long,
val title: String,
val description: String,
val start: Instant,
val end: Instant)
data class EventID(val id: Long) data class EventID(
@field:Positive val id: Long
)
data class FlatComment(val created: Instant, val comment: String, val username: String) data class FlatComment(
@field:PastOrPresent val created: Instant,
@field:NotBlank val comment: String,
@field:NotBlank val username: String
)
data class NewComment(val comment: String) data class NewComment(
@field:NotBlank val comment: String
)

View File

@ -3,25 +3,51 @@ create schema if not exists projectplanner;
create table if not exists projectplanner."user" create table if not exists projectplanner."user"
( (
id bigserial not null id bigserial not null
constraint user_pk constraint
primary key, user_pk
username varchar(50) not null, primary
email varchar(255) not null, key,
password varchar(255) not null username
); varchar
(
50
) not null,
email varchar
(
255
) not null,
password varchar
(
255
) not null
);
create unique index if not exists user_id_uindex create
unique index if not exists user_id_uindex
on projectplanner."user" (id); on projectplanner."user" (id);
create
unique index if not exists user_username_uindex
ON projectplanner."user" (username);
create table if not exists projectplanner.project create table if not exists projectplanner.project
( (
id bigserial not null id
constraint project_pk bigserial
primary key, not
title text not null null
constraint
project_pk
primary
key,
title
text
not
null
); );
create unique index if not exists project_id_uindex create
unique index if not exists project_id_uindex
on projectplanner.project (id); on projectplanner.project (id);
create table if not exists projectplanner.team create table if not exists projectplanner.team

View File

@ -12,30 +12,29 @@
</div> </div>
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="col-6 text-center"> <div class="col-6 text-center">
<form class="needs-validation" method="post" novalidate th:action="@{/register}" <form method="post" novalidate th:action="@{/register}"
th:object="${user_details}"> th:object="${user_details}">
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="emailInput">Email address</label> <label class="form-label" for="emailInput">Email address</label>
<input class="form-control form-control-lg" id="emailInput" required th:field="*{email}" <input class="form-control form-control-lg" id="emailInput" required th:field="*{email}"
type="email"/> type="email"/>
<div class="invalid-feedback"> <div class="text-danger" th:errors="*{email}" th:if="${#fields.hasErrors('email')}">Email Error
Please enter an email address.
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="usernameInput">Username</label> <label class="form-label" for="usernameInput">Username</label>
<input class="form-control form-control-lg" id="usernameInput" required th:field="*{username}" <input class="form-control form-control-lg" id="usernameInput" required th:field="*{username}"
type="text"/> type="text"/>
<div class="invalid-feedback"> <div class="text-danger" th:errors="*{username}" th:if="${#fields.hasErrors('username')}">Username
Please enter a username. Error
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="passwordInput">Password</label> <label class="form-label" for="passwordInput">Password</label>
<input class="form-control form-control-lg" id="passwordInput" required <input class="form-control form-control-lg" id="passwordInput" required
th:field="*{password}" type="password"/> th:field="*{password}" type="password"/>
<div class="invalid-feedback"> <div class="text-danger" th:errors="*{password}" th:if="${#fields.hasErrors('password')}">Password
Please enter a password. Error
</div> </div>
</div> </div>
<button class="btn btn-primary btn-lg" type="submit">Submit</button> <button class="btn btn-primary btn-lg" type="submit">Submit</button>
@ -43,26 +42,5 @@
</div> </div>
</div> </div>
</div> </div>
<script>
(function () {
'use strict'
// Fetch all the forms we want to apply custom Bootstrap validation styles to
let forms = document.querySelectorAll('.needs-validation')
// Loop over them and prevent submission
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
</script>
</body> </body>
</html> </html>