Improved validation, enforced uniqueness on usernames
This commit is contained in:
parent
db6282eec6
commit
d5f7f4f53c
4
pom.xml
4
pom.xml
@ -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>
|
||||||
|
@ -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(
|
||||||
|
@ -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, ""))
|
||||||
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user