Compare commits

..

29 Commits

Author SHA1 Message Date
semantic-release-bot
06098415f1 chore: Release v1.3.0-dev.6 [skip ci]
# [1.3.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.5...v1.3.0-dev.6) (2024-10-02)

### Bug Fixes

* Expose www-authenticate header to JS ([9ed724e](9ed724e161))

### Features

* Only allow requests from HTTPs ([a6d7da1](a6d7da1205))
2024-10-02 04:59:16 +00:00
oSumAtrIX
9ed724e161 fix: Expose www-authenticate header to JS 2024-10-02 06:57:20 +02:00
oSumAtrIX
a6d7da1205 feat: Only allow requests from HTTPs 2024-10-02 06:57:19 +02:00
oSumAtrIX
87174eadd6 ci: Use permissions and regular GitHub token instead of PAT 2024-10-02 06:57:19 +02:00
oSumAtrIX
fde2857915 build(Needs bump): Update dependencies 2024-09-30 23:21:46 +02:00
oSumAtrIX
fae8cb6b23 ci: Adjust release commit message 2024-09-30 22:34:25 +02:00
semantic-release-bot
a754159800 chore(release): 1.3.0-dev.5 [skip ci]
# [1.3.0-dev.5](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.4...v1.3.0-dev.5) (2024-09-30)

### Bug Fixes

* Allow more necessary HTTP methods for CORS ([080e2e5](080e2e582c))
2024-09-30 15:35:42 +00:00
oSumAtrIX
080e2e582c fix: Allow more necessary HTTP methods for CORS 2024-09-30 17:33:15 +02:00
semantic-release-bot
8ff1bbd41f chore(release): 1.3.0-dev.4 [skip ci]
# [1.3.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.3...v1.3.0-dev.4) (2024-09-29)

### Bug Fixes

* Configure CORS properly to allow authorization and content-type header ([6442757](6442757927))
2024-09-29 23:29:17 +00:00
oSumAtrIX
6442757927 fix: Configure CORS properly to allow authorization and content-type header 2024-09-30 01:27:24 +02:00
semantic-release-bot
710416ff36 chore(release): 1.3.0-dev.3 [skip ci]
# [1.3.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.2...v1.3.0-dev.3) (2024-09-29)

### Bug Fixes

* Add missing OK response to routes ([1181be1](1181be12e2))
* Respond with JSON when returning token ([1e3e46f](1e3e46ff4f))
* Specify a validation function to fix authentication ([53c3600](53c36002e9))

### Features

* Customize logging level through environment variable ([8b17d88](8b17d8894d))
* Improve response info description wording ([977d252](977d252497))
2024-09-29 21:19:18 +00:00
oSumAtrIX
1181be12e2 fix: Add missing OK response to routes 2024-09-29 23:15:35 +02:00
oSumAtrIX
53c36002e9 fix: Specify a validation function to fix authentication 2024-09-29 23:13:13 +02:00
oSumAtrIX
8b17d8894d feat: Customize logging level through environment variable 2024-09-29 01:03:49 +02:00
oSumAtrIX
1e3e46ff4f fix: Respond with JSON when returning token 2024-09-27 20:19:27 +02:00
oSumAtrIX
977d252497 feat: Improve response info description wording 2024-09-27 19:18:32 +02:00
oSumAtrIX
96bcd7719a chore: Remove unnecessary JWT token field 2024-09-27 19:16:34 +02:00
semantic-release-bot
2d85ce17f6 chore(release): 1.3.0-dev.2 [skip ci]
# [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](c26e129bda))
2024-09-27 12:23:28 +00:00
oSumAtrIX
c26e129bda fix: Expire token relative to current date time instead of just time 2024-09-27 14:16:45 +02:00
semantic-release-bot
84ea5e4a41 chore(release): 1.3.0-dev.1 [skip ci]
# [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](491533d3f4))
2024-09-11 14:22:06 +00:00
oSumAtrIX
491533d3f4 feat: Add missing parameter and response documentation 2024-09-09 11:59:03 +02:00
semantic-release-bot
ef5f0b5ddd chore(release): 1.2.0 [skip ci]
# [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](9f0eb5bfe9))
* Make sure, expected paths in configuration exist ([32bedb7](32bedb7fad))
* Return correct GPG keys url ([#187](https://github.com/ReVanced/revanced-api/issues/187)) ([74e5389](74e53891a1))

### Features

* Move /latest routes to parent ([4e8e83d](4e8e83db1a))
* Respond to all ping request methods ([df116bd](df116bd221))
2024-09-06 23:23:38 +00:00
oSumAtrIX
c0dc763f99 chore: Merge branch dev to main (#188)
This pull request will Merge branch `dev` to `main`.
2024-09-07 03:21:40 +04:00
semantic-release-bot
11327af879 chore(release): 1.2.0-dev.4 [skip ci]
# [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)

### Bug Fixes

* Add back deprecated routes for backwards compatibility ([9f0eb5b](9f0eb5bfe9))
* Make sure, expected paths in configuration exist ([32bedb7](32bedb7fad))
2024-09-06 08:27:20 +00:00
oSumAtrIX
9f0eb5bfe9 fix: Add back deprecated routes for backwards compatibility 2024-09-06 12:25:10 +04:00
oSumAtrIX
d2575d5191 docs: Add missing documentation and setup steps 2024-09-06 11:59:33 +04:00
oSumAtrIX
32bedb7fad fix: Make sure, expected paths in configuration exist 2024-09-06 11:40:10 +04:00
semantic-release-bot
d605efd54a chore(release): 1.2.0-dev.3 [skip ci]
# [1.2.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.2.0-dev.2...v1.2.0-dev.3) (2024-09-04)

### Bug Fixes

* Return correct GPG keys url ([#187](https://github.com/ReVanced/revanced-api/issues/187)) ([74e5389](74e53891a1))
2024-09-04 15:20:13 +00:00
Ushie
74e53891a1 fix: Return correct GPG keys url (#187)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-09-04 17:18:21 +02:00
23 changed files with 2745 additions and 1341 deletions

View File

@@ -12,7 +12,7 @@ jobs:
name: Release
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
packages: write
steps:
- name: Checkout
@@ -60,7 +60,7 @@ jobs:
DOCKER_REGISTRY_USER: ${{ github.actor }}
DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release
- name: Set Portainer stack webhook URL based on branch

View File

@@ -21,10 +21,10 @@
"@semantic-release/git",
{
"assets": [
"README.md",
"CHANGELOG.md",
"gradle.properties"
]
],
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[

View File

@@ -1,3 +1,88 @@
# [1.3.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.5...v1.3.0-dev.6) (2024-10-02)
### Bug Fixes
* Expose www-authenticate header to JS ([9ed724e](https://github.com/ReVanced/revanced-api/commit/9ed724e161f9029967f67e4c2066f2fdf7be0a27))
### Features
* Only allow requests from HTTPs ([a6d7da1](https://github.com/ReVanced/revanced-api/commit/a6d7da1205ef7bc23eba0b1fca2480a4327def19))
# [1.3.0-dev.5](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.4...v1.3.0-dev.5) (2024-09-30)
### Bug Fixes
* Allow more necessary HTTP methods for CORS ([080e2e5](https://github.com/ReVanced/revanced-api/commit/080e2e582cb8ea97421c402a4cb82414e11fb1cf))
# [1.3.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.3...v1.3.0-dev.4) (2024-09-29)
### Bug Fixes
* Configure CORS properly to allow authorization and content-type header ([6442757](https://github.com/ReVanced/revanced-api/commit/6442757927c0307c01b2793858d25df7e3fca122))
# [1.3.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.3.0-dev.2...v1.3.0-dev.3) (2024-09-29)
### Bug Fixes
* Add missing OK response to routes ([1181be1](https://github.com/ReVanced/revanced-api/commit/1181be12e2223b245019f64570bc8f7bef4e7dc2))
* Respond with JSON when returning token ([1e3e46f](https://github.com/ReVanced/revanced-api/commit/1e3e46ff4f7c12569b88fcd1bc252aeb5a611b63))
* Specify a validation function to fix authentication ([53c3600](https://github.com/ReVanced/revanced-api/commit/53c36002e9af89aa5fed71f831470b42d5d777c9))
### Features
* Customize logging level through environment variable ([8b17d88](https://github.com/ReVanced/revanced-api/commit/8b17d8894db8db4a168c30be50af91c04e173e14))
* Improve response info description wording ([977d252](https://github.com/ReVanced/revanced-api/commit/977d25249738b24cb6a3530543349efe1d71a9ba))
# [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)
### 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))
# [1.2.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.2.0-dev.2...v1.2.0-dev.3) (2024-09-04)
### Bug Fixes
* Return correct GPG keys url ([#187](https://github.com/ReVanced/revanced-api/issues/187)) ([74e5389](https://github.com/ReVanced/revanced-api/commit/74e53891a17bd3f76f358477e4228550e6f70149))
# [1.2.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.2.0-dev.1...v1.2.0-dev.2) (2024-08-24)

View File

@@ -96,20 +96,30 @@ so before you can pull the image, you need to [authenticate to the Container reg
1. Create an `.env` file using [.env.example](.env.example) as a template
2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template
3. Create a `docker-compose.yml` file using [docker-compose.example.yml](docker-compose.example.yml) as a template
4. Run `docker-compose up -d` to start the server
3. Create an `about.json` file using [about.example.json](about.example.json) as a template
4. Create a `docker-compose.yml` file using [docker-compose.example.yml](docker-compose.example.yml) as a template
5. Run `docker-compose up -d` to start the server
### 💻 Docker CLI
1. Create an `.env` file using [.env.example](.env.example) as a template
2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template
3. Start the container using the following command:
3. Create an `about.json` file using [about.example.json](about.example.json) as a template
4. Start the container using the following command:
```shell
docker run -d --name revanced-api \
# Mount the .env file
-v $(pwd)/.env:/app/.env \
# Mount the configuration.toml file
-v $(pwd)/configuration.toml:/app/configuration.toml \
# Mount the patches public key
-v $(pwd)/patches-public-key.asc:/app/patches-public-key.asc \
# Mount the integrations public key
-v $(pwd)/integrations-public-key.asc:/app/integrations-public-key.asc \
# Mount the static folder
-v $(pwd)/static:/app/static \
# Mount the about.json file
-v $(pwd)/about.json:/app/about.json \
# Mount the persistence folder
-v $(pwd)/persistence:/app/persistence \
# Expose the port 8888
@@ -132,7 +142,8 @@ A Java Runtime Environment (JRE) must be installed.
2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template
3. In the same folder, create a `configuration.toml` file
using [configuration.example.toml](configuration.example.toml) as a template
4. Run `java -jar revanced-api.jar start` to start the server
4. In the same folder, create an `about.json` file using [about.example.json](about.example.json) as a template
5. Run `java -jar revanced-api.jar start` to start the server
### 🛠️ From source
@@ -141,7 +152,8 @@ A Java Development Kit (JDK) and Git must be installed.
1. Run `git clone git@github.com:ReVanced/revanced-api.git` to clone the repository
2. Copy [.env.example](.env.example) to `.env` and fill in the required values
3. Copy [configuration.example.toml](configuration.example.toml) to `configuration.toml` and fill in the required values
4. Run `gradlew run --args=start` to start the server
4. Copy [about.example.json](about.example.json) to `about.json` and fill in the required values
5. Run `gradlew run --args=start` to start the server
## 📚 Everything else

View File

@@ -13,5 +13,5 @@ services:
environment:
- COMMAND=start
ports:
- 8888:8888
- "8888:8888"
restart: unless-stopped

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 1.2.0-dev.2
version = 1.3.0-dev.6

3648
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.2",
"semantic-release": "^24.0.0"
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.1.2"
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.api.command
import app.revanced.api.configuration.*
import io.github.cdimascio.dotenv.Dotenv
import io.ktor.server.engine.*
import io.ktor.server.jetty.*
import picocli.CommandLine
@@ -33,6 +34,8 @@ internal object StartAPICommand : Runnable {
private var configFile = File("configuration.toml")
override fun run() {
Dotenv.configure().systemProperties().load()
embeddedServer(Jetty, port, host) {
configureDependencies(configFile)
configureHTTP()

View File

@@ -7,12 +7,11 @@ 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
import com.akuleshov7.ktoml.source.decodeFromStream
import io.github.cdimascio.dotenv.Dotenv
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
@@ -38,11 +37,7 @@ import java.io.File
fun Application.configureDependencies(
configFile: File,
) {
val globalModule = module {
single {
Dotenv.configure().load()
}
val miscModule = module {
factory { params ->
val defaultRequestUri: String = params.get<String>()
val configBlock = params.getOrNull<(HttpClientConfig<OkHttpConfig>.() -> Unit)>() ?: {}
@@ -72,7 +67,7 @@ fun Application.configureDependencies(
)
}
get<Dotenv>()["BACKEND_API_TOKEN"]?.let {
System.getProperty("BACKEND_API_TOKEN")?.let {
install(Auth) {
bearer {
loadTokens {
@@ -98,12 +93,10 @@ fun Application.configureDependencies(
}
single {
val dotenv = get<Dotenv>()
TransactionManager.defaultDatabase = Database.connect(
url = dotenv["DB_URL"],
user = dotenv["DB_USER"],
password = dotenv["DB_PASSWORD"],
url = System.getProperty("DB_URL"),
user = System.getProperty("DB_USER"),
password = System.getProperty("DB_PASSWORD"),
)
AnnouncementRepository()
@@ -112,15 +105,13 @@ fun Application.configureDependencies(
val serviceModule = module {
single {
val dotenv = get<Dotenv>()
val jwtSecret = System.getProperty("JWT_SECRET")
val issuer = System.getProperty("JWT_ISSUER")
val validityInMin = System.getProperty("JWT_VALIDITY_IN_MIN").toLong()
val jwtSecret = dotenv["JWT_SECRET"]
val issuer = dotenv["JWT_ISSUER"]
val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt()
val authSHA256DigestString = System.getProperty("AUTH_SHA256_DIGEST")
val authSHA256DigestString = dotenv["AUTH_SHA256_DIGEST"]
AuthService(issuer, validityInMin, jwtSecret, authSHA256DigestString)
AuthenticationService(issuer, validityInMin, jwtSecret, authSHA256DigestString)
}
single {
val configuration = get<ConfigurationRepository>()
@@ -140,7 +131,7 @@ fun Application.configureDependencies(
install(Koin) {
modules(
globalModule,
miscModule,
repositoryModule,
serviceModule,
)

View File

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

View File

@@ -1,6 +1,7 @@
package app.revanced.api.configuration
import app.revanced.api.configuration.repository.ConfigurationRepository
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.*
import io.ktor.server.plugins.cors.routing.*
@@ -13,11 +14,16 @@ fun Application.configureHTTP() {
val configurationRepository = get<ConfigurationRepository>()
install(CORS) {
HttpMethod.DefaultMethods.minus(HttpMethod.Options).forEach(::allowMethod)
allowHeader(HttpHeaders.ContentType)
allowHeader(HttpHeaders.Authorization)
exposeHeader(HttpHeaders.WWWAuthenticate)
allowCredentials = true
configurationRepository.corsAllowedHosts.forEach { host ->
allowHost(
host = host,
schemes = listOf("http", "https")
)
allowHost(host = host, schemes = listOf("https"))
}
}

View File

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

View File

@@ -17,6 +17,7 @@ import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.json.decodeFromStream
import java.io.File
import java.nio.file.Path
import kotlin.io.path.createDirectories
/**
* The repository storing the configuration for the API.
@@ -60,6 +61,11 @@ internal class ConfigurationRepository(
@SerialName("about-json-file-path")
val about: APIAbout,
) {
init {
staticFilesPath.createDirectories()
versionedStaticFilesPath.createDirectories()
}
/**
* Am asset configuration whose asset is signed.
*

View File

@@ -98,7 +98,7 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) {
gpgKeys =
BackendMember.GpgKeys(
ids = gpgKeys.map { it.keyId },
url = "https://api.github.com/users/${user.login}.gpg",
url = "https://github.com/${user.login}.gpg",
),
)
}

View File

@@ -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.*
@@ -96,6 +94,8 @@ internal fun Route.announcementsRoute() = route("announcements") {
post<APIAnnouncement> { announcement ->
announcementService.new(announcement)
call.respond(HttpStatusCode.OK)
}
route("{id}") {
@@ -105,12 +105,16 @@ internal fun Route.announcementsRoute() = route("announcements") {
val id: Int by call.parameters
announcementService.update(id, announcement)
call.respond(HttpStatusCode.OK)
}
delete {
val id: Int by call.parameters
announcementService.delete(id)
call.respond(HttpStatusCode.OK)
}
route("archive") {
@@ -121,6 +125,8 @@ internal fun Route.announcementsRoute() = route("announcements") {
val archivedAt = call.receiveNullable<APIAnnouncementArchivedAt>()?.archivedAt
announcementService.archive(id, archivedAt)
call.respond(HttpStatusCode.OK)
}
}
@@ -131,6 +137,8 @@ internal fun Route.announcementsRoute() = route("announcements") {
val id: Int by call.parameters
announcementService.unarchive(id)
call.respond(HttpStatusCode.OK)
}
}
}
@@ -138,9 +146,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")
@@ -149,10 +167,11 @@ private fun Route.installAnnouncementRouteDocumentation() = installNotarizedRout
description("The new announcement")
}
response {
description("When the announcement was created")
description("The announcement is created")
responseCode(HttpStatusCode.OK)
responseType<Unit>()
}
canRespondUnauthorized()
}
}
@@ -239,16 +258,18 @@ private fun Route.installAnnouncementArchiveRouteDocumentation() = installNotari
description = "The date and time the announcement to be archived",
required = false,
),
authHeaderParameter,
)
post = PostInfo.builder {
description("Archive an announcement")
summary("Archive announcement")
response {
description("When the announcement was archived")
description("The announcement is archived")
responseCode(HttpStatusCode.OK)
responseType<Unit>()
}
canRespondUnauthorized()
}
}
@@ -263,16 +284,18 @@ private fun Route.installAnnouncementUnarchiveRouteDocumentation() = installNota
description = "The id of the announcement to unarchive",
required = true,
),
authHeaderParameter,
)
post = PostInfo.builder {
description("Unarchive an announcement")
summary("Unarchive announcement")
response {
description("When announcement was unarchived")
description("The announcement is unarchived")
responseCode(HttpStatusCode.OK)
responseType<Unit>()
}
canRespondUnauthorized()
}
}
@@ -287,6 +310,7 @@ private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRo
description = "The id of the announcement to update",
required = true,
),
authHeaderParameter,
)
patch = PatchInfo.builder {
@@ -297,20 +321,22 @@ private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRo
description("The new announcement")
}
response {
description("When announcement was updated")
description("The announcement is updated")
responseCode(HttpStatusCode.OK)
responseType<Unit>()
}
canRespondUnauthorized()
}
delete = DeleteInfo.builder {
description("Delete an announcement")
summary("Delete announcement")
response {
description("When the announcement was deleted")
description("The announcement is deleted")
responseCode(HttpStatusCode.OK)
responseType<Unit>()
}
canRespondUnauthorized()
}
}

View File

@@ -4,14 +4,14 @@ 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.schema.*
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 +23,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 +31,7 @@ internal fun Route.apiRoute() {
installTokenRouteDocumentation()
get {
call.respond(authService.newToken())
call.respond(authenticationService.newToken())
}
}
}
@@ -165,16 +165,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>()
responseType<APIToken>()
}
canRespondUnauthorized()
}
}

View File

@@ -14,9 +14,18 @@ import io.ktor.server.routing.*
import org.koin.ktor.ext.get as koinGet
internal fun Route.managerRoute() = route("manager") {
configure()
// TODO: Remove this deprecated route eventually.
route("latest") {
configure(deprecated = true)
}
}
private fun Route.configure(deprecated: Boolean = false) {
val managerService = koinGet<ManagerService>()
installManagerRouteDocumentation()
installManagerRouteDocumentation(deprecated)
rateLimit(RateLimitName("weak")) {
get {
@@ -24,7 +33,7 @@ internal fun Route.managerRoute() = route("manager") {
}
route("version") {
installManagerVersionRouteDocumentation()
installManagerVersionRouteDocumentation(deprecated)
get {
call.respond(managerService.latestVersion())
@@ -33,10 +42,11 @@ internal fun Route.managerRoute() = route("manager") {
}
}
private fun Route.installManagerRouteDocumentation() = installNotarizedRoute {
private fun Route.installManagerRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Manager")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the current manager release")
summary("Get current manager release")
response {
@@ -48,10 +58,11 @@ private fun Route.installManagerRouteDocumentation() = installNotarizedRoute {
}
}
private fun Route.installManagerVersionRouteDocumentation() = installNotarizedRoute {
private fun Route.installManagerVersionRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Manager")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the current manager release version")
summary("Get current manager release version")
response {

View File

@@ -17,9 +17,18 @@ import kotlin.time.Duration.Companion.days
import org.koin.ktor.ext.get as koinGet
internal fun Route.patchesRoute() = route("patches") {
configure()
// TODO: Remove this deprecated route eventually.
route("latest") {
configure(deprecated = true)
}
}
private fun Route.configure(deprecated: Boolean = false) {
val patchesService = koinGet<PatchesService>()
installPatchesRouteDocumentation()
installPatchesRouteDocumentation(deprecated)
rateLimit(RateLimitName("weak")) {
get {
@@ -27,7 +36,7 @@ internal fun Route.patchesRoute() = route("patches") {
}
route("version") {
installPatchesVersionRouteDocumentation()
installPatchesVersionRouteDocumentation(deprecated)
get {
call.respond(patchesService.latestVersion())
@@ -37,7 +46,7 @@ internal fun Route.patchesRoute() = route("patches") {
rateLimit(RateLimitName("strong")) {
route("list") {
installPatchesListRouteDocumentation()
installPatchesListRouteDocumentation(deprecated)
get {
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
@@ -49,7 +58,7 @@ internal fun Route.patchesRoute() = route("patches") {
route("keys") {
installCache(356.days)
installPatchesPublicKeyRouteDocumentation()
installPatchesPublicKeyRouteDocumentation(deprecated)
get {
call.respond(patchesService.publicKeys())
@@ -58,10 +67,11 @@ internal fun Route.patchesRoute() = route("patches") {
}
}
private fun Route.installPatchesRouteDocumentation() = installNotarizedRoute {
private fun Route.installPatchesRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Patches")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the current patches release")
summary("Get current patches release")
response {
@@ -73,10 +83,11 @@ private fun Route.installPatchesRouteDocumentation() = installNotarizedRoute {
}
}
private fun Route.installPatchesVersionRouteDocumentation() = installNotarizedRoute {
private fun Route.installPatchesVersionRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Patches")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the current patches release version")
summary("Get current patches release version")
response {
@@ -88,10 +99,11 @@ private fun Route.installPatchesVersionRouteDocumentation() = installNotarizedRo
}
}
private fun Route.installPatchesListRouteDocumentation() = installNotarizedRoute {
private fun Route.installPatchesListRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Patches")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the list of patches from the current patches release")
summary("Get list of patches from current patches release")
response {
@@ -103,10 +115,11 @@ private fun Route.installPatchesListRouteDocumentation() = installNotarizedRoute
}
}
private fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute {
private fun Route.installPatchesPublicKeyRouteDocumentation(deprecated: Boolean) = installNotarizedRoute {
tags = setOf("Patches")
get = GetInfo.builder {
if (deprecated) isDeprecated()
description("Get the public keys for verifying patches and integrations assets")
summary("Get patches and integrations public keys")
response {

View File

@@ -172,3 +172,6 @@ class APIAbout(
val links: List<Link>?,
)
}
@Serializable
class APIToken(val token: String)

View File

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

View File

@@ -0,0 +1,52 @@
package app.revanced.api.configuration.services
import app.revanced.api.configuration.schema.APIToken
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())
// This is required and not optional. Authentication will fail if this is not present.
validate { JWTPrincipal(it.payload) }
}
}
fun AuthenticationConfig.digest() {
digest("auth-digest") {
realm = "ReVanced"
algorithmName = "SHA-256"
digestProvider { _, _ ->
authSHA256Digest
}
}
}
fun newToken() = APIToken(
JWT.create()
.withIssuer(issuer)
.withExpiresAt(Instant.now().plus(validityInMin, ChronoUnit.MINUTES))
.sign(Algorithm.HMAC256(jwtSecret)),
)
}

View File

@@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<root level="\${LOG_LEVEL:-INFO}">
<appender-ref ref="STDOUT"/>
</root>
</configuration>