Add Privacy Policy
authorTheSaminator <TheSaminator@users.noreply.github.com>
Sun, 13 Feb 2022 14:58:34 +0000 (09:58 -0500)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Sun, 13 Feb 2022 14:58:34 +0000 (09:58 -0500)
src/jvmMain/kotlin/starshipfights/auth/providers.kt
src/jvmMain/kotlin/starshipfights/data/auth/user_sessions.kt
src/jvmMain/kotlin/starshipfights/info/views_main.kt
src/jvmMain/kotlin/starshipfights/info/views_user.kt

index 7322bae068e8e4c3e88814f4a6d82da4e46d4f26..df03c836cb5f0c7c7d82ffb793ab84b9be5a28f8 100644 (file)
@@ -335,6 +335,7 @@ object TestAuthProvider : AuthProvider {
                                                                profileBio = "BEEP BOOP I EXIST ONLY FOR TESTING BLOP BLARP.",
                                                                registeredAt = Instant.now(),
                                                                lastActivity = Instant.now(),
+                                                               showUserStatus = false,
                                                        ).also {
                                                                User.put(it)
                                                        }
@@ -455,6 +456,12 @@ class ProductionAuthProvider(val discordLogin: DiscordLogin) : AuthProvider {
                                
                                call.respondHtml(HttpStatusCode.OK, page("Login with Discord", call.standardNavBar(), null) {
                                        section {
+                                               p {
+                                                       style = "text-align:center"
+                                                       +"By logging in, you indicate your agreement to the "
+                                                       a(href = "/about#pp") { +"Privacy Policy" }
+                                                       +"."
+                                               }
                                                if (errorMsg != null)
                                                        p {
                                                                style = "color:#d22"
@@ -502,6 +509,7 @@ class ProductionAuthProvider(val discordLogin: DiscordLogin) : AuthProvider {
                                                profileBio = "Hi, I'm new here!",
                                                registeredAt = Instant.now(),
                                                lastActivity = Instant.now(),
+                                               showUserStatus = false,
                                        )
                                        
                                        val userSession = UserSession(
index 7fa0c6d19a05d2c2140de0de83c0fa08b05e32ea..f53352d982ef8f321e35026b8bd10d5601038907 100644 (file)
@@ -26,13 +26,17 @@ data class User(
        
        val registeredAt: @Contextual Instant,
        val lastActivity: @Contextual Instant,
+       val showUserStatus: Boolean,
        
        val status: UserStatus = UserStatus.AVAILABLE,
 ) : DataDocument<User> {
        val discordAvatarUrl: String
-               get() = discordAvatar?.let {
+               get() = discordAvatar?.takeIf { showDiscordName }?.let {
                        "https://cdn.discordapp.com/avatars/$discordId/$it." + (if (it.startsWith("a_")) "gif" else "png") + "?size=256"
-               } ?: "https://cdn.discordapp.com/embed/avatars/${(discordDiscriminator.lastOrNull()?.digitToInt() ?: 0) % 5}.png"
+               } ?: anonymousAvatarUrl
+       
+       private val anonymousAvatarUrl: String
+               get() = "https://cdn.discordapp.com/embed/avatars/${(discordDiscriminator.lastOrNull()?.digitToInt() ?: 0) % 5}.png"
        
        companion object Table : DocumentTable<User> by DocumentTable.create({
                unique(User::discordId)
index 594e4b482d1f058075204d92ebc67df3cb931e82..a4c5803dc61355baa460feee8fffb6c18d0bda16 100644 (file)
@@ -5,6 +5,8 @@ import kotlinx.coroutines.flow.take
 import kotlinx.coroutines.flow.toList
 import kotlinx.html.*
 import org.litote.kmongo.descending
+import org.litote.kmongo.eq
+import starshipfights.CurrentConfiguration
 import starshipfights.data.auth.User
 
 suspend fun ApplicationCall.mainPage(): HTML.() -> Unit {
@@ -25,17 +27,99 @@ suspend fun ApplicationCall.mainPage(): HTML.() -> Unit {
        }
 }
 
-suspend fun ApplicationCall.aboutPage(): HTML.() -> Unit = page("About", standardNavBar(), IndexSidebar) {
-       section {
-               img(alt = "Starship Fights Logo", src = "/static/images/logo.svg") {
-                       style = "width:100%"
+suspend fun ApplicationCall.aboutPage(): HTML.() -> Unit {
+       val owner = CurrentConfiguration.discordClient?.ownerId?.let {
+               User.locate(User::discordId eq it)
+       } ?: return page(
+               "About", standardNavBar(), null
+       ) {
+               section {
+                       img(alt = "Starship Fights Logo", src = "/static/images/logo.svg") {
+                               style = "width:100%"
+                       }
+                       p {
+                               +"This is a test instance of Starship Fights."
+                       }
+               }
+       }
+       
+       return page(
+               "About", standardNavBar(), PageNavSidebar(
+                       listOf(
+                               NavHead("Jump To"),
+                               NavLink("#pp", "Privacy Policy")
+                       )
+               )
+       ) {
+               section {
+                       img(alt = "Starship Fights Logo", src = "/static/images/logo.svg") {
+                               style = "width:100%"
+                       }
+                       p {
+                               +"Starship Fights is designed and programmed by the person behind "
+                               a(href = "https://nationstates.net/mechyrdia") { +"Mechyrdia" }
+                               +". He can be reached by telegram on NationStates, or by his "
+                               a(href = "/user/${owner.id}") { +"account on this site" }
+                               +"."
+                       }
                }
-               p {
-                       +"Starship Fights is designed and programmed by the person behind "
-                       a(href = "https://nationstates.net/mechyrdia") { +"Mechyrdia" }
-                       +". He can be reached by telegram on NationStates, or by his "
-                       a(href = "https://discord.id/?prefill=307880116715913217") { +"Discord account" }
-                       +"."
+               section {
+                       id = "pp"
+                       h1 { +"Privacy Policy" }
+                       h2 { +"What Data Do We Collect" }
+                       p { +"Starship Fights does not collect very much personal data; the only data it collects is relevant to either user authentication or user authorization. The following data is collected by the game:" }
+                       dl {
+                               dt { +"Discord ID" }
+                               dd { +"This is needed to keep your Starship Fights user account associated with your Discord login, so that you can keep your admirals and ships when you log in." }
+                               dt { +"Discord Profile Data (Name, Discriminator, Avatar)" }
+                               dd {
+                                       +"This is kept so that you have the option of showing what your Discord account is on your profile page. It's optional to display to other users, with the choice being in the "
+                                       a(href = "/me/manage") { +"User Preferences" }
+                                       +"page. Note that we do "
+                                       strong { +"not" }
+                                       +" request or track email addresses."
+                               }
+                               dt { +"Your browser's User-Agent" }
+                               dd {
+                                       +"This is associated with your session data as a layer of security, so that if someone were to (somehow) steal your session token and put it into their browser, that person wouldn't be logged in as you, since the User-Agent would probably be different."
+                               }
+                               dt { +"Your public-facing IP address" }
+                               dd {
+                                       +"This is associated with your sessions, so that it may be displayed to you when you look at your currently logged-in sessions on your "
+                                       a(href = "/me/manage") { +"User Preferences" }
+                                       +" page, so that you can log out of a session if you don't recognize its IP address."
+                               }
+                               dt { +"The date and time of your last activity" }
+                               dd {
+                                       +"This is associated with your user account as a whole, so that your Online/Offline status can be displayed. It's optional to display your current status, and the choice is in your "
+                                       a(href = "/me/manage") { +"User Preferences" }
+                                       +" page."
+                               }
+                       }
+                       h2 { +"How Do We Collect It" }
+                       p {
+                               +"Your Discord information is collected using the Discord API whenever you log in via Discord's OAuth2. Your User-Agent and IP address are collected using the HTTP requests that your browser sends to the website, and the date and time of your last activity is tracked using the system clock."
+                       }
+                       h2 { +"Who Can See It" }
+                       p {
+                               +"The only people who can see the data we collect are you and the system administrator. We do not sell data to advertisers. The site is hosted on "
+                               a(href = "https://hetzner.com/") { +"Hetzner Cloud" }
+                               +", who can "
+                               em { +"in theory" }
+                               +" access it."
+                       }
+                       p {
+                               +"Privacy policies are nice and all, but they're only as strong as the staff that implements them. I have no interest in abusing others, just as I have no interest in doxing or otherwise revealing what locations people log in from. Nor have I any interest in being worshipped as some kind of programmer-god messiah. I am impervious to such corrupting ambitions."
+                       }
+                       h2 { +"Who Can't See It" }
+                       p {
+                               +"We protect your data by a combination of requiring TLS-secured HTTP connections, and not allowing public access to the database; it's accessible only by the localhost network adapter. The database is accessed by the database administrator via SSH tunnelling secured by a private key, making it also infeasible to break in to."
+                       }
+                       h2 { +"When Was This Written" }
+                       dl {
+                               dt { +"February 13, 2022" }
+                               dd { +"Initial writing" }
+                       }
                }
        }
 }
index 607bf42319c9f73acf72d190302f86de496c41fb..99d1ff7f7b8fab13b5efe28adca055c80f65c2d0 100644 (file)
@@ -39,41 +39,42 @@ suspend fun ApplicationCall.userPage(): HTML.() -> Unit {
                        img(src = user.discordAvatarUrl) {
                                style = "border-radius:50%"
                        }
-                       p {
-                               style = "text-align:center"
-                               if (user.showDiscordName) {
+                       if (user.showDiscordName)
+                               p {
+                                       style = "text-align:center"
                                        +user.discordName
                                        +"#"
                                        +user.discordDiscriminator
-                                       br
                                }
-                               when (user.status) {
-                                       UserStatus.IN_BATTLE -> +"In Battle"
-                                       UserStatus.READY_FOR_BATTLE -> +"In Battle"
-                                       UserStatus.IN_MATCHMAKING -> +"In Matchmaking"
-                                       UserStatus.AVAILABLE -> if (hasOpenSessions) +"Online" else +"Offline"
+                       if (user.showUserStatus)
+                               p {
+                                       +when (user.status) {
+                                               UserStatus.IN_BATTLE -> "In Battle"
+                                               UserStatus.READY_FOR_BATTLE -> "In Battle"
+                                               UserStatus.IN_MATCHMAKING -> "In Matchmaking"
+                                               UserStatus.AVAILABLE -> if (hasOpenSessions) "Online" else "Offline"
+                                       }
                                }
-                       }
                        if (user.discordId == CurrentConfiguration.discordClient?.ownerId)
                                p {
                                        style = "text-align:center;border:2px solid #a82;padding:3px;background-color:#fc3;color:#a82;font-variant:small-caps;font-family:'Orbitron',sans-serif"
                                        +"Site Owner"
                                }
-                       hr { style = "border-color:#036" }
-                       p {
-                               style = "text-align:center"
-                               +"Registered at "
-                               span(classes = "moment") {
-                                       style = "display:none"
-                                       +user.registeredAt.toEpochMilli().toString()
-                               }
-                               br
-                               +"Last active at "
-                               span(classes = "moment") {
-                                       style = "display:none"
-                                       +user.lastActivity.toEpochMilli().toString()
+                       if (user.showUserStatus)
+                               p {
+                                       style = "text-align:center"
+                                       +"Registered at "
+                                       span(classes = "moment") {
+                                               style = "display:none"
+                                               +user.registeredAt.toEpochMilli().toString()
+                                       }
+                                       br
+                                       +"Last active at "
+                                       span(classes = "moment") {
+                                               style = "display:none"
+                                               +user.lastActivity.toEpochMilli().toString()
+                                       }
                                }
-                       }
                        if (isCurrentUser) {
                                hr { style = "border-color:#036" }
                                div(classes = "list") {
@@ -188,7 +189,7 @@ suspend fun ApplicationCall.manageUserPage(): HTML.() -> Unit {
                        }
                }
                section {
-                       h1 { +"Other Sessions" }
+                       h2 { +"Other Sessions" }
                        table {
                                tr {
                                        th { +"User-Agent" }