Skip to content

Commit

Permalink
URL upload adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
UnknownJoe796 committed Oct 12, 2023
1 parent 9d5507e commit 512ca1e
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ interface SecureHasher {
fun SecureHasher.sign(string: String): String = Base64.getEncoder().encodeToString(sign(string.toByteArray()))
fun SecureHasher.verify(string: String, base64Signature: String): Boolean =
verify(string.toByteArray(), Base64.getDecoder().decode(base64Signature))
fun SecureHasher.signUrl(string: String): String = Base64.getUrlEncoder().encodeToString(sign(string.toByteArray()))
fun SecureHasher.verifyUrl(string: String, base64Signature: String): Boolean =
verify(string.toByteArray(), Base64.getUrlDecoder().decode(base64Signature))

fun SecureHasher.signJwt(claims: JwtClaims): String = buildString {
val withDefaults = Json(Serialization.json) { encodeDefaults = true; explicitNulls = false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.lightningkite.lightningserver.files
import com.lightningkite.lightningserver.encryption.sign
import com.lightningkite.lightningserver.encryption.verify
import com.lightningkite.lightningserver.core.ContentType
import com.lightningkite.lightningserver.encryption.signUrl
import com.lightningkite.lightningserver.encryption.verifyUrl
import com.lightningkite.lightningserver.http.HttpContent
import com.lightningkite.lightningserver.settings.generalSettings
import kotlinx.datetime.Clock
Expand Down Expand Up @@ -73,7 +75,7 @@ class LocalFile(val system: LocalFileSystem, val file: File) : FileObject {
val readUntil = qp["readUntil"]?.toLongOrNull() ?: return false
if(System.currentTimeMillis() > readUntil) return false
val signedUrlStart = "$url?readUntil=$readUntil"
system.signer.verify(signedUrlStart, qp["signature"] ?: "")
system.signer.verifyUrl(signedUrlStart, qp["signature"] ?: "")
} ?: true
} catch (e: Exception) {
false
Expand All @@ -88,11 +90,11 @@ class LocalFile(val system: LocalFileSystem, val file: File) : FileObject {

override val signedUrl: String
get() = if(system.signedUrlExpiration == null) url else url.plus("?readUntil=${now().plus(system.signedUrlExpiration).toEpochMilliseconds()}").let {
it + "&signature=" + system.signer.sign(it)
it + "&signature=" + system.signer.signUrl(it)
}

override fun uploadUrl(timeout: Duration): String = url.plus("?writeUntil=${now().plus(timeout).toEpochMilliseconds()}").let {
it + "&signature=" + system.signer.sign(it)
it + "&signature=" + system.signer.signUrl(it)
}

internal fun checkSignatureWrite(queryParams: List<Pair<String, String>>): Boolean {
Expand All @@ -101,7 +103,7 @@ class LocalFile(val system: LocalFileSystem, val file: File) : FileObject {
val writeUntil = qp["writeUntil"]?.toLongOrNull() ?: return false
val signedUrlStart = "$url?writeUntil=$writeUntil"
if(System.currentTimeMillis() > writeUntil) return false
system.signer.verify(signedUrlStart, qp["signature"] ?: "")
system.signer.verifyUrl(signedUrlStart, qp["signature"] ?: "")
} catch (e: Exception) {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import com.lightningkite.now
import io.ktor.http.*
import kotlin.time.Duration
import kotlinx.datetime.Instant
import org.jetbrains.annotations.TestOnly
import kotlin.time.Duration.Companion.days

class UploadEarlyEndpoint(
Expand Down Expand Up @@ -48,12 +50,12 @@ class UploadEarlyEndpoint(
database().collection<UploadForNextRequest>().insertOne(newItem)
UploadInformation(
uploadUrl = newFile.uploadUrl(expiration),
futureCallToken = newFile.url.plus("?useUntil=${now().plus(expiration).toEpochMilliseconds()}").let {
it + "&token=" + signer().sign(it)
}
futureCallToken = signUrl(newFile.url)
)
}
)


val cleanupSchedule = schedule("cleanupUploads", 1.days) {
database().collection<UploadForNextRequest>().deleteMany(condition { it.expires lt now() }).forEach {
try {
Expand All @@ -68,14 +70,30 @@ class UploadEarlyEndpoint(
fun validateFile(url: String, params: Map<String, String>): Boolean {
val token = params["token"] ?: return false
val exp = params["useUntil"]?.toLongOrNull() ?: return false
if(System.currentTimeMillis() > exp) return false
if(now().toEpochMilliseconds() > exp) return false
val file = ServerFile(url)
if(!signer().verify(url.substringBefore('?') + "?useUntil=$exp", token)) return false
if(!verifyUrl(url, exp, token)) return false
GlobalScope.launch {
database().collection<UploadForNextRequest>()
.deleteMany(condition { it.file eq ServerFile(url) })
}
return true
}

@TestOnly
internal fun signUrl(url: String): String {
return url.plus("?useUntil=${now().plus(expiration).toEpochMilliseconds()}").let {
it + "&token=" + signer().signUrl(it)
}
}
@TestOnly
internal fun verifyUrl(url: String): Boolean {
val params = url.substringAfter('?').split('&').associate { it.substringBefore('=') to it.substringAfter('=').decodeURLQueryComponent() }
return verifyUrl(url.substringBefore('?'), params["useUntil"]!!.toLong(), params["token"]!!)
}
@TestOnly
internal fun verifyUrl(url: String, exp: Long, token: String): Boolean {
return signer().verifyUrl(url.substringBefore('?') + "?useUntil=$exp", token)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.lightningkite.lightningserver.engine.UnitTestEngine
import com.lightningkite.lightningserver.engine.engine
import com.lightningkite.lightningserver.exceptions.NotFoundException
import com.lightningkite.lightningserver.files.FilesSettings
import com.lightningkite.lightningserver.files.UploadEarlyEndpoint
import com.lightningkite.lightningserver.logging.LoggingSettings
import com.lightningkite.lightningserver.logging.loggingSettings
import com.lightningkite.lightningserver.serialization.Serialization
Expand Down Expand Up @@ -50,12 +51,15 @@ object TestSettings: ServerPathGroup(ServerPath.root) {
val cache = setting("cache", CacheSettings())
val files = setting("files", FilesSettings())


val authPath = ServerPath("auth")

init {
Authentication.isDeveloper = authRequired<TestUser> { it.get().isSuperAdmin }
}

val earlyUpload = UploadEarlyEndpoint(path("upload"), files, database)

val ws = ServerPath("test").restApiWebsocket<HasId<*>?, TestThing, UUID>(
database,
info = modelInfo(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.lightningkite.lightningserver.files

import com.lightningkite.default
import com.lightningkite.lightningserver.TestSettings
import com.lightningkite.lightningserver.core.ServerPath
import com.lightningkite.now
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import org.junit.Assert.*
import java.util.UUID
import kotlin.test.Test
import kotlin.time.Duration.Companion.milliseconds

class UploadEarlyEndpointTest {
@Test fun testSigning() {
var now = now()
Clock.default = object: Clock {
override fun now(): Instant = now
}
repeat(1000) {
now.plus(1.milliseconds)
assertTrue(TestSettings.earlyUpload.verifyUrl(TestSettings.earlyUpload.signUrl("https://test.com/test.jpg")))
}
Clock.default = Clock.System
}
}

0 comments on commit 512ca1e

Please sign in to comment.