mirror of
https://github.com/ReVanced/revanced-api.git
synced 2026-01-12 06:16:19 +00:00
Compare commits
6 Commits
v1.2.0-dev
...
v1.3.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d85ce17f6 | ||
|
|
c26e129bda | ||
|
|
84ea5e4a41 | ||
|
|
491533d3f4 | ||
|
|
ef5f0b5ddd | ||
|
|
c0dc763f99 |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,3 +1,32 @@
|
||||
# [1.3.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.1...v1.3.0-dev.2) (2024-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Expire token relative to current date time instead of just time ([c26e129](https://github.com/ReVanced/revanced-api/commit/c26e129bda09345761f291917f026c13e89a2572))
|
||||
|
||||
# [1.3.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.2.0...v1.3.0-dev.1) (2024-09-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add missing parameter and response documentation ([491533d](https://github.com/ReVanced/revanced-api/commit/491533d3f44ccd716eee80123d0875a05eb9435b))
|
||||
|
||||
# [1.2.0](https://github.com/ReVanced/revanced-api/compare/v1.1.0...v1.2.0) (2024-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add back deprecated routes for backwards compatibility ([9f0eb5b](https://github.com/ReVanced/revanced-api/commit/9f0eb5bfe9d0436e76462b9c094f8b1158e04a44))
|
||||
* Make sure, expected paths in configuration exist ([32bedb7](https://github.com/ReVanced/revanced-api/commit/32bedb7fad3eef8116625964e5e1f0a2543ea2a4))
|
||||
* Return correct GPG keys url ([#187](https://github.com/ReVanced/revanced-api/issues/187)) ([74e5389](https://github.com/ReVanced/revanced-api/commit/74e53891a17bd3f76f358477e4228550e6f70149))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Move /latest routes to parent ([4e8e83d](https://github.com/ReVanced/revanced-api/commit/4e8e83db1a20c76a81967af4e7e3a8634649790a))
|
||||
* Respond to all ping request methods ([df116bd](https://github.com/ReVanced/revanced-api/commit/df116bd22134c8222c72b28e9387bc9871d3473e))
|
||||
|
||||
# [1.2.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.2.0-dev.3...v1.2.0-dev.4) (2024-09-06)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
kotlin.code.style = official
|
||||
version = 1.2.0-dev.4
|
||||
version = 1.3.0-dev.2
|
||||
|
||||
@@ -7,7 +7,7 @@ import app.revanced.api.configuration.repository.GitHubBackendRepository
|
||||
import app.revanced.api.configuration.services.*
|
||||
import app.revanced.api.configuration.services.AnnouncementService
|
||||
import app.revanced.api.configuration.services.ApiService
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import app.revanced.api.configuration.services.AuthenticationService
|
||||
import app.revanced.api.configuration.services.OldApiService
|
||||
import app.revanced.api.configuration.services.PatchesService
|
||||
import com.akuleshov7.ktoml.Toml
|
||||
@@ -116,11 +116,11 @@ fun Application.configureDependencies(
|
||||
|
||||
val jwtSecret = dotenv["JWT_SECRET"]
|
||||
val issuer = dotenv["JWT_ISSUER"]
|
||||
val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt()
|
||||
val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toLong()
|
||||
|
||||
val authSHA256DigestString = dotenv["AUTH_SHA256_DIGEST"]
|
||||
|
||||
AuthService(issuer, validityInMin, jwtSecret, authSHA256DigestString)
|
||||
AuthenticationService(issuer, validityInMin, jwtSecret, authSHA256DigestString)
|
||||
}
|
||||
single {
|
||||
val configuration = get<ConfigurationRepository>()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.api.configuration
|
||||
|
||||
import io.bkbn.kompendium.core.metadata.MethodInfo
|
||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.content.*
|
||||
@@ -40,3 +41,11 @@ internal fun Route.staticFiles(
|
||||
extensions("json")
|
||||
},
|
||||
) = staticFiles(remotePath, dir.toFile(), null, block)
|
||||
|
||||
internal fun MethodInfo.Builder<*>.canRespondUnauthorized() {
|
||||
canRespond {
|
||||
responseCode(HttpStatusCode.Unauthorized)
|
||||
description("Unauthorized")
|
||||
responseType<Unit>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package app.revanced.api.configuration
|
||||
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import app.revanced.api.configuration.services.AuthenticationService
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
fun Application.configureSecurity() {
|
||||
get<AuthService>().configureSecurity(this)
|
||||
val authenticationService = get<AuthenticationService>()
|
||||
|
||||
install(Authentication) {
|
||||
with(authenticationService) {
|
||||
jwt()
|
||||
digest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.api.configuration.routes
|
||||
|
||||
import app.revanced.api.configuration.canRespondUnauthorized
|
||||
import app.revanced.api.configuration.installCache
|
||||
import app.revanced.api.configuration.installNotarizedRoute
|
||||
import app.revanced.api.configuration.respondOrNotFound
|
||||
@@ -8,10 +9,7 @@ import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt
|
||||
import app.revanced.api.configuration.schema.APIResponseAnnouncement
|
||||
import app.revanced.api.configuration.schema.APIResponseAnnouncementId
|
||||
import app.revanced.api.configuration.services.AnnouncementService
|
||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.PatchInfo
|
||||
import io.bkbn.kompendium.core.metadata.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.*
|
||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.ktor.http.*
|
||||
@@ -138,9 +136,19 @@ internal fun Route.announcementsRoute() = route("announcements") {
|
||||
}
|
||||
}
|
||||
|
||||
private val authHeaderParameter = Parameter(
|
||||
name = "Authorization",
|
||||
`in` = Parameter.Location.header,
|
||||
schema = TypeDefinition.STRING,
|
||||
required = true,
|
||||
examples = mapOf("Bearer authentication" to Parameter.Example("Bearer abc123")),
|
||||
)
|
||||
|
||||
private fun Route.installAnnouncementRouteDocumentation() = installNotarizedRoute {
|
||||
tags = setOf("Announcements")
|
||||
|
||||
parameters = listOf(authHeaderParameter)
|
||||
|
||||
post = PostInfo.builder {
|
||||
description("Create a new announcement")
|
||||
summary("Create announcement")
|
||||
@@ -153,6 +161,7 @@ private fun Route.installAnnouncementRouteDocumentation() = installNotarizedRout
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<Unit>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +248,7 @@ private fun Route.installAnnouncementArchiveRouteDocumentation() = installNotari
|
||||
description = "The date and time the announcement to be archived",
|
||||
required = false,
|
||||
),
|
||||
authHeaderParameter,
|
||||
)
|
||||
|
||||
post = PostInfo.builder {
|
||||
@@ -249,6 +259,7 @@ private fun Route.installAnnouncementArchiveRouteDocumentation() = installNotari
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<Unit>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +274,7 @@ private fun Route.installAnnouncementUnarchiveRouteDocumentation() = installNota
|
||||
description = "The id of the announcement to unarchive",
|
||||
required = true,
|
||||
),
|
||||
authHeaderParameter,
|
||||
)
|
||||
|
||||
post = PostInfo.builder {
|
||||
@@ -273,6 +285,7 @@ private fun Route.installAnnouncementUnarchiveRouteDocumentation() = installNota
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<Unit>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +300,7 @@ private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRo
|
||||
description = "The id of the announcement to update",
|
||||
required = true,
|
||||
),
|
||||
authHeaderParameter,
|
||||
)
|
||||
|
||||
patch = PatchInfo.builder {
|
||||
@@ -301,6 +315,7 @@ private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRo
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<Unit>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
|
||||
delete = DeleteInfo.builder {
|
||||
@@ -311,6 +326,7 @@ private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRo
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<Unit>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,17 @@ import app.revanced.api.configuration.*
|
||||
import app.revanced.api.configuration.installCache
|
||||
import app.revanced.api.configuration.installNoCache
|
||||
import app.revanced.api.configuration.installNotarizedRoute
|
||||
import app.revanced.api.configuration.repository.ConfigurationRepository
|
||||
import app.revanced.api.configuration.respondOrNotFound
|
||||
import app.revanced.api.configuration.schema.APIAbout
|
||||
import app.revanced.api.configuration.schema.APIContributable
|
||||
import app.revanced.api.configuration.schema.APIMember
|
||||
import app.revanced.api.configuration.schema.APIRateLimit
|
||||
import app.revanced.api.configuration.services.ApiService
|
||||
import app.revanced.api.configuration.services.AuthService
|
||||
import app.revanced.api.configuration.services.AuthenticationService
|
||||
import io.bkbn.kompendium.core.metadata.*
|
||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
@@ -23,7 +26,7 @@ import org.koin.ktor.ext.get as koinGet
|
||||
|
||||
internal fun Route.apiRoute() {
|
||||
val apiService = koinGet<ApiService>()
|
||||
val authService = koinGet<AuthService>()
|
||||
val authenticationService = koinGet<AuthenticationService>()
|
||||
|
||||
rateLimit(RateLimitName("strong")) {
|
||||
authenticate("auth-digest") {
|
||||
@@ -31,7 +34,7 @@ internal fun Route.apiRoute() {
|
||||
installTokenRouteDocumentation()
|
||||
|
||||
get {
|
||||
call.respond(authService.newToken())
|
||||
call.respond(authenticationService.newToken())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,16 +168,38 @@ private fun Route.installContributorsRouteDocumentation() = installNotarizedRout
|
||||
}
|
||||
|
||||
private fun Route.installTokenRouteDocumentation() = installNotarizedRoute {
|
||||
val configuration = koinGet<ConfigurationRepository>()
|
||||
|
||||
tags = setOf("API")
|
||||
|
||||
get = GetInfo.builder {
|
||||
description("Get a new authorization token")
|
||||
summary("Get authorization token")
|
||||
parameters(
|
||||
Parameter(
|
||||
name = "Authorization",
|
||||
`in` = Parameter.Location.header,
|
||||
schema = TypeDefinition.STRING,
|
||||
required = true,
|
||||
examples = mapOf(
|
||||
"Digest access authentication" to Parameter.Example(
|
||||
value = "Digest " +
|
||||
"username=\"ReVanced\", " +
|
||||
"realm=\"ReVanced\", " +
|
||||
"nonce=\"abc123\", " +
|
||||
"uri=\"/v${configuration.apiVersion}/token\", " +
|
||||
"algorithm=SHA-256, " +
|
||||
"response=\"yxz456\"",
|
||||
),
|
||||
), // Provide an example for the header
|
||||
),
|
||||
)
|
||||
response {
|
||||
description("The authorization token")
|
||||
mediaTypes("application/json")
|
||||
responseCode(HttpStatusCode.OK)
|
||||
responseType<String>()
|
||||
}
|
||||
canRespondUnauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.auth.jwt.*
|
||||
import java.util.*
|
||||
import kotlin.text.HexFormat
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
internal class AuthService private constructor(
|
||||
private val issuer: String,
|
||||
private val validityInMin: Int,
|
||||
private val jwtSecret: String,
|
||||
private val authSHA256Digest: ByteArray,
|
||||
) {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
constructor(issuer: String, validityInMin: Int, jwtSecret: String, authSHA256DigestString: String) : this(
|
||||
issuer,
|
||||
validityInMin,
|
||||
jwtSecret,
|
||||
authSHA256DigestString.hexToByteArray(HexFormat.Default),
|
||||
)
|
||||
|
||||
val configureSecurity: Application.() -> Unit = {
|
||||
install(Authentication) {
|
||||
jwt("jwt") {
|
||||
realm = "ReVanced"
|
||||
|
||||
verifier(JWT.require(Algorithm.HMAC256(jwtSecret)).withIssuer(issuer).build())
|
||||
}
|
||||
|
||||
digest("auth-digest") {
|
||||
realm = "ReVanced"
|
||||
algorithmName = "SHA-256"
|
||||
|
||||
digestProvider { _, _ ->
|
||||
authSHA256Digest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newToken(): String = JWT.create()
|
||||
.withIssuer(issuer)
|
||||
.withExpiresAt(Date(System.currentTimeMillis() + validityInMin.minutes.inWholeMilliseconds))
|
||||
.sign(Algorithm.HMAC256(jwtSecret))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package app.revanced.api.configuration.services
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.auth.jwt.*
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.text.HexFormat
|
||||
|
||||
internal class AuthenticationService private constructor(
|
||||
private val issuer: String,
|
||||
private val validityInMin: Long,
|
||||
private val jwtSecret: String,
|
||||
private val authSHA256Digest: ByteArray,
|
||||
) {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
constructor(issuer: String, validityInMin: Long, jwtSecret: String, authSHA256DigestString: String) : this(
|
||||
issuer,
|
||||
validityInMin,
|
||||
jwtSecret,
|
||||
authSHA256DigestString.hexToByteArray(HexFormat.Default),
|
||||
)
|
||||
|
||||
fun AuthenticationConfig.jwt() {
|
||||
jwt("jwt") {
|
||||
realm = "ReVanced"
|
||||
|
||||
verifier(JWT.require(Algorithm.HMAC256(jwtSecret)).withIssuer(issuer).build())
|
||||
}
|
||||
}
|
||||
|
||||
fun AuthenticationConfig.digest() {
|
||||
digest("auth-digest") {
|
||||
realm = "ReVanced"
|
||||
algorithmName = "SHA-256"
|
||||
|
||||
digestProvider { _, _ ->
|
||||
authSHA256Digest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newToken(): String {
|
||||
val issuedAt = Instant.now()
|
||||
|
||||
return JWT.create()
|
||||
.withIssuer(issuer)
|
||||
.withIssuedAt(issuedAt)
|
||||
.withExpiresAt(issuedAt.plus(validityInMin, ChronoUnit.MINUTES))
|
||||
.sign(Algorithm.HMAC256(jwtSecret))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user