From 441d7376cb1e926ff59389e1ee89730b2a1a7093 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:56:33 -0500 Subject: [PATCH] [+] Upload pfp endpoint --- build.gradle.kts | 3 +++ src/main/java/ext/Ext.kt | 5 ++++ .../icu/samnyan/aqua/net/UserRegistrar.kt | 26 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index abc006db..38446dfb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -74,6 +74,9 @@ dependencies { implementation("io.jsonwebtoken:jjwt-api:0.12.5") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5") + + // Content validation + implementation("org.apache.tika:tika-core:2.9.1") } group = "icu.samnya" diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index d7348359..7cae24d2 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -8,6 +8,8 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json +import org.apache.tika.Tika +import org.apache.tika.mime.MimeTypes import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestHeader @@ -44,6 +46,7 @@ operator fun Int.minus(message: String): Nothing { val emailRegex = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9-]+(\\.[\\p{L}0-9-]+)*(\\.[\\p{L}]{2,})$".toRegex() fun Str.isValidEmail(): Bool = emailRegex.matches(this) +// Global tools val HTTP = HttpClient(CIO) { install(ContentNegotiation) { json(Json { @@ -52,6 +55,8 @@ val HTTP = HttpClient(CIO) { }) } } +val TIKA = Tika() +val MIMES = MimeTypes.getDefaultMimeTypes() // Date and time fun millis() = System.currentTimeMillis() diff --git a/src/main/java/icu/samnyan/aqua/net/UserRegistrar.kt b/src/main/java/icu/samnyan/aqua/net/UserRegistrar.kt index 309a6bc2..0f983452 100644 --- a/src/main/java/icu/samnyan/aqua/net/UserRegistrar.kt +++ b/src/main/java/icu/samnyan/aqua/net/UserRegistrar.kt @@ -4,6 +4,7 @@ import ext.* import icu.samnyan.aqua.net.components.* import icu.samnyan.aqua.net.db.* import icu.samnyan.aqua.net.db.AquaUserServices.Companion.SETTING_FIELDS +import icu.samnyan.aqua.net.utils.PathProps import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.model.Card @@ -12,8 +13,10 @@ import jakarta.servlet.http.HttpServletRequest import org.slf4j.LoggerFactory import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile import java.time.Instant import java.time.LocalDateTime +import kotlin.io.path.writeBytes @RestController @API("/api/v2/user") @@ -28,8 +31,11 @@ class UserRegistrar( val cardRepo: CardRepository, val cardService: CardService, val validator: AquaUserServices, - val emailProps: EmailProperties + val emailProps: EmailProperties, + paths: PathProps ) { + val portraitPath = paths.aquaNetPortrait.path() + companion object { // Random long with length 9-10 // We chose 1e9 as the start because normal cards took 0...1e9-1 @@ -188,4 +194,22 @@ class UserRegistrar( mapOf("keychip" to new) } + + @API("/upload-pfp") + @Doc("Upload a profile picture for the user.", "Success message") + suspend fun uploadPfp(@RP token: Str, @RP file: MultipartFile) = jwt.auth(token) { u -> + // Processing the image would lead to many open factors for attack + // (e.g. the JFIF Pixel Flood attack that ImageIO is vulnerable to) + // So we check file magic, then store the image without any processing + val bytes = file.bytes + val mime = TIKA.detect(bytes) ?: (400 - "Invalid file type") + + // Check if the file is an image + if (!mime.startsWith("image/")) 400 - "Invalid file type" + + // Save the image + (portraitPath / "${u.auId}.${MIMES.forName(mime)?.extension ?: "jpg"}").writeBytes(bytes) + + SUCCESS + } } \ No newline at end of file