fix: better email validation

rejects email address where domain has no extension

related to #434
This commit is contained in:
Gauthier Roebroeck 2021-03-01 12:00:06 +08:00
parent 8c034035a0
commit 97871f7fbc
6 changed files with 90 additions and 12 deletions

View file

@ -11,7 +11,7 @@ const val ROLE_FILE_DOWNLOAD = "FILE_DOWNLOAD"
const val ROLE_PAGE_STREAMING = "PAGE_STREAMING"
data class KomgaUser(
@Email
@Email(regexp = ".+@.+\\..+")
@NotBlank
val email: String,
@NotBlank

View file

@ -28,7 +28,7 @@ class ClaimController(
@PostMapping
fun claimAdmin(
@Email @RequestHeader("X-Komga-Email") email: String,
@Email(regexp = ".+@.+\\..+") @RequestHeader("X-Komga-Email") email: String,
@NotBlank @RequestHeader("X-Komga-Password") password: String
): UserDto {
if (userDetailsLifecycle.countUsers() > 0)

View file

@ -0,0 +1,41 @@
package org.gotson.komga.interfaces.rest
import org.springframework.http.HttpStatus
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.ResponseStatus
import javax.validation.ConstraintViolationException
@ControllerAdvice
class ErrorHandlingControllerAdvice {
@ExceptionHandler(ConstraintViolationException::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
fun onConstraintValidationException(
e: ConstraintViolationException
): ValidationErrorResponse =
ValidationErrorResponse(
e.constraintViolations.map { Violation(it.propertyPath.toString(), it.message) }
)
@ExceptionHandler(MethodArgumentNotValidException::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
fun onMethodArgumentNotValidException(
e: MethodArgumentNotValidException
): ValidationErrorResponse =
ValidationErrorResponse(
e.bindingResult.fieldErrors.map { Violation(it.field, it.defaultMessage) }
)
}
data class ValidationErrorResponse(
val violations: List<Violation> = emptyList()
)
data class Violation(
val fieldName: String? = null,
val message: String? = null
)

View file

@ -45,7 +45,7 @@ fun KomgaUser.toWithSharedLibrariesDto() =
)
data class UserCreationDto(
@get:Email val email: String,
@get:Email(regexp = ".+@.+\\..+") val email: String,
@get:NotBlank val password: String,
val roles: List<String> = emptyList()
) {

View file

@ -0,0 +1,34 @@
package org.gotson.komga.interfaces.rest
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
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.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post
@ExtendWith(SpringExtension::class)
@SpringBootTest
@AutoConfigureMockMvc(printOnlyOnFailure = false)
@ActiveProfiles("test")
class ClaimControllerTest(
@Autowired private val mockMvc: MockMvc
) {
@ParameterizedTest
@ValueSource(strings = ["user", "user@domain"])
fun `given unclaimed server when claiming with invalid email address then returns bad request`(email: String) {
val password = "password"
mockMvc.post("/api/v1/claim") {
header("X-Komga-Email", email)
header("X-Komga-Password", password)
}.andExpect {
status { isBadRequest() }
}
}
}

View file

@ -1,7 +1,9 @@
package org.gotson.komga.interfaces.rest
import org.junit.jupiter.api.Test
import org.gotson.komga.domain.model.ROLE_ADMIN
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
@ -9,26 +11,27 @@ import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.patch
import org.springframework.test.web.servlet.post
@ExtendWith(SpringExtension::class)
@SpringBootTest
@AutoConfigureMockMvc(printOnlyOnFailure = false)
@ActiveProfiles("demo", "test")
@ActiveProfiles("test")
class UserControllerTest(
@Autowired private val mockMvc: MockMvc
) {
@Test
@WithMockCustomUser
fun `given demo profile is active when a user tries to update its password via api then returns forbidden`() {
val jsonString = """{"password":"new"}"""
@ParameterizedTest
@ValueSource(strings = ["user", "user@domain"])
@WithMockCustomUser(roles = [ROLE_ADMIN])
fun `when creating a user with invalid email then returns bad request`(email: String) {
val jsonString = """{"email":"$email","password":"password"}"""
mockMvc.patch("/api/v1/users/me/password") {
mockMvc.post("/api/v1/users") {
contentType = MediaType.APPLICATION_JSON
content = jsonString
}.andExpect {
status { isForbidden() }
status { isBadRequest() }
}
}
}