diff --git a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuth.kt b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuth.kt deleted file mode 100644 index ec2b8916c..000000000 --- a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuth.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.folivo.trixnity.applicationserviceapi.server - -import io.ktor.http.* -import io.ktor.server.auth.* -import io.ktor.server.response.* -import net.folivo.trixnity.core.ErrorResponse - -class MatrixQueryParameterAuthenticationProvider internal constructor( - configuration: Configuration, - private val field: String, - private val token: String -) : AuthenticationProvider(configuration) { - class Configuration internal constructor(name: String? = null) : Config(name) - - override suspend fun onAuthenticate(context: AuthenticationContext) { - val credentials = context.call.request.queryParameters[field] - val cause = when { - credentials == null -> AuthenticationFailedCause.NoCredentials - credentials != token -> AuthenticationFailedCause.InvalidCredentials - else -> null - } - - if (cause != null) { - context.challenge("MatrixQueryParameterAuth", cause) { challenge, call -> - when (cause) { - AuthenticationFailedCause.NoCredentials -> - call.respond(HttpStatusCode.Unauthorized, ErrorResponse.Unauthorized()) - else -> call.respond(HttpStatusCode.Forbidden, ErrorResponse.Forbidden()) - } - challenge.complete() - } - } else { - context.principal(UserIdPrincipal("homeserver")) - } - } -} - -fun AuthenticationConfig.matrixQueryParameter( - name: String? = null, - field: String, - token: String, -) { - val provider = - MatrixQueryParameterAuthenticationProvider( - MatrixQueryParameterAuthenticationProvider.Configuration(name), - field, - token - ) - register(provider) -} \ No newline at end of file diff --git a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuth.kt b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuth.kt new file mode 100644 index 000000000..d5b595e42 --- /dev/null +++ b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuth.kt @@ -0,0 +1,69 @@ +package net.folivo.trixnity.applicationserviceapi.server + +import io.ktor.http.* +import io.ktor.http.auth.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import net.folivo.trixnity.core.ErrorResponse + +class MatrixQueryParameterOrBearerAuthenticationProvider internal constructor( + configuration: Configuration, + private val field: String, + private val token: String +) : AuthenticationProvider(configuration) { + class Configuration internal constructor(name: String? = null) : Config(name) + + override suspend fun onAuthenticate(context: AuthenticationContext) { + val queryCredentials = context.call.request.queryParameters[field] + val accessTokenCredentials = context.call.request.getAccessTokenFromHeader() + val cause = when { + queryCredentials == null && accessTokenCredentials == null -> AuthenticationFailedCause.NoCredentials + accessTokenCredentials != null && queryCredentials != null && accessTokenCredentials != queryCredentials + || (accessTokenCredentials == null && queryCredentials != token) + || (queryCredentials == null && accessTokenCredentials != token) + || (accessTokenCredentials == queryCredentials && queryCredentials != token) -> AuthenticationFailedCause.InvalidCredentials + + else -> null + } + + if (cause != null) { + context.challenge("MatrixQueryParameterAuth", cause) { challenge, call -> + when (cause) { + AuthenticationFailedCause.NoCredentials -> + call.respond(HttpStatusCode.Unauthorized, ErrorResponse.Unauthorized()) + + else -> call.respond(HttpStatusCode.Forbidden, ErrorResponse.Forbidden()) + } + challenge.complete() + } + } else { + context.principal(UserIdPrincipal("homeserver")) + } + } +} + +private fun ApplicationRequest.getAccessTokenFromHeader(): String? { + return when (val authHeader = parseAuthorizationHeader()) { + is HttpAuthHeader.Single -> { + if (!authHeader.authScheme.equals("Bearer", ignoreCase = true)) null + else authHeader.blob + } + + else -> null + } +} + +fun AuthenticationConfig.matrixQueryParameterOrBearer( + name: String? = null, + field: String, + token: String, +) { + val provider = + MatrixQueryParameterOrBearerAuthenticationProvider( + MatrixQueryParameterOrBearerAuthenticationProvider.Configuration(name), + field, + token + ) + register(provider) +} \ No newline at end of file diff --git a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/matrixApplicationServiceApiServer.kt b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/matrixApplicationServiceApiServer.kt index 75659b0c8..4e4484fd7 100644 --- a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/matrixApplicationServiceApiServer.kt +++ b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonMain/kotlin/net/folivo/trixnity/applicationserviceapi/server/matrixApplicationServiceApiServer.kt @@ -16,7 +16,7 @@ fun Application.matrixApplicationServiceApiServer( routes: Route.() -> Unit, ) { install(Authentication) { - matrixQueryParameter("matrix-query-parameter-auth", "access_token", hsToken) + matrixQueryParameterOrBearer("matrix-query-parameter-auth", "access_token", hsToken) } matrixApiServer(json) { authenticate("matrix-query-parameter-auth") { diff --git a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuthTest.kt b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuthTest.kt similarity index 54% rename from trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuthTest.kt rename to trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuthTest.kt index 226d18e82..9e714d5e9 100644 --- a/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterAuthTest.kt +++ b/trixnity-applicationserviceapi/trixnity-applicationserviceapi-server/src/commonTest/kotlin/net/folivo/trixnity/applicationserviceapi/server/MatrixQueryParameterOrBearerAuthTest.kt @@ -23,7 +23,7 @@ private fun Application.testAppMatrixQueryParameterAuth() { json() } install(Authentication) { - matrixQueryParameter(null, "access_token", "validToken") + matrixQueryParameterOrBearer(null, "access_token", "validToken") } routing { authenticate { @@ -34,10 +34,10 @@ private fun Application.testAppMatrixQueryParameterAuth() { } } -class MatrixQueryParameterAuthTest { +class MatrixQueryParameterOrBearerAuthTest { @Test - fun `should forbid missing token`() = testApplication { + fun `should forbid missing query or auth token`() = testApplication { application { testAppMatrixQueryParameterAuth() } val response = client.get("/_matrix/something") assertEquals(HttpStatusCode.Unauthorized, response.status) @@ -47,7 +47,7 @@ class MatrixQueryParameterAuthTest { } @Test - fun `should forbid wrong token`() = testApplication { + fun `should forbid wrong query token`() = testApplication { application { testAppMatrixQueryParameterAuth() } val response = client.get("/_matrix/something?access_token=invalidToken") assertEquals(HttpStatusCode.Forbidden, response.status) @@ -57,9 +57,42 @@ class MatrixQueryParameterAuthTest { } @Test - fun `should permit right token`() = testApplication { + fun `should forbid wrong header token`() = testApplication { + application { testAppMatrixQueryParameterAuth() } + val response = client.get("/_matrix/something") { + bearerAuth("invalidToken") + } + assertEquals(HttpStatusCode.Forbidden, response.status) + assertEquals(ContentType.Application.Json.withCharset(UTF_8), response.contentType()) + Json.decodeFromString(ErrorResponseSerializer, response.body()) + .shouldBeInstanceOf() + } + + @Test + fun `should forbid non matching token`() = testApplication { + application { testAppMatrixQueryParameterAuth() } + val response = client.get("/_matrix/something?access_token=validToken") { + bearerAuth("invalidToken") + } + assertEquals(HttpStatusCode.Forbidden, response.status) + assertEquals(ContentType.Application.Json.withCharset(UTF_8), response.contentType()) + Json.decodeFromString(ErrorResponseSerializer, response.body()) + .shouldBeInstanceOf() + } + + @Test + fun `should permit right query token`() = testApplication { application { testAppMatrixQueryParameterAuth() } val response = client.get("/_matrix/something?access_token=validToken") assertEquals(HttpStatusCode.OK, response.status) } + + @Test + fun `should permit right header token`() = testApplication { + application { testAppMatrixQueryParameterAuth() } + val response = client.get("/_matrix/something") { + bearerAuth("validToken") + } + assertEquals(HttpStatusCode.OK, response.status) + } } \ No newline at end of file