mirror of
https://gitlab.com/mangadex-pub/mangadex_at_home.git
synced 2024-01-19 02:48:37 +00:00
105 lines
4.2 KiB
Kotlin
105 lines
4.2 KiB
Kotlin
/*
|
|
Mangadex@Home
|
|
Copyright (c) 2020, MangaDex Network
|
|
This file is part of MangaDex@Home.
|
|
|
|
MangaDex@Home is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
MangaDex@Home is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this MangaDex@Home. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package mdnet.server
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException
|
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
import com.fasterxml.jackson.databind.ObjectMapper
|
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
import com.fasterxml.jackson.module.kotlin.readValue
|
|
import mdnet.data.Token
|
|
import mdnet.logging.info
|
|
import mdnet.security.TweetNaclFast
|
|
import org.http4k.core.Filter
|
|
import org.http4k.core.HttpHandler
|
|
import org.http4k.core.Response
|
|
import org.http4k.core.Status
|
|
import org.http4k.lens.LensFailure
|
|
import org.http4k.lens.Path
|
|
import org.slf4j.LoggerFactory
|
|
import java.time.OffsetDateTime
|
|
import java.util.Base64
|
|
|
|
class TokenVerifier(tokenKey: ByteArray, isDisabled: Boolean) : Filter {
|
|
private val box = TweetNaclFast.SecretBox(tokenKey)
|
|
private val isDisabled = isDisabled
|
|
|
|
override fun invoke(next: HttpHandler): HttpHandler {
|
|
return then@{
|
|
if (isDisabled) {
|
|
return@then next(it)
|
|
}
|
|
|
|
val chapterHash = Path.of("chapterHash")(it)
|
|
|
|
val cleanedUri = it.uri.path.replaceBefore("/data", "/{token}")
|
|
|
|
val tokenArr = try {
|
|
val toDecode = try {
|
|
Path.of("token")(it)
|
|
} catch (e: LensFailure) {
|
|
LOGGER.info(e) { "Request for $cleanedUri rejected for missing token" }
|
|
return@then Response(Status.FORBIDDEN).body("Token is missing")
|
|
}
|
|
Base64.getUrlDecoder().decode(toDecode)
|
|
} catch (e: IllegalArgumentException) {
|
|
LOGGER.info(e) { "Request for $cleanedUri rejected for non-base64 token" }
|
|
return@then Response(Status.FORBIDDEN).body("Token is invalid base64")
|
|
}
|
|
if (tokenArr.size < 24) {
|
|
LOGGER.info { "Request for $cleanedUri rejected for invalid token" }
|
|
return@then Response(Status.FORBIDDEN)
|
|
}
|
|
val token = try {
|
|
JACKSON.readValue<Token>(
|
|
box.open(tokenArr.sliceArray(24 until tokenArr.size), tokenArr.sliceArray(0 until 24)).apply {
|
|
if (this == null) {
|
|
LOGGER.info { "Request for $cleanedUri rejected for invalid token" }
|
|
return@then Response(Status.FORBIDDEN)
|
|
}
|
|
}
|
|
)
|
|
} catch (e: JsonProcessingException) {
|
|
LOGGER.info(e) { "Request for $cleanedUri rejected for invalid token" }
|
|
return@then Response(Status.FORBIDDEN).body("Token is invalid")
|
|
}
|
|
|
|
if (OffsetDateTime.now().isAfter(token.expires)) {
|
|
LOGGER.info { "Request for $cleanedUri rejected for expired token" }
|
|
return@then Response(Status.GONE).body("Token has expired")
|
|
}
|
|
|
|
if (token.hash != chapterHash) {
|
|
LOGGER.info { "Request for $cleanedUri rejected for inapplicable token" }
|
|
return@then Response(Status.FORBIDDEN).body("Token is inapplicable for the image")
|
|
}
|
|
|
|
return@then next(it)
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private val LOGGER = LoggerFactory.getLogger(TokenVerifier::class.java)
|
|
private val JACKSON: ObjectMapper = jacksonObjectMapper()
|
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
.registerModule(JavaTimeModule())
|
|
}
|
|
}
|