[+] Upload pfp endpoint

pull/22/head
Azalea 2024-03-05 17:56:33 -05:00
parent c9ac38de01
commit 441d7376cb
3 changed files with 33 additions and 1 deletions

View File

@ -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"

View File

@ -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()

View File

@ -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
}
}