From d58821422b60d29d8ea30e5140edcae0affae3ef Mon Sep 17 00:00:00 2001 From: Ellet Date: Wed, 20 Dec 2023 16:44:41 +0300 Subject: [PATCH] Unfinished changes 2 --- example/src/main/kotlin/plugins/AppCheck.kt | 4 +- example/src/main/kotlin/plugins/Routing.kt | 2 +- library/build.gradle.kts | 11 - .../firebase_app_check/FirebaseAppCheck.kt | 10 +- .../FirebaseAppCheckExceptions.kt | 2 +- .../FirebaseAppCheckPluginConfiguration.kt | 20 +- .../FirebaseAppCheckSecureStrategy.kt | 4 +- .../FirebaseAppCheckTokenVerifierService.kt | 30 +-- .../{services => service}/jwt/DecodedJwt.kt | 2 +- .../ApplicationCallExtensions.kt | 16 +- .../utils/FirebaseAppCheckResponses.kt | 2 +- .../ktor_server/firebase_app_check/.gitignore | 0 ...irebaseAppCheckTokenVerifierServiceImpl.kt | 35 +++- ...irebaseAppCheckTokenVerifierService.jvm.kt | 190 ++++++++++++++++++ .../services/jwt/DecodedJwt.kt | 6 - .../firebase_app_check/ApplicationTest.kt | 34 ++-- ...irebaseAppCheckTokenVerifierServiceMock.kt | 27 +-- ...baseAppCheckTokenVerifierService.native.kt | 15 ++ 18 files changed, 289 insertions(+), 121 deletions(-) rename library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/{exceptions => }/FirebaseAppCheckExceptions.kt (96%) rename library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/{core => configurations}/FirebaseAppCheckPluginConfiguration.kt (90%) rename library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/{core => configurations}/FirebaseAppCheckSecureStrategy.kt (84%) rename library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/{services => service}/jwt/DecodedJwt.kt (94%) rename library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/{extensions => }/ApplicationCallExtensions.kt (87%) delete mode 100644 library/src/commonTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/.gitignore rename library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/{services => }/FirebaseAppCheckTokenVerifierServiceImpl.kt (88%) create mode 100644 library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.jvm.kt delete mode 100644 library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt create mode 100644 library/src/nativeMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.native.kt diff --git a/example/src/main/kotlin/plugins/AppCheck.kt b/example/src/main/kotlin/plugins/AppCheck.kt index ecdc45a..beb4262 100644 --- a/example/src/main/kotlin/plugins/AppCheck.kt +++ b/example/src/main/kotlin/plugins/AppCheck.kt @@ -2,8 +2,8 @@ package plugins import io.ktor.server.application.* import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckPlugin -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckSecureStrategy -import net.freshplatform.ktor_server.firebase_app_check.services.FirebaseAppCheckTokenVerifierServiceImpl +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckSecureStrategy +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckTokenVerifierServiceImpl import net.freshplatform.ktor_server.firebase_app_check.utils.FirebaseAppCheckMessages fun Application.configureAppCheck() { diff --git a/example/src/main/kotlin/plugins/Routing.kt b/example/src/main/kotlin/plugins/Routing.kt index 6769667..dfd3115 100644 --- a/example/src/main/kotlin/plugins/Routing.kt +++ b/example/src/main/kotlin/plugins/Routing.kt @@ -5,7 +5,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* import io.ktor.server.routing.* -import net.freshplatform.ktor_server.firebase_app_check.utils.extensions.protectRouteWithAppCheck +import net.freshplatform.ktor_server.firebase_app_check.utils.protectRouteWithAppCheck class MissingEnvironmentVariableException(variableName: String) : RuntimeException("The required environment variable '$variableName' is missing.") diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 279bde7..5734386 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.kotlin.multiplatform) -// application id("maven-publish") } @@ -12,10 +11,6 @@ version = libs.versions.library.get() description = "A Ktor server plugin for configuring Firebase App Check easily and with simplicity. It is not affiliated with Firebase or Google and may not be suitable for production use yet." -//application { -// mainClass.set("${group}.ktor_server.firebase_app_check.FirebaseAppCheckKt") -//} - kotlin { jvm() linuxX64() @@ -46,12 +41,6 @@ kotlin { implementation("io.ktor:ktor-server-tests-jvm:$ktorVersion") } } -// val nativeMain by creating { -// dependsOn(commonMain) -// } -// val nativeTest by creating { -// dependsOn(nativeMain) -// } } } diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheck.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheck.kt index dd8ed7d..6393fb9 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheck.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheck.kt @@ -3,10 +3,10 @@ package net.freshplatform.ktor_server.firebase_app_check import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.util.* -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckPluginConfiguration -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckSecureStrategy -import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierServiceUnimplemented -import net.freshplatform.ktor_server.firebase_app_check.utils.extensions.verifyAppTokenRequest +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckPluginConfiguration +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckSecureStrategy +import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierServiceImpl +import net.freshplatform.ktor_server.firebase_app_check.utils.verifyAppTokenRequest /** * A Ktor server plugin for configuring Firebase App Check easily and with simplicity. @@ -29,7 +29,7 @@ class FirebaseAppCheckPlugin( configure: FirebaseAppCheckPluginConfiguration.() -> Unit ): FirebaseAppCheckPlugin { val configuration = FirebaseAppCheckPluginConfiguration( - serviceImpl = FirebaseAppCheckTokenVerifierServiceUnimplemented() + serviceImpl = FirebaseAppCheckTokenVerifierServiceImpl() ) .apply(configure) require(configuration.firebaseProjectNumber.isNotBlank()) { diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/exceptions/FirebaseAppCheckExceptions.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckExceptions.kt similarity index 96% rename from library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/exceptions/FirebaseAppCheckExceptions.kt rename to library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckExceptions.kt index b52507a..cdb5053 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/exceptions/FirebaseAppCheckExceptions.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckExceptions.kt @@ -1,4 +1,4 @@ -package net.freshplatform.ktor_server.firebase_app_check.exceptions +package net.freshplatform.ktor_server.firebase_app_check /** * A custom exception class for Firebase App Check-related errors. diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckPluginConfiguration.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckPluginConfiguration.kt similarity index 90% rename from library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckPluginConfiguration.kt rename to library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckPluginConfiguration.kt index 13ff14e..e2c5011 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckPluginConfiguration.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckPluginConfiguration.kt @@ -1,18 +1,18 @@ -package net.freshplatform.ktor_server.firebase_app_check.core +package net.freshplatform.ktor_server.firebase_app_check.configurations import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckFetchPublicKeyErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckFetchPublicKeyErrorType.* -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckFetchPublicKeyException -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtErrorType.* -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtException +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckFetchPublicKeyErrorType +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckFetchPublicKeyErrorType.* +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckFetchPublicKeyException +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckVerifyJwtErrorType +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckVerifyJwtErrorType.* +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckVerifyJwtException import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierService -import net.freshplatform.ktor_server.firebase_app_check.services.jwt.DecodedJwt +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt import net.freshplatform.ktor_server.firebase_app_check.utils.FirebaseAppCheckMessages -import net.freshplatform.ktor_server.firebase_app_check.utils.extensions.protectRouteWithAppCheck +import net.freshplatform.ktor_server.firebase_app_check.utils.protectRouteWithAppCheck /** * Configuration class for Firebase App Check plugin. @@ -45,7 +45,7 @@ class FirebaseAppCheckPluginConfiguration( var isShouldVerifyToken: Boolean? = null, var firebaseAppCheckHeaderName: String = "X-Firebase-AppCheck", var firebaseAppCheckApiBaseUrl: String = "https://firebaseappcheck.googleapis.com", - var firebaseAppCheckPublicJwtSetUrl: String = "${firebaseAppCheckApiBaseUrl}/v1/jwks", + var firebaseAppCheckPublicKeyUrl: String = "${firebaseAppCheckApiBaseUrl}/v1/jwks", var secureStrategy: FirebaseAppCheckSecureStrategy = FirebaseAppCheckSecureStrategy.ProtectSpecificRoutes, var additionalSecurityCheck: suspend (decodedJwt: DecodedJwt) -> Boolean = { true diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckSecureStrategy.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckSecureStrategy.kt similarity index 84% rename from library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckSecureStrategy.kt rename to library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckSecureStrategy.kt index 96152a9..4719932 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/core/FirebaseAppCheckSecureStrategy.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/configurations/FirebaseAppCheckSecureStrategy.kt @@ -1,6 +1,6 @@ -package net.freshplatform.ktor_server.firebase_app_check.core +package net.freshplatform.ktor_server.firebase_app_check.configurations -import net.freshplatform.ktor_server.firebase_app_check.utils.extensions.protectRouteWithAppCheck +import net.freshplatform.ktor_server.firebase_app_check.utils.protectRouteWithAppCheck /** * A sealed class that defines different strategies for securing routes with Firebase App Check. diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.kt index 353ffb5..ccf44fe 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.kt @@ -1,9 +1,8 @@ package net.freshplatform.ktor_server.firebase_app_check.service -import net.freshplatform.ktor_server.firebase_app_check.services.jwt.DecodedJwt +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt import kotlin.time.Duration import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.minutes /** @@ -36,41 +35,30 @@ data class FetchFirebaseAppCheckPublicKeyCacheConfig( * @param enabled */ data class FetchFirebaseAppCheckPublicKeyRateLimitedConfig( - val enabled: Boolean + val enabled: Boolean = true ) /** * Object containing functions for fetching and verifying Firebase App Check tokens. */ interface FirebaseAppCheckTokenVerifierService { - /** - * Suspended function to fetch a Firebase App Check a public key. - * - * @param jwtString to get the kid which is the Key ID. - * @param url The URL for fetching the public key. - * @param config Configuration for public key fetching. - * @return The fetched public key. - */ - suspend fun fetchFirebaseAppCheckPublicKey( - jwtString: String, url: String, - config: FetchFirebaseAppCheckPublicKeyConfig = FetchFirebaseAppCheckPublicKeyConfig() - ): PublicKey /** * Suspended function to verify a Firebase App Check token. * - * @param jwtString The JWT string to be verified. - * @param publicKey The public key used for verification. + * @param firebaseAppCheckTokenJwt The JWT string to be verified. * @param firebaseProjectId The Firebase project ID. * @param firebaseProjectNumber The Firebase project number. * @param issuerBaseUrl The base URL of the Firebase App Check issuer. * @return The verified Decoded JWT. */ suspend fun verifyFirebaseAppCheckToken( - jwtString: String, - publicKey: PublicKey, + firebaseAppCheckTokenJwt: String, firebaseProjectId: String, firebaseProjectNumber: String, - issuerBaseUrl: String + issuerBaseUrl: String, + publicKeyUrl: String ): DecodedJwt -} \ No newline at end of file +} + +expect class FirebaseAppCheckTokenVerifierServiceImpl(): FirebaseAppCheckTokenVerifierService \ No newline at end of file diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/jwt/DecodedJwt.kt similarity index 94% rename from library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt rename to library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/jwt/DecodedJwt.kt index 13ecc23..30a4a58 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/jwt/DecodedJwt.kt @@ -1,4 +1,4 @@ -package net.freshplatform.ktor_server.firebase_app_check.services.jwt +package net.freshplatform.ktor_server.firebase_app_check.service.jwt data class DecodedJwt( diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/extensions/ApplicationCallExtensions.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/ApplicationCallExtensions.kt similarity index 87% rename from library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/extensions/ApplicationCallExtensions.kt rename to library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/ApplicationCallExtensions.kt index 2254168..fdc9cc7 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/extensions/ApplicationCallExtensions.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/ApplicationCallExtensions.kt @@ -1,4 +1,4 @@ -package net.freshplatform.ktor_server.firebase_app_check.utils.extensions +package net.freshplatform.ktor_server.firebase_app_check.utils import io.ktor.http.* import io.ktor.server.application.* @@ -6,8 +6,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckPlugin -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckSecureStrategy -import net.freshplatform.ktor_server.firebase_app_check.service.FetchFirebaseAppCheckPublicKeyConfig +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckSecureStrategy /** * A suspended function that verifies an incoming Firebase App Check token for an [ApplicationCall]. @@ -38,17 +37,12 @@ suspend fun ApplicationCall.verifyAppTokenRequest() { try { - val publicKey = pluginConfig.serviceImpl.fetchFirebaseAppCheckPublicKey( - jwtString = firebaseAppCheckToken, - url = pluginConfig.firebaseAppCheckPublicJwtSetUrl, - config = FetchFirebaseAppCheckPublicKeyConfig() - ) val verifiedJwt = pluginConfig.serviceImpl.verifyFirebaseAppCheckToken( firebaseProjectId = pluginConfig.firebaseProjectId, firebaseProjectNumber = pluginConfig.firebaseProjectNumber, - jwtString = firebaseAppCheckToken, - publicKey = publicKey, - issuerBaseUrl = pluginConfig.firebaseAppCheckApiBaseUrl + firebaseAppCheckTokenJwt = firebaseAppCheckToken, + issuerBaseUrl = pluginConfig.firebaseAppCheckApiBaseUrl, + publicKeyUrl = pluginConfig.firebaseAppCheckPublicKeyUrl ) // Optional: Check that the token’s subject matches your app’s App ID. val isShouldContinue = pluginConfig.additionalSecurityCheck(verifiedJwt) diff --git a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/FirebaseAppCheckResponses.kt b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/FirebaseAppCheckResponses.kt index 091d53b..153d67d 100644 --- a/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/FirebaseAppCheckResponses.kt +++ b/library/src/commonMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/utils/FirebaseAppCheckResponses.kt @@ -1,6 +1,6 @@ package net.freshplatform.ktor_server.firebase_app_check.utils -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckPluginConfiguration +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckPluginConfiguration /** * Data class containing messages related to Firebase App Check. diff --git a/library/src/commonTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/.gitignore b/library/src/commonTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/FirebaseAppCheckTokenVerifierServiceImpl.kt b/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceImpl.kt similarity index 88% rename from library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/FirebaseAppCheckTokenVerifierServiceImpl.kt rename to library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceImpl.kt index 54cab79..ac4a786 100644 --- a/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/FirebaseAppCheckTokenVerifierServiceImpl.kt +++ b/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceImpl.kt @@ -1,26 +1,42 @@ -package net.freshplatform.ktor_server.firebase_app_check.services +package net.freshplatform.ktor_server.firebase_app_check import com.auth0.jwk.* import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.exceptions.* -import com.auth0.jwt.interfaces.DecodedJWT import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckFetchPublicKeyErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckFetchPublicKeyException -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtException import net.freshplatform.ktor_server.firebase_app_check.service.FetchFirebaseAppCheckPublicKeyConfig import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierService -import net.freshplatform.ktor_server.firebase_app_check.services.jwt.DecodedJwt +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt import java.net.URL import java.security.PublicKey import java.security.interfaces.RSAPublicKey import kotlin.time.toJavaDuration class FirebaseAppCheckTokenVerifierServiceImpl : FirebaseAppCheckTokenVerifierService { - override suspend fun fetchFirebaseAppCheckPublicKey( + override suspend fun verifyFirebaseAppCheckToken( + firebaseAppCheckTokenJwt: String, + firebaseProjectId: String, + firebaseProjectNumber: String, + issuerBaseUrl: String, + publicKeyUrl: String + ): DecodedJwt { + val publicKey = fetchFirebaseAppCheckPublicKey( + jwtString = firebaseAppCheckTokenJwt, + url = publicKeyUrl, + config = FetchFirebaseAppCheckPublicKeyConfig() + ) + val verifiedJwt = verifyFirebaseAppCheckToken( + firebaseProjectId = firebaseProjectId, + firebaseProjectNumber = firebaseProjectNumber, + jwtString = firebaseAppCheckTokenJwt, + publicKey = publicKey, + issuerBaseUrl = issuerBaseUrl + ) + return verifiedJwt + } + private suspend fun fetchFirebaseAppCheckPublicKey( jwtString: String, url: String, config: FetchFirebaseAppCheckPublicKeyConfig @@ -102,7 +118,7 @@ class FirebaseAppCheckTokenVerifierServiceImpl : FirebaseAppCheckTokenVerifierSe } } - override suspend fun verifyFirebaseAppCheckToken( + private suspend fun verifyFirebaseAppCheckToken( jwtString: String, publicKey: PublicKey, firebaseProjectId: String, @@ -168,4 +184,5 @@ class FirebaseAppCheckTokenVerifierServiceImpl : FirebaseAppCheckTokenVerifierSe } } } + } \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.jvm.kt b/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.jvm.kt new file mode 100644 index 0000000..aef119e --- /dev/null +++ b/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.jvm.kt @@ -0,0 +1,190 @@ +package net.freshplatform.ktor_server.firebase_app_check.service + +import com.auth0.jwk.* +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import com.auth0.jwt.exceptions.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckFetchPublicKeyErrorType +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckFetchPublicKeyException +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckVerifyJwtErrorType +import net.freshplatform.ktor_server.firebase_app_check.FirebaseAppCheckVerifyJwtException +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt +import java.net.URL +import java.security.PublicKey +import java.security.interfaces.RSAPublicKey +import kotlin.time.toJavaDuration + +actual class FirebaseAppCheckTokenVerifierServiceImpl : FirebaseAppCheckTokenVerifierService { + override suspend fun verifyFirebaseAppCheckToken( + firebaseAppCheckTokenJwt: String, + firebaseProjectId: String, + firebaseProjectNumber: String, + issuerBaseUrl: String, + publicKeyUrl: String + ): DecodedJwt { + val publicKey = fetchFirebaseAppCheckPublicKey( + jwtString = firebaseAppCheckTokenJwt, + url = publicKeyUrl, + config = FetchFirebaseAppCheckPublicKeyConfig() + ) + val verifiedJwt = verifyFirebaseAppCheckToken( + firebaseProjectId = firebaseProjectId, + firebaseProjectNumber = firebaseProjectNumber, + jwtString = firebaseAppCheckTokenJwt, + publicKey = publicKey, + issuerBaseUrl = issuerBaseUrl + ) + return verifiedJwt + } + private suspend fun fetchFirebaseAppCheckPublicKey( + jwtString: String, + url: String, + config: FetchFirebaseAppCheckPublicKeyConfig + ): PublicKey { + return withContext(Dispatchers.IO) { + try { + val cacheConfig = config.cacheConfiguration + val rateLimitedConfig = config.rateLimitedConfig + val jwkProvider = + JwkProviderBuilder(URL(url)) + .cached( + cacheConfig.cacheSize, + cacheConfig.expiresIn.toJavaDuration(), + ) + .rateLimited( + rateLimitedConfig.enabled + ) + .build() + + val decodedJwt = JWT.decode(jwtString) + val kid = decodedJwt.getHeaderClaim("kid").asString() + jwkProvider.get(kid).publicKey + } catch (e: SigningKeyNotFoundException) { + throw FirebaseAppCheckFetchPublicKeyException( + message = e.message.toString(), + errorType = FirebaseAppCheckFetchPublicKeyErrorType.SigningKeyNotFound + ) + } catch (e: NetworkException) { + throw FirebaseAppCheckFetchPublicKeyException( + message = e.message.toString(), + errorType = FirebaseAppCheckFetchPublicKeyErrorType.NetworkError + ) + } catch (e: RateLimitReachedException) { + throw FirebaseAppCheckFetchPublicKeyException( + message = e.message.toString(), + errorType = FirebaseAppCheckFetchPublicKeyErrorType.RateLimitReached + ) + } catch (e: JwkException) { + throw FirebaseAppCheckFetchPublicKeyException( + message = e.message.toString(), + errorType = FirebaseAppCheckFetchPublicKeyErrorType.JwkError + ) + } catch (e: JWTDecodeException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenIsNotValid + ) + } catch (e: AlgorithmMismatchException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenAlgorithmIsNotCorrect + ) + } catch (e: SignatureVerificationException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenSignatureVerificationInvalid + ) + } catch (e: MissingClaimException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenMissingClaim + ) + } catch (e: IncorrectClaimException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenIncorrectClaim + ) + } catch (e: JWTVerificationException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.GenericJwtVerificationError + ) + } catch (e: Exception) { + throw FirebaseAppCheckFetchPublicKeyException( + message = e.message.toString(), + errorType = FirebaseAppCheckFetchPublicKeyErrorType.UnknownError + ) + } + } + } + + private suspend fun verifyFirebaseAppCheckToken( + jwtString: String, + publicKey: PublicKey, + firebaseProjectId: String, + firebaseProjectNumber: String, + issuerBaseUrl: String + ): DecodedJwt { + return withContext(Dispatchers.IO) { + try { + val verifier = JWT + .require(Algorithm.RSA256(publicKey as RSAPublicKey, null)) + .withAnyOfAudience("projects/${firebaseProjectNumber}", "projects/${firebaseProjectId}") + .withIssuer("${issuerBaseUrl}/${firebaseProjectNumber}") + .build() + val decodedJwt = verifier.verify(jwtString) + val tokenHeader = decodedJwt.getHeaderClaim("typ").asString() + if (tokenHeader != "JWT") { + throw FirebaseAppCheckVerifyJwtException( + message = "The token header of value $tokenHeader is not equal to 'JWT'", + errorType = FirebaseAppCheckVerifyJwtErrorType.HeaderTypeIsNotJwt + ) + } + DecodedJwt(decodedJwt.token) + } catch (e: TokenExpiredException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenExpired + ) + } catch (e: JWTDecodeException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenIsNotValid + ) + } catch (e: AlgorithmMismatchException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenAlgorithmIsNotCorrect + ) + } catch (e: SignatureVerificationException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenSignatureVerificationInvalid + ) + } catch (e: MissingClaimException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenMissingClaim + ) + } catch (e: IncorrectClaimException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.TokenIncorrectClaim + ) + } catch (e: JWTVerificationException) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.GenericJwtVerificationError + ) + } catch (e: Exception) { + throw FirebaseAppCheckVerifyJwtException( + message = e.message.toString(), + errorType = FirebaseAppCheckVerifyJwtErrorType.UnknownError + ) + } + } + } + +} \ No newline at end of file diff --git a/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt b/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt deleted file mode 100644 index 4763e06..0000000 --- a/library/src/jvmMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/services/jwt/DecodedJwt.kt +++ /dev/null @@ -1,6 +0,0 @@ -package net.freshplatform.ktor_server.firebase_app_check.services.jwt - -import com.auth0.jwt.interfaces.Claim -import com.auth0.jwt.interfaces.DecodedJWT -import java.util.* - diff --git a/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/ApplicationTest.kt b/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/ApplicationTest.kt index dd3bb19..31333a2 100644 --- a/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/ApplicationTest.kt +++ b/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/ApplicationTest.kt @@ -7,12 +7,10 @@ import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.testing.* -import net.freshplatform.ktor_server.firebase_app_check.core.FirebaseAppCheckPluginConfiguration -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtException +import net.freshplatform.ktor_server.firebase_app_check.configurations.FirebaseAppCheckPluginConfiguration import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierService import net.freshplatform.ktor_server.firebase_app_check.utils.FirebaseAppCheckMessages -import net.freshplatform.ktor_server.firebase_app_check.utils.extensions.protectRouteWithAppCheck +import net.freshplatform.ktor_server.firebase_app_check.utils.protectRouteWithAppCheck import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -81,17 +79,13 @@ class ApplicationTest { } } val jwtString = TestConstants.TOKEN_OF_THE_PROJECT - val publicKey = firebaseAppCheckTokenVerifierService.fetchFirebaseAppCheckPublicKey( - jwtString = jwtString, - url = pluginConfiguration.firebaseAppCheckPublicJwtSetUrl - ) try { firebaseAppCheckTokenVerifierService.verifyFirebaseAppCheckToken( - jwtString = jwtString, - publicKey = publicKey, + firebaseAppCheckTokenJwt = jwtString, firebaseProjectId = pluginConfiguration.firebaseProjectId, firebaseProjectNumber = pluginConfiguration.firebaseProjectNumber, - issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl + issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl, + publicKeyUrl = pluginConfiguration.firebaseAppCheckPublicKeyUrl ) } catch (e: Exception) { fail("Test failed while verify the firebase app check token: $e") @@ -100,10 +94,10 @@ class ApplicationTest { val verifiedJwtWithDifferentProjectId = assertFailsWith { firebaseAppCheckTokenVerifierService.verifyFirebaseAppCheckToken( - jwtString = jwtString, - publicKey = publicKey, - firebaseProjectId = "myapp-eb212", - firebaseProjectNumber = pluginConfiguration.firebaseProjectNumber, + firebaseAppCheckTokenJwt = jwtString, + publicKeyUrl = pluginConfiguration.firebaseAppCheckPublicKeyUrl, + firebaseProjectId = pluginConfiguration.firebaseProjectId, + firebaseProjectNumber = "32132312123", issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl ) } @@ -114,11 +108,11 @@ class ApplicationTest { val verifiedJwtWithDifferentProjectNumber = assertFailsWith { firebaseAppCheckTokenVerifierService.verifyFirebaseAppCheckToken( - jwtString = jwtString, - publicKey = publicKey, + firebaseAppCheckTokenJwt = jwtString, firebaseProjectId = pluginConfiguration.firebaseProjectId, firebaseProjectNumber = "32132312123", - issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl + issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl, + publicKeyUrl = pluginConfiguration.firebaseAppCheckPublicKeyUrl ) } assertEquals( @@ -128,8 +122,8 @@ class ApplicationTest { val invalidJwtException = assertFailsWith { firebaseAppCheckTokenVerifierService.verifyFirebaseAppCheckToken( - jwtString = "eyInvalidJwt", - publicKey = publicKey, + firebaseAppCheckTokenJwt = "eyInvalidJwt", + publicKeyUrl = pluginConfiguration.firebaseAppCheckPublicKeyUrl, firebaseProjectId = pluginConfiguration.firebaseProjectId, firebaseProjectNumber = pluginConfiguration.firebaseProjectNumber, issuerBaseUrl = pluginConfiguration.firebaseAppCheckApiBaseUrl diff --git a/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceMock.kt b/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceMock.kt index 955d7bb..d0de580 100644 --- a/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceMock.kt +++ b/library/src/jvmTest/kotlin/net/freshplatform/ktor_server/firebase_app_check/FirebaseAppCheckTokenVerifierServiceMock.kt @@ -2,14 +2,9 @@ package net.freshplatform.ktor_server.firebase_app_check import com.auth0.jwt.JWT import com.auth0.jwt.exceptions.JWTDecodeException -import com.auth0.jwt.interfaces.DecodedJWT -import kotlinx.coroutines.delay -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtErrorType -import net.freshplatform.ktor_server.firebase_app_check.exceptions.FirebaseAppCheckVerifyJwtException -import net.freshplatform.ktor_server.firebase_app_check.service.FetchFirebaseAppCheckPublicKeyConfig import net.freshplatform.ktor_server.firebase_app_check.service.FirebaseAppCheckTokenVerifierService +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt import java.security.PublicKey -import kotlin.time.Duration.Companion.milliseconds private class PublicKeyMock : PublicKey { override fun getAlgorithm(): String { @@ -26,24 +21,16 @@ private class PublicKeyMock : PublicKey { } class FirebaseAppCheckTokenVerifierServiceMock : FirebaseAppCheckTokenVerifierService { - override suspend fun fetchFirebaseAppCheckPublicKey( - jwtString: String, - url: String, - config: FetchFirebaseAppCheckPublicKeyConfig - ): PublicKey { - delay(20.milliseconds) - return PublicKeyMock() - } override suspend fun verifyFirebaseAppCheckToken( - jwtString: String, - publicKey: PublicKey, + firebaseAppCheckTokenJwt: String, firebaseProjectId: String, firebaseProjectNumber: String, - issuerBaseUrl: String - ): DecodedJWT { + issuerBaseUrl: String, + publicKeyUrl: String + ): DecodedJwt { try { - val verified = JWT.decode(jwtString) + val verified = JWT.decode(firebaseAppCheckTokenJwt) if (verified.audience.first() != "projects/$firebaseProjectNumber") { throw FirebaseAppCheckVerifyJwtException( "The ${verified.audience.first()} is not equal to projects/$firebaseProjectNumber", @@ -62,7 +49,7 @@ class FirebaseAppCheckTokenVerifierServiceMock : FirebaseAppCheckTokenVerifierSe errorType = FirebaseAppCheckVerifyJwtErrorType.GenericJwtVerificationError ) } - return verified + return DecodedJwt(verified.token) } catch (e: JWTDecodeException) { throw FirebaseAppCheckVerifyJwtException( "Token is not valid: $e", diff --git a/library/src/nativeMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.native.kt b/library/src/nativeMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.native.kt new file mode 100644 index 0000000..5d4c249 --- /dev/null +++ b/library/src/nativeMain/kotlin/net/freshplatform/ktor_server/firebase_app_check/service/FirebaseAppCheckTokenVerifierService.native.kt @@ -0,0 +1,15 @@ +package net.freshplatform.ktor_server.firebase_app_check.service + +import net.freshplatform.ktor_server.firebase_app_check.service.jwt.DecodedJwt + +actual class FirebaseAppCheckTokenVerifierServiceImpl : FirebaseAppCheckTokenVerifierService { + override suspend fun verifyFirebaseAppCheckToken( + firebaseAppCheckTokenJwt: String, + firebaseProjectId: String, + firebaseProjectNumber: String, + issuerBaseUrl: String, + publicKeyUrl: String + ): DecodedJwt { + TODO("Not yet implemented") + } +} \ No newline at end of file