Improve user sessions
authorTheSaminator <TheSaminator@users.noreply.github.com>
Mon, 7 Feb 2022 17:01:04 +0000 (12:01 -0500)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Mon, 7 Feb 2022 17:01:04 +0000 (12:01 -0500)
src/jvmMain/kotlin/starshipfights/auth/providers.kt
src/jvmMain/kotlin/starshipfights/data/data_routines.kt
src/jvmMain/kotlin/starshipfights/info/views_user.kt

index 1085bd7a80b470e1e9fbc1fddcc4f60ef1e5838e..53132ee9c831261183acc0501ab215c8b73b3c70 100644 (file)
@@ -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<Id<UserSession>>()?.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<UserSession>(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)
                        }
                }
index 053024b2a9dea23fd9898c5add476a494525e365..8960fa8a5a7a18bfdf7e06a717803f96eec19375 100644 (file)
@@ -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()
index eb16a901269ed7c6d04daec23dff79c6b90330d5..015a5c364be54a52e38a8db8c233f3c75f0dfe5f 100644 (file)
@@ -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<UserSession>()
+                               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"