Skip to content

Commit

Permalink
Merge pull request #83 from opatry/isolate-ktor-server-from-shared-code
Browse files Browse the repository at this point in the history
Isolate `HttpGoogleAuthentication` to avoid spreading ktor(-server) dependencies when not needed
  • Loading branch information
opatry authored Oct 29, 2024
2 parents 34c86ec + ee76cfd commit 7a8f55f
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 73 deletions.
38 changes: 38 additions & 0 deletions google/oauth-http/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

plugins {
alias(libs.plugins.jetbrains.kotlin.multiplatform)
}

kotlin {
jvm()

sourceSets {
commonMain.dependencies {
api(projects.google.oauth)

implementation(libs.bundles.ktor.client)
implementation(libs.bundles.ktor.server)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ import io.ktor.server.cio.CIO as ServerEngineCIO
* A Google OAuth2 authenticator using a localhost HTTP server for redirect URL interception and
* HTTP client for auth & token requests.
*/
open class HttpGoogleAuthenticator(private val config: ApplicationConfig) : GoogleAuthenticator {
class HttpGoogleAuthenticator(private val config: ApplicationConfig) : GoogleAuthenticator {

/**
* @property redirectUrl Redirect url
* @property clientId OAuth2 Client ID
* @property clientSecret OAuth2 Client Secret
* @property authUri OAuth2 Authorization URI
* @property tokenUri OAuth2 Token URI
*/
data class ApplicationConfig(
val redirectUrl: String,
Expand Down
3 changes: 1 addition & 2 deletions google/oauth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ kotlin {

sourceSets {
commonMain.dependencies {
implementation(libs.bundles.ktor.client)
implementation(libs.bundles.ktor.server)
implementation(libs.kotlinx.serialization)
}
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
rootProject.name = "Taskfolio"

include(":google:oauth")
include(":google:oauth-http")
include(":google:tasks")
include(":lucide-icons")
include(":tasks-core")
Expand Down
65 changes: 0 additions & 65 deletions tasks-app-android/src/main/assets/licenses_android.json
Original file line number Diff line number Diff line change
Expand Up @@ -1877,23 +1877,6 @@
"Apache-2.0"
]
},
{
"uniqueId": "com.typesafe:config",
"developers": [
{
"name": "Havoc Pennington"
}
],
"artifactVersion": "1.4.3",
"description": "configuration library for JVM languages using HOCON files",
"name": "config",
"licenses": [
"Apache-2.0"
],
"organization": {
"name": "com.typesafe"
}
},
{
"uniqueId": "io.coil-kt.coil3:coil-android",
"developers": [
Expand Down Expand Up @@ -2272,34 +2255,6 @@
"Apache-2.0"
]
},
{
"uniqueId": "io.ktor:ktor-server-cio-jvm",
"developers": [
{
"name": "Jetbrains Team"
}
],
"artifactVersion": "3.0.0",
"description": "Ktor is a framework for quickly creating web applications in Kotlin with minimal effort.",
"name": "ktor-server-cio",
"licenses": [
"Apache-2.0"
]
},
{
"uniqueId": "io.ktor:ktor-server-core-jvm",
"developers": [
{
"name": "Jetbrains Team"
}
],
"artifactVersion": "3.0.0",
"description": "Ktor is a framework for quickly creating web applications in Kotlin with minimal effort.",
"name": "ktor-server-core",
"licenses": [
"Apache-2.0"
]
},
{
"uniqueId": "io.ktor:ktor-sse-jvm",
"developers": [
Expand Down Expand Up @@ -2394,26 +2349,6 @@
"name": "JUnit"
}
},
{
"uniqueId": "org.fusesource.jansi:jansi",
"developers": [
{
"name": "Hiram Chirino"
},
{
"name": "Guillaume Nodet"
}
],
"artifactVersion": "2.4.1",
"description": "Jansi is a java library for generating and interpreting ANSI escape sequences.",
"name": "Jansi",
"licenses": [
"Apache-2.0"
],
"organization": {
"name": "FuseSource, Corp."
}
},
{
"uniqueId": "org.hamcrest:hamcrest-core",
"developers": [
Expand Down
2 changes: 1 addition & 1 deletion tasks-app-shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ kotlin {
}

jvmMain.dependencies {
implementation(libs.bundles.ktor.server)
implementation(projects.google.oauthHttp)
}

commonMain.dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,21 @@ import android.content.Context
import com.google.android.gms.auth.api.identity.AuthorizationRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.common.api.Scope
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.CurlUserAgent
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.withTimeout
import net.opatry.google.auth.GoogleAuthenticator
import net.opatry.google.auth.HttpGoogleAuthenticator
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
Expand All @@ -38,7 +50,32 @@ import kotlin.time.Duration.Companion.minutes
class PlayServicesGoogleAuthenticator(
private val context: Context,
private val config: ApplicationConfig
) : HttpGoogleAuthenticator(config) {
) : GoogleAuthenticator {

/**
* @property redirectUrl Redirect url
* @property clientId OAuth2 Client ID
* @property clientSecret OAuth2 Client Secret
* @property authUri OAuth2 Authorization URI
* @property tokenUri OAuth2 Token URI
*/
data class ApplicationConfig(
val redirectUrl: String,
val clientId: String,
val clientSecret: String,
val authUri: String,
val tokenUri: String,
)

private val httpClient: HttpClient by lazy {
HttpClient(CIO) {
CurlUserAgent()
install(ContentNegotiation) {
json()
}
}
}

override suspend fun authorize(
scopes: List<GoogleAuthenticator.Scope>,
force: Boolean,
Expand Down Expand Up @@ -68,4 +105,30 @@ class PlayServicesGoogleAuthenticator(
}
}
}

override suspend fun getToken(grant: GoogleAuthenticator.Grant): GoogleAuthenticator.OAuthToken {
val response = httpClient.post(config.tokenUri) {
parameter("client_id", config.clientId)
parameter("client_secret", config.clientSecret)
parameter("grant_type", grant.type)
when (grant) {
is GoogleAuthenticator.Grant.AuthorizationCode -> {
parameter("code", grant.code)
// TODO PKCE "code_verifier"
parameter("redirect_uri", config.redirectUrl)
}

is GoogleAuthenticator.Grant.RefreshToken -> {
parameter("refresh_token", grant.refreshToken)
}
}
contentType(ContentType.Application.FormUrlEncoded)
}

if (response.status.isSuccess()) {
return response.body()
} else {
throw ClientRequestException(response, response.bodyAsText())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import net.opatry.google.auth.GoogleAuth
import net.opatry.google.auth.GoogleAuthenticator
import net.opatry.google.auth.HttpGoogleAuthenticator.ApplicationConfig
import net.opatry.tasks.app.auth.PlayServicesGoogleAuthenticator
import net.opatry.tasks.app.auth.PlayServicesGoogleAuthenticator.ApplicationConfig
import org.koin.dsl.module


Expand Down
1 change: 0 additions & 1 deletion tasks-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ kotlin {

api(libs.kotlinx.datetime)
implementation(libs.bundles.ktor.client)
implementation(libs.bundles.ktor.server)
implementation(projects.google.oauth)
implementation(projects.google.tasks)

Expand Down

0 comments on commit 7a8f55f

Please sign in to comment.