From 1b8e1b92f30a85f44474f704b63dfcf0e26f52bb Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Mon, 7 Feb 2022 12:01:04 -0500 Subject: [PATCH] Improve user sessions --- .../kotlin/starshipfights/auth/providers.kt | 31 ++++++++- .../starshipfights/data/data_routines.kt | 11 +-- .../kotlin/starshipfights/info/views_user.kt | 67 ++++++++++++++++++- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/jvmMain/kotlin/starshipfights/auth/providers.kt b/src/jvmMain/kotlin/starshipfights/auth/providers.kt index 1085bd7..53132ee 100644 --- a/src/jvmMain/kotlin/starshipfights/auth/providers.kt +++ b/src/jvmMain/kotlin/starshipfights/auth/providers.kt @@ -14,7 +14,10 @@ import io.ktor.util.* import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.html.* +import org.litote.kmongo.and import org.litote.kmongo.eq +import org.litote.kmongo.ne +import org.litote.kmongo.setValue import starshipfights.CurrentConfiguration import starshipfights.data.Id import starshipfights.data.admiralty.Admiral @@ -172,9 +175,10 @@ interface AuthProvider { } get("/logout") { - call.sessions.get>()?.let { sessId -> + call.getUserSession()?.let { sess -> launch { - UserSession.del(sessId) + val newTime = System.currentTimeMillis() - 100 + UserSession.update(UserSession::id eq sess.id, setValue(UserSession::expirationMillis, newTime)) } } @@ -182,6 +186,29 @@ interface AuthProvider { redirect("/") } + get("/logout/{id}") { + val id = Id(call.parameters.getOrFail("id")) + call.getUserSession()?.let { sess -> + launch { + val newTime = System.currentTimeMillis() - 100 + UserSession.update(and(UserSession::id eq id, UserSession::user eq sess.user), setValue(UserSession::expirationMillis, newTime)) + } + } + + redirect("/me/manage") + } + + get("/logout-all") { + call.getUserSession()?.let { sess -> + launch { + val newTime = System.currentTimeMillis() - 100 + UserSession.update(and(UserSession::user eq sess.user, UserSession::id ne sess.id), setValue(UserSession::expirationMillis, newTime)) + } + } + + redirect("/me/manage") + } + currentProvider.installRouting(this) } } diff --git a/src/jvmMain/kotlin/starshipfights/data/data_routines.kt b/src/jvmMain/kotlin/starshipfights/data/data_routines.kt index 053024b..8960fa8 100644 --- a/src/jvmMain/kotlin/starshipfights/data/data_routines.kt +++ b/src/jvmMain/kotlin/starshipfights/data/data_routines.kt @@ -3,7 +3,6 @@ package starshipfights.data import kotlinx.coroutines.* import org.litote.kmongo.div import org.litote.kmongo.lt -import org.litote.kmongo.lte import org.litote.kmongo.setValue import starshipfights.data.admiralty.Admiral import starshipfights.data.admiralty.BattleRecord @@ -30,15 +29,7 @@ object DataRoutines : CoroutineScope { UserSession.initialize() return launch { - launch { - while (currentCoroutineContext().isActive) { - launch { - UserSession.remove(UserSession::expirationMillis lte System.currentTimeMillis()) - } - delay(900_000) - } - } - + // Repair ships launch { while (currentCoroutineContext().isActive) { val now = Instant.now() diff --git a/src/jvmMain/kotlin/starshipfights/info/views_user.kt b/src/jvmMain/kotlin/starshipfights/info/views_user.kt index eb16a90..015a5c3 100644 --- a/src/jvmMain/kotlin/starshipfights/info/views_user.kt +++ b/src/jvmMain/kotlin/starshipfights/info/views_user.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.toList import kotlinx.html.* +import org.litote.kmongo.and import org.litote.kmongo.eq import org.litote.kmongo.or import starshipfights.auth.getUser @@ -15,6 +16,7 @@ import starshipfights.data.admiralty.BattleRecord import starshipfights.data.admiralty.DrydockStatus import starshipfights.data.admiralty.ShipInDrydock import starshipfights.data.auth.User +import starshipfights.data.auth.UserSession import starshipfights.data.auth.usernameRegexStr import starshipfights.data.auth.usernameTooltip import starshipfights.game.Faction @@ -22,6 +24,11 @@ import starshipfights.game.GlobalSide import starshipfights.game.toUrlSlug import starshipfights.redirect import java.time.Instant +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.* + +private val instantFormatter: DateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.US) suspend fun ApplicationCall.userPage(): HTML.() -> Unit { val username = parameters["name"]!! @@ -74,7 +81,9 @@ suspend fun ApplicationCall.userPage(): HTML.() -> Unit { } suspend fun ApplicationCall.manageUserPage(): HTML.() -> Unit { - val currentUser = getUser() ?: redirect("/login") + val currentSession = getUserSession() ?: redirect("/login") + val currentUser = User.get(currentSession.user) ?: redirect("/login") + val allUserSessions = UserSession.select(and(UserSession::user eq currentUser.id)).toList() return page( "User Preferences", standardNavBar(), PageNavSidebar( @@ -114,6 +123,58 @@ suspend fun ApplicationCall.manageUserPage(): HTML.() -> Unit { } } } + section { + h1 { +"Other Active Sessions" } + table { + tr { + th { +"User-Agent" } + th { +"Client IPs" } + th { +Entities.nbsp } + } + val now = System.currentTimeMillis() + val expiredSessions = mutableListOf() + allUserSessions.forEach { session -> + if (session.expirationMillis < now) { + expiredSessions += session + return@forEach + } + + tr { + td { +session.userAgent } + td { + session.clientAddresses.forEachIndexed { i, clientAddress -> + if (i != 0) br + +clientAddress + } + } + td { + a(href = "/logout/${session.id}") { +"Logout" } + } + } + } + tr { + td { + colSpan = "3" + a(href = "/logout-all") { +"Logout All" } + } + } + expiredSessions.forEach { session -> + tr { + td { +session.userAgent } + td { + session.clientAddresses.forEachIndexed { i, clientAddress -> + if (i != 0) br + +clientAddress + } + } + td { + +"Expired at " + +instantFormatter.format(Instant.ofEpochMilli(session.expirationMillis)) + } + } + } + } + } } } @@ -282,6 +343,7 @@ suspend fun ApplicationCall.admiralPage(): HTML.() -> Unit { tr { th { +"Role" } th { +"Against" } + th { +"Time" } th { +"Result" } } records.sortedBy { it.whenEnded }.forEach { record -> @@ -302,6 +364,9 @@ suspend fun ApplicationCall.admiralPage(): HTML.() -> Unit { +opponent.fullName } } + td { + +instantFormatter.format(record.whenEnded) + } td { +when (recordRoles[record.id]) { record.winner -> "Victory" -- 2.25.1