Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): store authentication activity
closes #160
- Loading branch information
Showing
17 changed files
with
352 additions
and
11 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
komga/src/flyway/resources/db/migration/sqlite/V20210625155626__authentication_activity.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
); |
13 changes: 13 additions & 0 deletions
13
komga/src/main/kotlin/org/gotson/komga/domain/model/AuthenticationActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(), | ||
) |
17 changes: 17 additions & 0 deletions
17
...a/src/main/kotlin/org/gotson/komga/domain/persistence/AuthenticationActivityRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/AuthenticationActivityDao.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
komga/src/main/kotlin/org/gotson/komga/infrastructure/security/LoginListener.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
...main/kotlin/org/gotson/komga/infrastructure/security/UserAgentWebAuthenticationDetails.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} |
9 changes: 9 additions & 0 deletions
9
...otlin/org/gotson/komga/infrastructure/security/UserAgentWebAuthenticationDetailsSource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Oops, something went wrong.