mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 15:34:17 +01:00
parent
77a55be1d4
commit
de96e0dcef
17 changed files with 352 additions and 11 deletions
|
|
@ -0,0 +1,11 @@
|
|||
create table AUTHENTICATION_ACTIVITY
|
||||
(
|
||||
USER_ID varchar NULL DEFAULT NULL,
|
||||
EMAIL varchar NULL DEFAULT NULL,
|
||||
IP varchar NULL DEFAULT NULL,
|
||||
USER_AGENT varchar NULL DEFAULT NULL,
|
||||
SUCCESS boolean NOT NULL,
|
||||
ERROR varchar NULL DEFAULT NULL,
|
||||
DATE_TIME datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (USER_ID) references USER (ID)
|
||||
);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.gotson.komga.domain.model
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class AuthenticationActivity(
|
||||
val userId: String? = null,
|
||||
val email: String? = null,
|
||||
val ip: String? = null,
|
||||
val userAgent: String? = null,
|
||||
val success: Boolean,
|
||||
val error: String? = null,
|
||||
val dateTime: LocalDateTime = LocalDateTime.now(),
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.gotson.komga.domain.persistence
|
||||
|
||||
import org.gotson.komga.domain.model.AuthenticationActivity
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface AuthenticationActivityRepository {
|
||||
fun findAll(pageable: Pageable): Page<AuthenticationActivity>
|
||||
fun findAllByUser(user: KomgaUser, pageable: Pageable): Page<AuthenticationActivity>
|
||||
|
||||
fun insert(activity: AuthenticationActivity)
|
||||
|
||||
fun deleteByUser(user: KomgaUser)
|
||||
fun deleteOlderThan(dateTime: LocalDateTime)
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ interface KomgaUserRepository {
|
|||
fun count(): Long
|
||||
|
||||
fun findByIdOrNull(id: String): KomgaUser?
|
||||
fun findByEmailIgnoreCase(email: String): KomgaUser?
|
||||
fun findByEmailIgnoreCaseOrNull(email: String): KomgaUser?
|
||||
|
||||
fun findAll(): Collection<KomgaUser>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package org.gotson.komga.domain.service
|
|||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.gotson.komga.domain.model.UserEmailAlreadyExistsException
|
||||
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
|
||||
import org.gotson.komga.domain.persistence.KomgaUserRepository
|
||||
import org.gotson.komga.domain.persistence.ReadProgressRepository
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
|
|
@ -20,18 +21,19 @@ private val logger = KotlinLogging.logger {}
|
|||
class KomgaUserLifecycle(
|
||||
private val userRepository: KomgaUserRepository,
|
||||
private val readProgressRepository: ReadProgressRepository,
|
||||
private val authenticationActivityRepository: AuthenticationActivityRepository,
|
||||
private val passwordEncoder: PasswordEncoder,
|
||||
private val sessionRegistry: SessionRegistry
|
||||
private val sessionRegistry: SessionRegistry,
|
||||
|
||||
) : UserDetailsService {
|
||||
|
||||
override fun loadUserByUsername(username: String): UserDetails =
|
||||
userRepository.findByEmailIgnoreCase(username)?.let {
|
||||
userRepository.findByEmailIgnoreCaseOrNull(username)?.let {
|
||||
KomgaPrincipal(it)
|
||||
} ?: throw UsernameNotFoundException(username)
|
||||
|
||||
fun updatePassword(user: UserDetails, newPassword: String, expireSessions: Boolean): UserDetails {
|
||||
userRepository.findByEmailIgnoreCase(user.username)?.let { komgaUser ->
|
||||
userRepository.findByEmailIgnoreCaseOrNull(user.username)?.let { komgaUser ->
|
||||
logger.info { "Changing password for user ${user.username}" }
|
||||
val updatedUser = komgaUser.copy(password = passwordEncoder.encode(newPassword))
|
||||
userRepository.update(updatedUser)
|
||||
|
|
@ -58,8 +60,11 @@ class KomgaUserLifecycle(
|
|||
@Transactional
|
||||
fun deleteUser(user: KomgaUser) {
|
||||
logger.info { "Deleting user: $user" }
|
||||
|
||||
readProgressRepository.deleteByUserId(user.id)
|
||||
authenticationActivityRepository.deleteByUser(user)
|
||||
userRepository.delete(user.id)
|
||||
|
||||
expireSessions(user)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
package org.gotson.komga.infrastructure.jooq
|
||||
|
||||
import org.gotson.komga.domain.model.AuthenticationActivity
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
|
||||
import org.gotson.komga.jooq.Tables
|
||||
import org.gotson.komga.jooq.tables.records.AuthenticationActivityRecord
|
||||
import org.jooq.Condition
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.impl.DSL
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageImpl
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class AuthenticationActivityDao(
|
||||
private val dsl: DSLContext
|
||||
) : AuthenticationActivityRepository {
|
||||
|
||||
private val aa = Tables.AUTHENTICATION_ACTIVITY
|
||||
|
||||
private val sorts = mapOf(
|
||||
"dateTime" to aa.DATE_TIME,
|
||||
"email" to aa.EMAIL,
|
||||
"success" to aa.SUCCESS,
|
||||
"ip" to aa.IP,
|
||||
"error" to aa.ERROR,
|
||||
"userId" to aa.USER_ID,
|
||||
"userAgent" to aa.USER_AGENT,
|
||||
)
|
||||
|
||||
override fun findAll(pageable: Pageable): Page<AuthenticationActivity> {
|
||||
val conditions: Condition = DSL.trueCondition()
|
||||
return findAll(conditions, pageable)
|
||||
}
|
||||
|
||||
override fun findAllByUser(user: KomgaUser, pageable: Pageable): Page<AuthenticationActivity> {
|
||||
val conditions = aa.USER_ID.eq(user.id).or(aa.EMAIL.eq(user.email))
|
||||
return findAll(conditions, pageable)
|
||||
}
|
||||
|
||||
private fun findAll(conditions: Condition, pageable: Pageable): PageImpl<AuthenticationActivity> {
|
||||
val count = dsl.fetchCount(aa)
|
||||
|
||||
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||
|
||||
val items = dsl.selectFrom(aa)
|
||||
.where(conditions)
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetchInto(aa)
|
||||
.map { it.toDomain() }
|
||||
|
||||
val pageSort = if (orderBy.size > 1) pageable.sort else Sort.unsorted()
|
||||
return PageImpl(
|
||||
items,
|
||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
||||
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
||||
count.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun insert(activity: AuthenticationActivity) {
|
||||
dsl.insertInto(aa, aa.USER_ID, aa.EMAIL, aa.IP, aa.USER_AGENT, aa.SUCCESS, aa.ERROR)
|
||||
.values(activity.userId, activity.email, activity.ip, activity.userAgent, activity.success, activity.error)
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun deleteByUser(user: KomgaUser) {
|
||||
dsl.deleteFrom(aa)
|
||||
.where(aa.USER_ID.eq(user.id))
|
||||
.or(aa.EMAIL.eq(user.email))
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun deleteOlderThan(dateTime: LocalDateTime) {
|
||||
dsl.deleteFrom(aa)
|
||||
.where(aa.DATE_TIME.lt(dateTime))
|
||||
.execute()
|
||||
}
|
||||
|
||||
private fun AuthenticationActivityRecord.toDomain() =
|
||||
AuthenticationActivity(
|
||||
userId = userId,
|
||||
email = email,
|
||||
ip = ip,
|
||||
userAgent = userAgent,
|
||||
success = success,
|
||||
error = error,
|
||||
dateTime = dateTime.toCurrentTimeZone(),
|
||||
)
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ class KomgaUserDao(
|
|||
.where(u.EMAIL.equalIgnoreCase(email))
|
||||
)
|
||||
|
||||
override fun findByEmailIgnoreCase(email: String): KomgaUser? =
|
||||
override fun findByEmailIgnoreCaseOrNull(email: String): KomgaUser? =
|
||||
selectBase()
|
||||
.where(u.EMAIL.equalIgnoreCase(email))
|
||||
.fetchAndMap()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package org.gotson.komga.infrastructure.security
|
||||
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.AuthenticationActivity
|
||||
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
|
||||
import org.gotson.komga.domain.persistence.KomgaUserRepository
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.EventObject
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class LoginListener(
|
||||
private val authenticationActivityRepository: AuthenticationActivityRepository,
|
||||
private val userRepository: KomgaUserRepository,
|
||||
) {
|
||||
|
||||
@EventListener
|
||||
fun onSuccess(event: AuthenticationSuccessEvent) {
|
||||
val user = (event.authentication.principal as KomgaPrincipal).user
|
||||
val activity = AuthenticationActivity(
|
||||
userId = user.id,
|
||||
email = user.email,
|
||||
ip = event.getIp(),
|
||||
userAgent = event.getUserAgent(),
|
||||
success = true,
|
||||
)
|
||||
|
||||
logger.info { activity }
|
||||
authenticationActivityRepository.insert(activity)
|
||||
}
|
||||
|
||||
@EventListener
|
||||
fun onFailure(event: AbstractAuthenticationFailureEvent) {
|
||||
val user = event.authentication.principal.toString()
|
||||
val activity = AuthenticationActivity(
|
||||
userId = userRepository.findByEmailIgnoreCaseOrNull(user)?.id,
|
||||
email = user,
|
||||
ip = event.getIp(),
|
||||
userAgent = event.getUserAgent(),
|
||||
success = false,
|
||||
error = event.exception.message,
|
||||
)
|
||||
|
||||
logger.info { activity }
|
||||
authenticationActivityRepository.insert(activity)
|
||||
}
|
||||
|
||||
private fun EventObject.getIp(): String? =
|
||||
when (source) {
|
||||
is WebAuthenticationDetails -> (source as WebAuthenticationDetails).remoteAddress
|
||||
is AbstractAuthenticationToken -> ((source as AbstractAuthenticationToken).details as WebAuthenticationDetails).remoteAddress
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun EventObject.getUserAgent(): String? =
|
||||
when (source) {
|
||||
is UserAgentWebAuthenticationDetails -> (source as UserAgentWebAuthenticationDetails).userAgent
|
||||
is AbstractAuthenticationToken -> ((source as AbstractAuthenticationToken).details as UserAgentWebAuthenticationDetails).userAgent
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.core.session.SessionRegistry
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ class SecurityConfiguration(
|
|||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
// @formatter:off
|
||||
val userAgentWebAuthenticationDetailsSource = UserAgentWebAuthenticationDetailsSource()
|
||||
|
||||
http
|
||||
.cors()
|
||||
|
|
@ -51,11 +53,13 @@ class SecurityConfiguration(
|
|||
}
|
||||
|
||||
.httpBasic()
|
||||
.authenticationDetailsSource(userAgentWebAuthenticationDetailsSource)
|
||||
|
||||
.and()
|
||||
.logout()
|
||||
.logoutUrl("/api/v1/users/logout")
|
||||
.deleteCookies("JSESSIONID")
|
||||
.invalidateHttpSession(true)
|
||||
|
||||
.and()
|
||||
.sessionManagement()
|
||||
|
|
@ -67,10 +71,13 @@ class SecurityConfiguration(
|
|||
|
||||
http
|
||||
.rememberMe()
|
||||
.key(komgaProperties.rememberMe.key)
|
||||
.tokenValiditySeconds(komgaProperties.rememberMe.validity)
|
||||
.alwaysRemember(true)
|
||||
.userDetailsService(komgaUserDetailsLifecycle)
|
||||
.rememberMeServices(
|
||||
TokenBasedRememberMeServices(komgaProperties.rememberMe.key, komgaUserDetailsLifecycle).apply {
|
||||
setTokenValiditySeconds(komgaProperties.rememberMe.validity)
|
||||
setAlwaysRemember(true)
|
||||
setAuthenticationDetailsSource(userAgentWebAuthenticationDetailsSource)
|
||||
}
|
||||
)
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package org.gotson.komga.infrastructure.security
|
||||
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class UserAgentWebAuthenticationDetails(request: HttpServletRequest) : WebAuthenticationDetails(request) {
|
||||
val userAgent: String = request.getHeader("User-Agent")
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.gotson.komga.infrastructure.security
|
||||
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class UserAgentWebAuthenticationDetailsSource : WebAuthenticationDetailsSource() {
|
||||
override fun buildDetails(context: HttpServletRequest): UserAgentWebAuthenticationDetails =
|
||||
UserAgentWebAuthenticationDetails(context)
|
||||
}
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
package org.gotson.komga.interfaces.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
|
||||
import org.gotson.komga.domain.model.ROLE_PAGE_STREAMING
|
||||
import org.gotson.komga.domain.model.UserEmailAlreadyExistsException
|
||||
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
|
||||
import org.gotson.komga.domain.persistence.KomgaUserRepository
|
||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||
import org.gotson.komga.domain.service.KomgaUserLifecycle
|
||||
import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.interfaces.rest.dto.AuthenticationActivityDto
|
||||
import org.gotson.komga.interfaces.rest.dto.PasswordUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.RolesUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.SharedLibrariesUpdateDto
|
||||
|
|
@ -17,7 +21,12 @@ import org.gotson.komga.interfaces.rest.dto.UserDto
|
|||
import org.gotson.komga.interfaces.rest.dto.UserWithSharedLibrariesDto
|
||||
import org.gotson.komga.interfaces.rest.dto.toDto
|
||||
import org.gotson.komga.interfaces.rest.dto.toWithSharedLibrariesDto
|
||||
import org.springdoc.core.converters.models.PageableAsQueryParam
|
||||
import org.springframework.core.env.Environment
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
|
@ -29,6 +38,7 @@ import org.springframework.web.bind.annotation.PathVariable
|
|||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
|
@ -42,6 +52,7 @@ class UserController(
|
|||
private val userLifecycle: KomgaUserLifecycle,
|
||||
private val userRepository: KomgaUserRepository,
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val authenticationActivityRepository: AuthenticationActivityRepository,
|
||||
env: Environment
|
||||
) {
|
||||
|
||||
|
|
@ -126,4 +137,48 @@ class UserController(
|
|||
logger.info { "Updated user shared libraries: $updatedUser" }
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@GetMapping("me/authentication-activity")
|
||||
@PageableAsQueryParam
|
||||
fun getMyAuthenticationActivity(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<AuthenticationActivityDto> {
|
||||
val sort =
|
||||
if (page.sort.isSorted) page.sort
|
||||
else Sort.by(Sort.Order.desc("dateTime"))
|
||||
|
||||
val pageRequest =
|
||||
if (unpaged) UnpagedSorted(sort)
|
||||
else PageRequest.of(
|
||||
page.pageNumber,
|
||||
page.pageSize,
|
||||
sort
|
||||
)
|
||||
|
||||
return authenticationActivityRepository.findAllByUser(principal.user, pageRequest).map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("authentication-activity")
|
||||
@PageableAsQueryParam
|
||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||
fun getAuthenticationActivity(
|
||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<AuthenticationActivityDto> {
|
||||
val sort =
|
||||
if (page.sort.isSorted) page.sort
|
||||
else Sort.by(Sort.Order.desc("dateTime"))
|
||||
|
||||
val pageRequest =
|
||||
if (unpaged) UnpagedSorted(sort)
|
||||
else PageRequest.of(
|
||||
page.pageNumber,
|
||||
page.pageSize,
|
||||
sort
|
||||
)
|
||||
|
||||
return authenticationActivityRepository.findAll(pageRequest).map { it.toDto() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package org.gotson.komga.interfaces.rest.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat
|
||||
import org.gotson.komga.domain.model.AuthenticationActivity
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class AuthenticationActivityDto(
|
||||
val userId: String?,
|
||||
val email: String?,
|
||||
val ip: String?,
|
||||
val userAgent: String?,
|
||||
val success: Boolean,
|
||||
val error: String?,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val dateTime: LocalDateTime,
|
||||
)
|
||||
|
||||
fun AuthenticationActivity.toDto() =
|
||||
AuthenticationActivityDto(
|
||||
userId = userId,
|
||||
email = email,
|
||||
ip = ip,
|
||||
userAgent = userAgent,
|
||||
success = success,
|
||||
error = error,
|
||||
dateTime = dateTime,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.gotson.komga.interfaces.scheduler
|
||||
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class AuthenticationActivityCleanupController(
|
||||
private val authenticationActivityRepository: AuthenticationActivityRepository,
|
||||
) {
|
||||
|
||||
// Run every day
|
||||
@Scheduled(fixedRate = 86_400_000)
|
||||
fun cleanup() {
|
||||
val olderThan = LocalDateTime.now(ZoneId.of("Z")).minusMonths(1)
|
||||
logger.info { "Remove authentication activity older than $olderThan (UTC)" }
|
||||
authenticationActivityRepository.deleteOlderThan(olderThan)
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ class BookImporterTest(
|
|||
@AfterAll
|
||||
fun teardown() {
|
||||
libraryRepository.deleteAll()
|
||||
readProgressRepository.deleteAll()
|
||||
userRepository.deleteAll()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class BookLifecycleTest(
|
|||
@AfterAll
|
||||
fun teardown() {
|
||||
libraryRepository.deleteAll()
|
||||
readProgressRepository.deleteAll()
|
||||
userRepository.deleteAll()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,8 +173,8 @@ class KomgaUserDaoTest(
|
|||
KomgaUser("user1@example.org", "p", false)
|
||||
)
|
||||
|
||||
val found = komgaUserDao.findByEmailIgnoreCase("USER1@EXAMPLE.ORG")
|
||||
val notFound = komgaUserDao.findByEmailIgnoreCase("USER2@EXAMPLE.ORG")
|
||||
val found = komgaUserDao.findByEmailIgnoreCaseOrNull("USER1@EXAMPLE.ORG")
|
||||
val notFound = komgaUserDao.findByEmailIgnoreCaseOrNull("USER2@EXAMPLE.ORG")
|
||||
|
||||
assertThat(found).isNotNull
|
||||
assertThat(found?.email).isEqualTo("user1@example.org")
|
||||
|
|
|
|||
Loading…
Reference in a new issue