Compare commits

..

13 commits

Author SHA1 Message Date
Ecorous 87b50e0a2e
fix logback.groovy 2024-06-18 18:17:23 +01:00
Ecorous 5512c89bc6
add env var for log file 2024-06-18 18:15:37 +01:00
Ecorous 10a9a99651
fix an oopsy x2 2024-06-18 18:11:58 +01:00
Ecorous 5fe594ed39
fix an oopsy 2024-06-18 18:10:15 +01:00
Ecorous 21ed28b3a1
add proper logging 2024-06-18 18:08:19 +01:00
moehreag 8427003d37 allow all origins 2024-06-17 16:18:50 +02:00
moehreag ddd7bb6ba3 correct routes 2024-06-17 16:09:00 +02:00
moehreag e0e35eb97c tweak logging for debugging purposes 2024-06-17 15:27:27 +02:00
moehreag bc8a864fc1 tweak logging in error case 2024-06-17 14:34:00 +02:00
moehreag 76daadaebe move to correct package 2024-06-17 13:03:46 +02:00
moehreag 3b8c103ec1 add logging for failed authentication 2024-06-17 13:00:45 +02:00
TheKodeToad 596aa06809 oops 2024-06-12 14:22:04 -04:00
Ecorous 2c8016e77f Merge pull request 'Stuff' (#1) from TheKodeToad/upload into mistress
Reviewed-on: #1
Reviewed-by: owlsys <owlsys@noreply.localhost>
Reviewed-by: Ecorous <ecorous@outlook.com>
2024-06-12 13:07:49 -04:00
14 changed files with 66 additions and 57 deletions

View file

@ -14,7 +14,7 @@ group = "dev.frogmc"
version = "0.0.1" version = "0.0.1"
application { application {
mainClass.set("dev.frogmc.ApplicationKt") mainClass.set("dev.frogmc.meta.ApplicationKt")
val isDevelopment: Boolean = project.ext.has("development") val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -1,12 +1,15 @@
package dev.frogmc package dev.frogmc.meta
import dev.frogmc.plugins.* import dev.frogmc.meta.plugins.configureHTTP
import dev.frogmc.meta.plugins.configureRouting
import dev.frogmc.meta.plugins.configureSerialization
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.engine.* import io.ktor.server.engine.*
import io.ktor.server.netty.* import io.ktor.server.netty.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
val logger = LoggerFactory.getLogger("FrogMC/Meta") val logger: Logger = LoggerFactory.getLogger("FrogMC/Meta")
fun main() { fun main() {

View file

@ -1,4 +1,4 @@
package dev.frogmc package dev.frogmc.meta
object Config { object Config {
val POSTGRES_DATABASE = getEnv("DATABASE", "frogmc") val POSTGRES_DATABASE = getEnv("DATABASE", "frogmc")
@ -6,7 +6,7 @@ object Config {
val POSTGRES_PASSWORD = getEnv("PASSWORD", "example") val POSTGRES_PASSWORD = getEnv("PASSWORD", "example")
val POSTGRES_HOST = getEnv("HOST", "localhost") val POSTGRES_HOST = getEnv("HOST", "localhost")
val POSTGRES_PORT = getEnv("PORT", "5432") val POSTGRES_PORT = getEnv("PORT", "5432")
val UPLOAD_SECRET = getEnv("UPLOAD_SECRET", "").toByteArray() val UPLOAD_SECRET = getEnv("UPLOAD_SECRET", "")
private fun getEnv(key: String, default: String): String { private fun getEnv(key: String, default: String): String {
return System.getenv("FROGMC_META_$key") ?: default return System.getenv("FROGMC_META_$key") ?: default

View file

@ -1,6 +1,8 @@
package dev.frogmc package dev.frogmc.meta
import dev.frogmc.types.* import dev.frogmc.meta.types.LibraryVersion
import dev.frogmc.meta.types.LibraryVersions
import dev.frogmc.meta.types.LoaderVersions
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@ -21,8 +23,7 @@ object DB {
transaction(db) { transaction(db) {
SchemaUtils.create( SchemaUtils.create(
LoaderVersions, LoaderVersions,
LibraryVersions, LibraryVersions
File
) )
} }
if (db == null) { if (db == null) {

View file

@ -1,16 +1,20 @@
package dev.frogmc.plugins package dev.frogmc.meta.plugins
import dev.frogmc.Config import dev.frogmc.meta.Config
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.response.* import io.ktor.server.response.*
import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
val authPlugin = createRouteScopedPlugin("auth") { val authPlugin = createRouteScopedPlugin("auth") {
onCall { onCall {
val authorization = it.request.headers["Authorization"] val authorization = it.request.headers["Authorization"]
if (authorization.isNullOrEmpty() || !MessageDigest.isEqual(authorization.toByteArray(), Config.UPLOAD_SECRET)) if (authorization.isNullOrEmpty() || !MessageDigest.isEqual(
it.respond(HttpStatusCode.Unauthorized); authorization.toByteArray(),
Config.UPLOAD_SECRET.toByteArray()
)
) {
it.respond(HttpStatusCode.Unauthorized)
}
} }
} }

View file

@ -1,18 +1,16 @@
package dev.frogmc.plugins package dev.frogmc.meta.plugins
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*
fun Application.configureHTTP() { fun Application.configureHTTP() {
install(CORS) { install(CORS) {
anyHost()
allowMethod(HttpMethod.Options) allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put) allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete) allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch) allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization) allowHeader(HttpHeaders.Authorization)
allowHeader("MyCustomHeader")
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
} }
} }

View file

@ -1,21 +1,19 @@
package dev.frogmc.plugins package dev.frogmc.meta.plugins
import dev.frogmc.DB import dev.frogmc.meta.DB
import dev.frogmc.types.LoaderVersion import dev.frogmc.meta.types.LoaderVersion
import dev.frogmc.types.PartialLoaderVersion import dev.frogmc.meta.types.PartialLoaderVersion
import dev.frogmc.types.LoaderVersions import dev.frogmc.meta.types.LoaderVersions
import dev.frogmc.types.ModrinthVersion import dev.frogmc.meta.types.ModrinthVersion
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.http.content.* import io.ktor.server.http.content.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.html.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
@ -31,7 +29,7 @@ fun Application.configureRouting() {
call.respond(transaction(DB.db) { call.respond(transaction(DB.db) {
LoaderVersions LoaderVersions
.select(LoaderVersions.version, LoaderVersions.releaseDate) .select(LoaderVersions.version, LoaderVersions.releaseDate)
.orderBy(LoaderVersions.releaseDate to SortOrder.ASC) .orderBy(LoaderVersions.releaseDate to SortOrder.DESC)
.map { .map {
PartialLoaderVersion( PartialLoaderVersion(
it[LoaderVersions.version], it[LoaderVersions.version],
@ -40,7 +38,7 @@ fun Application.configureRouting() {
} }
}) })
} }
get("/versions/latest") { get("/version/latest") {
val row = transaction(DB.db) { val row = transaction(DB.db) {
LoaderVersions LoaderVersions
.selectAll() .selectAll()
@ -61,7 +59,7 @@ fun Application.configureRouting() {
) )
) )
} }
get("/versions/{version}") { get("/version/{version}") {
val version = call.parameters["version"] ?: return@get call.respond(HttpStatusCode.BadRequest) val version = call.parameters["version"] ?: return@get call.respond(HttpStatusCode.BadRequest)
val row = transaction { val row = transaction {
LoaderVersions LoaderVersions
@ -83,7 +81,7 @@ fun Application.configureRouting() {
) )
) )
} }
route("/versions/upload") { route("/version/upload") {
install(authPlugin) install(authPlugin)
post { post {
val versionObj = call.receive<LoaderVersion>() val versionObj = call.receive<LoaderVersion>()
@ -103,7 +101,7 @@ fun Application.configureRouting() {
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
} }
route("/versions/delete/{version}") { route("/version/delete/{version}") {
install(authPlugin) install(authPlugin)
delete { delete {
val version = val version =
@ -126,11 +124,11 @@ fun Application.configureRouting() {
val versions = DB.getLibraryVersions() val versions = DB.getLibraryVersions()
call.respond(versions) call.respond(versions)
} }
get("/versions/latest") { get("/version/latest") {
val versions = DB.getLibraryVersions() val versions = DB.getLibraryVersions()
call.respond(versions.first()) call.respond(versions.first())
} }
get("/versions/{version}/download") { get("/version/{version}/download") {
val version = call.parameters["version"] ?: return@get call.respond( val version = call.parameters["version"] ?: return@get call.respond(
HttpStatusCode.BadRequest, HttpStatusCode.BadRequest,
"message" to "Invalid version." "message" to "Invalid version."
@ -152,7 +150,7 @@ fun Application.configureRouting() {
val mrVersion = json.decodeFromString<ModrinthVersion>(mrVersionString) val mrVersion = json.decodeFromString<ModrinthVersion>(mrVersionString)
call.respond(mrVersion.files[0]) call.respond(mrVersion.files[0])
} }
get("/versions/{version}") { get("/version/{version}") {
val version = call.parameters["version"] ?: return@get call.respond( val version = call.parameters["version"] ?: return@get call.respond(
HttpStatusCode.BadRequest, HttpStatusCode.BadRequest,
"message" to "Invalid version." "message" to "Invalid version."

View file

@ -1,4 +1,4 @@
package dev.frogmc.plugins package dev.frogmc.meta.plugins
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.* import io.ktor.server.application.*

View file

@ -1,4 +1,4 @@
package dev.frogmc.types package dev.frogmc.meta.types
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package dev.frogmc.types package dev.frogmc.meta.types
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
@ -19,9 +19,3 @@ object LibraryVersions : Table() {
override val primaryKey = PrimaryKey(version) override val primaryKey = PrimaryKey(version)
} }
object File : Table() {
val version = text("version")
val sha1 = text("sha1")
val size = text("size")
val url = text("url")
}

View file

@ -1,14 +1,24 @@
import ch.qos.logback.core.joran.spi.ConsoleTarget import ch.qos.logback.core.joran.spi.ConsoleTarget
import ch.qos.logback.core.ConsoleAppender import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.FileAppender
def environment = System.getenv("ENVIRONMENT") ?: "production" def environment = System.getenv("ENVIRONMENT") ?: "production"
def defaultLevel = INFO def defaultLevel = INFO
def defaultTarget = ConsoleTarget.SystemErr def defaultTarget = ConsoleTarget.SystemErr
appender("FILE", FileAppender) {
file = System.getenv("FROGMC_META_LOG") ?: "logs_meta.log"
append = true
encoder(PatternLayoutEncoder) {
pattern = "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} - %msg%n"
}
}
appender("CONSOLE", ConsoleAppender) { appender("CONSOLE", ConsoleAppender) {
encoder(PatternLayoutEncoder) { encoder(PatternLayoutEncoder) {
pattern = "WAWA:: %boldGreen(%d{yyyy-MM-dd}) %boldYellow(%d{HH:mm:ss}) %gray(|) %highlight(%5level) %gray(|) %boldMagenta(%40.40logger{40}) %gray(|) %msg%n" pattern = "%boldGreen(%d{yyyy-MM-dd}) %boldYellow(%d{HH:mm:ss}) %gray(|) %highlight(%5level) %gray(|) %boldMagenta(%40.40logger{40}) %gray(|) %msg%n"
withJansi = true withJansi = true
} }
@ -16,4 +26,4 @@ appender("CONSOLE", ConsoleAppender) {
target = defaultTarget target = defaultTarget
} }
root(defaultLevel, ["CONSOLE"]) root(defaultLevel, ["CONSOLE", "FILE"])

View file

@ -17,6 +17,7 @@ importsAcceptList = [
'ch.qos.logback.core.BasicStatusManager', 'ch.qos.logback.core.BasicStatusManager',
'ch.qos.logback.core.ConsoleAppender', 'ch.qos.logback.core.ConsoleAppender',
'ch.qos.logback.core.FileAppender',
'ch.qos.logback.core.hook.ShutdownHook', 'ch.qos.logback.core.hook.ShutdownHook',
'ch.qos.logback.core.hook.ShutdownHookBase', 'ch.qos.logback.core.hook.ShutdownHookBase',
'ch.qos.logback.core.hook.DelayingShutdownHook', 'ch.qos.logback.core.hook.DelayingShutdownHook',

View file

@ -18,28 +18,28 @@
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/loader/versions/latest</code> <code>GET /v1/loader/version/latest</code>
<p>Fetches the latest version of FrogMC loader.</p> <p>Fetches the latest version of FrogMC loader.</p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/loader/versions/{version}</code> <code>GET /v1/loader/version/{version}</code>
<p>Fetches a specific version of FrogMC loader.</p> <p>Fetches a specific version of FrogMC loader.</p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/loader/versions/{version}/download</code> <code>GET /v1/loader/version/{version}/download</code>
<p>Fetches the download URL of a specific version of FrogMC loader.</p> <p>Fetches the download URL of a specific version of FrogMC loader.</p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>POST /v1/loader/versions/upload</code> <code>POST /v1/loader/version/upload</code>
<p>Uploads a version of the loader.</p> <p>Uploads a version of the loader.</p>
<p><strong>This endpoint requires authorization.</strong></p> <p><strong>This endpoint requires authorization.</strong></p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>DELETE /v1/loader/versions/delete/{version}</code> <code>DELETE /v1/loader/version/delete/{version}</code>
<p>Deletes a version of the loader.</p> <p>Deletes a version of the loader.</p>
<p><strong>This endpoint requires authorization.</strong></p> <p><strong>This endpoint requires authorization.</strong></p>
</div> </div>
@ -50,17 +50,17 @@
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/library/versions/latest</code> <code>GET /v1/library/version/latest</code>
<p>Fetches the latest version of FrogMC library.</p> <p>Fetches the latest version of FrogMC library.</p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/library/versions/{version}</code> <code>GET /v1/library/version/{version}</code>
<p>Fetches a specific version of FrogMC library.</p> <p>Fetches a specific version of FrogMC library.</p>
</div> </div>
<br> <br>
<div class="route"> <div class="route">
<code>GET /v1/library/versions/{version}/download</code> <code>GET /v1/library/version/{version}/download</code>
<p>Fetches the download URL of a specific version of FrogMC library.</p> <p>Fetches the download URL of a specific version of FrogMC library.</p>
</div> </div>
</div> </div>